Skip to content

Commit

Permalink
feat: allow outputReferences function, publish filter util for it (#1145
Browse files Browse the repository at this point in the history
)

* feat: allow outputReferences function, publish filter util for it

* chore: update types and docs
  • Loading branch information
jorenbroekema committed Jun 28, 2024
1 parent 5fb5a61 commit 8873031
Show file tree
Hide file tree
Showing 34 changed files with 661 additions and 850 deletions.
65 changes: 65 additions & 0 deletions .changeset/stupid-deers-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
'style-dictionary': major
---

BREAKING: Allow specifying a `function` for `outputReferences`, conditionally outputting a ref or not per token. Also exposes `outputReferencesFilter` utility function which will determine whether a token should be outputting refs based on whether those referenced tokens were filtered out or not.

If you are maintaining a custom format that allows `outputReferences` option, you'll need to take into account that it can be a function, and pass the correct options to it.

Before:

```js
StyleDictionary.registerFormat({
name: 'custom/es6',
formatter: async (dictionary) => {
const { allTokens, options, file } = dictionary;
const { usesDtcg } = options;

const compileTokenValue = (token) => {
let value = usesDtcg ? token.$value : token.value;
const originalValue = usesDtcg ? token.original.$value : token.original.value;

// Look here 👇
const shouldOutputRefs = outputReferences && usesReferences(originalValue);

if (shouldOutputRefs) {
// ... your code for putting back the reference in the output
value = ...
}
return value;
}
return `${allTokens.reduce((acc, token) => `${acc}export const ${token.name} = ${compileTokenValue(token)};\n`, '')}`;
},
});
```

After

