Skip to content

Commit

Permalink
feat: allow outputReferences function, publish filter util for it
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenbroekema committed Apr 3, 2024
1 parent ee08a7e commit 00373dd
Show file tree
Hide file tree
Showing 28 changed files with 454 additions and 687 deletions.
5 changes: 5 additions & 0 deletions .changeset/stupid-deers-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'style-dictionary': minor
---

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.
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}']);
});
});
7 changes: 6 additions & 1 deletion docs/src/content/docs/reference/Hooks/Formats/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ The design token that is passed to the filter function has already been [transfo

## References in output files

Starting with version 3.0, some formats can keep the references in the output. This is a bit hard to explain, so let's look at an example. Say you have this very basic set of design tokens:
Some formats can keep the references in the output. This is a bit hard to explain, so let's look at an example. Say you have this very basic set of design tokens:

```json
// tokens.json
Expand Down Expand Up @@ -152,6 +152,11 @@ Not all formats use the `outputReferences` option because that file format might

You can create custom formats that output references as well. See the [Custom format with output references](#custom-format-with-output-references) section.

### Filters

When combining [`filters`](/reference/hooks/filters) with `outputReferences`, it could happen that a token is referencing another token that is getting filtered out.
When that happens, Style Dictionary will throw a warning. However, it is possible to configure `outputReferences` to use [our `outputReferencesFilter` utility function](/reference/utils/references/#outputreferencesfilter), which will prevent tokens that reference other tokens that are filtered out from outputting references, they will output the resolved values instead.

## File headers

By default Style Dictionary adds a file header comment in the top of files built using built-in formats like this:
Expand Down
Loading

0 comments on commit 00373dd

Please sign in to comment.