diff --git a/.changeset/nice-queens-suffer.md b/.changeset/nice-queens-suffer.md new file mode 100644 index 0000000..a4041c7 --- /dev/null +++ b/.changeset/nice-queens-suffer.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': patch +--- + +Add excludeParentKeys option to the transform options, in order to exclude parent keys from your token files. This is useful if you use a single-file export from Tokens Studio Figma Plugin. diff --git a/README.md b/README.md index 51552ac..7efa099 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ to work with Design Tokens that are exported from [Tokens Studio](https://tokens Generic: - Expands composition tokens into multiple, optionally also does so for typography, border and shadow tokens -> parser +- Optionally excludes parent keys from your tokens file, e.g. when using single-file export from Tokens Studio Figma plugin -> parser - Maps token descriptions to comments -> `ts/descriptionToComment` - Check and evaluate Math expressions (transitive) -> `ts/resolveMath` - Transform dimensions tokens to have `px` as a unit when missing (transitive) -> `ts/size/px` @@ -105,6 +106,7 @@ registerTransforms({ token.value.width !== 0 && filePath.startsWith(path.resolve('tokens/core')), shadow: false, }, + excludeParentKeys: true, }); ``` @@ -112,11 +114,13 @@ Options: | name | type | required | default | description | | ------------------ | ------------------------ | -------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| excludeParentKeys | boolean | ❌ | `false` | Whether or not to exclude parent keys from your token files | | expand | boolean \| ExpandOptions | ❌ | See props below | `false` to not register the parser at all. By default, expands composition tokens. Optionally, border, shadow and typography as well. | | expand.composition | boolean \| ExpandFilter | ❌ | `true` | Whether or not to expand compositions. Also allows a filter callback function to conditionally expand per token/filePath | | expand.typography | boolean \| ExpandFilter | ❌ | `false` | Whether or not to expand typography. Also allows a filter callback function to conditionally expand per token/filePath | | expand.shadow | boolean \| ExpandFilter | ❌ | `false` | Whether or not to expand shadows. Also allows a filter callback function to conditionally expand per token/filePath | | expand.border | boolean \| ExpandFilter | ❌ | `false` | Whether or not to expand borders. Also allows a filter callback function to conditionally expand per token/filePath | +| | > Note: you can also import and use the `expandComposites` function to run the expansion on your token object manually. > Handy if you have your own parsers set up (e.g. for JS files), and you want the expansions to work there too. @@ -179,5 +183,5 @@ sd.cleanAllPlatforms(); sd.buildAllPlatforms(); ``` -> Note: make sure to choose either the full transformGroup, **OR** its separate transforms, so you can adjust or add your own. +> Note: make sure to choose either the full transformGroup, **OR** its separate transforms so you can adjust or add your own. > [Combining a transformGroup with a transforms array can give unexpected results](https://github.com/amzn/style-dictionary/issues/813). diff --git a/package-lock.json b/package-lock.json index 275fca8..4703a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tokens-studio/sd-transforms", - "version": "0.7.0", + "version": "0.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tokens-studio/sd-transforms", - "version": "0.7.0", + "version": "0.8.1", "license": "MIT", "dependencies": { "@tokens-studio/types": "^0.2.1", diff --git a/src/TransformOptions.ts b/src/TransformOptions.ts index 9cc95b3..ad65b2f 100644 --- a/src/TransformOptions.ts +++ b/src/TransformOptions.ts @@ -26,4 +26,5 @@ export interface ExpandOptions { export interface TransformOptions { expand?: ExpandOptions | false; + excludeParentKeys?: boolean; } diff --git a/src/index.ts b/src/index.ts index 89d0f00..aeb270f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export { registerTransforms } from './registerTransforms.js'; export { expandComposites } from './parsers/expand-composites.js'; +export { excludeParentKeys } from './parsers/exclude-parent-keys.js'; export { mapDescriptionToComment } from './mapDescriptionToComment.js'; export { checkAndEvaluateMath } from './checkAndEvaluateMath.js'; diff --git a/src/parsers/exclude-parent-keys.ts b/src/parsers/exclude-parent-keys.ts new file mode 100644 index 0000000..59ebe70 --- /dev/null +++ b/src/parsers/exclude-parent-keys.ts @@ -0,0 +1,18 @@ +import { DeepKeyTokenMap } from '@tokens-studio/types'; +import { TransformOptions } from '../TransformOptions.js'; + +export function excludeParentKeys( + dictionary: DeepKeyTokenMap, + transformOpts?: TransformOptions, +): DeepKeyTokenMap { + if (!transformOpts?.excludeParentKeys) { + return dictionary; + } + const copy = {} as DeepKeyTokenMap; + Object.values(dictionary).forEach(set => { + Object.entries(set).forEach(([key, tokenGroup]) => { + copy[key] = tokenGroup; + }); + }); + return copy; +} diff --git a/src/registerTransforms.ts b/src/registerTransforms.ts index 333d77c..f3da669 100644 --- a/src/registerTransforms.ts +++ b/src/registerTransforms.ts @@ -13,6 +13,7 @@ import { mapDescriptionToComment } from './mapDescriptionToComment.js'; import { transformColorModifiers } from './color-modifiers/transformColorModifiers.js'; import { TransformOptions } from './TransformOptions.js'; import { expandComposites } from './parsers/expand-composites.js'; +import { excludeParentKeys } from './parsers/exclude-parent-keys.js'; import { transformOpacity } from './transformOpacity.js'; const isBrowser = typeof window === 'object'; @@ -41,7 +42,8 @@ export async function registerTransforms(sd: Core, transformOpts?: TransformOpti pattern: /\.json$/, parse: ({ filePath, contents }) => { const obj = JSON.parse(contents); - const expanded = expandComposites(obj, filePath, transformOpts); + const excluded = excludeParentKeys(obj, transformOpts); + const expanded = expandComposites(excluded, filePath, transformOpts); return expanded; }, }); diff --git a/test/integration/exclude-parent-keys.test.ts b/test/integration/exclude-parent-keys.test.ts new file mode 100644 index 0000000..06622fc --- /dev/null +++ b/test/integration/exclude-parent-keys.test.ts @@ -0,0 +1,66 @@ +import { expect } from '@esm-bundle/chai'; +import StyleDictionary from 'style-dictionary'; +import { promises } from 'fs'; +import path from 'path'; +import { cleanup, init } from './utils.js'; + +const outputDir = 'test/integration/tokens/'; +const outputFileName = 'vars.css'; +const outputFilePath = path.resolve(outputDir, outputFileName); + +const cfg = { + source: ['test/integration/tokens/exclude-parent-keys.tokens.json'], + platforms: { + css: { + transformGroup: 'tokens-studio', + prefix: 'sd', + buildPath: outputDir, + files: [ + { + destination: outputFileName, + format: 'css/variables', + }, + ], + }, + }, +}; +let transformOpts = {}; +let dict: StyleDictionary.Core | undefined; + +function before() { + if (dict) { + cleanup(dict); + } + dict = init(cfg, transformOpts); +} + +function after() { + if (dict) { + cleanup(dict); + } +} + +describe('exclude parent keys', () => { + afterEach(() => { + after(); + }); + + it('does not expand parent keys by default and throws on broken references', async () => { + expect(before).to.throw('Problems were found when trying to resolve property references'); + }); + + it('optionally excludes parent keys', async () => { + transformOpts = { + excludeParentKeys: true, + }; + before(); + + const file = await promises.readFile(outputFilePath, 'utf-8'); + expect(file).to.include( + ` + --sdCoreColor: #FFFFFF; + --sdSemanticColor: #FFFFFF; + --sdButtonColor: #FFFFFF;`, + ); + }); +}); diff --git a/test/integration/tokens/exclude-parent-keys.tokens.json b/test/integration/tokens/exclude-parent-keys.tokens.json new file mode 100644 index 0000000..07775ac --- /dev/null +++ b/test/integration/tokens/exclude-parent-keys.tokens.json @@ -0,0 +1,24 @@ +{ + "foo": { + "core": { + "color": { + "value": "#FFFFFF", + "type": "color" + } + }, + "semantic": { + "color": { + "value": "{core.color}", + "type": "color" + } + } + }, + "bar": { + "button": { + "color": { + "value": "{semantic.color}", + "type": "color" + } + } + } +} diff --git a/test/spec/parsers/excludeParentKeys.spec.ts b/test/spec/parsers/excludeParentKeys.spec.ts new file mode 100644 index 0000000..53d3bcc --- /dev/null +++ b/test/spec/parsers/excludeParentKeys.spec.ts @@ -0,0 +1,58 @@ +import { expect } from '@esm-bundle/chai'; +import { DeepKeyTokenMap } from '@tokens-studio/types'; +import { excludeParentKeys } from '../../../src/parsers/exclude-parent-keys.js'; + +const tokenObj = { + foo: { + core: { + color: { + value: '#FFFFFF', + type: 'color', + }, + }, + semantic: { + color: { + value: '{core.color}', + type: 'color', + }, + }, + }, + bar: { + button: { + color: { + value: '{semantic.color}', + type: 'color', + }, + }, + }, +} as DeepKeyTokenMap; + +describe('exclude parent keys', () => { + it('should not exclude parent keys by default', () => { + expect(excludeParentKeys(tokenObj)).to.eql(tokenObj); + expect(excludeParentKeys(tokenObj, { excludeParentKeys: false })).to.eql(tokenObj); + }); + + it('should exclude parent keys if the option is passed', () => { + expect(excludeParentKeys(tokenObj, { excludeParentKeys: true })).to.eql({ + core: { + color: { + value: '#FFFFFF', + type: 'color', + }, + }, + semantic: { + color: { + value: '{core.color}', + type: 'color', + }, + }, + button: { + color: { + value: '{semantic.color}', + type: 'color', + }, + }, + }); + }); +});