diff --git a/.changeset/rich-pianos-walk.md b/.changeset/rich-pianos-walk.md new file mode 100644 index 000000000..188105b1f --- /dev/null +++ b/.changeset/rich-pianos-walk.md @@ -0,0 +1,5 @@ +--- +'style-dictionary': minor +--- + +Support W3C Draft specification for Design Tokens, by adding support for $value, $type and $description properties. diff --git a/.eslintrc.json b/.eslintrc.json index 178d34929..f2a531d7b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,16 +8,21 @@ "mocha": true, "es6": true }, - "globals": { - "Buffer": true, - "escape": true - }, "extends": ["eslint:recommended"], + "plugins": ["mocha"], "rules": { - "no-console": 0, "no-unused-vars": 1, - "no-control-regex": 0, - "comma-dangle": 0, - "no-prototype-builtins": 0 - } + "no-console": ["error", { "allow": ["warn", "error"] }], + "mocha/no-skipped-tests": "warn", + "mocha/no-exclusive-tests": "error", + "no-var": "error" + }, + "overrides": [ + { + "files": ["examples/**/*.js"], + "rules": { + "no-console": 0 + } + } + ] } diff --git a/__integration__/__snapshots__/w3c-forward-compat.test.snap.js b/__integration__/__snapshots__/w3c-forward-compat.test.snap.js new file mode 100644 index 000000000..5bc862cdb --- /dev/null +++ b/__integration__/__snapshots__/w3c-forward-compat.test.snap.js @@ -0,0 +1,16 @@ +/* @web/test-runner snapshot v1 */ +export const snapshots = {}; + +snapshots["should match snapshot"] = +`/** + * Do not edit directly + * Generated on Sat, 01 Jan 2000 00:00:00 GMT + */ + +:root { + --colors-black-500: rgb(0, 0, 0); + --colors-black-dimension: 5px; /* Some description */ +} +`; +/* end snapshot should match snapshot */ + diff --git a/__integration__/_constants.js b/__integration__/_constants.js index d7588175a..dbc0dba15 100644 --- a/__integration__/_constants.js +++ b/__integration__/_constants.js @@ -6,6 +6,7 @@ export const cleanConsoleOutput = (str) => { // https://github.com/chalk/ansi-regex/blob/main/index.js#L3 .map((s) => s + // eslint-disable-next-line no-control-regex .replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '') .trim(), ); diff --git a/__integration__/w3c-forward-compat.test.js b/__integration__/w3c-forward-compat.test.js new file mode 100644 index 000000000..b47add1f4 --- /dev/null +++ b/__integration__/w3c-forward-compat.test.js @@ -0,0 +1,92 @@ +/* + * 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 StyleDictionary from 'style-dictionary'; +import { fs } from 'style-dictionary/fs'; +import Color from 'tinycolor2'; +import { resolve } from '../lib/resolve.js'; +import { buildPath } from './_constants.js'; +import { clearOutput } from '../__tests__/__helpers.js'; + +describe('integration', () => { + afterEach(() => { + clearOutput(buildPath); + }); + + /** + * Integration test for forward compatibility with https://design-tokens.github.io/community-group/format/ + * - $value special property + * - $type special property & inherits from ancestors + * - $description special property + */ + describe('W3C DTCG draft spec forward compatibility', async () => { + const sd = new StyleDictionary({ + tokens: { + colors: { + $type: 'color', // $type should be inherited by the children + black: { + 500: { + $value: '#000000', // $value should work + value: {}, // should be allowed as $ prop takes precedence -> bad practice though + type: 'dimension', // should be allowed as the inherited $type takes precedence -> bad practice though + }, + dimension: { + $value: '5', + value: 'something else', // should be allowed as $ prop takes precedence -> bad practice though + $type: 'dimension', + type: 'color', // should be allowed as $ prop takes precedence -> bad practice though + $description: 'Some description', + }, + }, + }, + }, + transform: { + 'custom/css/color': { + type: 'value', + matcher: (token) => token.$type === 'color', + transformer: (token) => { + return Color(token.$value).toRgbString(); + }, + }, + 'custom/add/px': { + type: 'value', + matcher: (token) => token.$type === 'dimension', + transformer: (token) => { + return `${token.$value}px`; + }, + }, + }, + platforms: { + css: { + transforms: ['name/cti/kebab', 'custom/css/color', 'custom/add/px'], + buildPath, + files: [ + { + destination: 'vars.css', + format: 'css/variables', + }, + ], + }, + }, + }); + await sd.buildAllPlatforms(); + + const output = fs.readFileSync(resolve(`${buildPath}vars.css`), { + encoding: `UTF-8`, + }); + + it(`should match snapshot`, async () => { + await expect(output).to.matchSnapshot(); + }); + }); +}); diff --git a/__tests__/StyleDictionary.test.js b/__tests__/StyleDictionary.test.js index 78cc43dbc..dcc687967 100644 --- a/__tests__/StyleDictionary.test.js +++ b/__tests__/StyleDictionary.test.js @@ -82,7 +82,7 @@ describe('StyleDictionary class + extend method', () => { }); const output = fileToJSON('__tests__/__tokens/paddings.json'); traverseObj(output, (obj) => { - if (obj.hasOwnProperty('value') && !obj.filePath) { + if (Object.hasOwn(obj, 'value') && !obj.filePath) { obj.filePath = '__tests__/__tokens/paddings.json'; obj.isSource = false; } @@ -98,7 +98,7 @@ describe('StyleDictionary class + extend method', () => { }); const output = fileToJSON('__tests__/__tokens/paddings.json'); traverseObj(output, (obj) => { - if (obj.hasOwnProperty('value') && !obj.filePath) { + if (Object.hasOwn(obj, 'value') && !obj.filePath) { obj.filePath = '__tests__/__tokens/paddings.json'; obj.isSource = false; } @@ -148,7 +148,7 @@ describe('StyleDictionary class + extend method', () => { }); const output = fileToJSON('__tests__/__tokens/paddings.json'); traverseObj(output, (obj) => { - if (obj.hasOwnProperty('value') && !obj.filePath) { + if (Object.hasOwn(obj, 'value') && !obj.filePath) { obj.filePath = '__tests__/__tokens/paddings.json'; obj.isSource = true; } @@ -164,7 +164,7 @@ describe('StyleDictionary class + extend method', () => { }); const output = fileToJSON('__tests__/__tokens/paddings.json'); traverseObj(output, (obj) => { - if (obj.hasOwnProperty('value') && !obj.filePath) { + if (Object.hasOwn(obj, 'value') && !obj.filePath) { obj.filePath = filePath; obj.isSource = true; } @@ -180,7 +180,7 @@ describe('StyleDictionary class + extend method', () => { }); const output = fileToJSON('__tests__/__tokens/paddings.json'); traverseObj(output, (obj) => { - if (obj.hasOwnProperty('value') && !obj.filePath) { + if (Object.hasOwn(obj, 'value') && !obj.filePath) { obj.filePath = '__tests__/__tokens/paddings.json'; obj.isSource = true; } @@ -200,7 +200,7 @@ describe('StyleDictionary class + extend method', () => { }); const output = fileToJSON('__tests__/__tokens/paddings.json'); traverseObj(output, (obj) => { - if (obj.hasOwnProperty('value') && !obj.filePath) { + if (Object.hasOwn(obj, 'value') && !obj.filePath) { obj.filePath = '__tests__/__tokens/paddings.json'; obj.isSource = true; } @@ -288,4 +288,42 @@ describe('StyleDictionary class + extend method', () => { expect(obj.polluted).to.be.undefined; }); + + it('should allow using $type value on a token group, children inherit, local overrides take precedence', async () => { + const sd = new StyleDictionary({ + tokens: { + dimensions: { + $type: 'dimension', + sm: { + $value: '5', + }, + md: { + $value: '10', + }, + nested: { + deep: { + lg: { + $value: '15', + }, + }, + }, + nope: { + $value: '20', + $type: 'spacing', + }, + }, + }, + platforms: { + css: { + transformGroup: 'css', + }, + }, + }); + await sd.hasInitialized; + + expect(sd.tokens.dimensions.sm.$type).to.equal('dimension'); + expect(sd.tokens.dimensions.md.$type).to.equal('dimension'); + expect(sd.tokens.dimensions.nested.deep.lg.$type).to.equal('dimension'); + expect(sd.tokens.dimensions.nope.$type).to.equal('spacing'); + }); }); diff --git a/__tests__/common/filters.test.js b/__tests__/common/filters.test.js index a6caf8cd5..7eba8a9b2 100644 --- a/__tests__/common/filters.test.js +++ b/__tests__/common/filters.test.js @@ -17,7 +17,7 @@ describe('common', () => { describe('filters', () => { describe('removePrivate', () => { it('should keep a regular token in for distribution', () => { - var regularToken = { + const regularToken = { name: 'color-border', value: '#1a1aed', }; @@ -26,7 +26,7 @@ describe('common', () => { }); it('should keep an unfiltered token in for distribution', () => { - var unfilteredToken = { + const unfilteredToken = { name: 'color-border', value: '#1a1aed', private: false, @@ -36,7 +36,7 @@ describe('common', () => { }); it('should remove a filtered token from the distribution output', () => { - var filteredToken = { + const filteredToken = { name: 'color-border', value: '#1a1aed', private: true, diff --git a/__tests__/common/formatHelpers/__snapshots__/createPropertyFormatter.test.snap.js b/__tests__/common/formatHelpers/__snapshots__/createPropertyFormatter.test.snap.js index 8862a3c36..7a0b2aa62 100644 --- a/__tests__/common/formatHelpers/__snapshots__/createPropertyFormatter.test.snap.js +++ b/__tests__/common/formatHelpers/__snapshots__/createPropertyFormatter.test.snap.js @@ -34,3 +34,21 @@ snapshots["common formatHelpers createPropertyFormatter commentStyle allows over $color-green: #00FF00;`; /* end snapshot common formatHelpers createPropertyFormatter commentStyle allows overriding formatting commentStyle 2 */ +snapshots["common formatHelpers createPropertyFormatter commentStyle supports W3C spec $description property for comments 1"] = +` --color-red: #FF0000; /* Foo bar qux red */`; +/* end snapshot common formatHelpers createPropertyFormatter commentStyle supports W3C spec $description property for comments 1 */ + +snapshots["common formatHelpers createPropertyFormatter commentStyle supports W3C spec $description property for comments 2"] = +` --color-green: #00FF00; /* Foo bar qux green */`; +/* end snapshot common formatHelpers createPropertyFormatter commentStyle supports W3C spec $description property for comments 2 */ + +snapshots["common formatHelpers createPropertyFormatter commentStyle supports W3C spec $description property for comments 3"] = +` /** + * Foo + * bar + * qux + * blue + */ + --color-blue: #0000FF;`; +/* end snapshot common formatHelpers createPropertyFormatter commentStyle supports W3C spec $description property for comments 3 */ + diff --git a/__tests__/common/formatHelpers/createPropertyFormatter.test.js b/__tests__/common/formatHelpers/createPropertyFormatter.test.js index c29c0db1e..705ba6e4c 100644 --- a/__tests__/common/formatHelpers/createPropertyFormatter.test.js +++ b/__tests__/common/formatHelpers/createPropertyFormatter.test.js @@ -290,16 +290,6 @@ describe('common', () => { }, path: ['color', 'red'], }, - blue: { - name: 'color-blue', - value: '#0000FF', - comment: 'Foo\nbar\nqux', - attributes: { - category: 'color', - type: 'blue', - }, - path: ['color', 'blue'], - }, green: { name: 'color-green', value: '#00FF00', @@ -310,6 +300,16 @@ describe('common', () => { }, path: ['color', 'green'], }, + blue: { + name: 'color-blue', + value: '#0000FF', + comment: 'Foo\nbar\nqux', + attributes: { + category: 'color', + type: 'blue', + }, + path: ['color', 'blue'], + }, }, }; @@ -356,9 +356,58 @@ describe('common', () => { const sassRed = sassFormatter(commentDictionary.color.green); await expect(cssRed).to.matchSnapshot(1); - await expect(sassRed).to.matchSnapshot(2); }); + + it('supports W3C spec $description property for comments', async () => { + const descriptionDictionary = { + color: { + red: { + name: 'color-red', + value: '#FF0000', + $description: 'Foo bar qux red', + attributes: { + category: 'color', + type: 'red', + }, + path: ['color', 'red'], + }, + green: { + name: 'color-green', + value: '#00FF00', + $description: 'Foo bar qux green', + attributes: { + category: 'color', + type: 'green', + }, + path: ['color', 'green'], + }, + blue: { + name: 'color-blue', + value: '#0000FF', + $description: 'Foo\nbar\nqux\nblue', + attributes: { + category: 'color', + type: 'blue', + }, + path: ['color', 'blue'], + }, + }, + }; + // long commentStyle + const cssFormatter = createPropertyFormatter({ + format: 'css', + dictionary: { tokens: descriptionDictionary }, + }); + + const cssRed = cssFormatter(descriptionDictionary.color.red); + const cssGreen = cssFormatter(descriptionDictionary.color.green); + const cssBlue = cssFormatter(descriptionDictionary.color.blue); + + await expect(cssRed).to.matchSnapshot(1); + await expect(cssGreen).to.matchSnapshot(2); + await expect(cssBlue).to.matchSnapshot(3); + }); }); }); }); diff --git a/__tests__/common/transforms.test.js b/__tests__/common/transforms.test.js index 7187825f2..207dd6dd2 100644 --- a/__tests__/common/transforms.test.js +++ b/__tests__/common/transforms.test.js @@ -175,7 +175,7 @@ describe('common', () => { describe('attribute/color', () => { it('should handle normal colors', () => { - var attributes = transforms['attribute/color'].transformer({ + const attributes = transforms['attribute/color'].transformer({ value: '#aaaaaa', }); expect(attributes).to.have.nested.property('rgb.a', 1); @@ -183,10 +183,10 @@ describe('common', () => { expect(attributes).to.have.nested.property('hsl.s', 0); }); it('should handle colors with transparency', () => { - var attributes = transforms['attribute/color'].transformer({ + const attributes = transforms['attribute/color'].transformer({ value: '#aaaaaa99', }); - var attributes2 = transforms['attribute/color'].transformer({ + const attributes2 = transforms['attribute/color'].transformer({ value: 'rgba(170,170,170,0.6)', }); expect(attributes).to.have.nested.property('rgb.a', 0.6); @@ -242,35 +242,35 @@ describe('common', () => { describe('color/hex', () => { it('should handle hex colors', () => { - var value = transforms['color/hex'].transformer({ + const value = transforms['color/hex'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('#aaaaaa'); }); it('should handle hex8 colors', () => { - var value = transforms['color/hex'].transformer({ + const value = transforms['color/hex'].transformer({ value: '#aaaaaaaa', }); expect(value).to.equal('#aaaaaa'); }); it('should handle rgb colors', () => { - var value = transforms['color/hex'].transformer({ + const value = transforms['color/hex'].transformer({ value: 'rgb(170,170,170)', }); expect(value).to.equal('#aaaaaa'); }); it('should handle rgb (object) colors', () => { - var value = transforms['color/hex'].transformer({ + const value = transforms['color/hex'].transformer({ value: { r: '170', g: '170', b: '170', }, }); - var value2 = transforms['color/hex'].transformer({ + const value2 = transforms['color/hex'].transformer({ value: 'rgb(170,170,170)', }); expect(value).to.equal('#aaaaaa'); @@ -278,14 +278,14 @@ describe('common', () => { }); it('should handle hsl colors', () => { - var value = transforms['color/hex'].transformer({ + const value = transforms['color/hex'].transformer({ value: { h: '0', s: '0', l: '0.5', }, }); - var value2 = transforms['color/hex'].transformer({ + const value2 = transforms['color/hex'].transformer({ value: 'hsl(0,0,0.5)', }); expect(value).to.equal('#808080'); @@ -295,21 +295,21 @@ describe('common', () => { describe('color/hex8', () => { it('should handle hex colors', () => { - var value = transforms['color/hex8'].transformer({ + const value = transforms['color/hex8'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('#aaaaaaff'); }); it('should handle rgb colors', () => { - var value = transforms['color/hex8'].transformer({ + const value = transforms['color/hex8'].transformer({ value: 'rgb(170,170,170)', }); expect(value).to.equal('#aaaaaaff'); }); it('should handle rgba colors', () => { - var value = transforms['color/hex8'].transformer({ + const value = transforms['color/hex8'].transformer({ value: 'rgba(170,170,170,0.6)', }); expect(value).to.equal('#aaaaaa99'); @@ -318,14 +318,14 @@ describe('common', () => { describe('color/hex8android', () => { it('should handle colors without alpha', () => { - var value = transforms['color/hex8android'].transformer({ + const value = transforms['color/hex8android'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('#ffaaaaaa'); }); it('should handle colors with alpha', () => { - var value = transforms['color/hex8android'].transformer({ + const value = transforms['color/hex8android'].transformer({ value: '#aaaaaa99', }); expect(value).to.equal('#99aaaaaa'); @@ -334,14 +334,14 @@ describe('common', () => { describe('color/rgb', () => { it('should handle normal colors', () => { - var value = transforms['color/rgb'].transformer({ + const value = transforms['color/rgb'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('rgb(170, 170, 170)'); }); it('should handle colors with transparency', () => { - var value = transforms['color/rgb'].transformer({ + const value = transforms['color/rgb'].transformer({ value: '#aaaaaa99', }); expect(value).to.equal('rgba(170, 170, 170, 0.6)'); @@ -350,14 +350,14 @@ describe('common', () => { describe('color/hsl-4', () => { it('should handle normal colors', () => { - var value = transforms['color/hsl-4'].transformer({ + const value = transforms['color/hsl-4'].transformer({ value: '#009688', }); expect(value).to.equal('hsl(174 100% 29%)'); }); it('should handle colors with transparency', () => { - var value = transforms['color/hsl-4'].transformer({ + const value = transforms['color/hsl-4'].transformer({ value: '#00968899', }); expect(value).to.equal('hsl(174 100% 29% / 0.6)'); @@ -366,14 +366,14 @@ describe('common', () => { describe('color/hsl', () => { it('should handle normal colors', () => { - var value = transforms['color/hsl'].transformer({ + const value = transforms['color/hsl'].transformer({ value: '#009688', }); expect(value).to.equal('hsl(174, 100%, 29%)'); }); it('should handle colors with transparency', () => { - var value = transforms['color/hsl'].transformer({ + const value = transforms['color/hsl'].transformer({ value: '#00968899', }); expect(value).to.equal('hsla(174, 100%, 29%, 0.6)'); @@ -382,14 +382,14 @@ describe('common', () => { describe('color/composeColor', () => { it('should handle color without alpha', () => { - var value = transforms['color/composeColor'].transformer({ + const value = transforms['color/composeColor'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('Color(0xffaaaaaa)'); }); it('should handle color with alpha', () => { - var value = transforms['color/composeColor'].transformer({ + const value = transforms['color/composeColor'].transformer({ value: '#aaaaaaff', }); expect(value).to.equal('Color(0xffaaaaaa)'); @@ -398,7 +398,7 @@ describe('common', () => { describe('color/UIColor', () => { it('should handle normal colors', () => { - var value = transforms['color/UIColor'].transformer({ + const value = transforms['color/UIColor'].transformer({ value: '#aaaaaa', }); expect(value).to.equal( @@ -407,7 +407,7 @@ describe('common', () => { }); it('should retain enough precision when converting to decimal', () => { - var value = transforms['color/UIColor'].transformer({ + const value = transforms['color/UIColor'].transformer({ value: '#1d1d1d', }); expect(value).to.equal( @@ -416,7 +416,7 @@ describe('common', () => { }); it('should handle colors with transparency', () => { - var value = transforms['color/UIColor'].transformer({ + const value = transforms['color/UIColor'].transformer({ value: '#aaaaaa99', }); expect(value).to.equal( @@ -427,21 +427,21 @@ describe('common', () => { describe('color/UIColorSwift', () => { it('should handle normal colors', () => { - var value = transforms['color/UIColorSwift'].transformer({ + const value = transforms['color/UIColorSwift'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('UIColor(red: 0.667, green: 0.667, blue: 0.667, alpha: 1)'); }); it('should retain enough precision when converting to decimal', () => { - var value = transforms['color/UIColorSwift'].transformer({ + const value = transforms['color/UIColorSwift'].transformer({ value: '#1d1d1d', }); expect(value).to.equal('UIColor(red: 0.114, green: 0.114, blue: 0.114, alpha: 1)'); }); it('should handle colors with transparency', () => { - var value = transforms['color/UIColorSwift'].transformer({ + const value = transforms['color/UIColorSwift'].transformer({ value: '#aaaaaa99', }); expect(value).to.equal('UIColor(red: 0.667, green: 0.667, blue: 0.667, alpha: 0.6)'); @@ -450,21 +450,21 @@ describe('common', () => { describe('color/ColorSwiftUI', () => { it('should handle normal colors', () => { - var value = transforms['color/ColorSwiftUI'].transformer({ + const value = transforms['color/ColorSwiftUI'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('Color(red: 0.667, green: 0.667, blue: 0.667, opacity: 1)'); }); it('should retain enough precision when converting to decimal', () => { - var value = transforms['color/ColorSwiftUI'].transformer({ + const value = transforms['color/ColorSwiftUI'].transformer({ value: '#1d1d1d', }); expect(value).to.equal('Color(red: 0.114, green: 0.114, blue: 0.114, opacity: 1)'); }); it('should handle colors with transparency', () => { - var value = transforms['color/ColorSwiftUI'].transformer({ + const value = transforms['color/ColorSwiftUI'].transformer({ value: '#aaaaaa99', }); expect(value).to.equal('Color(red: 0.667, green: 0.667, blue: 0.667, opacity: 0.6)'); @@ -473,14 +473,14 @@ describe('common', () => { describe('color/hex8flutter', () => { it('should handle colors without alpha', () => { - var value = transforms['color/hex8flutter'].transformer({ + const value = transforms['color/hex8flutter'].transformer({ value: '#aaaaaa', }); expect(value).to.equal('Color(0xFFAAAAAA)'); }); it('should handle colors with alpha', () => { - var value = transforms['color/hex8flutter'].transformer({ + const value = transforms['color/hex8flutter'].transformer({ value: '#aaaaaa99', }); expect(value).to.equal('Color(0x99AAAAAA)'); @@ -489,14 +489,14 @@ describe('common', () => { describe('color/css', () => { it('should handle normal colors', () => { - var value = transforms['color/css'].transformer({ + const value = transforms['color/css'].transformer({ value: 'rgb(170, 170, 170)', }); expect(value).to.equal('#aaaaaa'); }); it('should handle colors with transparency', () => { - var value = transforms['color/css'].transformer({ + const value = transforms['color/css'].transformer({ value: '#aaaaaa99', }); expect(value).to.equal('rgba(170, 170, 170, 0.6)'); @@ -505,13 +505,13 @@ describe('common', () => { describe('color/sketch', () => { it('should retain hex specificity', () => { - var originalHex = '#0b7dbb'; - var value = transforms['color/sketch'].transformer({ + const originalHex = '#0b7dbb'; + const value = transforms['color/sketch'].transformer({ original: { value: originalHex, }, }); - var newHex = Color({ + const newHex = Color({ r: value.red * 255, g: value.green * 255, b: value.blue * 255, @@ -522,10 +522,10 @@ describe('common', () => { describe('size/sp', () => { it('should work', () => { - var value = transforms['size/sp'].transformer({ + const value = transforms['size/sp'].transformer({ value: '12px', }); - var value2 = transforms['size/sp'].transformer({ + const value2 = transforms['size/sp'].transformer({ value: '12', }); expect(value).to.equal('12.00sp'); @@ -538,10 +538,10 @@ describe('common', () => { describe('size/dp', () => { it('should work', () => { - var value = transforms['size/dp'].transformer({ + const value = transforms['size/dp'].transformer({ value: '12px', }); - var value2 = transforms['size/dp'].transformer({ + const value2 = transforms['size/dp'].transformer({ value: '12', }); expect(value).to.equal('12.00dp'); @@ -554,7 +554,7 @@ describe('common', () => { describe('size/object', () => { it('should work', () => { - var value = transforms['size/object'].transformer({ + const value = transforms['size/object'].transformer({ value: '1px', }); expect(value.original).to.equal('1px'); @@ -563,7 +563,7 @@ describe('common', () => { expect(value.scale).to.equal(16); }); it('should work with custom base font', () => { - var value = transforms['size/object'].transformer({ value: '1' }, { basePxFontSize: 14 }); + const value = transforms['size/object'].transformer({ value: '1' }, { basePxFontSize: 14 }); expect(value.original).to.equal('1'); expect(value.number).to.equal(1); expect(value.decimal).equal(0.01); @@ -576,13 +576,16 @@ describe('common', () => { describe('size/remToSp', () => { it('should work', () => { - var value = transforms['size/remToSp'].transformer({ + const value = transforms['size/remToSp'].transformer({ value: '1', }); expect(value).to.equal('16.00sp'); }); it('converts rem to sp using custom base font', () => { - var value = transforms['size/remToSp'].transformer({ value: '1' }, { basePxFontSize: 14 }); + const value = transforms['size/remToSp'].transformer( + { value: '1' }, + { basePxFontSize: 14 }, + ); expect(value).to.equal('14.00sp'); }); it('should throw an error if prop value is Nan', () => { @@ -592,13 +595,16 @@ describe('common', () => { describe('size/remToDp', () => { it('should work', () => { - var value = transforms['size/remToDp'].transformer({ + const value = transforms['size/remToDp'].transformer({ value: '1', }); expect(value).to.equal('16.00dp'); }); it('converts rem to dp using custom base font', () => { - var value = transforms['size/remToDp'].transformer({ value: '1' }, { basePxFontSize: 14 }); + const value = transforms['size/remToDp'].transformer( + { value: '1' }, + { basePxFontSize: 14 }, + ); expect(value).to.equal('14.00dp'); }); it('should throw an error if prop value is Nan', () => { @@ -608,7 +614,7 @@ describe('common', () => { describe('size/px', () => { it('should work', () => { - var value = transforms['size/px'].transformer({ + const value = transforms['size/px'].transformer({ value: '10', }); expect(value).to.equal('10px'); @@ -620,13 +626,16 @@ describe('common', () => { describe('size/remToPt', () => { it('should work', () => { - var value = transforms['size/remToPt'].transformer({ + const value = transforms['size/remToPt'].transformer({ value: '1', }); expect(value).to.equal('16.00f'); }); it('converts rem to pt using custom base font', () => { - var value = transforms['size/remToPt'].transformer({ value: '1' }, { basePxFontSize: 14 }); + const value = transforms['size/remToPt'].transformer( + { value: '1' }, + { basePxFontSize: 14 }, + ); expect(value).to.equal('14.00f'); }); it('should throw an error if prop value is Nan', () => { @@ -636,13 +645,13 @@ describe('common', () => { describe('size/compose/remToSp', () => { it('should work', () => { - var value = transforms['size/compose/remToSp'].transformer({ + const value = transforms['size/compose/remToSp'].transformer({ value: '1', }); expect(value).to.equal('16.00.sp'); }); it('converts rem to sp using custom base font', () => { - var value = transforms['size/compose/remToSp'].transformer( + const value = transforms['size/compose/remToSp'].transformer( { value: '1' }, { basePxFontSize: 14 }, ); @@ -655,7 +664,7 @@ describe('common', () => { describe('size/compose/em', () => { it('should work', () => { - var value = transforms['size/compose/em'].transformer({ + const value = transforms['size/compose/em'].transformer({ value: '10', }); expect(value).to.equal('10.em'); @@ -667,13 +676,13 @@ describe('common', () => { describe('size/compose/remToDp', () => { it('should work', () => { - var value = transforms['size/compose/remToDp'].transformer({ + const value = transforms['size/compose/remToDp'].transformer({ value: '1', }); expect(value).to.equal('16.00.dp'); }); it('converts rem to dp using custom base font', () => { - var value = transforms['size/compose/remToDp'].transformer( + const value = transforms['size/compose/remToDp'].transformer( { value: '1' }, { basePxFontSize: 14 }, ); @@ -686,13 +695,13 @@ describe('common', () => { describe('size/swift/remToCGFloat', () => { it('should work', () => { - var value = transforms['size/swift/remToCGFloat'].transformer({ + const value = transforms['size/swift/remToCGFloat'].transformer({ value: '1', }); expect(value).to.equal('CGFloat(16.00)'); }); it('converts rem to CGFloat using custom base font', () => { - var value = transforms['size/swift/remToCGFloat'].transformer( + const value = transforms['size/swift/remToCGFloat'].transformer( { value: '1' }, { basePxFontSize: 14 }, ); @@ -705,13 +714,16 @@ describe('common', () => { describe('size/remToPx', () => { it('should work', () => { - var value = transforms['size/remToPx'].transformer({ + const value = transforms['size/remToPx'].transformer({ value: '1', }); expect(value).to.equal('16px'); }); it('converts rem to px using custom base font', () => { - var value = transforms['size/remToPx'].transformer({ value: '1' }, { basePxFontSize: 14 }); + const value = transforms['size/remToPx'].transformer( + { value: '1' }, + { basePxFontSize: 14 }, + ); expect(value).to.equal('14px'); }); it('should throw an error if prop value is Nan', () => { @@ -742,7 +754,7 @@ describe('common', () => { describe('size/rem', () => { it('should work', () => { - var value = transforms['size/rem'].transformer({ + const value = transforms['size/rem'].transformer({ value: '1', }); expect(value).to.equal('1rem'); @@ -754,13 +766,13 @@ describe('common', () => { describe('size/flutter/remToDouble', () => { it('should work', () => { - var value = transforms['size/flutter/remToDouble'].transformer({ + const value = transforms['size/flutter/remToDouble'].transformer({ value: '1', }); expect(value).to.equal('16.00'); }); it('converts rem to double using custom base font', () => { - var value = transforms['size/flutter/remToDouble'].transformer( + const value = transforms['size/flutter/remToDouble'].transformer( { value: '1' }, { basePxFontSize: 14 }, ); @@ -770,7 +782,7 @@ describe('common', () => { describe('content/quote', () => { it('should work', () => { - var value = transforms['content/quote'].transformer({ + const value = transforms['content/quote'].transformer({ value: 'hello', }); expect(value).to.equal("'hello'"); @@ -779,7 +791,7 @@ describe('common', () => { describe('content/icon', () => { it('should work', () => { - var value = transforms['content/icon'].transformer({ + const value = transforms['content/icon'].transformer({ value: '', }); expect(value).to.equal("'\\E001'"); @@ -788,7 +800,7 @@ describe('common', () => { describe('content/objC/literal', () => { it('should work', () => { - var value = transforms['content/objC/literal'].transformer({ + const value = transforms['content/objC/literal'].transformer({ value: 'hello', }); expect(value).to.equal('@"hello"'); @@ -797,7 +809,7 @@ describe('common', () => { describe('asset/objC/literal', () => { it('should work', () => { - var value = transforms['asset/objC/literal'].transformer({ + const value = transforms['asset/objC/literal'].transformer({ value: 'hello', }); expect(value).to.equal('@"hello"'); @@ -806,7 +818,7 @@ describe('common', () => { describe('font/objC/literal', () => { it('should work', () => { - var value = transforms['font/objC/literal'].transformer({ + const value = transforms['font/objC/literal'].transformer({ value: 'hello', }); expect(value).to.equal('@"hello"'); @@ -815,7 +827,7 @@ describe('common', () => { describe('time/seconds', () => { it('should work', () => { - var value = transforms['time/seconds'].transformer({ + const value = transforms['time/seconds'].transformer({ value: '1000', }); expect(value).to.equal('1.00s'); @@ -827,7 +839,7 @@ describe('common', () => { // the filePath of the token to determine where the asset is located relative to the token that refers to it describe.skip('asset/path', () => { it('should work', () => { - var value = transforms['asset/path'].transformer({ + const value = transforms['asset/path'].transformer({ value: 'foo.json', }); expect(value).to.equal(join('foo.json')); diff --git a/__tests__/exportPlatform.test.js b/__tests__/exportPlatform.test.js index cec0e62c9..7d0add719 100644 --- a/__tests__/exportPlatform.test.js +++ b/__tests__/exportPlatform.test.js @@ -292,7 +292,8 @@ describe('exportPlatform', () => { dangerErrorValue: { value: '{color.errorWithValue}' }, }, }; - const dict = new StyleDictionary({ + + const sd = new StyleDictionary({ tokens, platforms: { css: { @@ -300,7 +301,7 @@ describe('exportPlatform', () => { }, }, }); - const actual = await dict.exportPlatform('css'); + const actual = await sd.exportPlatform('css'); it('should work if referenced directly', () => { expect(actual.color.error.value).to.equal('#ff0000'); @@ -352,7 +353,8 @@ describe('exportPlatform', () => { const transitiveTransform = Object.assign({}, StyleDictionary.transform['color/css'], { transitive: true, }); - const dict = new StyleDictionary({ + + const sd = new StyleDictionary({ tokens, transform: { transitiveTransform, @@ -363,7 +365,8 @@ describe('exportPlatform', () => { }, }, }); - const actual = await dict.exportPlatform('css'); + const actual = await sd.exportPlatform('css'); + it('should work if referenced directly', () => { expect(actual.color.blue.value).to.equal('#3d2c29'); }); @@ -377,55 +380,111 @@ describe('exportPlatform', () => { expect(actual.color.red.value).to.equal('#ff2b00'); }); }); -}); - -describe('reference warnings', () => { - const errorMessage = `Problems were found when trying to resolve property references`; - const platforms = { - css: { - transformGroup: `css`, - }, - }; - it('should throw if there are simple property reference errors', async () => { - const tokens = { - a: '#ff0000', - b: '{c}', + describe('reference warnings', () => { + const errorMessage = `Problems were found when trying to resolve property references`; + const platforms = { + css: { + transformGroup: `css`, + }, }; - const dict = new StyleDictionary({ - tokens, - platforms, + + it('should throw if there are simple property reference errors', async () => { + const tokens = { + a: '#ff0000', + b: '{c}', + }; + + const sd = new StyleDictionary({ + tokens, + platforms, + }); + + await expect(sd.exportPlatform('css')).to.eventually.be.rejectedWith(errorMessage); }); - await expect(dict.exportPlatform('css')).to.eventually.be.rejectedWith(errorMessage); - }); + it('should throw if there are circular reference errors', async () => { + const tokens = { + a: '{b}', + b: '{a}', + }; - it('should throw if there are circular reference errors', async () => { - const tokens = { - a: '{b}', - b: '{a}', - }; - const dict = new StyleDictionary({ - tokens, - platforms, + const sd = new StyleDictionary({ + tokens, + platforms, + }); + await expect(sd.exportPlatform('css')).to.eventually.be.rejectedWith(errorMessage); + }); + + it('should throw if there are complex property reference errors', async () => { + const tokens = { + color: { + core: { + red: { valuer: '#ff0000' }, // notice misspelling + blue: { 'value:': '#0000ff' }, + }, + danger: { value: '{color.core.red.value}' }, + warning: { value: '{color.base.red.valuer}' }, + info: { value: '{color.core.blue.value}' }, + error: { value: '{color.danger.value}' }, + }, + }; + const sd = new StyleDictionary({ + tokens, + platforms, + }); + await expect(sd.exportPlatform('css')).to.eventually.be.rejectedWith(errorMessage); }); - await expect(dict.exportPlatform('css')).to.eventually.be.rejectedWith(errorMessage); }); - it('should throw if there are complex property reference errors', async () => { - const tokens = { - color: { - core: { - red: { valuer: '#ff0000' }, // notice misspelling - blue: { 'value:': '#0000ff' }, + describe('w3c forward compatibility', () => { + it('should allow using $value instead of value, even combining them', async () => { + const sd = new StyleDictionary({ + tokens: { + dimensions: { + sm: { + $value: '5', + type: 'dimension', + }, + md: { + $value: '10', + value: '2000', + type: 'dimension', + }, + lg: { + value: '20', + type: 'dimension', + }, + }, }, - danger: { value: '{color.core.red.value}' }, - warning: { value: '{color.base.red.valuer}' }, - info: { value: '{color.core.blue.value}' }, - error: { value: '{color.danger.value}' }, - }, - }; - const dict = new StyleDictionary({ tokens, platforms }); - await expect(dict.exportPlatform('css')).to.eventually.be.rejectedWith(errorMessage); + transform: { + 'custom/add/px': { + type: 'value', + matcher: (token) => token.type === 'dimension', + transformer: (token) => { + return `${token.$value ?? token.value}px`; + }, + }, + }, + platforms: { + css: { + transforms: ['name/cti/kebab', 'custom/add/px'], + }, + }, + }); + + const tokens = await sd.exportPlatform('css'); + + expect(tokens.dimensions.sm.$value).to.equal('5px'); + expect(tokens.dimensions.md.$value).to.equal('10px'); + + // unaffected, because $value took precedence in both the transform and transformed output + expect(tokens.dimensions.md.value).to.equal('2000'); + + // the transform in this test happens to fallback to regular value prop if $value does not exist + // Whether or not a transform should ignore "value" or use it as a fallback is up to the transform author: + // whether or not they want to support both the old and new format simultaneously + expect(tokens.dimensions.lg.value).to.equal('20px'); + }); }); }); diff --git a/__tests__/formats/__snapshots__/all.test.snap.js b/__tests__/formats/__snapshots__/all.test.snap.js index 4e0d426a0..c647b83b5 100644 --- a/__tests__/formats/__snapshots__/all.test.snap.js +++ b/__tests__/formats/__snapshots__/all.test.snap.js @@ -227,7 +227,8 @@ snapshots["formats all should match typescript/module-declarations snapshot"] = export default tokens; declare interface DesignToken { - value: any; + value?: any; + $value?: any; name?: string; comment?: string; themeable?: boolean; diff --git a/__tests__/transform/property.test.js b/__tests__/transform/property.test.js index 4879823fb..c33cff467 100644 --- a/__tests__/transform/property.test.js +++ b/__tests__/transform/property.test.js @@ -13,7 +13,7 @@ import { expect } from 'chai'; import token from '../../lib/transform/token.js'; -var options = { +const options = { transforms: [ { type: 'attribute', diff --git a/__tests__/transform/propertySetup.test.js b/__tests__/transform/propertySetup.test.js index 587676627..c4a7426d4 100644 --- a/__tests__/transform/propertySetup.test.js +++ b/__tests__/transform/propertySetup.test.js @@ -28,7 +28,7 @@ describe('transform', () => { }); it('should work if all the args are proper', () => { - var test = tokenSetup({ value: '#fff' }, 'white', ['color', 'base']); + const test = tokenSetup({ value: '#fff' }, 'white', ['color', 'base']); expect(typeof test).to.equal('object'); expect(test).to.have.property('value'); expect(test).to.have.property('original'); @@ -37,25 +37,28 @@ describe('transform', () => { }); it('should not do anything and return the property if it has been setup previously', () => { - var original = { value: '#fff', original: {} }; - var test = tokenSetup(original, 'white', ['color', 'base']); + const original = { value: '#fff', original: {} }; + const test = tokenSetup(original, 'white', ['color', 'base']); expect(test).to.eql(original); }); it('should use attributes if already set', () => { - var attributes = { foo: 'bar' }; - var test = tokenSetup({ value: '#fff', attributes: attributes }, 'white', ['color', 'base']); + const attributes = { foo: 'bar' }; + const test = tokenSetup({ value: '#fff', attributes: attributes }, 'white', [ + 'color', + 'base', + ]); expect(test.attributes).to.eql(attributes); }); it('should use the name on the property if set', () => { - var name = 'name'; - var test = tokenSetup({ value: '#fff', name: name }, 'white', ['color', 'base']); + const name = 'name'; + const test = tokenSetup({ value: '#fff', name: name }, 'white', ['color', 'base']); expect(test).to.have.property('name', name); }); it('should use the name passed in if not set on the property', () => { - var test = tokenSetup({ value: '#fff' }, 'white', ['color', 'base']); + const test = tokenSetup({ value: '#fff' }, 'white', ['color', 'base']); expect(test).to.have.property('name', 'white'); }); diff --git a/bin/style-dictionary.js b/bin/style-dictionary.js index 464732649..d07b04c36 100755 --- a/bin/style-dictionary.js +++ b/bin/style-dictionary.js @@ -74,13 +74,14 @@ program console.error('Please supply 1 type of project from: ' + types.join(', ')); process.exit(1); } - + /* eslint-disable no-console */ console.log('Copying starter files...\n'); node_fs.copySync(path.join(__dirname, '..', 'examples', type), process.cwd()); console.log('Source style dictionary starter files created!\n'); console.log( 'Running `style-dictionary build` for the first time to generate build artifacts.\n', ); + /* eslint-disable no-console */ styleDictionaryBuild(); }); diff --git a/examples/advanced/custom-parser/sd.config.js b/examples/advanced/custom-parser/sd.config.js index dfa90cce4..e9fdc0f6c 100644 --- a/examples/advanced/custom-parser/sd.config.js +++ b/examples/advanced/custom-parser/sd.config.js @@ -22,7 +22,7 @@ StyleDictionary.registerParser({ const output = {}; for (const key in object) { - if (object.hasOwnProperty(key)) { + if (Object.hasOwn(object, key)) { const element = object[key]; output[`${pathParts}-${key}`] = element; } diff --git a/examples/advanced/s3/upload.js b/examples/advanced/s3/upload.js index 514dda30e..75a1c02fc 100644 --- a/examples/advanced/s3/upload.js +++ b/examples/advanced/s3/upload.js @@ -1,19 +1,19 @@ -var AWS = require('aws-sdk'), +const AWS = require('aws-sdk'), fs = require('fs-extra'); // Create an S3 client -var s3 = new AWS.S3(); +const s3 = new AWS.S3(); // Enter your bucket name here -var bucketName = 'style-dictionary-test'; +const bucketName = 'style-dictionary-test'; // Change working directory to ./build process.chdir('build'); -var files = fs.walkSync('./'); +const files = fs.walkSync('./'); s3.createBucket({ Bucket: bucketName }, function () { files.forEach(function (file) { - var options = { + const options = { Bucket: bucketName, Key: file, Body: fs.readFileSync(file), diff --git a/lib/StyleDictionary.js b/lib/StyleDictionary.js index 97662f990..095e1ab51 100644 --- a/lib/StyleDictionary.js +++ b/lib/StyleDictionary.js @@ -217,6 +217,7 @@ export default class StyleDictionary extends Register { if (options.log === 'error') { throw new Error(warn); } else { + // eslint-disable-next-line no-console console.log(warn); } } @@ -348,6 +349,7 @@ export default class StyleDictionary extends Register { */ async getPlatform(platform) { await this.hasInitialized; + // eslint-disable-next-line no-console console.log('\n' + platform); if (!this.options?.platforms?.[platform]) { diff --git a/lib/buildFile.js b/lib/buildFile.js index 67a2aef42..1c32d3602 100644 --- a/lib/buildFile.js +++ b/lib/buildFile.js @@ -66,11 +66,12 @@ export default function buildFile(file, platform = {}, dictionary) { // if tokens object is empty, return without creating a file if ( - filteredTokens.hasOwnProperty('tokens') && + Object.hasOwn(filteredTokens, 'tokens') && Object.keys(filteredTokens.tokens).length === 0 && filteredTokens.tokens.constructor === Object ) { let warnNoFile = `No tokens for ${destination}. File not created.`; + // eslint-disable-next-line no-console console.log(chalk.rgb(255, 140, 0)(warnNoFile)); return null; } @@ -95,9 +96,9 @@ export default function buildFile(file, platform = {}, dictionary) { Object.keys(nameCollisionObj).forEach((tokenName) => { if (nameCollisionObj[tokenName].length > 1) { let collisions = nameCollisionObj[tokenName] - .map((tokens) => { - let tokenPathText = chalk.rgb(255, 69, 0)(tokens.path.join('.')); - let valueText = chalk.rgb(255, 140, 0)(tokens.value); + .map((token) => { + let tokenPathText = chalk.rgb(255, 69, 0)(token.path.join('.')); + let valueText = chalk.rgb(255, 140, 0)(token.$value ?? token.value); return tokenPathText + ' ' + valueText; }) .join('\n '); @@ -127,6 +128,7 @@ export default function buildFile(file, platform = {}, dictionary) { // don't show name collision warnings for nested type formats // because they are not relevant. if ((nested || tokenNamesCollisionCount === 0) && filteredReferencesCount === 0) { + // eslint-disable-next-line no-console console.log(chalk.bold.green(`✔︎ ${fullDestination}`)); } else { const warnHeader = `⚠️ ${fullDestination}`; @@ -153,6 +155,7 @@ export default function buildFile(file, platform = {}, dictionary) { if (platform?.log === 'error') { throw new Error(warn); } else { + // eslint-disable-next-line no-console console.log(chalk.rgb(255, 140, 0).bold(warn)); } } @@ -175,6 +178,7 @@ export default function buildFile(file, platform = {}, dictionary) { if (platform?.log === 'error') { throw new Error(warn); } else { + // eslint-disable-next-line no-console console.log(chalk.rgb(255, 140, 0).bold(warn)); } } diff --git a/lib/cleanDir.js b/lib/cleanDir.js index 16e23ccca..4d63432a6 100644 --- a/lib/cleanDir.js +++ b/lib/cleanDir.js @@ -42,6 +42,7 @@ export default function cleanDir(file, platform = {}) { while (dir) { if (fs.existsSync(dir)) { if (fs.readdirSync(dir, 'buffer').length === 0) { + // eslint-disable-next-line no-console console.log(chalk.bold.red('-') + ' ' + dir); fs.rmdirSync(dir); } else { diff --git a/lib/cleanFile.js b/lib/cleanFile.js index a1d0c1e43..f3bdeaf5b 100644 --- a/lib/cleanFile.js +++ b/lib/cleanFile.js @@ -37,10 +37,12 @@ export default function cleanFile(file, platform = {}) { } if (!fs.existsSync(destination)) { + // eslint-disable-next-line no-console console.log(chalk.bold.red('!') + ' ' + destination + ', does not exist'); return; } fs.unlinkSync(destination); + // eslint-disable-next-line no-console console.log(chalk.bold.red('-') + ' ' + destination); } diff --git a/lib/common/actions.js b/lib/common/actions.js index 5b59f3e5c..e28a8eb4a 100644 --- a/lib/common/actions.js +++ b/lib/common/actions.js @@ -42,7 +42,7 @@ export default { const dir = `${imagesDir}${token.attributes.state}`; const path = `${dir}/${name}.png`; fs.mkdirSync(dir, { recursive: true }); - fs.copyFileSync(token.value, path); + fs.copyFileSync(token.$value ?? token.value, path); } }); }, @@ -69,10 +69,12 @@ export default { */ copy_assets: { do: function (dictionary, config) { + // eslint-disable-next-line no-console console.log('Copying assets directory to ' + config.buildPath + 'assets'); fs.copyFileSync('assets', config.buildPath + 'assets'); }, undo: function (dictionary, config) { + // eslint-disable-next-line no-console console.log('Removing assets directory from ' + config.buildPath + 'assets'); fs.unlinkSync(config.buildPath + 'assets'); }, diff --git a/lib/common/formatHelpers/createPropertyFormatter.js b/lib/common/formatHelpers/createPropertyFormatter.js index af0298c86..6d8cf4e1f 100644 --- a/lib/common/formatHelpers/createPropertyFormatter.js +++ b/lib/common/formatHelpers/createPropertyFormatter.js @@ -33,12 +33,12 @@ const defaultFormatting = { /** * Split a string comment by newlines and * convert to multi-line comment if necessary - * @param {string} to_ret_prop + * @param {string} to_ret_token * @param {string} comment * @param {Formatting} options * @returns {string} */ -function addComment(to_ret_prop, comment, options) { +function addComment(to_ret_token, comment, options) { const { commentStyle, indentation } = options; let { commentPosition } = options; @@ -75,17 +75,17 @@ function addComment(to_ret_prop, comment, options) { } if (commentPosition === 'above') { - // put the comment above the prop if it's multi-line or if commentStyle ended with -above - to_ret_prop = `${processedComment}\n${to_ret_prop}`; + // put the comment above the token if it's multi-line or if commentStyle ended with -above + to_ret_token = `${processedComment}\n${to_ret_token}`; } else { - to_ret_prop = `${to_ret_prop} ${processedComment}`; + to_ret_token = `${to_ret_token} ${processedComment}`; } - return to_ret_prop; + return to_ret_token; } /** - * Creates a function that can be used to format a property. This can be useful + * Creates a function that can be used to format a token. This can be useful * to use as the function on `dictionary.allTokens.map`. The formatting * is configurable either by supplying a `format` option or a `formatting` object * which uses: prefix, indentation, separator, suffix, and commentStyle. @@ -111,9 +111,9 @@ function addComment(to_ret_prop, comment, options) { * @param {boolean} [options.outputReferenceFallbacks] - Whether or not to output css variable fallback values when using output references. You will want to pass this from the `options` object sent to the formatter function. * @param {Dictionary} options.dictionary - The dictionary object sent to the formatter function * @param {string} [options.format] - Available formats are: 'css', 'sass', 'less', and 'stylus'. If you want to customize the format and can't use one of those predefined formats, use the `formatting` option - * @param {Formatting} [options.formatting] - Custom formatting properties that define parts of a declaration line in code. The configurable strings are: prefix, indentation, separator, suffix, and commentStyle. Those are used to generate a line like this: `${indentation}${prefix}${prop.name}${separator} ${prop.value}${suffix}` + * @param {Formatting} [options.formatting] - Custom formatting properties that define parts of a declaration line in code. The configurable strings are: prefix, indentation, separator, suffix, and commentStyle. Those are used to generate a line like this: `${indentation}${prefix}${token.name}${separator} ${token.value}${suffix}` * @param {boolean} [options.themeable] [false] - Whether tokens should default to being themeable. - * @returns {(prop: import('../../../types/DesignToken.d.ts').TransformedToken) => string} + * @returns {(token: import('../../../types/DesignToken.d.ts').TransformedToken) => string} */ export default function createPropertyFormatter({ outputReferences = false, @@ -157,9 +157,9 @@ export default function createPropertyFormatter({ }; let { prefix, commentStyle, indentation, separator, suffix } = mergedOptions; const { tokens, unfilteredTokens } = dictionary; - return function (prop) { - let to_ret_prop = `${indentation}${prefix}${prop.name}${separator} `; - let value = prop.value; + return function (token) { + let to_ret_token = `${indentation}${prefix}${token.name}${separator} `; + let value = token.$value ?? token.value; /** * A single value can have multiple references either by interpolation: @@ -173,15 +173,15 @@ export default function createPropertyFormatter({ * This will see if there are references and if there are, replace * the resolved value with the reference's name. */ - if (outputReferences && usesReferences(prop.original.value)) { + if (outputReferences && usesReferences(token.original.value)) { // Formats that use this function expect `value` to be a string // or else you will get '[object Object]' in the output - const refs = getReferences(prop.original.value, tokens, { unfilteredTokens }, []); + const refs = getReferences(token.original.value, tokens, { unfilteredTokens }, []); // original can either be an object value, which requires transitive value transformation in web CSS formats // or a different (primitive) type, meaning it can be stringified. const originalIsObject = - typeof prop.original.value === 'object' && prop.original.value !== null; + typeof token.original.value === 'object' && token.original.value !== null; if (!originalIsObject) { // when original is object value, we replace value by matching ref.value and putting a var instead. @@ -190,7 +190,7 @@ export default function createPropertyFormatter({ // when original is string value, we replace value by matching original.value and putting a var instead // this is more friendly to transitive transforms that transform the string values - value = prop.original.value; + value = token.original.value; } refs.forEach((ref) => { @@ -198,11 +198,15 @@ export default function createPropertyFormatter({ // because Style Dictionary resolved this in the resolution step. // Here we are undoing that by replacing the value with // the reference's name - if (Object.hasOwn(ref, 'value') && Object.hasOwn(ref, 'name')) { + if ( + (Object.hasOwn(ref, 'value') || Object.hasOwn(ref, '$value')) && + Object.hasOwn(ref, 'name') + ) { + const refVal = ref.$value ?? ref.value; const replaceFunc = function () { if (format === 'css') { if (outputReferenceFallbacks) { - return `var(${prefix}${ref.name}, ${ref.value})`; + return `var(${prefix}${ref.name}, ${refVal})`; } else { return `var(${prefix}${ref.name})`; } @@ -211,26 +215,27 @@ export default function createPropertyFormatter({ } }; value = value.replace( - originalIsObject ? ref.value : new RegExp(`{${ref.path.join('.')}(.value)?}`, 'g'), + originalIsObject ? refVal : new RegExp(`{${ref.path.join('\\.')}(\\.\\$?value)?}`, 'g'), replaceFunc, ); } }); } - to_ret_prop += prop?.attributes?.category === 'asset' ? `"${value}"` : value; + to_ret_token += token?.attributes?.category === 'asset' ? `"${value}"` : value; - const themeable_prop = typeof prop.themeable === 'boolean' ? prop.themeable : themeable; - if (format === 'sass' && themeable_prop) { - to_ret_prop += ' !default'; + const themeable_token = typeof token.themeable === 'boolean' ? token.themeable : themeable; + if (format === 'sass' && themeable_token) { + to_ret_token += ' !default'; } - to_ret_prop += suffix; + to_ret_token += suffix; - if (prop.comment && commentStyle !== 'none') { - to_ret_prop = addComment(to_ret_prop, prop.comment, mergedOptions); + const comment = token.$description ?? token.comment; + if (comment && commentStyle !== 'none') { + to_ret_token = addComment(to_ret_token, comment, mergedOptions); } - return to_ret_prop; + return to_ret_token; }; } diff --git a/lib/common/formatHelpers/iconsWithPrefix.js b/lib/common/formatHelpers/iconsWithPrefix.js index 94f096f42..a7d0ba681 100644 --- a/lib/common/formatHelpers/iconsWithPrefix.js +++ b/lib/common/formatHelpers/iconsWithPrefix.js @@ -44,9 +44,9 @@ export default function iconsWithPrefix(prefix, allTokens, options) { return token.attributes?.category === 'content' && token.attributes.type === 'icon'; }) .map(function (token) { - var varName = prefix + token.name + ': ' + token.value + ';'; - var className = '.' + options.prefix + '-icon.' + token.attributes?.item + ':before '; - var declaration = '{ content: ' + prefix + token.name + '; }'; + const varName = prefix + token.name + ': ' + (token.$value ?? token.value) + ';'; + const className = '.' + options.prefix + '-icon.' + token.attributes?.item + ':before '; + const declaration = '{ content: ' + prefix + token.name + '; }'; return varName + '\n' + className + declaration; }) .join('\n'); diff --git a/lib/common/formatHelpers/minifyDictionary.js b/lib/common/formatHelpers/minifyDictionary.js index 4746f7e34..b64f447f0 100644 --- a/lib/common/formatHelpers/minifyDictionary.js +++ b/lib/common/formatHelpers/minifyDictionary.js @@ -39,11 +39,11 @@ export default function minifyDictionary(obj) { /** @type {Tokens} */ const toRet = {}; - if (obj.hasOwnProperty('value')) { - return obj.value; + if (Object.hasOwn(obj, '$value') || Object.hasOwn(obj, 'value')) { + return obj.$value ?? obj.value; } else { - for (var name in obj) { - if (obj.hasOwnProperty(name)) { + for (const name in obj) { + if (Object.hasOwn(obj, name)) { toRet[name] = minifyDictionary(obj[name]); } } diff --git a/lib/common/formats.js b/lib/common/formats.js index ee5c78b3c..780c0ec1b 100644 --- a/lib/common/formats.js +++ b/lib/common/formats.js @@ -312,7 +312,7 @@ const formats = { '{\n' + dictionary.allTokens .map(function (token) { - return ` "${token.name}": ${JSON.stringify(token.value)}`; + return ` "${token.name}": ${JSON.stringify(token.$value ?? token.value)}`; }) .join(',\n') + '\n}' + @@ -446,7 +446,12 @@ const formats = { fileHeader({ file }) + dictionary.allTokens .map(function (token) { - let to_ret = 'export const ' + token.name + ' = ' + JSON.stringify(token.value) + ';'; + let to_ret = + 'export const ' + + token.name + + ' = ' + + JSON.stringify(token.$value ?? token.value) + + ';'; if (token.comment) to_ret = to_ret.concat(' // ' + token.comment); return to_ret; }) @@ -494,12 +499,16 @@ const formats = { return ( fileHeader({ file }) + dictionary.allTokens - .map(function (prop) { - let to_ret_prop = ''; - if (prop.comment) to_ret_prop += '/** ' + prop.comment + ' */\n'; - to_ret_prop += - 'export const ' + prop.name + ' : ' + getTypeScriptType(prop.value, options) + ';'; - return to_ret_prop; + .map(function (token) { + let to_ret_token = ''; + if (token.comment) to_ret_token += '/** ' + token.comment + ' */\n'; + to_ret_token += + 'export const ' + + token.name + + ' : ' + + getTypeScriptType(token.$value ?? token.value, options) + + ';'; + return to_ret_token; }) .join('\n') + '\n' @@ -568,12 +577,11 @@ const formats = { */ function treeWalker(obj) { let type = Object.create(null); - let has = Object.prototype.hasOwnProperty.bind(obj); - if (has('value')) { + if (Object.hasOwn(obj, '$value') || Object.hasOwn(obj, 'value')) { type = 'DesignToken'; } else { for (let k in obj) - if (has(k)) { + if (Object.hasOwn(obj, k)) { switch (typeof obj[k]) { case 'object': type[k] = treeWalker(obj[k]); @@ -585,7 +593,8 @@ const formats = { // TODO: find a browser+node compatible way to read from '../../types/DesignToken.d.ts' const designTokenInterface = `interface DesignToken { - value: any; + value?: any; + $value?: any; name?: string; comment?: string; themeable?: boolean; @@ -1227,7 +1236,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul '{\n' + dictionary.allTokens .map(function (token) { - return ` "${token.name}": ${JSON.stringify(token.value)}`; + return ` "${token.name}": ${JSON.stringify(token.$value ?? token.value)}`; }) .join(',\n') + '\n}' + @@ -1266,7 +1275,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul return token.attributes?.category === 'color' && token.attributes.type === 'base'; }) .map(function (token) { - return token.value; + return token.$value ?? token.value; }); return JSON.stringify(to_ret, null, 2) + '\n'; }, @@ -1302,7 +1311,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul { name: token.name, }, - token.value, + token.$value ?? token.value, ); }), }; diff --git a/lib/common/templates/android/colors.template.js b/lib/common/templates/android/colors.template.js index fcbf85386..df743345a 100644 --- a/lib/common/templates/android/colors.template.js +++ b/lib/common/templates/android/colors.template.js @@ -14,13 +14,13 @@ export default ` // express or implied. See the License for the specific language governing // permissions and limitations under the License. -var props = dictionary.allTokens.filter(function(prop) { - return prop.attributes.category === 'color'; +const tokens = dictionary.allTokens.filter(function(token) { + return token.attributes.category === 'color'; }); %> <%= fileHeader({file, commentStyle: 'xml'}) %> - <% props.forEach(function(prop) { - %><%= prop.value %><% if (prop.comment) { %><% } %> + <% tokens.forEach(function(token) { + %><%= token.$value ?? token.value %><% if (token.comment) { %><% } %> <% }); %> `; diff --git a/lib/common/templates/android/dimens.template.js b/lib/common/templates/android/dimens.template.js index 4ffbd9cbd..9bae0d66f 100644 --- a/lib/common/templates/android/dimens.template.js +++ b/lib/common/templates/android/dimens.template.js @@ -14,12 +14,12 @@ export default `xml version="1.0" encoding="UTF-8"?> // express or implied. See the License for the specific language governing // permissions and limitations under the License. -var props = dictionary.allTokens.filter(function(prop) { - return prop.attributes.category === 'size' && prop.attributes.type !== 'font' +const tokens = dictionary.allTokens.filter(function(token) { + return token.attributes.category === 'size' && token.attributes.type !== 'font' }); %> <%= fileHeader({file, commentStyle: 'xml'}) %> - <% props.forEach(function(prop) { - %><%= prop.value %><% if (prop.comment) { %><% } %> + <% tokens.forEach(function(token) { + %><%= token.$value ?? token.value %><% if (token.comment) { %><% } %> <% }); %> `; diff --git a/lib/common/templates/android/fontDimens.template.js b/lib/common/templates/android/fontDimens.template.js index e2803e250..374b7954c 100644 --- a/lib/common/templates/android/fontDimens.template.js +++ b/lib/common/templates/android/fontDimens.template.js @@ -14,12 +14,12 @@ export default ` // express or implied. See the License for the specific language governing // permissions and limitations under the License. -var props = dictionary.allTokens.filter(function(prop) { - return prop.attributes.category === 'size' && prop.attributes.type === 'font'; +const tokens = dictionary.allTokens.filter(function(token) { + return token.attributes.category === 'size' && token.attributes.type === 'font'; }); %> <%= fileHeader({file, commentStyle: 'xml'}) %> - <% props.forEach(function(prop) { - %><%= prop.value %><% if (prop.comment) { %><% } %> + <% tokens.forEach(function(token) { + %><%= token.$value ?? token.value %><% if (token.comment) { %><% } %> <% }); %> `; diff --git a/lib/common/templates/android/integers.template.js b/lib/common/templates/android/integers.template.js index 9aeaa7b3b..5eebe767f 100644 --- a/lib/common/templates/android/integers.template.js +++ b/lib/common/templates/android/integers.template.js @@ -14,12 +14,12 @@ export default ` // express or implied. See the License for the specific language governing // permissions and limitations under the License. -var props = dictionary.allTokens.filter(function(prop) { - return prop.attributes.category === 'time'; +var tokens = dictionary.allTokens.filter(function(token) { + return token.attributes.category === 'time'; }); %> <%= fileHeader({file, commentStyle: 'xml'}) %> - <% props.forEach(function(prop) { - %><%= prop.value %><% if (prop.comment) { %><% } %> + <% tokens.forEach(function(token) { + %><%= token.$value ?? token.value %><% if (token.comment) { %><% } %> <% }); %> `; diff --git a/lib/common/templates/android/resources.template.js b/lib/common/templates/android/resources.template.js index 9e779f135..08dc39302 100644 --- a/lib/common/templates/android/resources.template.js +++ b/lib/common/templates/android/resources.template.js @@ -43,29 +43,29 @@ export default (opts) => { }; /** - * @param {Token} prop + * @param {Token} token * @returns {string} */ - function propToType(prop) { + function tokenToType(token) { if (resourceType) { return resourceType; } - if (prop.attributes?.category && resourceMap[prop.attributes.category]) { - return resourceMap[prop.attributes.category]; + if (token.attributes?.category && resourceMap[token.attributes.category]) { + return resourceMap[token.attributes.category]; } return 'string'; } /** - * @param {Token} prop + * @param {Token} token * @param {Tokens} tokens * @returns {string} */ - function propToValue(prop, tokens) { - if (file?.options && file.options.outputReferences && usesReferences(prop.original.value)) { - return `@${propToType(prop)}/${getReferences(prop.original.value, tokens)[0].name}`; + function tokenToValue(token, tokens) { + if (file?.options && file.options.outputReferences && usesReferences(token.original.value)) { + return `@${tokenToType(token)}/${getReferences(token.original.value, tokens)[0].name}`; } else { - return prop.value; + return token.$value ?? token.value; } } @@ -76,11 +76,11 @@ ${fileHeader ? fileHeader({ file, commentStyle: 'xml' }) : ''} ${ /** @type {Token[]} */ (dictionary.allTokens) .map( - (prop) => - `<${propToType(prop)} name="${prop.name}">${propToValue( - prop, + (token) => + `<${tokenToType(token)} name="${token.name}">${tokenToValue( + token, dictionary.tokens, - )}${prop.comment ? `` : ''}`, + )}${token.comment ? `` : ''}`, ) .join(`\n `) } diff --git a/lib/common/templates/android/strings.template.js b/lib/common/templates/android/strings.template.js index fefdef3a6..c2deebb0f 100644 --- a/lib/common/templates/android/strings.template.js +++ b/lib/common/templates/android/strings.template.js @@ -14,12 +14,12 @@ export default ` // express or implied. See the License for the specific language governing // permissions and limitations under the License. -var props = dictionary.allTokens.filter(function(prop) { - return prop.attributes.category === 'content'; +const tokens = dictionary.allTokens.filter(function(token) { + return token.attributes.category === 'content'; }); %> <%= fileHeader({file, commentStyle: 'xml'}) %> - <% props.forEach(function(prop) { - %><%= prop.value %><% if (prop.comment) { %><% } %> + <% tokens.forEach(function(token) { + %><%= token.$value ?? token.value %><% if (token.comment) { %><% } %> <% }); %> `; diff --git a/lib/common/templates/ios/colors.m.template.js b/lib/common/templates/ios/colors.m.template.js index b7d2dd755..5da9deac9 100644 --- a/lib/common/templates/ios/colors.m.template.js +++ b/lib/common/templates/ios/colors.m.template.js @@ -31,7 +31,7 @@ export default `<% dispatch_once(&onceToken, ^{ colorArray = @[ -<%= dictionary.allTokens.map(function(prop){ return prop.value; }).join(',\\n') %> +<%= dictionary.allTokens.map(function(token){ return token.$value ?? token.value; }).join(',\\n') %> ]; }); diff --git a/lib/common/templates/ios/macros.template.js b/lib/common/templates/ios/macros.template.js index 51e88d472..907fa5682 100644 --- a/lib/common/templates/ios/macros.template.js +++ b/lib/common/templates/ios/macros.template.js @@ -20,6 +20,6 @@ export default `<% #import #import -<% dictionary.allTokens.forEach(function(prop) { - %>#define <%= prop.name %> <%= prop.value %> +<% dictionary.allTokens.forEach(function(token) { + %>#define <%= token.name %> <%= token.$value ?? token.value %> <% }); %>`; diff --git a/lib/common/templates/ios/plist.template.js b/lib/common/templates/ios/plist.template.js index 4919cddfc..44568c6f9 100644 --- a/lib/common/templates/ios/plist.template.js +++ b/lib/common/templates/ios/plist.template.js @@ -13,33 +13,33 @@ export default `<% // express or implied. See the License for the specific language governing // permissions and limitations under the License. -var props = dictionary.allTokens.filter(function(prop) { - return prop.attributes.category !== 'asset' && - prop.attributes.category !== 'border' && - prop.attributes.category !== 'shadow' && - prop.attributes.category !== 'transition'; +const tokens = dictionary.allTokens.filter(function(token) { + return token.attributes.category !== 'asset' && + token.attributes.category !== 'border' && + token.attributes.category !== 'shadow' && + token.attributes.category !== 'transition'; }); %> <%= fileHeader({ file, commentStyle: 'xml' }) %> - <% props.forEach(function(prop) { - %><%= prop.name %> - <% if (prop.attributes.category === 'color') { %> + <% tokens.forEach(function(token) { + %><%= token.name %> + <% if (token.attributes.category === 'color') { %> r - <%= prop.value[0]/255 %> + <%= (token.$value ?? token.value)[0]/255 %> g - <%= prop.value[1]/255 %> + <%= (token.$value ?? token.value)[1]/255 %> b - <%= prop.value[2]/255 %> + <%= (token.$value ?? token.value)[2]/255 %> a 1 - <% } else if (prop.attributes.category === 'size') { %> - <%= prop.value %> + <% } else if (token.attributes.category === 'size') { %> + <%= token.$value ?? token.value %> <% } else { %> - <%= prop.value %> - <% } %><% if (prop.comment) { %><% } %><% }); %> + <%= token.$value ?? token.value %> + <% } %><% if (token.comment) { %><% } %><% }); %> `; diff --git a/lib/common/templates/ios/singleton.m.template.js b/lib/common/templates/ios/singleton.m.template.js index a1e1d66ba..2a81dcff8 100644 --- a/lib/common/templates/ios/singleton.m.template.js +++ b/lib/common/templates/ios/singleton.m.template.js @@ -42,25 +42,28 @@ export default `<% @end -<% function buildDictionary(tokens, indent) { +<% function buildDictionary(token, indent) { indent = indent || ' '; var to_ret = '@{\\n'; - if (tokens.hasOwnProperty('value')) { - var value = tokens.attributes.category === 'size' || tokens.attributes.category === 'time' ? '@' + tokens.value : tokens.value; + if (Object.hasOwn(token, '$value') || Object.hasOwn(token, 'value')) { + let value = token.$value ?? token.value; + if (token.attributes.category === 'size' || token.attributes.category === 'time') { + value = '@' + value; + } to_ret += indent + '@"value": ' + value + ',\\n'; - to_ret += indent + '@"name": @"' + tokens.name + '",\\n'; + to_ret += indent + '@"name": @"' + token.name + '",\\n'; - for(var name in tokens.attributes) { - if (tokens.attributes[name]) { - to_ret += indent + '@"' + name + '": @"' + tokens.attributes[name] + '",\\n'; + for(const name in token.attributes) { + if (token.attributes[name]) { + to_ret += indent + '@"' + name + '": @"' + token.attributes[name] + '",\\n'; } } // remove last comma return to_ret.slice(0, -2) + '\\n' + indent + '}'; } else { - for(var name in tokens) { - to_ret += indent + '@"' + name + '": ' + buildDictionary(tokens[name], indent + ' ') + ',\\n'; + for(const name in token) { + to_ret += indent + '@"' + name + '": ' + buildDictionary(token[name], indent + ' ') + ',\\n'; } // remove last comma return to_ret.slice(0, -2) + '\\n' + indent + '}'; diff --git a/lib/common/templates/ios/static.m.template.js b/lib/common/templates/ios/static.m.template.js index 8e5f6c73b..a59a2f84b 100644 --- a/lib/common/templates/ios/static.m.template.js +++ b/lib/common/templates/ios/static.m.template.js @@ -19,5 +19,5 @@ export default `<% <%= fileHeader({ file, commentStyle: 'short' }) %> #import "<%= file.className %>.h" -<% dictionary.allTokens.forEach(function(prop) { %> -<%= file.type %> const <%= prop.name %> = <%= prop.value %>;<% }); %>`; +<% dictionary.allTokens.forEach(function(token) { %> +<%= file.type %> const <%= token.name %> = <%= token.$value ?? token.value %>;<% }); %>`; diff --git a/lib/common/templates/ios/strings.m.template.js b/lib/common/templates/ios/strings.m.template.js index a0005113d..c1c792df5 100644 --- a/lib/common/templates/ios/strings.m.template.js +++ b/lib/common/templates/ios/strings.m.template.js @@ -19,8 +19,8 @@ export default `<% <%= fileHeader({ file, commentStyle: 'short' }) %> #import "<%= file.className %>.h" -<% dictionary.allTokens.forEach(function(prop) { %> -NSString * const <%= prop.name %> = <%= prop.value %>;<% }); %> +<% dictionary.allTokens.forEach(function(token) { %> +NSString * const <%= token.name %> = <%= token.$value ?? token.value %>;<% }); %> @implementation <%= file.className %> @@ -39,14 +39,14 @@ NSString * const <%= prop.name %> = <%= prop.value %>;<% }); %> @end -<% function buildProperty(prop) { - var to_ret = '@{\\n'; - to_ret += ' ' + '@"value": ' + prop.value + ',\\n'; - to_ret += ' ' + '@"name": @"' + prop.name + '",\\n'; +<% function buildProperty(token) { + let to_ret = '@{\\n'; + to_ret += ' ' + '@"value": ' + (token.$value ?? token.value) + ',\\n'; + to_ret += ' ' + '@"name": @"' + token.name + '",\\n'; - for(var name in prop.attributes) { - if (prop.attributes[name]) { - to_ret += ' ' + '@"' + name + '": @"' + prop.attributes[name] + '",\\n'; + for(const name in token.attributes) { + if (token.attributes[name]) { + to_ret += ' ' + '@"' + name + '": @"' + token.attributes[name] + '",\\n'; } } diff --git a/lib/common/templates/scss/map-deep.template.js b/lib/common/templates/scss/map-deep.template.js index 42fc32cdd..50c894118 100644 --- a/lib/common/templates/scss/map-deep.template.js +++ b/lib/common/templates/scss/map-deep.template.js @@ -27,7 +27,7 @@ export default `<% output += \`''\` } else if (typeof obj === "string") { output += \`'\${obj}'\` - } else if (obj.hasOwnProperty('value')) { + } else if (Object.hasOwn(obj, '$value') || Object.hasOwn(obj, 'value')) { // if we have found a leaf (a property with a value) append the value output += \`$\${obj.name}\`; } else { diff --git a/lib/common/templates/scss/map-flat.template.js b/lib/common/templates/scss/map-flat.template.js index 4ead77660..3c683d6b8 100644 --- a/lib/common/templates/scss/map-flat.template.js +++ b/lib/common/templates/scss/map-flat.template.js @@ -15,14 +15,15 @@ export default `<% %> <%= fileHeader({file, commentStyle: 'long'}) %><% - var output = ''; + let output = ''; output += \`$\${file.mapName||'tokens'}: (\\n\`; - output += allTokens.map(function(prop){ - var line = ''; - if(prop.comment) { - line += ' // ' + prop.comment + '\\n'; + output += allTokens.map(function(token){ + let line = ''; + if(token.comment) { + line += ' // ' + token.comment + '\\n'; } - line += ' \\'' + prop.name + '\\': ' + (prop.attributes.category==='asset' ? '"'+prop.value+'"' : prop.value) + const value = token.$value ?? token.value; + line += ' \\'' + token.name + '\\': ' + (token.attributes.category==='asset' ? '"' + value + '"' : value) return line; }).join(',\\n'); output += '\\n);'; diff --git a/lib/common/templates/static-style-guide/index.html.template.js b/lib/common/templates/static-style-guide/index.html.template.js index da2daa811..be65b396d 100644 --- a/lib/common/templates/static-style-guide/index.html.template.js +++ b/lib/common/templates/static-style-guide/index.html.template.js @@ -13,26 +13,27 @@ export default `<% // express or implied. See the License for the specific language governing // permissions and limitations under the License. - var colorRegex = /^(#(?:[\\da-f]{3}){1,2}|rgb\\((?:\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((?:\\d{1,3},\\s*){3}\\d*\\.?\\d+\\)|hsl\\(\\d{1,3}(?:,\\s*\\d{1,3}%){2}\\)|hsla\\(\\d{1,3}(?:,\\s*\\d{1,3}%){2},\\s*\\d*\\.?\\d+\\)|IndianRed|LightCoral|Salmon|DarkSalmon|LightSalmon|Crimson|Red|FireBrick|DarkRed|Pink|LightPink|HotPink|DeepPink|MediumVioletRed|PaleVioletRed|LightSalmon|Coral|Tomato|OrangeRed|DarkOrange|Orange|Gold|Yellow|LightYellow|LemonChiffon|LightGoldenrodYellow|PapayaWhip|Moccasin|PeachPuff|PaleGoldenrod|Khaki|DarkKhaki|Lavender|Thistle|Plum|Violet|Orchid|Fuchsia|Magenta|MediumOrchid|MediumPurple|RebeccaPurple|BlueViolet|DarkViolet|DarkOrchid|DarkMagenta|Purple|Indigo|SlateBlue|DarkSlateBlue|MediumSlateBlue|GreenYellow|Chartreuse|LawnGreen|Lime|LimeGreen|PaleGreen|LightGreen|MediumSpringGreen|SpringGreen|MediumSeaGreen|SeaGreen|ForestGreen|Green|DarkGreen|YellowGreen|OliveDrab|Olive|DarkOliveGreen|MediumAquamarine|DarkSeaGreen|LightSeaGreen|DarkCyan|Teal|Aqua|Cyan|LightCyan|PaleTurquoise|Aquamarine|Turquoise|MediumTurquoise|DarkTurquoise|CadetBlue|SteelBlue|LightSteelBlue|PowderBlue|LightBlue|SkyBlue|LightSkyBlue|DeepSkyBlue|DodgerBlue|CornflowerBlue|MediumSlateBlue|RoyalBlue|Blue|MediumBlue|DarkBlue|Navy|MidnightBlue|Cornsilk|BlanchedAlmond|Bisque|NavajoWhite|Wheat|BurlyWood|Tan|RosyBrown|SandyBrown|Goldenrod|DarkGoldenrod|Peru|Chocolate|SaddleBrown|Sienna|Brown|Maroon|White|Snow|HoneyDew|MintCream|Azure|AliceBlue|GhostWhite|WhiteSmoke|SeaShell|Beige|OldLace|FloralWhite|Ivory|AntiqueWhite|Linen|LavenderBlush|MistyRose|Gainsboro|LightGray|Silver|DarkGray|Gray|DimGray|LightSlateGray|SlateGray|DarkSlateGray|Black)$/i; + const colorRegex = /^(#(?:[\\da-f]{3}){1,2}|rgb\\((?:\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((?:\\d{1,3},\\s*){3}\\d*\\.?\\d+\\)|hsl\\(\\d{1,3}(?:,\\s*\\d{1,3}%){2}\\)|hsla\\(\\d{1,3}(?:,\\s*\\d{1,3}%){2},\\s*\\d*\\.?\\d+\\)|IndianRed|LightCoral|Salmon|DarkSalmon|LightSalmon|Crimson|Red|FireBrick|DarkRed|Pink|LightPink|HotPink|DeepPink|MediumVioletRed|PaleVioletRed|LightSalmon|Coral|Tomato|OrangeRed|DarkOrange|Orange|Gold|Yellow|LightYellow|LemonChiffon|LightGoldenrodYellow|PapayaWhip|Moccasin|PeachPuff|PaleGoldenrod|Khaki|DarkKhaki|Lavender|Thistle|Plum|Violet|Orchid|Fuchsia|Magenta|MediumOrchid|MediumPurple|RebeccaPurple|BlueViolet|DarkViolet|DarkOrchid|DarkMagenta|Purple|Indigo|SlateBlue|DarkSlateBlue|MediumSlateBlue|GreenYellow|Chartreuse|LawnGreen|Lime|LimeGreen|PaleGreen|LightGreen|MediumSpringGreen|SpringGreen|MediumSeaGreen|SeaGreen|ForestGreen|Green|DarkGreen|YellowGreen|OliveDrab|Olive|DarkOliveGreen|MediumAquamarine|DarkSeaGreen|LightSeaGreen|DarkCyan|Teal|Aqua|Cyan|LightCyan|PaleTurquoise|Aquamarine|Turquoise|MediumTurquoise|DarkTurquoise|CadetBlue|SteelBlue|LightSteelBlue|PowderBlue|LightBlue|SkyBlue|LightSkyBlue|DeepSkyBlue|DodgerBlue|CornflowerBlue|MediumSlateBlue|RoyalBlue|Blue|MediumBlue|DarkBlue|Navy|MidnightBlue|Cornsilk|BlanchedAlmond|Bisque|NavajoWhite|Wheat|BurlyWood|Tan|RosyBrown|SandyBrown|Goldenrod|DarkGoldenrod|Peru|Chocolate|SaddleBrown|Sienna|Brown|Maroon|White|Snow|HoneyDew|MintCream|Azure|AliceBlue|GhostWhite|WhiteSmoke|SeaShell|Beige|OldLace|FloralWhite|Ivory|AntiqueWhite|Linen|LavenderBlush|MistyRose|Gainsboro|LightGray|Silver|DarkGray|Gray|DimGray|LightSlateGray|SlateGray|DarkSlateGray|Black)$/i; - var checkForStyle = function(property) { - var toStyle = ""; - if(property.value.match(colorRegex)) { - if(property.path.indexOf('font')>-1) { - toStyle += "color:" + property.value + ";"; + const checkForStyle = function(token) { + let toStyle = ""; + const value = token.$value ?? token.value; + if(value.match(colorRegex)) { + if(token.path.indexOf('font')>-1) { + toStyle += "color:" + value + ";"; } else { - toStyle += "background-color:" + property.value + ";"; + toStyle += "background-color:" + value + ";"; } - if (property.attributes.font === 'inverse') { + if (token.attributes.font === 'inverse') { toStyle += "color:var(--color-font-inverse-base);"; } } return toStyle; }; - var checkPropGetInverse = function (property) { - if(property.path.indexOf('inverse')>-1) { + const checkPropGetInverse = function (token) { + if(token.path.indexOf('inverse')>-1) { return 'inverse'; } }; @@ -58,16 +59,16 @@ export default `<%
- <% _.each(allTokens, function(property) { %> -
" style="<%= checkForStyle(property) %>"> -
<%= property.path.join(".") %>
-
<%= property.name %>
- <% if(property.attributes && JSON.stringify(property.attributes)!=="{}") { %> -
+ <% _.each(allTokens, function(token) { %> +
" style="<%= checkForStyle(token) %>"> +
<%= token.path.join(".") %>
+
<%= token.name %>
+ <% if(token.attributes && JSON.stringify(token.attributes)!=="{}") { %> +
<% } %> -
<%= property.value %><% if(property.attributes.category === 'content' && property.attributes.type === 'icon') { %><%= property.value %><% } %>
- <% if(property.attributes && JSON.stringify(property.attributes)!=="{}") { %> -
<%= JSON.stringify(property.attributes) %>
+
<%= token.$value ?? token.value %><% if(token.attributes.category === 'content' && token.attributes.type === 'icon') { %><%= token.$value ?? token.value %><% } %>
+ <% if(token.attributes && JSON.stringify(token.attributes)!=="{}") { %> +
<%= JSON.stringify(token.attributes) %>
<% } %>
<% }); %> diff --git a/lib/common/transforms.js b/lib/common/transforms.js index 87221bac5..04d606eb9 100644 --- a/lib/common/transforms.js +++ b/lib/common/transforms.js @@ -93,7 +93,7 @@ function isContent(token) { * @returns {string} */ function wrapValueWith(character, token) { - return `${character}${token.value}${character}`; + return `${character}${token.$value ?? token.value}${character}`; } /** @@ -180,7 +180,7 @@ export default { type: 'attribute', matcher: isColor, transformer: function (token) { - var color = Color(token.value); + const color = Color(token.$value ?? token.value); return { hex: color.toHex(), rgb: color.toRgb(), @@ -323,7 +323,7 @@ export default { 'name/ti/constant': { type: 'name', transformer: function (token, options) { - var path = token.path.slice(1); + const path = token.path.slice(1); return snakeCase([options.prefix].concat(path).join(' ')).toUpperCase(); }, }, @@ -366,7 +366,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - return Color(token.value).toRgbString(); + return Color(token.$value ?? token.value).toRgbString(); }, }, @@ -386,7 +386,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - return Color(token.value).toHslString(); + return Color(token.$value ?? token.value).toHslString(); }, }, @@ -406,9 +406,9 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - var color = Color(token.value); - var o = color.toHsl(); - var vals = `${Math.round(o.h)} ${Math.round(o.s * 100)}% ${Math.round(o.l * 100)}%`; + const color = Color(token.$value ?? token.value); + const o = color.toHsl(); + const vals = `${Math.round(o.h)} ${Math.round(o.s * 100)}% ${Math.round(o.l * 100)}%`; if (color.getAlpha() === 1) { return `hsl(${vals})`; } else { @@ -432,7 +432,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - return Color(token.value).toHexString(); + return Color(token.$value ?? token.value).toHexString(); }, }, @@ -451,7 +451,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - return Color(token.value).toHex8String(); + return Color(token.$value ?? token.value).toHex8String(); }, }, @@ -470,7 +470,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - var str = Color(token.value).toHex8(); + const str = Color(token.$value ?? token.value).toHex8(); return '#' + str.slice(6) + str.slice(0, 6); }, }, @@ -490,7 +490,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - var str = Color(token.value).toHex8(); + const str = Color(token.$value ?? token.value).toHex8(); return 'Color(0x' + str.slice(6) + str.slice(0, 6) + ')'; }, }, @@ -510,7 +510,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - var rgb = Color(token.value).toRgb(); + const rgb = Color(token.$value ?? token.value).toRgb(); return ( '[UIColor colorWithRed:' + (rgb.r / 255).toFixed(3) + @@ -543,7 +543,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - const { r, g, b, a } = Color(token.value).toRgb(); + const { r, g, b, a } = Color(token.$value ?? token.value).toRgb(); const rFixed = (r / 255.0).toFixed(3); const gFixed = (g / 255.0).toFixed(3); const bFixed = (b / 255.0).toFixed(3); @@ -566,7 +566,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - const { r, g, b, a } = Color(token.value).toRgb(); + const { r, g, b, a } = Color(token.$value ?? token.value).toRgb(); const rFixed = (r / 255.0).toFixed(3); const gFixed = (g / 255.0).toFixed(3); const bFixed = (b / 255.0).toFixed(3); @@ -590,7 +590,7 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - var color = Color(token.value); + const color = Color(token.$value ?? token.value); if (color.getAlpha() === 1) { return color.toHexString(); } else { @@ -646,8 +646,8 @@ export default { type: 'value', matcher: isFontSize, transformer: function (token) { - const val = parseFloat(token.value); - if (isNaN(val)) throwSizeError(token.name, token.value, 'sp'); + const val = parseFloat(token.$value ?? token.value); + if (isNaN(val)) throwSizeError(token.name, token.$value ?? token.value, 'sp'); return val.toFixed(2) + 'sp'; }, }, @@ -667,8 +667,8 @@ export default { type: 'value', matcher: isNotFontSize, transformer: function (token) { - const val = parseFloat(token.value); - if (isNaN(val)) throwSizeError(token.name, token.value, 'dp'); + const val = parseFloat(token.$value ?? token.value); + if (isNaN(val)) throwSizeError(token.name, token.$value ?? token.value, 'dp'); return val.toFixed(2) + 'dp'; }, }, @@ -693,14 +693,15 @@ export default { type: 'value', matcher: isSize, transformer: function (token, options) { - var val = parseFloat(token.value); - if (isNaN(val)) throwSizeError(token.name, token.value, 'object'); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'object'); return { - original: token.value, - number: val, - decimal: val / 100, - scale: val * getBasePxFontSize(options), + original: value, + number: parsedVal, + decimal: parsedVal / 100, + scale: parsedVal * getBasePxFontSize(options), }; }, }, @@ -720,10 +721,11 @@ export default { type: 'value', matcher: isFontSize, transformer: function (token, options) { - const val = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); const baseFont = getBasePxFontSize(options); - if (isNaN(val)) throwSizeError(token.name, token.value, 'sp'); - return (val * baseFont).toFixed(2) + 'sp'; + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'sp'); + return (parsedVal * baseFont).toFixed(2) + 'sp'; }, }, @@ -742,10 +744,11 @@ export default { type: 'value', matcher: isNotFontSize, transformer: function (token, options) { - const val = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); const baseFont = getBasePxFontSize(options); - if (isNaN(val)) throwSizeError(token.name, token.value, 'dp'); - return (val * baseFont).toFixed(2) + 'dp'; + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'dp'); + return (parsedVal * baseFont).toFixed(2) + 'dp'; }, }, @@ -764,9 +767,10 @@ export default { type: 'value', matcher: isSize, transformer: function (token) { - const val = parseFloat(token.value); - if (isNaN(val)) throwSizeError(token.name, token.value, 'px'); - return val + 'px'; + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'px'); + return parsedVal + 'px'; }, }, @@ -785,9 +789,9 @@ export default { type: 'value', matcher: isSize, transformer: function (token) { - const val = parseFloat(token.value); - if (isNaN(val)) throwSizeError(token.name, token.value, 'rem'); - return val + 'rem'; + const parsedVal = parseFloat(token.$value ?? token.value); + if (isNaN(parsedVal)) throwSizeError(token.name, token.$value ?? token.value, 'rem'); + return parsedVal + 'rem'; }, }, @@ -806,10 +810,11 @@ export default { type: 'value', matcher: isSize, transformer: function (token, options) { - const val = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); const baseFont = getBasePxFontSize(options); - if (isNaN(val)) throwSizeError(token.name, token.value, 'pt'); - return (val * baseFont).toFixed(2) + 'f'; + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'pt'); + return (parsedVal * baseFont).toFixed(2) + 'f'; }, }, @@ -828,10 +833,11 @@ export default { type: 'value', matcher: isFontSize, transformer: function (token, options) { - const val = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); const baseFont = getBasePxFontSize(options); - if (isNaN(val)) throwSizeError(token.name, token.value, 'sp'); - return (val * baseFont).toFixed(2) + '.sp'; + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'sp'); + return (parsedVal * baseFont).toFixed(2) + '.sp'; }, }, @@ -850,10 +856,11 @@ export default { type: 'value', matcher: isNotFontSize, transformer: function (token, options) { - const val = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); const baseFont = getBasePxFontSize(options); - if (isNaN(val)) throwSizeError(token.name, token.value, 'dp'); - return (val * baseFont).toFixed(2) + '.dp'; + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'dp'); + return (parsedVal * baseFont).toFixed(2) + '.dp'; }, }, @@ -872,9 +879,10 @@ export default { type: 'value', matcher: isFontSize, transformer: function (token) { - const val = parseFloat(token.value); - if (isNaN(val)) throwSizeError(token.name, token.value, 'em'); - return val + '.em'; + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'em'); + return parsedVal + '.em'; }, }, @@ -892,10 +900,11 @@ export default { type: 'value', matcher: isSize, transformer: function (token, options) { - const val = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); const baseFont = getBasePxFontSize(options); - if (isNaN(val)) throwSizeError(token.name, token.value, 'CGFloat'); - return `CGFloat(${(val * baseFont).toFixed(2)})`; + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'CGFloat'); + return `CGFloat(${(parsedVal * baseFont).toFixed(2)})`; }, }, @@ -914,10 +923,11 @@ export default { type: 'value', matcher: isSize, transformer: function (token, options) { - const val = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); const baseFont = getBasePxFontSize(options); - if (isNaN(val)) throwSizeError(token.name, token.value, 'px'); - return (val * baseFont).toFixed(0) + 'px'; + if (isNaN(parsedVal)) throwSizeError(token.name, value, 'px'); + return (parsedVal * baseFont).toFixed(0) + 'px'; }, }, @@ -938,17 +948,18 @@ export default { matcher: isSize, transformer: (token, options) => { const baseFont = getBasePxFontSize(options); - const floatVal = parseFloat(token.value); + const value = token.$value ?? token.value; + const parsedVal = parseFloat(value); - if (isNaN(floatVal)) { - throwSizeError(token.name, token.value, 'rem'); + if (isNaN(parsedVal)) { + throwSizeError(token.name, value, 'rem'); } - if (floatVal === 0) { + if (parsedVal === 0) { return '0'; } - return `${floatVal / baseFont}rem`; + return `${parsedVal / baseFont}rem`; }, }, @@ -969,7 +980,7 @@ export default { return token.attributes?.category === 'content' && token.attributes.type === 'icon'; }, transformer: function (token) { - return token.value.replace( + return (token.$value ?? token.value).replace( UNICODE_PATTERN, /** * @param {string} match @@ -1091,7 +1102,7 @@ export default { return token.attributes?.category === 'time'; }, transformer: function (token) { - return (parseFloat(token.value) / 1000).toFixed(2) + 's'; + return (parseFloat(token.$value ?? token.value) / 1000).toFixed(2) + 's'; }, }, @@ -1110,7 +1121,7 @@ export default { type: 'value', matcher: isAsset, transformer: function (token) { - return convertToBase64(token.value); + return convertToBase64(token.$value ?? token.value); }, }, @@ -1129,7 +1140,7 @@ export default { type: 'value', matcher: isAsset, transformer: function (token) { - return join(process.cwd(), token.value); + return join(process?.cwd() ?? '/', token.$value ?? token.value); }, }, @@ -1181,7 +1192,9 @@ export default { type: 'value', matcher: isColor, transformer: function (token) { - var str = Color(token.value).toHex8().toUpperCase(); + const str = Color(token.$value ?? token.value) + .toHex8() + .toUpperCase(); return `Color(0x${str.slice(6)}${str.slice(0, 6)})`; }, }, @@ -1251,7 +1264,7 @@ export default { matcher: isSize, transformer: function (token, options) { const baseFont = getBasePxFontSize(options); - return (parseFloat(token.value) * baseFont).toFixed(2); + return (parseFloat(token.$value ?? token.value) * baseFont).toFixed(2); }, }, }; diff --git a/lib/filterTokens.js b/lib/filterTokens.js index 2f716ee8c..b365327e2 100644 --- a/lib/filterTokens.js +++ b/lib/filterTokens.js @@ -33,20 +33,21 @@ import isPlainObject from 'is-plain-obj'; function filterTokenObject(tokens, filter) { // Use reduce to generate a new object with the unwanted tokens filtered // out - return Object.entries(tokens ?? []).reduce((acc, [key, value]) => { - // If the value is not an object, we don't know what it is. We return it as-is. - if (!isPlainObject(value)) { + return Object.entries(tokens ?? []).reduce((acc, [key, token]) => { + const tokenValue = token.$value ?? token.value; + // If the token is not an object, we don't know what it is. We return it as-is. + if (!isPlainObject(token)) { return acc; - // If the value has a `value` member we know it's a property, pass it to + // If the token has a `value` member we know it's a property, pass it to // the filter function and either include it in the final `acc` object or // exclude it (by returning the `acc` object without it added). - } else if (typeof value.value !== 'undefined') { - return filter(/** @type {Token} */ (value)) ? { ...acc, [key]: value } : acc; + } else if (typeof tokenValue !== 'undefined') { + return filter(/** @type {Token} */ (token)) ? { ...acc, [key]: token } : acc; // If we got here we have an object that is not a property. We'll assume // it's an object containing multiple tokens and recursively filter it // using the `filterTokenObject` function. } else { - const filtered = filterTokenObject(value, filter); + const filtered = filterTokenObject(token, filter); // If the filtered object is not empty then add it to the final `acc` // object. If it is empty then every property inside of it was filtered // out, then exclude it entirely from the final `acc` object. diff --git a/lib/transform/object.js b/lib/transform/object.js index 604d49246..7eac1f2b9 100644 --- a/lib/transform/object.js +++ b/lib/transform/object.js @@ -47,7 +47,7 @@ export default function transformObject( transformedObj = {}, ) { for (const name in obj) { - if (!obj.hasOwnProperty(name)) { + if (!Object.hasOwn(obj, name)) { continue; } @@ -60,7 +60,7 @@ export default function transformObject( // value: "#ababab" // ... // } - if (isObj && 'value' in objProp) { + if (isObj && (Object.hasOwn(objProp, '$value') || Object.hasOwn(objProp, 'value'))) { const pathName = getName(path); const alreadyTransformed = transformedPropRefs.indexOf(pathName) !== -1; @@ -74,26 +74,26 @@ export default function transformObject( // Note: tokenSetup won't re-run if property has already been setup // it is safe to run this multiple times on the same property. - const setupProperty = tokenSetup(/** @type {Token|TransformedToken} */ (objProp), name, path); + const token = tokenSetup(/** @type {Token|TransformedToken} */ (objProp), name, path); const deferProp = () => { // If property path isn't in the deferred array, add it now. if (deferredPropValueTransforms.indexOf(pathName) === -1) { deferredPropValueTransforms.push(pathName); } - transformedObj[name] = setupProperty; + transformedObj[name] = token; path.pop(); }; // If property has a reference, defer its transformations until later - if (usesReferences(setupProperty.value, options)) { + if (usesReferences(token.$value ?? token.value, options)) { deferProp(); continue; } // If we got here, the property hasn't been transformed yet and // does not use a value reference. Transform the property now and assign it. - const transformedToken = transformToken(setupProperty, options); + const transformedToken = transformToken(token, options); // If a value transform returns undefined, it means the transform wants it to be deferred // e.g. due to a ref existing in a sibling prop that the transform relies on. // Example: { value: "#fff", darken: "{darken-amount}" } diff --git a/lib/transform/token.js b/lib/transform/token.js index 84e7405a7..f4697b0df 100644 --- a/lib/transform/token.js +++ b/lib/transform/token.js @@ -21,15 +21,15 @@ import usesReferences from '../utils/references/usesReferences.js'; */ /** - * Applies all transforms to a property. This is a pure function, - * it returns a new property object rather than mutating it inline. + * Applies all transforms to a token. This is a pure function, + * it returns a new token object rather than mutating it inline. * @private - * @param {Token} property + * @param {Token} token * @param {PlatformConfig} options * @returns {Token|undefined} - A new property object with transforms applied. */ -export default function transformProperty(property, options) { - const to_ret = structuredClone(property); +export default function transformProperty(token, options) { + const to_ret = structuredClone(token); const transforms = /** @type {Omit[]} */ (options.transforms) || []; @@ -45,15 +45,19 @@ export default function transformProperty(property, options) { } // Don't try to transform the value if it is referencing another value // Only try to transform if the value is not a string or if it has '{}' - if (transform.type === 'value' && !usesReferences(property.value, options)) { + if (transform.type === 'value' && !usesReferences(token.$value ?? token.value, options)) { // Only transform non-referenced values (from original) // and transitive transforms if the value has been resolved - if (!usesReferences(property.original.value, options) || transform.transitive) { + if (!usesReferences(token.original.value, options) || transform.transitive) { const transformedValue = transform.transformer(to_ret, options); if (transformedValue === undefined) { return undefined; } - to_ret.value = transformedValue; + if (token.$value !== undefined) { + to_ret.$value = transformedValue; + } else { + to_ret.value = transformedValue; + } } } diff --git a/lib/utils/combineJSON.js b/lib/utils/combineJSON.js index 538e7457d..6bc1364e2 100644 --- a/lib/utils/combineJSON.js +++ b/lib/utils/combineJSON.js @@ -108,7 +108,7 @@ export default async function combineJSON( if (file_content) { // Add some side data on each property to make filtering easier traverseObj(file_content, (obj) => { - if (obj.hasOwnProperty('value') && !obj.filePath) { + if ((Object.hasOwn(obj, '$value') || Object.hasOwn(obj, 'value')) && !obj.filePath) { obj.filePath = filePath; obj.isSource = source; diff --git a/lib/utils/flattenTokens.js b/lib/utils/flattenTokens.js index 344a36997..041e4108c 100644 --- a/lib/utils/flattenTokens.js +++ b/lib/utils/flattenTokens.js @@ -29,9 +29,12 @@ import isPlainObject from 'is-plain-obj'; */ export default function flattenTokens(tokens, to_ret = []) { for (let name in tokens) { - if (tokens.hasOwnProperty(name)) { + if (Object.hasOwn(tokens, name)) { // TODO: this is a bit fragile and arbitrary to stop when we get to a 'value' property. - if (isPlainObject(tokens[name]) && 'value' in tokens[name]) { + if ( + isPlainObject(tokens[name]) && + (Object.hasOwn(tokens[name], '$value') || Object.hasOwn(tokens[name], 'value')) + ) { to_ret.push(/** @type {Token} */ (tokens[name])); } else if (isPlainObject(tokens[name])) { flattenTokens(tokens[name], to_ret); diff --git a/lib/utils/preprocess.js b/lib/utils/preprocess.js index 5b4d2809f..869a04053 100644 --- a/lib/utils/preprocess.js +++ b/lib/utils/preprocess.js @@ -11,20 +11,56 @@ * and limitations under the License. */ +import isPlainObject from 'is-plain-obj'; + /** - * Run all registered preprocessors on the dictionary, - * returning the preprocessed dictionary in each step. - * * @typedef {import('../../types/DesignToken.d.ts').DesignTokens} DesignTokens * @typedef {import('../../types/Preprocessor.d.ts').Preprocessor} Preprocessor * @typedef {import('../../types/Preprocessor.d.ts').preprocessor} preprocessor + */ + +/** + * @param {DesignTokens} tokens + * @returns + */ +function typeW3CDelegate(tokens) { + const clone = structuredClone(tokens); + + /** + * @param {DesignTokens} slice + * @param {string} [_type] + */ + const recurse = (slice, _type) => { + let type = _type; // keep track of type through the stack + Object.values(slice).forEach((prop) => { + if (isPlainObject(prop)) { + if (typeof prop.$type === 'string') { + type = prop.$type; + } + // prop is a design token, but no $type prop currently, + // so we add it if we know what the type is from our ancestor tree + if ((prop.$value || prop.value) && !prop.$type && type) { + prop.$type = type; + } + recurse(prop, type); + } + }); + }; + + recurse(clone); + return clone; +} + +/** + * Run all registered preprocessors on the dictionary, + * returning the preprocessed dictionary in each step. * * @param {DesignTokens} tokens * @param {Record} [preprocessorObj] * @returns {Promise} */ export async function preprocess(tokens, preprocessorObj = {}) { - let processedTokens = tokens; + let processedTokens = typeW3CDelegate(tokens); const preprocessors = Object.values(preprocessorObj); if (preprocessors.length > 0) { diff --git a/lib/utils/references/getReferences.js b/lib/utils/references/getReferences.js index 1fe61b367..80da9fe9e 100644 --- a/lib/utils/references/getReferences.js +++ b/lib/utils/references/getReferences.js @@ -70,7 +70,7 @@ export default function _getReferences( */ function findReference(match, variable) { // remove 'value' to access the whole token object - variable = variable.trim().replace('.value', ''); + variable = variable.trim().replace('.value', '').replace('.$value', ''); // Find what the value is referencing const pathName = getPathFromName(variable, opts.separator ?? defaults.separator); @@ -98,7 +98,7 @@ export default function _getReferences( if (typeof value === 'string') { // function inside .replace runs multiple times if there are multiple matches - // TODO: we don't need the replace's return value, considering using something else here + // TODO: we don't need the replace's return value, consider using something else here value.replace(regex, findReference); } @@ -107,12 +107,14 @@ export default function _getReferences( // function which iterates over the object to see if there is a reference if (typeof value === 'object') { for (const key in value) { - if (value.hasOwnProperty(key) && typeof value[key] === 'string') { - value[key].replace(regex, findReference); - } - // if it is an object, we go further down the rabbit hole - if (value.hasOwnProperty(key) && typeof value[key] === 'object') { - _getReferences(value[key], tokens, opts, references); + if (Object.hasOwn(value, key)) { + if (typeof value[key] === 'string') { + value[key].replace(regex, findReference); + } + // if it is an object, we go further down the rabbit hole + if (typeof value[key] === 'object') { + _getReferences(value[key], tokens, opts, references); + } } } } diff --git a/lib/utils/references/resolveReferences.js b/lib/utils/references/resolveReferences.js index 7ad17c5db..e2ca7bbe8 100644 --- a/lib/utils/references/resolveReferences.js +++ b/lib/utils/references/resolveReferences.js @@ -87,11 +87,16 @@ export function _resolveReferences( // Find what the value is referencing const pathName = getPathFromName(variable, separator); - const refHasValue = pathName[pathName.length - 1] === 'value'; + const refHasValue = ['value', '$value'].includes(pathName[pathName.length - 1]); + // FIXME: shouldn't these two "refHasValue" conditions be reversed?? if (refHasValue && ignorePaths.indexOf(variable) !== -1) { return ''; - } else if (!refHasValue && ignorePaths.indexOf(`${variable}.value`) !== -1) { + } else if ( + !refHasValue && + (ignorePaths.indexOf(`${variable}.value`) !== -1 || + ignorePaths.indexOf(`${variable}.$value`) !== -1) + ) { return ''; } @@ -104,8 +109,8 @@ export function _resolveReferences( // we should take the '.value' of the reference // per the W3C draft spec where references do not have .value // https://design-tokens.github.io/community-group/format/#aliases-references - if (!refHasValue && ref && ref.hasOwnProperty('value')) { - ref = ref.value; + if (!refHasValue && ref && (Object.hasOwn(ref, '$value') || Object.hasOwn(ref, 'value'))) { + ref = ref.$value ?? ref.value; } if (typeof ref !== 'undefined') { @@ -117,7 +122,7 @@ export function _resolveReferences( const reference = to_ret.slice(1, -1); // Compare to found circular references - if (foundCirc.hasOwnProperty(reference)) { + if (Object.hasOwn(foundCirc, reference)) { // If the current reference is a member of a circular reference, do nothing } else if (stack.indexOf(reference) !== -1) { // If the current stack already contains the current reference, we found a new circular reference diff --git a/lib/utils/references/usesReferences.js b/lib/utils/references/usesReferences.js index a16942254..db0155827 100644 --- a/lib/utils/references/usesReferences.js +++ b/lib/utils/references/usesReferences.js @@ -34,7 +34,7 @@ export default function usesReferences(value, regexOrOptions = {}) { // if any element passes the regex test, // the whole thing should be true for (const key in value) { - if (value.hasOwnProperty(key)) { + if (Object.hasOwn(value, key)) { const element = value[key]; let reference = usesReferences(element, regexOrOptions); if (reference) { diff --git a/lib/utils/resolveObject.js b/lib/utils/resolveObject.js index c9c948df0..6041ad1d3 100644 --- a/lib/utils/resolveObject.js +++ b/lib/utils/resolveObject.js @@ -58,7 +58,7 @@ function traverseObj(slice, fullObj, opts, current_context, foundCirc) { if (!Object.hasOwn(slice, key)) { continue; } - const value = slice[key]; + const prop = slice[key]; // We want to check for ignoredKeys, this is to // skip over attributes that should not be @@ -68,12 +68,11 @@ function traverseObj(slice, fullObj, opts, current_context, foundCirc) { } current_context.push(key); - if (typeof value === 'object') { - traverseObj(value, fullObj, opts, current_context, foundCirc); - } else if (typeof value === 'string') { - let val = /** @type {string} */ (value); - if (val.indexOf('{') > -1) { - const ref = _resolveReferences(val, fullObj, { + if (typeof prop === 'object') { + traverseObj(prop, fullObj, opts, current_context, foundCirc); + } else if (typeof prop === 'string') { + if (/** @type {string} */ (prop).indexOf('{') > -1) { + const ref = _resolveReferences(prop, fullObj, { ...opts, current_context, foundCirc, diff --git a/package-lock.json b/package-lock.json index 0497c0005..ef0a122c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "docsify-cli": "^4.4.3", "eslint": "^8.7.0", "eslint-config-react-app": "^7.0.1", + "eslint-plugin-mocha": "^10.2.0", "fs-extra": "^10.0.0", "hanbi": "^1.0.1", "husky": "^8.0.3", @@ -8620,6 +8621,22 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/eslint-plugin-mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz", + "integrity": "sha512-ZhdxzSZnd1P9LqDPF0DBcFLpRIGdh1zkF2JHnQklKQOvrQtT73kdP5K9V2mzvbLR+cCAO9OI48NXK/Ax9/ciCQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "rambda": "^7.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-plugin-react": { "version": "7.33.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", @@ -8729,6 +8746,33 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -14112,6 +14156,12 @@ "node": ">= 12.0.0" } }, + "node_modules/rambda": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-7.5.0.tgz", + "integrity": "sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==", + "dev": true + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", diff --git a/package.json b/package.json index a9b965312..74ac753e4 100644 --- a/package.json +++ b/package.json @@ -101,13 +101,13 @@ "@bundled-es-modules/deepmerge": "^4.3.1", "@bundled-es-modules/glob": "^10.3.13", "@bundled-es-modules/memfs": "^4.2.3", - "path-unified": "^0.1.0", "chalk": "^5.3.0", "change-case": "^5.3.0", "commander": "^8.3.0", "is-plain-obj": "^4.1.0", "json5": "^2.2.2", "lodash-es": "^4.17.21", + "path-unified": "^0.1.0", "tinycolor2": "^1.6.0" }, "devDependencies": { @@ -127,6 +127,7 @@ "docsify-cli": "^4.4.3", "eslint": "^8.7.0", "eslint-config-react-app": "^7.0.1", + "eslint-plugin-mocha": "^10.2.0", "fs-extra": "^10.0.0", "hanbi": "^1.0.1", "husky": "^8.0.3", diff --git a/types/DesignToken.d.ts b/types/DesignToken.d.ts index 25b70cda3..4e5dd18b2 100644 --- a/types/DesignToken.d.ts +++ b/types/DesignToken.d.ts @@ -16,7 +16,11 @@ * Make sure to also change it there when this type changes! */ export interface DesignToken { - value: any; + value?: any; + $value?: any; + type?: string; + $type?: string; + $description?: string; name?: string; comment?: string; themeable?: boolean;