```js
StyleDictionary.registerFormat({
name: 'custom/es6',
formatter: async (dictionary) => {
const { allTokens, options, file } = dictionary;
const { usesDtcg } = options;

const compileTokenValue = (token) => {
let value = usesDtcg ? token.$value : token.value;
const originalValue = usesDtcg ? token.original.$value : token.original.value;

// Look here 👇
const shouldOutputRef =
usesReferences(original) &&
(typeof options.outputReferences === 'function'
? outputReferences(token, { dictionary, usesDtcg })
: options.outputReferences);

if (shouldOutputRefs) {
// ... your code for putting back the reference in the output
value = ...
}
return value;
}
return `${allTokens.reduce((acc, token) => `${acc}export const ${token.name} = ${compileTokenValue(token)};\n`, '')}`;
},
});
```
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"ignorePatterns": ["/docs/dist/**/*"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.changeset/
**/*.snap.js
/docs/dist
7 changes: 6 additions & 1 deletion __integration__/__snapshots__/outputReferences.test.snap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export const snapshots = {};
snapshots["integration output references should warn the user if filters out references briefly"] =
`⚠️ __integration__/build/filteredVariables.css
While building filteredVariables.css, filtered out token references were found; output may be unexpected. Ignore this warning if intentional.
Here are the references that are used but not defined in the file:
Use log.verbosity "verbose" or use CLI option --verbose for more details.`;
/* end snapshot integration output references should warn the user if filters out references briefly */
Expand All @@ -23,3 +22,9 @@ color.core.blue.0
This is caused when combining a filter and \`outputReferences\`.`;
/* end snapshot integration output references should warn the user if filters out references with a detailed message when using verbose logging */

snapshots["integration output references should not warn the user if filters out references is prevented with outputReferencesFilter"] =
`
css
✔︎ __integration__/build/filteredVariables.css`;
/* end snapshot integration output references should not warn the user if filters out references is prevented with outputReferencesFilter */

3 changes: 0 additions & 3 deletions __integration__/logging/__snapshots__/file.test.snap.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,23 +391,20 @@ snapshots["integration logging file filtered references should warn users briefl
css
⚠️ __integration__/build/filteredReferences.css
While building filteredReferences.css, filtered out token references were found; output may be unexpected. Ignore this warning if intentional.
Here are the references that are used but not defined in the file:
Use log.verbosity "verbose" or use CLI option --verbose for more details.`;
/* end snapshot integration logging file filtered references should warn users briefly of filtered references by default */

snapshots["integration logging file filtered references should throw a brief error of filtered references with log level set to error"] =
`⚠️ __integration__/build/filteredReferences.css
While building filteredReferences.css, filtered out token references were found; output may be unexpected. Ignore this warning if intentional.
Here are the references that are used but not defined in the file:
Use log.verbosity "verbose" or use CLI option --verbose for more details.`;
/* end snapshot integration logging file filtered references should throw a brief error of filtered references with log level set to error */

snapshots["integration logging file filtered references should throw a brief error of filtered references with log level set to error on platform level"] =
`⚠️ __integration__/build/filteredReferences.css
While building filteredReferences.css, filtered out token references were found; output may be unexpected. Ignore this warning if intentional.
Here are the references that are used but not defined in the file:
Use log.verbosity "verbose" or use CLI option --verbose for more details.`;
/* end snapshot integration logging file filtered references should throw a brief error of filtered references with log level set to error on platform level */
Expand Down
33 changes: 33 additions & 0 deletions __integration__/outputReferences.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import StyleDictionary from 'style-dictionary';
import { restore, stubMethod } from 'hanbi';
import { buildPath, cleanConsoleOutput } from './_constants.js';
import { clearOutput } from '../__tests__/__helpers.js';
import { outputReferencesFilter } from '../lib/utils/references/outputReferencesFilter.js';

describe('integration', async () => {
let stub;
Expand Down Expand Up @@ -57,6 +58,38 @@ describe('integration', async () => {
await expect(stub.lastCall.args.map(cleanConsoleOutput).join('\n')).to.matchSnapshot();
});

it('should not warn the user if filters out references is prevented with outputReferencesFilter', async () => {
const sd = new StyleDictionary({
// we are only testing showFileHeader options so we don't need
// the full source.
log: { verbosity: 'verbose' },
source: [`__integration__/tokens/**/[!_]*.json?(c)`],
platforms: {
css: {
transformGroup: 'css',
buildPath,
files: [
{
destination: 'filteredVariables.css',
format: 'css/variables',
// filter tokens and use outputReferences
// Style Dictionary should build this file ok
// but warn the user
filter: (token) => token.attributes.type === 'background',
options: {
outputReferences: outputReferencesFilter,
},
},
],
},
},
});
await sd.buildAllPlatforms();
await expect(
[...stub.calls].map((cal) => cal.args.map(cleanConsoleOutput)).join('\n'),
).to.matchSnapshot();
});

it('should warn the user if filters out references with a detailed message when using verbose logging', async () => {
const sd = new StyleDictionary({
log: { verbosity: 'verbose' },
Expand Down
174 changes: 174 additions & 0 deletions __tests__/common/formatHelpers/createPropertyFormatter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
import { expect } from 'chai';
import createPropertyFormatter from '../../../lib/common/formatHelpers/createPropertyFormatter.js';
import flattenTokens from '../../../lib/utils/flattenTokens.js';
import { outputReferencesFilter } from '../../../lib/utils/references/outputReferencesFilter.js';

const dictionary = {
foo: {
Expand Down Expand Up @@ -221,6 +223,178 @@ describe('common', () => {
);
});

it('should support conditionally outputting references', () => {
const unfilteredTokens = {
foo: {
value: '5px',
original: {
value: '5px',
type: 'spacing',
},
name: 'foo',
path: ['foo'],
type: 'spacing',
},
bar: {
value: '5px',
original: {
value: '{foo}',
type: 'spacing',
},
name: 'bar',
path: ['bar'],
type: 'spacing',
},
qux: {
value: '5px',
original: {
value: '{foo}',
type: 'spacing',
},
name: 'qux',
path: ['qux'],
type: 'spacing',
},
};
const tokens = { ...unfilteredTokens };
const allTokens = flattenTokens(tokens);
const propFormatter = createPropertyFormatter({
dictionary: {
tokens,
unfilteredTokens,
allTokens,
},
format: 'css',
// outputReferences function that only outputs the refs if the token name is "bar"
outputReferences: (token) => token.name === 'bar',
});
expect(propFormatter(tokens.bar)).to.equal(' --bar: var(--foo);');
expect(propFormatter(tokens.qux)).to.equal(' --qux: 5px;');
});

it('should make it easy to not output refs for tokens that contains refs that are filtered out', () => {
const unfilteredTokens = {
foo: {
value: '5px',
original: {
value: '5px',
type: 'spacing',
},
name: 'foo',
path: ['foo'],
type: 'spacing',
},
bar: {
value: '10px',
original: {
value: '10px',
type: 'spacing',
},
name: 'bar',
path: ['bar'],
type: 'spacing',
},
'ref foo': {
value: '5px',
original: {
value: '{foo}',
type: 'spacing',
},
name: 'ref-foo',
path: ['ref foo'],
type: 'spacing',
},
'ref bar': {
value: '10px',
original: {
value: '{bar}',
type: 'spacing',
},
name: 'ref-bar',
path: ['ref bar'],
type: 'spacing',
},
};
const tokens = { ...unfilteredTokens };
delete tokens.foo;
const allTokens = flattenTokens(tokens);
const propFormatter = createPropertyFormatter({
dictionary: {
tokens,
unfilteredTokens,
allTokens,
},
format: 'css',
// outputReferences function that only outputs the refs if the referred tokens are not filtered out
outputReferences: outputReferencesFilter,
});

expect(propFormatter(tokens['ref foo'])).to.equal(' --ref-foo: 5px;');
expect(propFormatter(tokens['ref bar'])).to.equal(' --ref-bar: var(--bar);');
});

it('DTCG: should make it easy to not output refs for tokens that contains refs that are filtered out', () => {
const unfilteredTokens = {
foo: {
$value: '5px',
original: {
$value: '5px',
type: 'spacing',
},
name: 'foo',
path: ['foo'],
$type: 'spacing',
},
bar: {
$value: '10px',
original: {
$value: '10px',
$type: 'spacing',
},
name: 'bar',
path: ['bar'],
$type: 'spacing',
},
'ref foo': {
$value: '5px',
original: {
$value: '{foo}',
$type: 'spacing',
},
name: 'ref-foo',
path: ['ref foo'],
$type: 'spacing',
},
'ref bar': {
$value: '10px',
original: {
$value: '{bar}',
$type: 'spacing',
},
name: 'ref-bar',
path: ['ref bar'],
$type: 'spacing',
},
};
const tokens = { ...unfilteredTokens };
delete tokens.foo;
const allTokens = flattenTokens(tokens, true);
const propFormatter = createPropertyFormatter({
dictionary: {
tokens,
unfilteredTokens,
allTokens,
},
format: 'css',
usesDtcg: true,
// outputReferences function that only outputs the refs if the referred tokens are not filtered out
outputReferences: outputReferencesFilter,
});

expect(propFormatter(tokens['ref foo'])).to.equal(' --ref-foo: 5px;');
expect(propFormatter(tokens['ref bar'])).to.equal(' --ref-bar: var(--bar);');
});

it('should support object value references for outputReferences', () => {
// The ref is an object type value, which means there will usually be some kind of transform (e.g. a CSS shorthand transform)
// to change it from an object to a string. In our example, we use a border CSS shorthand for border token.
Expand Down
31 changes: 31 additions & 0 deletions __tests__/utils/groupMessages.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
import { expect } from 'chai';
import { GroupMessages } from '../../lib/utils/groupMessages.js';

// TODO: add more tests

describe('groupMessage', () => {
it('should allow removing messages', () => {
const grpMessages = new GroupMessages();
const FILTER_WARNINGS = grpMessages.GROUP.FilteredOutputReferences;
grpMessages.add(FILTER_WARNINGS, '{foo.bar}');
grpMessages.add(FILTER_WARNINGS, '{baz.qux}');
grpMessages.add(FILTER_WARNINGS, '{another.one}');

expect(grpMessages.count(FILTER_WARNINGS)).to.equal(3);
grpMessages.remove(FILTER_WARNINGS, '{baz.qux}');
expect(grpMessages.count(FILTER_WARNINGS)).to.equal(2);
expect(grpMessages.fetchMessages(FILTER_WARNINGS)).to.eql(['{foo.bar}', '{another.one}']);
});
});
Loading

0 comments on commit 8873031

Please sign in to comment.