From 4bf68a3b2529d7e5e3856113b30fbf810547996e Mon Sep 17 00:00:00 2001 From: jorenbroekema Date: Fri, 8 Nov 2024 14:08:04 +0100 Subject: [PATCH] feat: apply stripMeta option to new javascript/esm format --- .changeset/violet-goats-smile.md | 5 + .../__snapshots__/es6Module.test.snap.js | 125 ++++++++++++++++- __tests__/formats/es6Module.test.js | 129 +++++++++++++++++- .../reference/Hooks/Formats/predefined.md | 24 +++- lib/common/formats.js | 71 ++++++---- lib/utils/stripMeta.js | 5 +- 6 files changed, 322 insertions(+), 37 deletions(-) create mode 100644 .changeset/violet-goats-smile.md diff --git a/.changeset/violet-goats-smile.md b/.changeset/violet-goats-smile.md new file mode 100644 index 000000000..a6e7a5112 --- /dev/null +++ b/.changeset/violet-goats-smile.md @@ -0,0 +1,5 @@ +--- +'style-dictionary': minor +--- + +Apply stripMeta from "json" format to the new "javascript/esm" as well. diff --git a/__tests__/formats/__snapshots__/es6Module.test.snap.js b/__tests__/formats/__snapshots__/es6Module.test.snap.js index 9be090669..258e48e9f 100644 --- a/__tests__/formats/__snapshots__/es6Module.test.snap.js +++ b/__tests__/formats/__snapshots__/es6Module.test.snap.js @@ -7,12 +7,133 @@ snapshots["formats javascript/esm should be a valid JS file and match snapshot"] */ export default { - color: { + colors: { red: { - value: "#FF0000", + 500: { + value: "#ff0000", + type: "color", + path: ["colors", "red", "500"], + filePath: "tokens.json", + attributes: { + foo: "bar", + }, + name: "colors-red-500", + }, + }, + }, + dimensions: { + xs: { + value: "15px", + type: "dimension", + path: ["dimension", "xs"], + filePath: "tokens.json", + attributes: { + foo: "bar", + }, + name: "dimension-xs", }, }, }; `; /* end snapshot formats javascript/esm should be a valid JS file and match snapshot */ +snapshots["formats javascript/esm should optionally allow stripping StyleDictionary metadata"] = +`/** + * Do not edit directly, this file was auto-generated. + */ + +export default { + colors: { + red: { + 500: { + value: "#ff0000", + type: "color", + }, + }, + }, + dimensions: { + xs: { + value: "15px", + type: "dimension", + }, + }, +}; +`; +/* end snapshot formats javascript/esm should optionally allow stripping StyleDictionary metadata */ + +snapshots["formats javascript/esm should optionally allow stripping everything but an allowlist of props"] = +`/** + * Do not edit directly, this file was auto-generated. + */ + +export default { + colors: { + red: { + 500: { + value: "#ff0000", + type: "color", + }, + }, + }, + dimensions: { + xs: { + value: "15px", + type: "dimension", + }, + }, +}; +`; +/* end snapshot formats javascript/esm should optionally allow stripping everything but an allowlist of props */ + +snapshots["formats javascript/esm should optionally allow stripping custom list of metadata props"] = +`/** + * Do not edit directly, this file was auto-generated. + */ + +export default { + colors: { + red: { + 500: { + value: "#ff0000", + type: "color", + path: ["colors", "red", "500"], + name: "colors-red-500", + }, + }, + }, + dimensions: { + xs: { + value: "15px", + type: "dimension", + path: ["dimension", "xs"], + name: "dimension-xs", + }, + }, +}; +`; +/* end snapshot formats javascript/esm should optionally allow stripping custom list of metadata props */ + +snapshots["formats javascript/esm should optionally allow stripping StyleDictionary metadata for DTCG formatted tokens"] = +`/** + * Do not edit directly, this file was auto-generated. + */ + +export default { + colors: { + red: { + 500: { + $value: "#ff0000", + $type: "color", + }, + }, + }, + dimensions: { + xs: { + $value: "15px", + $type: "dimension", + }, + }, +}; +`; +/* end snapshot formats javascript/esm should optionally allow stripping StyleDictionary metadata for DTCG formatted tokens */ + diff --git a/__tests__/formats/es6Module.test.js b/__tests__/formats/es6Module.test.js index 05cfae406..b6738174a 100644 --- a/__tests__/formats/es6Module.test.js +++ b/__tests__/formats/es6Module.test.js @@ -24,8 +24,60 @@ const file = { }; const tokens = { - color: { - red: { value: '#FF0000' }, + colors: { + red: { + 500: { + value: '#ff0000', + type: 'color', + path: ['colors', 'red', '500'], + filePath: 'tokens.json', + attributes: { + foo: 'bar', + }, + name: 'colors-red-500', + }, + }, + }, + dimensions: { + xs: { + value: '15px', + type: 'dimension', + path: ['dimension', 'xs'], + filePath: 'tokens.json', + attributes: { + foo: 'bar', + }, + name: 'dimension-xs', + }, + }, +}; + +const DTCGTokens = { + colors: { + red: { + 500: { + $value: '#ff0000', + $type: 'color', + path: ['colors', 'red', '500'], + filePath: 'tokens.json', + attributes: { + foo: 'bar', + }, + name: 'colors-red-500', + }, + }, + }, + dimensions: { + xs: { + $value: '15px', + $type: 'dimension', + path: ['dimension', 'xs'], + filePath: 'tokens.json', + attributes: { + foo: 'bar', + }, + name: 'dimension-xs', + }, }, }; @@ -46,5 +98,78 @@ describe('formats', () => { ), ).to.matchSnapshot(); }); + + it('should optionally allow stripping StyleDictionary metadata', async () => { + await expect( + await format( + createFormatArgs({ + dictionary: { tokens, allTokens: flattenTokens(tokens) }, + file, + platform: {}, + options: { + stripMeta: true, + }, + }), + {}, + file, + ), + ).to.matchSnapshot(); + }); + + it('should optionally allow stripping everything but an allowlist of props', async () => { + await expect( + await format( + createFormatArgs({ + dictionary: { tokens, allTokens: flattenTokens(tokens) }, + file, + platform: {}, + options: { + stripMeta: { + keep: ['value', 'type'], + }, + }, + }), + {}, + file, + ), + ).to.matchSnapshot(); + }); + + it('should optionally allow stripping custom list of metadata props', async () => { + await expect( + await format( + createFormatArgs({ + dictionary: { tokens, allTokens: flattenTokens(tokens) }, + file, + platform: {}, + options: { + stripMeta: { + strip: ['attributes', 'filePath'], + }, + }, + }), + {}, + file, + ), + ).to.matchSnapshot(); + }); + + it('should optionally allow stripping StyleDictionary metadata for DTCG formatted tokens', async () => { + await expect( + await format( + createFormatArgs({ + dictionary: { tokens: DTCGTokens, allTokens: flattenTokens(DTCGTokens) }, + file, + platform: {}, + options: { + usesDtcg: true, + stripMeta: true, + }, + }), + {}, + file, + ), + ).to.matchSnapshot(); + }); }); }); diff --git a/docs/src/content/docs/reference/Hooks/Formats/predefined.md b/docs/src/content/docs/reference/Hooks/Formats/predefined.md index bf8eabd02..213f0873f 100644 --- a/docs/src/content/docs/reference/Hooks/Formats/predefined.md +++ b/docs/src/content/docs/reference/Hooks/Formats/predefined.md @@ -308,10 +308,13 @@ Creates an ES6 module object of the style dictionary. } ``` -| Param | Type | Description | -| ---------------- | --------- | --------------------------------------------------------- | -| `options` | `Object` | | -| `options.minify` | `boolean` | Whether or not to minify the output. Defaults to `false`. | +| Param | Type | Description | +| ------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `options` | `Object` | | +| `options.minify` | `boolean` | Whether or not to minify the output. Defaults to `false`. | +| `options.stripMeta` | `Object \| boolean` | Control whether meta data is stripped from the output tokens. `false` by default. If set to `true`, will strip known Style Dictionary meta props: `['attributes', 'filePath', 'name', 'path', 'comment']`. Note that using minify means that meta is stripped as a side effect of this already, so using both is unnecessary. | +| `options.stripMeta.keep` | `string[]` | Array of property keys to keep in the output. | +| `options.stripMeta.strip` | `string[]` | Array of property keys to strip from the output. | Example: @@ -348,6 +351,19 @@ export default { }; ``` +Example with `stripMeta` flag: + +```js title="vars.js" +export default { + colors: { + black: { + $value: '#000000', + $type: 'color', + }, + }, +}; +``` + --- ### typescript/es6-declarations diff --git a/lib/common/formats.js b/lib/common/formats.js index 30f4c113e..80b94c67d 100644 --- a/lib/common/formats.js +++ b/lib/common/formats.js @@ -10,10 +10,10 @@ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ -import * as prettier from 'prettier/standalone.mjs'; -import prettierPluginBabel from 'prettier/plugins/babel.mjs'; -import prettierPluginEstree from 'prettier/plugins/estree.mjs'; -import prettierPluginTypescript from 'prettier/plugins/typescript.mjs'; +import * as prettier from 'prettier/standalone'; +import prettierPluginBabel from 'prettier/plugins/babel'; +import prettierPluginEstree from 'prettier/plugins/estree'; +import prettierPluginTypescript from 'prettier/plugins/typescript'; import { fileHeader, @@ -28,7 +28,7 @@ import { setComposeObjectProperties, } from './formatHelpers/index.js'; -import { stripMeta } from '../utils/stripMeta.js'; +import { stripMeta as stripMetaUtil } from '../utils/stripMeta.js'; import androidColors from './templates/android/colors.template.js'; import androidDimens from './templates/android/dimens.template.js'; @@ -60,12 +60,40 @@ import plistTemplate from './templates/ios/plist.template.js'; * @typedef {import('../../types/Format.d.ts').OutputReferences} OutputReferences * @typedef {import('../../types/DesignToken.d.ts').TransformedToken} Token * @typedef {import('../../types/DesignToken.d.ts').TransformedTokens} Tokens + * @typedef {import('../../types/Config.d.ts').Config} Config + * @typedef {import('../../types/Config.d.ts').LocalOptions} LocalOptions + * @typedef {import('../utils/stripMeta.js').StripMetaOptions} StripMetaOptions */ /** * @namespace Formats */ +/** + * Strip meta properties from tokens object + * + * @param {Tokens} tokens + * @param {Config & LocalOptions & { stripMeta: boolean | StripMetaOptions}} options + */ +function stripMetaProps(tokens, options) { + const sdMetaProps = ['attributes', 'filePath', 'name', 'path', 'comment']; + const { stripMeta, usesDtcg } = options; + let opts = /** @type {StripMetaOptions} */ ({ usesDtcg }); + + if (stripMeta) { + if (stripMeta === true) { + opts.strip = sdMetaProps; + } else { + opts = { + usesDtcg: usesDtcg ?? false, + ...stripMeta, + }; + } + tokens = stripMetaUtil(tokens, opts); + } + return tokens; +} + /** * Prettier format JS contents * @param {string} content @@ -573,6 +601,12 @@ const formats = { */ 'javascript/esm': async function ({ dictionary, file, options }) { const { formatting, minify = false } = options; + let { tokens } = dictionary; + tokens = stripMetaProps( + tokens, + /** @type {LocalOptions & Config & { stripMeta: boolean | StripMetaOptions}} */ (options), + ); + const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), @@ -580,7 +614,7 @@ const formats = { }); const dictionaryString = JSON.stringify( - minify ? minifyDictionary(dictionary.tokens, options.usesDtcg) : dictionary.tokens, + minify ? minifyDictionary(tokens, options.usesDtcg) : tokens, null, 2, ); @@ -1398,28 +1432,11 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * ``` */ json: function ({ dictionary, options }) { - const { usesDtcg } = options; - - /** @type {boolean | { keep?: string[]; strip?: string[] }} */ - const stripMetaOptions = options.stripMeta; - const sdMetaProps = ['attributes', 'filePath', 'name', 'path', 'comment']; let { tokens } = dictionary; - - if (stripMetaOptions) { - /** @type {{ strip?: string[]; keep?: string[]; usesDtcg?: boolean }} */ - let opts = { usesDtcg }; - - if (stripMetaOptions === true) { - opts.strip = sdMetaProps; - } else { - opts = { - ...opts, - ...stripMetaOptions, - }; - } - tokens = stripMeta(tokens, opts); - } - + tokens = stripMetaProps( + tokens, + /** @type {LocalOptions & Config & { stripMeta: boolean | StripMetaOptions}} */ (options), + ); return JSON.stringify(tokens, null, 2) + '\n'; }, diff --git a/lib/utils/stripMeta.js b/lib/utils/stripMeta.js index 31f17b66f..0ab8b6516 100644 --- a/lib/utils/stripMeta.js +++ b/lib/utils/stripMeta.js @@ -13,11 +13,12 @@ /** * @typedef {import('../../types/DesignToken.d.ts').TransformedTokens} TransformedTokens + * @typedef {{strip?: string[]; keep?: string[]; usesDtcg?: boolean;}} StripMetaOptions */ /** * @param {TransformedTokens} obj - * @param {{ strip?: string[]; keep?: string[]; usesDtcg?: boolean }} options + * @param {StripMetaOptions} options */ export function _stripMeta(obj, options) { const { strip, keep, usesDtcg } = options; @@ -42,7 +43,7 @@ export function _stripMeta(obj, options) { /** * @param {TransformedTokens} obj - * @param {{ strip?: string[]; keep?: string[]; usesDtcg?: boolean }} options + * @param {StripMetaOptions} options */ export function stripMeta(obj, options) { const clone = structuredClone(obj);