From 32bd40d3a94dd3be49ea795e3dbcc70e149bd6eb Mon Sep 17 00:00:00 2001 From: Danny Banks Date: Fri, 5 Feb 2021 16:15:26 -0800 Subject: [PATCH] feat(format): use named parameters in formatter functions (#533) --- .eslintrc.json | 1 - .../__snapshots__/customFormats.test.js.snap | 1599 +++++++++++++++++ __integration__/customFormats.test.js | 166 ++ __tests__/buildFiles.test.js | 4 +- __tests__/buildPlatform.test.js | 4 +- __tests__/formats/__constants.js | 64 + __tests__/formats/all.test.js | 41 +- __tests__/formats/androidResources.test.js | 260 +-- __tests__/formats/es6Constants.test.js | 63 +- __tests__/formats/javascriptModule.test.js | 29 +- .../formats/javascriptModuleFlat.test.js | 58 +- __tests__/formats/javascriptObject.test.js | 25 +- __tests__/formats/javascriptUmd.test.js | 29 +- __tests__/formats/json.test.js | 29 +- __tests__/formats/jsonFlat.test.js | 42 +- __tests__/formats/jsonNested.test.js | 49 +- __tests__/formats/lessIcons.test.js | 56 +- __tests__/formats/lessVariables.test.js | 61 +- __tests__/formats/scssIcons.test.js | 56 +- __tests__/formats/scssVariables.test.js | 71 +- docs/api.md | 4 +- docs/assets/styles.css | 5 + docs/config.md | 38 +- docs/formats.md | 121 +- lib/buildFile.js | 17 +- lib/buildPlatform.js | 16 +- lib/cleanActions.js | 2 +- lib/cleanDir.js | 2 +- lib/cleanFile.js | 5 +- lib/cleanPlatform.js | 3 +- lib/common/formats.js | 73 +- lib/extend.js | 1 - lib/register/format.js | 36 +- lib/utils/createDictionary.js | 42 + lib/utils/createFormatArgs.js | 35 + lib/utils/references/getReference.js | 1 + lib/utils/references/usesReference.js | 2 +- package.json | 5 +- scripts/generateDocs.js | 2 +- scripts/handlebars/templates/formats.hbs | 63 +- 40 files changed, 2656 insertions(+), 524 deletions(-) create mode 100644 __integration__/__snapshots__/customFormats.test.js.snap create mode 100644 __integration__/customFormats.test.js create mode 100644 __tests__/formats/__constants.js create mode 100644 lib/utils/createDictionary.js create mode 100644 lib/utils/createFormatArgs.js diff --git a/.eslintrc.json b/.eslintrc.json index edc9d0dc0..84513ec2b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,7 +18,6 @@ ], "rules": { "no-console": 0, - "no-unused-vars": 1, "comma-dangle": 0, "no-prototype-builtins": 0, "jest/valid-title": 0 diff --git a/__integration__/__snapshots__/customFormats.test.js.snap b/__integration__/__snapshots__/customFormats.test.js.snap new file mode 100644 index 000000000..db6897af7 --- /dev/null +++ b/__integration__/__snapshots__/customFormats.test.js.snap @@ -0,0 +1,1599 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`integration custom formats inline custom with new args should match snapshot 1`] = ` +"{ + \\"dictionary\\": { + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ] + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ], + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"platform\\": { + \\"transformGroup\\": \\"js\\", + \\"buildPath\\": \\"__integration__/build/\\", + \\"options\\": { + \\"otherOption\\": \\"platform option\\" + }, + \\"files\\": [ + { + \\"destination\\": \\"inlineCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + }, + { + \\"destination\\": \\"inlineCustomFormatWithNewArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } + ], + \\"transforms\\": [ + { + \\"type\\": \\"attribute\\" + }, + { + \\"type\\": \\"name\\" + }, + { + \\"type\\": \\"value\\" + }, + { + \\"type\\": \\"value\\" + } + ], + \\"actions\\": [] + }, + \\"file\\": { + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + }, + \\"destination\\": \\"inlineCustomFormatWithNewArgs.json\\" + }, + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + } +}" +`; + +exports[`integration custom formats inline custom with old args should match snapshot 1`] = ` +"{ + \\"dictionary\\": { + \\"dictionary\\": { + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ] + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ], + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"platform\\": { + \\"transformGroup\\": \\"js\\", + \\"buildPath\\": \\"__integration__/build/\\", + \\"options\\": { + \\"otherOption\\": \\"platform option\\" + }, + \\"files\\": [ + { + \\"destination\\": \\"inlineCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + }, + { + \\"destination\\": \\"inlineCustomFormatWithNewArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } + ], + \\"transforms\\": [ + { + \\"type\\": \\"attribute\\" + }, + { + \\"type\\": \\"name\\" + }, + { + \\"type\\": \\"value\\" + }, + { + \\"type\\": \\"value\\" + } + ], + \\"actions\\": [] + }, + \\"file\\": { + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + }, + \\"destination\\": \\"inlineCustomFormatWithOldArgs.json\\" + }, + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + } + }, + \\"platform\\": { + \\"transformGroup\\": \\"js\\", + \\"buildPath\\": \\"__integration__/build/\\", + \\"options\\": { + \\"otherOption\\": \\"platform option\\" + }, + \\"files\\": [ + { + \\"destination\\": \\"inlineCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + }, + { + \\"destination\\": \\"inlineCustomFormatWithNewArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } + ], + \\"transforms\\": [ + { + \\"type\\": \\"attribute\\" + }, + { + \\"type\\": \\"name\\" + }, + { + \\"type\\": \\"value\\" + }, + { + \\"type\\": \\"value\\" + } + ], + \\"actions\\": [] + }, + \\"file\\": { + \\"destination\\": \\"inlineCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } +}" +`; + +exports[`integration custom formats register custom format with new args should match snapshot 1`] = ` +"{ + \\"dictionary\\": { + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ] + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ], + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"platform\\": { + \\"transformGroup\\": \\"js\\", + \\"buildPath\\": \\"__integration__/build/\\", + \\"options\\": { + \\"otherOption\\": \\"platform option\\" + }, + \\"files\\": [ + { + \\"destination\\": \\"registerCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + }, + { + \\"destination\\": \\"registerCustomFormatWithNewArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } + ], + \\"transforms\\": [ + { + \\"type\\": \\"attribute\\" + }, + { + \\"type\\": \\"name\\" + }, + { + \\"type\\": \\"value\\" + }, + { + \\"type\\": \\"value\\" + } + ], + \\"actions\\": [] + }, + \\"file\\": { + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + }, + \\"destination\\": \\"registerCustomFormatWithNewArgs.json\\" + }, + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + } +}" +`; + +exports[`integration custom formats register custom format with old args should match snapshot 1`] = ` +"{ + \\"dictionary\\": { + \\"dictionary\\": { + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ] + }, + \\"allProperties\\": [ + { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + ], + \\"properties\\": { + \\"size\\": { + \\"padding\\": { + \\"small\\": { + \\"value\\": \\"0.5rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 0.5 + }, + \\"name\\": \\"SizePaddingSmall\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"small\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"small\\" + ] + }, + \\"medium\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingMedium\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"medium\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"medium\\" + ] + }, + \\"large\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingLarge\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"large\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"large\\" + ] + }, + \\"xl\\": { + \\"value\\": \\"1rem\\", + \\"filePath\\": \\"__integration__/tokens/size/padding.json\\", + \\"isSource\\": true, + \\"original\\": { + \\"value\\": 1 + }, + \\"name\\": \\"SizePaddingXl\\", + \\"attributes\\": { + \\"category\\": \\"size\\", + \\"type\\": \\"padding\\", + \\"item\\": \\"xl\\" + }, + \\"path\\": [ + \\"size\\", + \\"padding\\", + \\"xl\\" + ] + } + } + } + }, + \\"platform\\": { + \\"transformGroup\\": \\"js\\", + \\"buildPath\\": \\"__integration__/build/\\", + \\"options\\": { + \\"otherOption\\": \\"platform option\\" + }, + \\"files\\": [ + { + \\"destination\\": \\"registerCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + }, + { + \\"destination\\": \\"registerCustomFormatWithNewArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } + ], + \\"transforms\\": [ + { + \\"type\\": \\"attribute\\" + }, + { + \\"type\\": \\"name\\" + }, + { + \\"type\\": \\"value\\" + }, + { + \\"type\\": \\"value\\" + } + ], + \\"actions\\": [] + }, + \\"file\\": { + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + }, + \\"destination\\": \\"registerCustomFormatWithOldArgs.json\\" + }, + \\"options\\": { + \\"otherOption\\": \\"Test\\", + \\"showFileHeader\\": true + } + }, + \\"platform\\": { + \\"transformGroup\\": \\"js\\", + \\"buildPath\\": \\"__integration__/build/\\", + \\"options\\": { + \\"otherOption\\": \\"platform option\\" + }, + \\"files\\": [ + { + \\"destination\\": \\"registerCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + }, + { + \\"destination\\": \\"registerCustomFormatWithNewArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } + ], + \\"transforms\\": [ + { + \\"type\\": \\"attribute\\" + }, + { + \\"type\\": \\"name\\" + }, + { + \\"type\\": \\"value\\" + }, + { + \\"type\\": \\"value\\" + } + ], + \\"actions\\": [] + }, + \\"file\\": { + \\"destination\\": \\"registerCustomFormatWithOldArgs.json\\", + \\"options\\": { + \\"showFileHeader\\": true, + \\"otherOption\\": \\"Test\\" + } + } +}" +`; diff --git a/__integration__/customFormats.test.js b/__integration__/customFormats.test.js new file mode 100644 index 000000000..c63493b54 --- /dev/null +++ b/__integration__/customFormats.test.js @@ -0,0 +1,166 @@ +/* + * 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. + */ + +const fs = require('fs-extra'); +const StyleDictionary = require('../index'); +const {buildPath} = require('./_constants'); + +describe('integration', () => { + describe('custom formats', () => { + const styleDictionary = StyleDictionary.extend({ + source: [`__integration__/tokens/size/padding.json`], + // Adding formats directly to SD + format: { + inlineCustomFormatWithOldArgs: (dictionary, platform, file) => { + return JSON.stringify({dictionary, platform, file}, null, 2); + }, + inlineCustomFormatWithNewArgs: (opts) => { + return JSON.stringify(opts, null, 2); + }, + }, + platforms: { + inlineCustomFormats: { + transformGroup: 'js', + buildPath, + options: { + otherOption: `platform option` + }, + files: [{ + destination: 'inlineCustomFormatWithOldArgs.json', + format: 'inlineCustomFormatWithOldArgs', + options: { + showFileHeader: true, + otherOption: 'Test' + } + },{ + destination: 'inlineCustomFormatWithNewArgs.json', + format: 'inlineCustomFormatWithNewArgs', + options: { + showFileHeader: true, + otherOption: 'Test' + } + }] + }, + customFormats: { + transformGroup: 'js', + buildPath, + options: { + otherOption: `platform option` + }, + files: [{ + destination: 'registerCustomFormatWithOldArgs.json', + format: 'registerCustomFormatWithOldArgs', + options: { + showFileHeader: true, + otherOption: 'Test' + } + },{ + destination: 'registerCustomFormatWithNewArgs.json', + format: 'registerCustomFormatWithNewArgs', + options: { + showFileHeader: true, + otherOption: 'Test' + } + }] + } + } + }); + + styleDictionary.registerFormat({ + name: 'registerCustomFormatWithOldArgs', + formatter: (dictionary, platform, file) => { + return JSON.stringify({dictionary, platform, file}, null, 2); + } + }); + + styleDictionary.registerFormat({ + name: 'registerCustomFormatWithNewArgs', + formatter: (opts) => { + return JSON.stringify(opts, null, 2); + } + }); + + styleDictionary.buildAllPlatforms(); + + describe(`inline custom with old args`, () => { + const output = fs.readFileSync(`${buildPath}inlineCustomFormatWithOldArgs.json`, {encoding:'UTF-8'}); + + it(`should match snapshot`, () => { + expect(output).toMatchSnapshot(); + }); + + it(`should receive proper arguments`, () => { + const { dictionary, platform, file } = JSON.parse(output); + expect(dictionary).toHaveProperty(`properties`); + expect(dictionary).toHaveProperty(`allProperties`); + expect(platform).toHaveProperty(`options.otherOption`, `platform option`); + expect(file).toHaveProperty(`options.otherOption`, `Test`); + }); + }); + + describe(`inline custom with new args`, () => { + const output = fs.readFileSync(`${buildPath}inlineCustomFormatWithNewArgs.json`, {encoding:'UTF-8'}); + it(`should match snapshot`, () => { + expect(output).toMatchSnapshot(); + }); + + it(`should receive proper arguments`, () => { + const { dictionary, platform, file, options } = JSON.parse(output); + expect(dictionary).toHaveProperty(`properties`); + expect(dictionary).toHaveProperty(`allProperties`); + expect(platform).toHaveProperty(`options.otherOption`, `platform option`); + expect(file).toHaveProperty(`options.otherOption`, `Test`); + expect(options).toHaveProperty(`otherOption`, `Test`); + }); + }); + + + describe(`register custom format with old args`, () => { + const output = fs.readFileSync(`${buildPath}registerCustomFormatWithOldArgs.json`, {encoding:'UTF-8'}); + + it(`should match snapshot`, () => { + expect(output).toMatchSnapshot(); + }); + + it(`should receive proper arguments`, () => { + const { dictionary, platform, file } = JSON.parse(output); + expect(dictionary).toHaveProperty(`properties`); + expect(dictionary).toHaveProperty(`allProperties`); + expect(platform).toHaveProperty(`options.otherOption`, `platform option`); + expect(file).toHaveProperty(`options.otherOption`, `Test`); + }); + }); + + describe(`register custom format with new args`, () => { + const output = fs.readFileSync(`${buildPath}registerCustomFormatWithNewArgs.json`, {encoding:'UTF-8'}); + + it(`should match snapshot`, () => { + expect(output).toMatchSnapshot(); + }); + + it(`should receive proper arguments`, () => { + const { dictionary, platform, file, options } = JSON.parse(output); + expect(dictionary).toHaveProperty(`properties`); + expect(dictionary).toHaveProperty(`allProperties`); + expect(platform).toHaveProperty(`options.otherOption`, `platform option`); + expect(file).toHaveProperty(`options.otherOption`, `Test`); + expect(options).toHaveProperty(`otherOption`, `Test`); + }); + }); + + }); +}); + +afterAll(() => { + fs.emptyDirSync(buildPath); +}); \ No newline at end of file diff --git a/__tests__/buildFiles.test.js b/__tests__/buildFiles.test.js index 7897a9f2c..2ffb88895 100644 --- a/__tests__/buildFiles.test.js +++ b/__tests__/buildFiles.test.js @@ -93,13 +93,13 @@ describe('buildFiles', () => { it('should throw if build path doesn\'t have a trailing slash', () => { expect( - buildFiles.bind(null, dictionary, platformWithBadBuildPath), + buildFiles.bind(null, dictionary, platformWithBadBuildPath) ).toThrow('Build path must end in a trailing slash or you will get weird file names.'); }); it('should throw if missing a format', () => { expect( - buildFiles.bind(null, dictionary, platformWithoutFormatter), + buildFiles.bind(null, dictionary, platformWithoutFormatter) ).toThrow('Please supply a format'); }); diff --git a/__tests__/buildPlatform.test.js b/__tests__/buildPlatform.test.js index fc90b70c0..065665df4 100644 --- a/__tests__/buildPlatform.test.js +++ b/__tests__/buildPlatform.test.js @@ -24,7 +24,7 @@ describe('buildPlatform', () => { it('should throw if passed a platform that doesn\'t exist', () => { expect( - StyleDictionaryExtended.buildPlatform.bind(test, 'foobar'), + StyleDictionaryExtended.buildPlatform.bind(test, 'foobar') ).toThrow('Platform "foobar" does not exist'); expect( @@ -171,7 +171,7 @@ Unknown transformGroup "bar" found in platform "foo": `; expect( - StyleDictionaryExtended.buildPlatform.bind(StyleDictionaryExtended, 'foo'), + StyleDictionaryExtended.buildPlatform.bind(StyleDictionaryExtended, 'foo') ).toThrow(err); }); diff --git a/__tests__/formats/__constants.js b/__tests__/formats/__constants.js new file mode 100644 index 000000000..de6c91b27 --- /dev/null +++ b/__tests__/formats/__constants.js @@ -0,0 +1,64 @@ +const createDictionary = require("../../lib/utils/createDictionary"); + +const colorPropertyName = "color-base-red-400"; +const colorPropertyValue = "#EF5350"; + +const colorProperties = { + color: { + base: { + red: { + 400: { + "name": colorPropertyName, + "value": colorPropertyValue, + "original": { + "value": colorPropertyValue + }, + "attributes": { + "category": "color", + "type": "base", + "item": "red", + "subitem": "400" + }, + "path": [ + "color", + "base", + "red", + "400" + ] + } + } + } + } +}; + +const iconPropertyName = "content-icon-email"; +const iconPropertyValue = "'\\E001'"; +const itemClass = "3d_rotation"; + +const iconProperties = { + content: { + icon: { + email: { + "name": iconPropertyName, + "value": iconPropertyValue, + "original": { + "value": iconPropertyValue + }, + "attributes": { + "category": "content", + "type": "icon", + "item": itemClass + }, + path: ['content','icon','email'] + } + } + } +}; + +const colorDictionary = createDictionary({ properties: colorProperties }); +const iconDictionary = createDictionary({ properties: iconProperties }); + +module.exports = { + colorDictionary, + iconDictionary +} \ No newline at end of file diff --git a/__tests__/formats/all.test.js b/__tests__/formats/all.test.js index fc00641d4..b9a901480 100644 --- a/__tests__/formats/all.test.js +++ b/__tests__/formats/all.test.js @@ -13,6 +13,8 @@ var formats = require('../../lib/common/formats'); var _ = require('lodash'); +var createDictionary = require('../../lib/utils/createDictionary'); +var createFormatArgs = require('../../lib/utils/createFormatArgs'); var file = { "destination": "__output/", @@ -24,37 +26,21 @@ var file = { } }; -var dictionary = { - "allProperties": [{ +var properties = { + "color": { + "red": { value: '#FF0000', original: { value: '#FF0000' }, name: 'color_red', comment: 'comment', attributes: { - category: 'color', - type: 'red', - item: undefined, - subitem: undefined, - state: undefined + category: 'color', + type: 'red', + item: undefined, + subitem: undefined, + state: undefined }, path: ['color','red'] - }], - "properties": { - "color": { - "red": { - value: '#FF0000', - original: { value: '#FF0000' }, - name: 'color_red', - comment: 'comment', - attributes: { - category: 'color', - type: 'red', - item: undefined, - subitem: undefined, - state: undefined - }, - path: ['color','red'] - } } } }; @@ -63,7 +49,12 @@ describe('formats', () => { _.each(_.keys(formats), function(key) { var formatter = formats[key].bind(file); - var output = formatter(dictionary, {}, file); + const dictionary = createDictionary({ properties }); + var output = formatter(createFormatArgs({ + dictionary, + file, + platform: {}, + }), {}, file); describe('all', () => { diff --git a/__tests__/formats/androidResources.test.js b/__tests__/formats/androidResources.test.js index 2e68f23b7..49d4780ba 100644 --- a/__tests__/formats/androidResources.test.js +++ b/__tests__/formats/androidResources.test.js @@ -12,141 +12,71 @@ */ const formats = require('../../lib/common/formats'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -const dictionary = { - "properties": { - "size": { - "font": { - "small": { - "value": "12rem", - "original": { - "value": "12px" - }, - "name": "size-font-small", - "attributes": { - "category": "size", - "type": "font", - "item": "small" - }, - "path": [ - "size", - "font", - "small" - ] +const properties = { + "size": { + "font": { + "small": { + "value": "12rem", + "original": { + "value": "12px" }, - "large": { - "value": "18rem", - "original": { - "value": "18px" - }, - "name": "size-font-large", - "attributes": { - "category": "size", - "type": "font", - "item": "large" - }, - "path": [ - "size", - "font", - "large" - ] - } - } - }, - "color": { - "base": { - "red": { - "value": "#ff0000", - "comment": "comment", - "original": { - "value": "#FF0000", - "comment": "comment" - }, - "name": "color-base-red", - "attributes": { - "category": "color", - "type": "base", - "item": "red" - }, - "path": [ - "color", - "base", - "red" - ] - } + "name": "size-font-small", + "attributes": { + "category": "size", + "type": "font", + "item": "small" + }, + "path": [ + "size", + "font", + "small" + ] }, - "white": { - "value": "#ffffff", + "large": { + "value": "18rem", + "original": { + "value": "18px" + }, + "name": "size-font-large", + "attributes": { + "category": "size", + "type": "font", + "item": "large" + }, + "path": [ + "size", + "font", + "large" + ] + } + } + }, + "color": { + "base": { + "red": { + "value": "#ff0000", + "comment": "comment", "original": { - "value": "#ffffff" + "value": "#FF0000", + "comment": "comment" }, - "name": "color-white", + "name": "color-base-red", "attributes": { "category": "color", - "type": "white" + "type": "base", + "item": "red" }, "path": [ "color", - "white" + "base", + "red" ] } }, - }, - "allProperties": [ - { - "value": "12rem", - "original": { - "value": "12px" - }, - "name": "size-font-small", - "attributes": { - "category": "size", - "type": "font", - "item": "small" - }, - "path": [ - "size", - "font", - "small" - ] - }, - { - "value": "18rem", - "original": { - "value": "18px" - }, - "name": "size-font-large", - "attributes": { - "category": "size", - "type": "font", - "item": "large" - }, - "path": [ - "size", - "font", - "large" - ] - }, - { - "value": "#ff0000", - "comment": "comment", - "original": { - "value": "#FF0000", - "comment": "comment" - }, - "name": "color-base-red", - "attributes": { - "category": "color", - "type": "base", - "item": "red" - }, - "path": [ - "color", - "base", - "red" - ] - }, - { + "white": { "value": "#ffffff", "original": { "value": "#ffffff" @@ -160,45 +90,30 @@ const dictionary = { "color", "white" ] - }, - ] + } + }, }; -const customDictionary = { - properties: { - fontColor: { - primary: { - name: "fontColorPrimary", - value: "#000000", - attributes: { - category: "fontColor" - } - } - }, - backgroundColor: { - secondary: { - name: "backgroundColorSecondary", - value: "#F2F3F4", - attributes: { - category: "backgroundColor" - } +const customProperties = { + backgroundColor: { + secondary: { + name: "backgroundColorSecondary", + value: "#F2F3F4", + attributes: { + category: "backgroundColor" } } }, - allProperties: [{ - name: "backgroundColorSecondary", - value: "#F2F3F4", - attributes: { - category: "backgroundColor" - } - },{ - name: "fontColorPrimary", - value: "#000000", - attributes: { - category: "fontColor" + fontColor: { + primary: { + name: "fontColorPrimary", + value: "#000000", + attributes: { + category: "fontColor" + } } - }] -} + }, +}; const format = formats['android/resources']; const file = { @@ -206,27 +121,44 @@ const file = { "format": 'android/resources' }; +const dictionary = createDictionary({ properties }); +const customDictionary = createDictionary({ properties: customProperties }); + describe('formats', () => { describe(`android/resources`, () => { it('should match default snapshot', () => { - expect(format(dictionary, {}, file)).toMatchSnapshot(); + expect(format(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file)).toMatchSnapshot(); }); it('with resourceType override should match snapshot', () => { - expect(format(dictionary, {}, {resourceType: "dimen"})).toMatchSnapshot(); + const file = {resourceType: "dimen"} + expect(format(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file)).toMatchSnapshot(); }); it('with resourceMap override should match snapshot', () => { + const file = { + resourceMap: { + color: 'color', + fontColor: 'color', + backgroundColor: 'color' + } + }; expect( - format(customDictionary, {}, { - resourceMap: { - color: 'color', - fontColor: 'color', - backgroundColor: 'color' - } - }) + format(createFormatArgs({ + dictionary: customDictionary, + file, + platform: {} + }), {}, file) ).toMatchSnapshot(); }); diff --git a/__tests__/formats/es6Constants.test.js b/__tests__/formats/es6Constants.test.js index 732d6fe74..c2db78ac0 100644 --- a/__tests__/formats/es6Constants.test.js +++ b/__tests__/formats/es6Constants.test.js @@ -10,11 +10,13 @@ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ -var fs = require('fs-extra'); -var helpers = require('../__helpers'); -var formats = require('../../lib/common/formats'); +const fs = require('fs-extra'); +const helpers = require('../__helpers'); +const formats = require('../../lib/common/formats'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "javascript/es6", "filter": { @@ -24,29 +26,32 @@ var file = { } }; -var dictionary = { - "allProperties": [{ - "name": "red", - "value": "#EF5350", - "original": { - "value": "#EF5350" - }, - "attributes": { - "category": "color", - "type": "base", - "item": "red", - "subitem": "400" - }, - "path": [ - "color", - "base", - "red", - "400" - ] - }] +const properties = { + color: { + red: { + "name": "red", + "value": "#EF5350", + "original": { + "value": "#EF5350" + }, + "attributes": { + "category": "color", + "type": "base", + "item": "red", + "subitem": "400" + }, + "path": [ + "color", + "base", + "red", + "400" + ] + } + } }; -var formatter = formats['javascript/es6'].bind(file); +const formatter = formats['javascript/es6'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('javascript/es6', () => { @@ -59,8 +64,12 @@ describe('formats', () => { }); it('should be a valid JS file', () => { - fs.writeFileSync('./__tests__/__output/output.js', formatter(dictionary, {}, file) ); - var test = require('../__output/output.js'); + fs.writeFileSync('./__tests__/__output/output.js', formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file) ); + const test = require('../__output/output.js'); expect(test.red).toEqual(dictionary.allProperties[0].value); }); }); diff --git a/__tests__/formats/javascriptModule.test.js b/__tests__/formats/javascriptModule.test.js index 2cc06021b..8f24f4b59 100644 --- a/__tests__/formats/javascriptModule.test.js +++ b/__tests__/formats/javascriptModule.test.js @@ -11,11 +11,13 @@ * and limitations under the License. */ -var fs = require('fs-extra'); -var helpers = require('../__helpers'); -var formats = require('../../lib/common/formats'); +const fs = require('fs-extra'); +const helpers = require('../__helpers'); +const formats = require('../../lib/common/formats'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "javascript/module", "filter": { @@ -25,15 +27,14 @@ var file = { } }; -var dictionary = { - "properties": { - "color": { - "red": {"value": "#FF0000"} - } +const properties = { + "color": { + "red": {"value": "#FF0000"} } }; -var formatter = formats['javascript/module'].bind(file); +const formatter = formats['javascript/module'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('javascript/module', () => { @@ -47,8 +48,12 @@ describe('formats', () => { }); it('should be a valid JS file', () => { - fs.writeFileSync('./__tests__/__output/output.js', formatter(dictionary, {}, file) ); - var test = require('../__output/output.js'); + fs.writeFileSync('./__tests__/__output/output.js', formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file) ); + const test = require('../__output/output.js'); expect(test.color.red.value).toEqual(dictionary.properties.color.red.value); }); diff --git a/__tests__/formats/javascriptModuleFlat.test.js b/__tests__/formats/javascriptModuleFlat.test.js index 969b5866e..47378aeac 100644 --- a/__tests__/formats/javascriptModuleFlat.test.js +++ b/__tests__/formats/javascriptModuleFlat.test.js @@ -11,36 +11,38 @@ * and limitations under the License. */ -var fs = require('fs-extra'); -var helpers = require('../__helpers'); -var formats = require('../../lib/common/formats'); +const fs = require('fs-extra'); +const helpers = require('../__helpers'); +const formats = require('../../lib/common/formats'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "javascript/module-flat", }; - -var dictionary = { - "allProperties": [{ - "name": "ColorRed", - "value": "#EF5350", - "original": { - "value": "#EF5350" - }, - "attributes": { - "category": "color", - "type": "base", - "item": "red" - }, - "path": [ - "color", - "base", - "red" - ] - }] +const properties = { + color: { + red: { + value: "#EF5350", + name: "ColorRed", + original: { + value: "#EF5350" + }, + attributes: { + category: "color", + type: "red" + }, + path: [ + "color", + "red" + ] + } + } }; -var formatter = formats['javascript/module-flat'].bind(file); +const formatter = formats['javascript/module-flat'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('javascript/module-flat', () => { @@ -54,8 +56,12 @@ describe('formats', () => { }); it('should be a valid JS file', () => { - fs.writeFileSync('./__tests__/__output/output.js', formatter(dictionary) ); - var test = require('../__output/output.js'); + fs.writeFileSync('./__tests__/__output/output.js', formatter(createFormatArgs({ + dictionary, + file: {}, + platform: {} + }), {}, {}) ); + const test = require('../__output/output.js'); expect(test.ColorRed).toEqual(dictionary.allProperties[0].value); }); diff --git a/__tests__/formats/javascriptObject.test.js b/__tests__/formats/javascriptObject.test.js index 56de41c3a..50276a2d6 100644 --- a/__tests__/formats/javascriptObject.test.js +++ b/__tests__/formats/javascriptObject.test.js @@ -11,30 +11,35 @@ * and limitations under the License. */ -var formats = require('../../lib/common/formats'); -var vm = require('vm'); +const formats = require('../../lib/common/formats'); +const vm = require('vm'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "javascript/object", "name": "foo" }; -var dictionary = { - "properties": { - "color": { - "red": {"value": "#FF0000"} - } +const properties = { + "color": { + "red": {"value": "#FF0000"} } }; -var formatter = formats['javascript/object'].bind(file); +const formatter = formats['javascript/object'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('javascript/object', () => { it('should be valid JS syntax', () => { - const script = new vm.Script(formatter(dictionary, {}, file)); + const script = new vm.Script(formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file)); const context = {}; script.runInNewContext(context); expect(context.foo.color.red.value).toEqual(dictionary.properties.color.red.value); diff --git a/__tests__/formats/javascriptUmd.test.js b/__tests__/formats/javascriptUmd.test.js index e8a54527d..59ff5c7c5 100644 --- a/__tests__/formats/javascriptUmd.test.js +++ b/__tests__/formats/javascriptUmd.test.js @@ -11,11 +11,13 @@ * and limitations under the License. */ -var formats = require('../../lib/common/formats'); -var fs = require('fs-extra'); -var helpers = require('../__helpers'); +const formats = require('../../lib/common/formats'); +const fs = require('fs-extra'); +const helpers = require('../__helpers'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "javascript/umd", "filter": { @@ -25,15 +27,14 @@ var file = { } }; -var dictionary = { - "properties": { - "color": { - "red": {"value": "#FF0000"} - } +const properties = { + "color": { + "red": {"value": "#FF0000"} } }; -var formatter = formats['javascript/umd'].bind(file); +const formatter = formats['javascript/umd'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('javascript/umd', () => { @@ -47,8 +48,12 @@ describe('formats', () => { }); it('should be a valid JS file', () => { - fs.writeFileSync('./__tests__/__output/umd.js', formatter(dictionary, {}, file) ); - var test = require('../__output/umd.js'); + fs.writeFileSync('./__tests__/__output/umd.js', formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file) ); + const test = require('../__output/umd.js'); expect(test.color.red.value).toEqual(dictionary.properties.color.red.value); }); diff --git a/__tests__/formats/json.test.js b/__tests__/formats/json.test.js index 795d6edd5..422ad6bb6 100644 --- a/__tests__/formats/json.test.js +++ b/__tests__/formats/json.test.js @@ -11,24 +11,25 @@ * and limitations under the License. */ -var formats = require('../../lib/common/formats'); -var fs = require('fs-extra'); -var helpers = require('../__helpers'); +const formats = require('../../lib/common/formats'); +const fs = require('fs-extra'); +const helpers = require('../__helpers'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "json" }; -var dictionary = { - "properties": { - "color": { - "red": {"value": "#FF0000"} - } +const properties = { + "color": { + "red": {"value": "#FF0000"} } }; -var formatter = formats['json'].bind(file); +const formatter = formats['json'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('json', () => { @@ -42,8 +43,12 @@ describe('formats', () => { }); it('should be a valid JSON file', () => { - fs.writeFileSync('./__tests__/__output/output.json', formatter(dictionary, {}, file) ); - var test = require('../__output/output.json'); + fs.writeFileSync('./__tests__/__output/output.json', formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file) ); + const test = require('../__output/output.json'); expect(test.color.red.value).toEqual(dictionary.properties.color.red.value); }); }); diff --git a/__tests__/formats/jsonFlat.test.js b/__tests__/formats/jsonFlat.test.js index 4c7397a47..565b68348 100644 --- a/__tests__/formats/jsonFlat.test.js +++ b/__tests__/formats/jsonFlat.test.js @@ -11,36 +11,18 @@ * and limitations under the License. */ -var formats = require('../../lib/common/formats'); -var fs = require('fs-extra'); -var helpers = require('../__helpers'); +const formats = require('../../lib/common/formats'); +const fs = require('fs-extra'); +const helpers = require('../__helpers'); +const { colorDictionary } = require('./__constants'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "json/flat" }; -var dictionary = { - "allProperties": [{ - "name": "color-base-red", - "value": "#EF5350", - "original": { - "value": "#EF5350" - }, - "attributes": { - "category": "color", - "type": "base", - "item": "red" - }, - "path": [ - "color", - "base", - "red" - ] - }] -}; - -var formatter = formats['json/flat'].bind(file); +const formatter = formats['json/flat'].bind(file); describe('formats', () => { describe('json/flat', () => { @@ -54,9 +36,13 @@ describe('formats', () => { }); it('should be a valid JSON file', () => { - fs.writeFileSync('./__tests__/__output/output.flat.json', formatter(dictionary, {}, file) ); - var test = require('../__output/output.flat.json'); - expect(test['color-base-red']).toEqual(dictionary.allProperties[0].value); + fs.writeFileSync('./__tests__/__output/output.flat.json', formatter(createFormatArgs({ + dictionary: colorDictionary, + file, + platform: {} + }), {}, file) ); + const test = require('../__output/output.flat.json'); + expect(test['color-base-red-400']).toEqual(colorDictionary.allProperties[0].value); }); }); diff --git a/__tests__/formats/jsonNested.test.js b/__tests__/formats/jsonNested.test.js index 79263b113..32ba7709a 100644 --- a/__tests__/formats/jsonNested.test.js +++ b/__tests__/formats/jsonNested.test.js @@ -11,33 +11,34 @@ * and limitations under the License. */ -var formats = require('../../lib/common/formats'); -var fs = require('fs-extra'); -var helpers = require('../__helpers'); +const formats = require('../../lib/common/formats'); +const fs = require('fs-extra'); +const helpers = require('../__helpers'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { destination: 'output/', format: 'json/nested', }; -var dictionary = { - properties: { - color: { - base: { - comment: 'This is a comment', - metadata: [1,2,3], - red: { - primary: { value: '#611D1C' }, - secondary: { - inverse: { value: '#000000' }, - }, +const properties = { + color: { + base: { + comment: 'This is a comment', + metadata: [1,2,3], + red: { + primary: { value: '#611D1C' }, + secondary: { + inverse: { value: '#000000' }, }, }, }, }, }; -var formatter = formats['json/nested'].bind(file); +const formatter = formats['json/nested'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', function() { describe('json/nested', function() { @@ -50,8 +51,12 @@ describe('formats', function() { }); it('should be a valid JSON file', function() { - fs.writeFileSync('./__tests__/__output/json-nested.json', formatter(dictionary, {}, file)); - var test = require('../__output/json-nested.json'); + fs.writeFileSync('./__tests__/__output/json-nested.json', formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file)); + const test = require('../__output/json-nested.json'); expect(test.color.base.red.primary) .toEqual(dictionary.properties.color.base.red.primary.value); expect(test.color.base.red.secondary.inverse) @@ -62,8 +67,12 @@ describe('formats', function() { // non-token data is anything in the dictionary object that is not a token object // i.e. anything in the rest of the object that doesn't have a 'value' - fs.writeFileSync('./__tests__/__output/json-nested.json', formatter(dictionary)); - var test = require('../__output/json-nested.json'); + fs.writeFileSync('./__tests__/__output/json-nested.json', formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file)); + const test = require('../__output/json-nested.json'); expect(test.color.base.comment) .toEqual(dictionary.properties.color.base.comment); expect(test.color.base.metadata) diff --git a/__tests__/formats/lessIcons.test.js b/__tests__/formats/lessIcons.test.js index 03653d2af..d58dd6095 100644 --- a/__tests__/formats/lessIcons.test.js +++ b/__tests__/formats/lessIcons.test.js @@ -11,45 +11,57 @@ * and limitations under the License. */ -var formats = require('../../lib/common/formats'); -var less = require('less'); +const formats = require('../../lib/common/formats'); +const less = require('less'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "less/icons", "name": "foo" }; -var propertyName = "content-icon-email"; -var propertyValue = "'\\E001'"; -var itemClass = "3d_rotation"; - -var dictionary = { - "allProperties": [{ - "name": propertyName, - "value": propertyValue, - "original": { - "value": propertyValue - }, - "attributes": { - "category": "content", - "type": "icon", - "item": itemClass +const propertyName = "content-icon-email"; +const propertyValue = "'\\E001'"; +const itemClass = "3d_rotation"; + +const properties = { + content: { + icon: { + email: { + "name": propertyName, + "value": propertyValue, + "original": { + "value": propertyValue + }, + "attributes": { + "category": "content", + "type": "icon", + "item": itemClass + }, + path: ['content','icon','email'] + } } - }] + } }; -var config = { +const platform = { prefix: 'sd' // Style-Dictionary Prefix }; -var formatter = formats['less/icons'].bind(file); +const formatter = formats['less/icons'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('less/icons', () => { it('should have a valid less syntax', () => { - return less.render(formatter(dictionary, config, file)) + return less.render(formatter(createFormatArgs({ + dictionary, + file, + platform + }), platform, file)) .then(function(output) { expect(output).toBeDefined(); }) diff --git a/__tests__/formats/lessVariables.test.js b/__tests__/formats/lessVariables.test.js index 24bc0234f..8957e25a3 100644 --- a/__tests__/formats/lessVariables.test.js +++ b/__tests__/formats/lessVariables.test.js @@ -13,6 +13,8 @@ var formats = require('../../lib/common/formats'); var less = require('less'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); var file = { "destination": "__output/", @@ -20,38 +22,49 @@ var file = { "name": "foo" }; -var propertyName = "color-base-red-400"; -var propertyValue = "#EF5350"; +const propertyName = "color-base-red-400"; +const propertyValue = "#EF5350"; -var dictionary = { - "allProperties": [{ - "name": propertyName, - "value": propertyValue, - "original": { - "value": propertyValue - }, - "attributes": { - "category": "color", - "type": "base", - "item": "red", - "subitem": "400" - }, - "path": [ - "color", - "base", - "red", - "400" - ] - }] +const properties = { + color: { + base: { + red: { + 400: { + "name": propertyName, + "value": propertyValue, + "original": { + "value": propertyValue + }, + "attributes": { + "category": "color", + "type": "base", + "item": "red", + "subitem": "400" + }, + "path": [ + "color", + "base", + "red", + "400" + ] + } + } + } + } }; -var formatter = formats['less/variables'].bind(file); +const formatter = formats['less/variables'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('less/variables', () => { it('should have a valid less syntax', () => { - return less.render(formatter(dictionary, {}, file)) + return less.render(formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file)) .then(function(output) { expect(output).toBeDefined(); }) diff --git a/__tests__/formats/scssIcons.test.js b/__tests__/formats/scssIcons.test.js index fec539a26..84a87b92c 100644 --- a/__tests__/formats/scssIcons.test.js +++ b/__tests__/formats/scssIcons.test.js @@ -11,46 +11,58 @@ * and limitations under the License. */ -var formats = require('../../lib/common/formats'); -var scss = require('node-sass'); +const formats = require('../../lib/common/formats'); +const scss = require('node-sass'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); -var file = { +const file = { "destination": "__output/", "format": "scss/icons", "name": "foo" }; -var propertyName = "content-icon-email"; -var propertyValue = "'\\E001'"; -var itemClass = "3d_rotation"; - -var dictionary = { - "allProperties": [{ - "name": propertyName, - "value": propertyValue, - "original": { - "value": propertyValue - }, - "attributes": { - "category": "content", - "type": "icon", - "item": itemClass +const propertyName = "content-icon-email"; +const propertyValue = "'\\E001'"; +const itemClass = "3d_rotation"; + +const properties = { + content: { + icon: { + email: { + "name": propertyName, + "value": propertyValue, + "original": { + "value": propertyValue + }, + "attributes": { + "category": "content", + "type": "icon", + "item": itemClass + }, + path: ['content','icon','email'] + } } - }] + } }; -var config = { +const platform = { prefix: 'sd' // Style-Dictionary Prefix }; -var formatter = formats['scss/icons'].bind(file); +const formatter = formats['scss/icons'].bind(file); +const dictionary = createDictionary({ properties }); describe('formats', () => { describe('scss/icons', () => { it('should have a valid scss syntax', () => { const result = scss.renderSync({ - data: formatter(dictionary, config, file), + data: formatter(createFormatArgs({ + dictionary, + file, + platform + }), platform, file), }); expect(result.css).toBeDefined(); }); diff --git a/__tests__/formats/scssVariables.test.js b/__tests__/formats/scssVariables.test.js index cb1215776..6bd304b10 100644 --- a/__tests__/formats/scssVariables.test.js +++ b/__tests__/formats/scssVariables.test.js @@ -14,6 +14,8 @@ var formats = require('../../lib/common/formats'); var scss = require('node-sass'); var _ = require('lodash'); +const createDictionary = require('../../lib/utils/createDictionary'); +const createFormatArgs = require('../../lib/utils/createFormatArgs'); var file = { "destination": "__output/", @@ -21,52 +23,71 @@ var file = { "name": "foo" }; -var propertyName = "color-base-red-400"; -var propertyValue = "#EF5350"; +const propertyName = "color-base-red-400"; +const propertyValue = "#EF5350"; -var dictionary = { - "allProperties": [{ - "name": propertyName, - "value": propertyValue, - "original": { - "value": propertyValue - }, - "attributes": { - "category": "color", - "type": "base", - "item": "red", - "subitem": "400" - }, - "path": [ - "color", - "base", - "red", - "400" - ] - }] +const properties = { + color: { + base: { + red: { + 400: { + "name": propertyName, + "value": propertyValue, + "original": { + "value": propertyValue + }, + "attributes": { + "category": "color", + "type": "base", + "item": "red", + "subitem": "400" + }, + "path": [ + "color", + "base", + "red", + "400" + ] + } + } + } + } }; var formatter = formats['scss/variables'].bind(file); +const dictionary = createDictionary({properties}); describe('formats', () => { describe('scss/variables', () => { it('should have a valid scss syntax', () => { const result = scss.renderSync({ - data: formatter(dictionary, {}, file), + data: formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file), }); expect(result.css).toBeDefined(); }); it('should optionally use !default', () => { var themeableDictionary = _.cloneDeep(dictionary), - formattedScss = formatter(dictionary), + formattedScss = formatter(createFormatArgs({ + dictionary, + file, + platform: {} + }), {}, file), themeableScss = ""; expect(formattedScss).not.toMatch("!default"); themeableDictionary.allProperties[0].themeable = true; - themeableScss = formatter(themeableDictionary); + themeableScss = formatter(createFormatArgs({ + dictionary: themeableDictionary, + file, + platform: {} + }), {}, file); expect(themeableScss).toMatch("#EF5350 !default;"); }); diff --git a/docs/api.md b/docs/api.md index fe4ad514b..e406aec2a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -219,13 +219,13 @@ Add a custom format to the style dictionary | --- | --- | --- | | format | Object | | | format.name | String | Name of the format to be referenced in your config.json | -| format.formatter | function | Function to perform the format. Takes 2 arguments, `dictionary` and `config` Must return a string. Function is bound to its file object in the `platform.files` array. | +| format.formatter | function | Function to perform the format. Takes a single argument. See [creating custom formats](formats.md#creating-formats) Must return a string, which is then written to a file. | **Example** ```js StyleDictionary.registerFormat({ name: 'json', - formatter: function(dictionary, config) { + formatter: function({dictionary, platform, options, file}) { return JSON.stringify(dictionary.properties, null, 2); } }) diff --git a/docs/assets/styles.css b/docs/assets/styles.css index 7cf32a6ca..ba887e1f4 100644 --- a/docs/assets/styles.css +++ b/docs/assets/styles.css @@ -351,6 +351,11 @@ section.cover .cover-main { color: var(--theme-color-dark); } +.alert { + padding: 2rem; + border: 0.1rem solid var(--theme-color-secondary-light); +} + /* Badges */ .markdown-section > p:first-child > a { diff --git a/docs/config.md b/docs/config.md index bd3ae7af2..3cb103e34 100644 --- a/docs/config.md +++ b/docs/config.md @@ -5,7 +5,43 @@ Style dictionaries are configuration driven. Your config file defines what execu By default, Style Dictionary looks for a `config.json` file in the root of your package. If not found, it looks for a `config.js` file in the root of your package. You can also specify a custom location when you use the [CLI](using_the_cli.md). If you want a custom build system using the [npm module](using_the_npm_module.md), you can specify a custom location for a configuration file or use a plain Javascript object. ## config.js -You can find out more about creating configurations in JS in our documentation about using the [npm module](using_the_npm_module.md). +You can find out more about creating configurations in JS in our documentation about using the [npm module](using_the_npm_module.md). Additionally you can create your configuration as a Node module and export it: + +```javascript +// config.js +module.exports = { + source: [`tokens/**/*.json`], + // If you don't want to call the registerTransform method a bunch of times + // you can override the whole transform object directly. This works because + // the .extend method copies everything in the config + // to itself, allowing you to override things. It's also doing a deep merge + // to protect from accidentally overriding nested attributes. + transform: { + // Now we can use the transform 'myTransform' below + myTransform: { + type: 'name', + transformer: (prop) => prop.path.join('_').toUpperCase() + } + }, + // Same with formats, you can now write them directly to this config + // object. The name of the format is the key. + format: { + myFormat: ({dictionary, platform}) => { + return dictionary.allProperties.map(prop => `${prop.name}: ${prop.value}`).join('\n'); + } + }, + platforms: { + // ... + } +} +``` + +```json5 +// package.json + "scripts": { + "build": "style-dictionary build" + } +``` ## config.json Here is a quick example: diff --git a/docs/formats.md b/docs/formats.md index 619bf2362..1f605c1e3 100644 --- a/docs/formats.md +++ b/docs/formats.md @@ -75,9 +75,7 @@ The token/property that is passed to the filter function has already been [trans ### References in output files -Starting with version 3.0, some formats can keep the references in the output. - -This is a bit hard to explain, so let's look at an example. Say you have this very basic set of design tokens: +Starting with version 3.0, some formats can keep the references in the output. This is a bit hard to explain, so let's look at an example. Say you have this very basic set of design tokens: ```json5 // tokens.json @@ -90,6 +88,8 @@ This is a bit hard to explain, so let's look at an example. Say you have this ve } ``` +With this configuration: + ```json5 // config.json { @@ -101,6 +101,7 @@ This is a bit hard to explain, so let's look at an example. Say you have this ve "destination": "variables.css", "format": "css/variables", "options": { + // Look here 👇 "outputReferences": true } }] @@ -140,18 +141,122 @@ Not all formats use the `outputReferences` option because that file format might * [ios-swift/class.swift](#ios-swiftclassswift) * [flutter/class.dart](#flutterclassdart) +You can create custom formats that output references as well. See the [Custom format with output references](#custom-format-with-output-references) section. + ### Creating formats -You can create custom formats using the [`registerFormat`](api.md#registerformat) function. If you want to add configuration to your custom format, `this` is bound to the file object. Using this, you can access attributes on the file object with `this.myCustomAttribute` if the file object looks like: +You can create custom formats using the [`registerFormat`](api.md#registerformat) function or by directly including them in your [configuration](config.md). A format has a name and a formatter function, which takes an object as the argument and should return a string which is then written to a file. + +### formatter +> format.formatter(args) ⇒ String + +The formatter function that is called when Style Dictionary builds files. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParamTypeDescription
argsObject

A single argument to support named parameters and destructuring.

+
args.dictionaryObject

The transformed and resolved dictionary object

+
args.dictionary.propertiesObject

Object structure of the tokens/properties that has been transformed and references resolved.

+
args.dictionary.allPropertiesArray

Flattened array of all the tokens/properties. This makes it easy to output a list, like a list of SCSS variables.

+
args.dictionary.usesReferencefunction

Use this function to see if a token's value uses a reference. This is the same function style dictionary uses internally to detect a reference.

+
args.dictionary.getReferencefunction

Use this function to get the token/property that it references. You can use this to output a reference in your custom format. For example: dictionary.getReference(token.original.value) // returns the referenced token object

+
args.platformObject

The platform configuration this format is being called in.

+
args.fileObject

The file configuration this format is being called in.

+
args.optionsObject

Merged options object that combines platform level configuration and file level configuration. File options take precedence.

+
+ +**Example** +```js +StyleDictionary.registerFormat({ + name: 'myCustomFormat', + formatter: function({dictionary, platform, options, file}) { + return JSON.stringify(dictionary.properties, null, 2); + } +}) +``` + +* * * + + +It is recommended for any configuration needed for your custom format to use the `options` object. Style Dictionary will merge platform and file options so that in your Style Dictionary configuration you can specify options at a platform or file level. + +To use your custom format, you call it by name in the file configuration object: ```json { - "destination": "destination", - "format": "myCustomFormat", - "myCustomAttribute": "Hello world" + "source": ["tokens/**/*.json"], + "platforms": { + "css": { + "options": { + "showFileHeader": true + }, + "transformGroup": "css", + "files": [{ + "destination": "destination", + "format": "myCustomFormat", + "options": { + "showFileHeader": false + } + }] + } + } } ``` +
+Note: to support legacy ways of defining custom formats, this in the formatter function is bound to the file object and when Style Dictionary calls the formatter function it passes 3 arguments: dictionary, platform, and file. Starting in 3.0 all data the formatter needs is in the first argument as shown above to make it easier to grab the arguments by name rather than by position. We recommend not using this or the positional arguments in your custom format. +
+ +#### Custom format with output references + +To take advantage of outputting references in your custom formats there are 2 helper methods in the `dictionary` argument passed to your formatter function: `usesReference(value)` and `getReference(value)`. Here is an example using those: + +```javascript +StyleDictionary.registerFormat({ + name: `es6WithReferences`, + formatter: function({dictionary}) { + return dictionary.allProperties.map(token => { + let value = JSON.stringify(token.value); + // the `dictionary` object now has `usesReference()` and + // `getReference()` methods. `usesReference()` will return true if + // the value has a reference in it. `getReference()` will return + // the reference to the whole token so that you can access its + // name or any other attributes. + if (dictionary.usesReference(token.original.value)) { + // Note: make sure to use `token.original.value` because + // `token.value` is already resolved at this point. + const reference = dictionary.getReference(token.original.value); + value = reference.name; + } + return `export const ${token.name} = ${value};` + }).join(`\n`) + } +}); +``` ### Using a template / templating engine to create a format @@ -185,7 +290,7 @@ const template = Handlebars.compile( fs.readFileSync('templates/MyTemplate.hbs') styleDictionary.registerFormat({ name: 'my/format', - formatter: function(dictionary, platform) { + formatter: function({dictionary, platform}) { return template({ properties: dictionary.properties, options: platform diff --git a/lib/buildFile.js b/lib/buildFile.js index 4dfd72aa3..5a7ca8f0d 100644 --- a/lib/buildFile.js +++ b/lib/buildFile.js @@ -15,8 +15,9 @@ var path = require('path'), fs = require('fs-extra'), chalk = require('chalk'), filterProperties = require('./filterProperties'), - GroupMessages = require('./utils/groupMessages'), - deepExtend = require('./utils/deepExtend'); + GroupMessages = require('./utils/groupMessages'); + +const createFormatArgs = require('./utils/createFormatArgs'); /** * Takes the style property object and a format and returns a @@ -35,11 +36,6 @@ function buildFile(file = {}, platform = {}, dictionary = {}) { if (typeof destination !== 'string') throw new Error('Please enter a valid destination'); - // This will merge platform and file-level configuration - // where the file configuration takes precedence - const {options} = platform; - file = deepExtend([{}, {options}, file]); - // to maintain backwards compatability we bind the format to the file object format = format.bind(file); var fullDestination = destination; @@ -93,8 +89,11 @@ function buildFile(file = {}, platform = {}, dictionary = {}) { }); let propertyNamesCollisionCount = GroupMessages.count(PROPERTY_NAME_COLLISION_WARNINGS); - - fs.writeFileSync(fullDestination, format(filteredProperties, platform, file)); + fs.writeFileSync(fullDestination, format(createFormatArgs({ + dictionary: filteredProperties, + platform, + file + }), platform, file)); console.log((propertyNamesCollisionCount>0 ? '⚠️ ' : chalk.bold.green('✔︎ ')) + ' ' + fullDestination); if(propertyNamesCollisionCount > 0) { diff --git a/lib/buildPlatform.js b/lib/buildPlatform.js index 4104ac51a..78f8fb0ae 100644 --- a/lib/buildPlatform.js +++ b/lib/buildPlatform.js @@ -11,12 +11,11 @@ * and limitations under the License. */ -var flattenProperties = require('./utils/flattenProperties'), - transformConfig = require('./transform/config'), +var transformConfig = require('./transform/config'), buildFiles = require('./buildFiles'), - performActions = require('./performActions'), - getReference = require('./utils/references/getReference'), - usesReference = require('./utils/references/usesReference'); + performActions = require('./performActions'); + +const createDictionary = require('./utils/createDictionary'); /** * Takes a platform and performs all transforms to @@ -59,12 +58,7 @@ function buildPlatform(platform) { // This is the dictionary object we pass to the file // building and action methods. - var dictionary = { - properties: properties, - allProperties: flattenProperties( properties ), - getReference: getReference, - usesReference: usesReference - }; + const dictionary = createDictionary({properties}); buildFiles(dictionary, platformConfig); performActions(dictionary, platformConfig); diff --git a/lib/cleanActions.js b/lib/cleanActions.js index 9f7ac2594..074ab8a63 100644 --- a/lib/cleanActions.js +++ b/lib/cleanActions.js @@ -26,7 +26,7 @@ var _ = require('lodash'); */ function cleanActions(dictionary, platform) { if (platform.actions) { - _.each(platform.actions, function(action, i) { + _.each(platform.actions, function(action) { if (typeof action.undo === 'function') { action.undo(dictionary, platform); } diff --git a/lib/cleanDir.js b/lib/cleanDir.js index bc8a63d05..0b79ff24d 100644 --- a/lib/cleanDir.js +++ b/lib/cleanDir.js @@ -25,7 +25,7 @@ var path = require('path'), * @param {Object} dictionary (unused) * @returns {null} */ -function cleanDir(file = {}, platform = {}, dictionary = {}) { +function cleanDir(file = {}, platform = {}) { var { destination } = file; if (typeof destination !== 'string') diff --git a/lib/cleanFile.js b/lib/cleanFile.js index 70ca7293b..320e1dc43 100644 --- a/lib/cleanFile.js +++ b/lib/cleanFile.js @@ -11,8 +11,7 @@ * and limitations under the License. */ -var path = require('path'), - fs = require('fs-extra'), +var fs = require('fs-extra'), chalk = require('chalk'); @@ -27,7 +26,7 @@ var path = require('path'), * @param {Function} filter (unused) * @returns {null} */ -function cleanFile(file = {}, platform = {}, dictionary = {}) { +function cleanFile(file = {}, platform = {}) { var { destination } = file; if (typeof destination !== 'string') diff --git a/lib/cleanPlatform.js b/lib/cleanPlatform.js index 650f249bd..0cf006366 100644 --- a/lib/cleanPlatform.js +++ b/lib/cleanPlatform.js @@ -15,8 +15,7 @@ var flattenProperties = require('./utils/flattenProperties'), transformConfig = require('./transform/config'), cleanFiles = require('./cleanFiles'), cleanDirs = require('./cleanDirs'), - cleanActions = require('./cleanActions'), - chalk = require('chalk'); + cleanActions = require('./cleanActions'); /** * Takes a platform and performs all transforms to diff --git a/lib/common/formats.js b/lib/common/formats.js index 73c458cce..35954f943 100644 --- a/lib/common/formats.js +++ b/lib/common/formats.js @@ -60,7 +60,7 @@ function formattedVariables(format, dictionary, outputReferences = false) { // Some languages are imperative, meaning a variable has to be defined // before it is used. If `outputReferences` is true, check if the token // has a reference, and if it does send it to the end of the array. - .sort((a,b) => { + .sort((a) => { if (outputReferences) { if (dictionary.usesReference(a.original.value)) { return 1; @@ -159,8 +159,7 @@ module.exports = { * } * ``` */ - 'css/variables': function(dictionary, config = {}, file = {}) { - const { options = {} } = file; + 'css/variables': function({dictionary, options}) { return fileHeader(options.showFileHeader) + ':root {\n' + formattedVariables('css', dictionary, options.outputReferences) + @@ -187,11 +186,11 @@ module.exports = { ), // This will soon be removed, is left here only for backwards compatibility - 'sass/map-flat': function(dictionary, config) { + 'sass/map-flat': function({dictionary, options}) { GroupMessages.add(SASS_MAP_FORMAT_DEPRECATION_WARNINGS, "sass/map-flat"); const templateMapFlat = _.template(fs.readFileSync(__dirname + '/templates/scss/map-flat.template')); return templateMapFlat({ - showFileHeader: config.showFileHeader, + showFileHeader: options.showFileHeader, allProperties: dictionary.allProperties }); }, @@ -223,11 +222,11 @@ module.exports = { ), // This will soon be removed, is left here only for backwards compatibility - 'sass/map-deep': function(dictionary, config) { + 'sass/map-deep': function({dictionary, options}) { GroupMessages.add(SASS_MAP_FORMAT_DEPRECATION_WARNINGS, "sass/map-deep"); const templateMapDeep = _.template(fs.readFileSync(__dirname + '/templates/scss/map-deep.template')); return templateMapDeep({ - showFileHeader: config.showFileHeader, + showFileHeader: options.showFileHeader, properties: dictionary.properties, allProperties: dictionary.allProperties }); @@ -249,8 +248,7 @@ module.exports = { * $color-background-alt: #eeeeee !default; * ``` */ - 'scss/variables': function(dictionary, config = {}, file = {}) { - const { options = {} } = file; + 'scss/variables': function({dictionary, options}) { return fileHeader(options.showFileHeader, 'short') + formattedVariables('sass', dictionary, options.outputReferences); }, @@ -266,9 +264,8 @@ module.exports = { * .icon.email:before { content:$content-icon-email; } * ``` */ - 'scss/icons': function(dictionary, config, file) { - const { options = {} } = file; - return fileHeader(options.showFileHeader, 'short') + iconsWithPrefix('$', dictionary.allProperties, config, 'short'); + 'scss/icons': function({dictionary, options}) { + return fileHeader(options.showFileHeader, 'short') + iconsWithPrefix('$', dictionary.allProperties, options, 'short'); }, /** @@ -285,8 +282,7 @@ module.exports = { * \@color-background-alt: #eeeeee; * ``` */ - 'less/variables': function(dictionary, config, file) { - const { options = {} } = file; + 'less/variables': function({dictionary, options}) { return fileHeader(options.showFileHeader, 'short') + formattedVariables('less', dictionary, options.outputReferences); }, @@ -302,9 +298,8 @@ module.exports = { * .icon.email:before { content:\@content-icon-email; } * ``` */ - 'less/icons': function(dictionary, config, file) { - const { options = {} } = file; - return fileHeader(options.showFileHeader, 'short') + iconsWithPrefix('@', dictionary.allProperties, config, 'short'); + 'less/icons': function({dictionary, options}) { + return fileHeader(options.showFileHeader, 'short') + iconsWithPrefix('@', dictionary.allProperties, options, 'short'); }, /** @@ -325,8 +320,8 @@ module.exports = { * } * ``` */ - 'javascript/module': function(dictionary, config, file) { - return fileHeader(file.options) + + 'javascript/module': function({dictionary, options}) { + return fileHeader(options) + 'module.exports = ' + JSON.stringify(dictionary.properties, null, 2) + ';'; }, @@ -343,10 +338,10 @@ module.exports = { *} *``` */ - 'javascript/module-flat': function(dictionary) { + 'javascript/module-flat': function({dictionary}) { return fileHeader(this.options) + 'module.exports = ' + - module.exports['json/flat'](dictionary) + ';'; + module.exports['json/flat']({dictionary}) + ';'; }, /** @@ -368,8 +363,8 @@ module.exports = { * } * ``` */ - 'javascript/object': function(dictionary, config, file) { - return fileHeader(file.options) + + 'javascript/object': function({dictionary, options, file}) { + return fileHeader(options) + 'var ' + (file.name || '_styleDictionary') + ' = ' + @@ -407,9 +402,9 @@ module.exports = { * })) * ``` */ - 'javascript/umd': function(dictionary, config, file) { + 'javascript/umd': function({dictionary, options, file}) { var name = file.name || '_styleDictionary' - return fileHeader(file.options) + + return fileHeader(options) + '(function(root, factory) {\n' + ' if (typeof module === "object" && module.exports) {\n' + ' module.exports = factory();\n' + @@ -457,8 +452,8 @@ module.exports = { * export const ColorBackgroundAlt = '#fcfcfcfc'; * ``` */ - 'javascript/es6': function(dictionary, config, file) { - return fileHeader(file.options) + + 'javascript/es6': function({dictionary, options}) { + return fileHeader(options) + dictionary.allProperties.map(function(prop) { var to_ret_prop = 'export const ' + prop.name + ' = ' + JSON.stringify(prop.value) + ';'; if (prop.comment) @@ -494,11 +489,11 @@ module.exports = { * 14sp * ``` */ - 'android/resources': function(dictionary, platform, file) { + 'android/resources': function({dictionary, options, file}) { const template = _.template( fs.readFileSync(__dirname + '/templates/android/resources.template') ); - return template({dictionary, file}); + return template({dictionary, file, options}); }, /** @@ -777,11 +772,11 @@ module.exports = { * } * ``` */ - 'ios-swift/class.swift': function(dictionary, platform, file) { + 'ios-swift/class.swift': function({dictionary, options, file}) { const template = _.template( fs.readFileSync(__dirname + '/templates/ios-swift/class.swift.template') ); - return template({dictionary, file}); + return template({dictionary, file, options}); }, /** @@ -827,7 +822,7 @@ module.exports = { * } * ``` */ - 'json': function(dictionary) { + 'json': function({dictionary}) { return JSON.stringify(dictionary.properties, null, 2); }, @@ -849,7 +844,7 @@ module.exports = { * } * ``` */ - 'json/asset': function(dictionary) { + 'json/asset': function({dictionary}) { return JSON.stringify({asset: dictionary.properties.asset}, null, 2); }, @@ -869,7 +864,7 @@ module.exports = { * } * ``` */ - 'json/nested': function(dictionary) { + 'json/nested': function({dictionary}) { return JSON.stringify(minifyDictionary(dictionary.properties), null, 2); }, @@ -885,7 +880,7 @@ module.exports = { * } * ``` */ - 'json/flat': function(dictionary) { + 'json/flat': function({dictionary}) { return '{\n' + dictionary.allProperties.map(function(prop) { return ` "${prop.name}": ${JSON.stringify(prop.value)}`; }).join(',\n') + '\n}'; @@ -909,7 +904,7 @@ module.exports = { * } * ``` */ - 'sketch/palette': function(dictionary) { + 'sketch/palette': function({dictionary}) { var to_ret = { 'compatibleVersion':'1.0', 'pluginVersion':'1.1' @@ -945,7 +940,7 @@ module.exports = { * } * ``` */ - 'sketch/palette/v2': function(dictionary) { + 'sketch/palette/v2': function({dictionary}) { var to_ret = { compatibleVersion: '2.0', pluginVersion: '2.2', @@ -981,10 +976,10 @@ module.exports = { * static const contentFontFamily1 = "NewJune"; * ``` */ - 'flutter/class.dart': function(dictionary, platform, file) { + 'flutter/class.dart': function({dictionary, options, file}) { const template = _.template( fs.readFileSync(__dirname + '/templates/flutter/class.dart.template') ); - return template({dictionary, file}); + return template({dictionary, file, options}); }, }; diff --git a/lib/extend.js b/lib/extend.js index 29c8bf0a4..589aa58f7 100644 --- a/lib/extend.js +++ b/lib/extend.js @@ -17,7 +17,6 @@ var combineJSON = require('./utils/combineJSON'), deepExtend = require('./utils/deepExtend'), resolveCwd = require('resolve-cwd'), _ = require('lodash'), - chalk = require('chalk'), GroupMessages = require('./utils/groupMessages'); var PROPERTY_VALUE_COLLISIONS = GroupMessages.GROUP.PropertyValueCollisions; diff --git a/lib/register/format.js b/lib/register/format.js index 0b3670805..80e88b8b7 100644 --- a/lib/register/format.js +++ b/lib/register/format.js @@ -11,20 +11,50 @@ * and limitations under the License. */ +/** + * @module format + */ + +/** + * The formatter function that is called when Style Dictionary builds files. + * + * @function formatter + * @memberof module:format + * @param {Object} args - A single argument to support named parameters and destructuring. + * @param {Object} args.dictionary - The transformed and resolved dictionary object + * @param {Object} args.dictionary.properties - Object structure of the tokens/properties that has been transformed and references resolved. + * @param {Array} args.dictionary.allProperties - Flattened array of all the tokens/properties. This makes it easy to output a list, like a list of SCSS variables. + * @param {function(value): Boolean} args.dictionary.usesReference - Use this function to see if a token's value uses a reference. This is the same function style dictionary uses internally to detect a reference. + * @param {function(value): Value} args.dictionary.getReference - Use this function to get the token/property that it references. You can use this to output a reference in your custom format. For example: `dictionary.getReference(token.original.value) // returns the referenced token object` + * @param {Object} args.platform - The platform configuration this format is being called in. + * @param {Object} args.file - The file configuration this format is being called in. + * @param {Object} args.options - Merged options object that combines platform level configuration and file level configuration. File options take precedence. + * @returns {String} + * @example + * ```js + * StyleDictionary.registerFormat({ + * name: 'myCustomFormat', + * formatter: function({dictionary, platform, options, file}) { + * return JSON.stringify(dictionary.properties, null, 2); + * } + * }) + * ``` + */ + /** * Add a custom format to the style dictionary * @static * @memberof module:style-dictionary * @param {Object} format * @param {String} format.name - Name of the format to be referenced in your config.json - * @param {Function} format.formatter - Function to perform the format. Takes 2 arguments, `dictionary` and `config` - * Must return a string. Function is bound to its file object in the `platform.files` array. + * @param {function} format.formatter - Function to perform the format. Takes a single argument. See [creating custom formats](formats.md#creating-formats) + * Must return a string, which is then written to a file. * @returns {module:style-dictionary} * @example * ```js * StyleDictionary.registerFormat({ * name: 'json', - * formatter: function(dictionary, config) { + * formatter: function({dictionary, platform, options, file}) { * return JSON.stringify(dictionary.properties, null, 2); * } * }) diff --git a/lib/utils/createDictionary.js b/lib/utils/createDictionary.js new file mode 100644 index 000000000..7c9f027ca --- /dev/null +++ b/lib/utils/createDictionary.js @@ -0,0 +1,42 @@ +/* + * 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. + */ + +const flattenProperties = require('./flattenProperties'); +const getReference = require('./references/getReference'); +const usesReference = require('./references/usesReference'); + +/** + * + * @typedef Dictionary + * @property {Object} properties + * @property {Array} allProperties + * @property {Dictionary.getReference} getReference + * @property {Dictionary.usesReference} usesReference + */ + +/** + * Creates the dictionary object that is passed to formats and actions. + * @param {Object} args + * @param {Object} args.properties + * @returns {Dictionary} + */ +function createDictionary({ properties }) { + return { + properties: properties, + allProperties: flattenProperties( properties ), + getReference: getReference, + usesReference: usesReference + } +} + +module.exports = createDictionary; \ No newline at end of file diff --git a/lib/utils/createFormatArgs.js b/lib/utils/createFormatArgs.js new file mode 100644 index 000000000..b79096f3b --- /dev/null +++ b/lib/utils/createFormatArgs.js @@ -0,0 +1,35 @@ +/* + * 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. + */ + +const deepExtend = require('./deepExtend'); + +function createFormatArgs({ dictionary, platform, file = {} }) { + const {allProperties, properties, usesReference, getReference} = dictionary; + // This will merge platform and file-level configuration + // where the file configuration takes precedence + const {options} = platform; + file = deepExtend([{}, {options}, file]); + + return { + dictionary, + usesReference, + getReference, + allProperties, + properties, + platform, + file, + options: file.options || {} + } +} + +module.exports = createFormatArgs; \ No newline at end of file diff --git a/lib/utils/references/getReference.js b/lib/utils/references/getReference.js index 377036c4b..e60e57875 100644 --- a/lib/utils/references/getReference.js +++ b/lib/utils/references/getReference.js @@ -25,6 +25,7 @@ const resolveReference = require('./resolveReference'); * --color-background-base: var(--color-core-white); * ``` * + * @memberof Dictionary * @param {string} value the value that contains a reference * @returns {any} */ diff --git a/lib/utils/references/usesReference.js b/lib/utils/references/usesReference.js index 9f104645b..6d970ef9b 100644 --- a/lib/utils/references/usesReference.js +++ b/lib/utils/references/usesReference.js @@ -15,7 +15,7 @@ const createRegex = require('./createReferenceRegex'); /** * Checks if the value uses a value reference. - * @private + * @memberof Dictionary * @param {string} value * @param {Object|RegExp} regexOrOptions * @returns {boolean} - True, if the value uses a value reference diff --git a/package.json b/package.json index 514b94a2a..770844cbd 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "types/index.d.ts" ], "scripts": { - "lint": "eslint --fix index.js lib/**/*.js __tests__/**/*.js", + "lint": "eslint --fix index.js \"lib/**/*.js\" \"__tests__/**/*.js\"", "test": "npm run test-types && npm run lint && jest --runInBand --silent", "test-types": "tsd", "test-watch": "npm run test-types && npm run lint && jest --runInBand --watch", @@ -65,7 +65,8 @@ "/__tests__/__configs/", "/__tests__/__json_files/", "/__tests__/__properties/", - "/__tests__/__output/" + "/__tests__/__output/", + "/__tests__/formats/__constants.js" ], "transform": { "^.+\\.json5$": "json5-jest", diff --git a/scripts/generateDocs.js b/scripts/generateDocs.js index 95747cb62..ebccfd603 100644 --- a/scripts/generateDocs.js +++ b/scripts/generateDocs.js @@ -86,7 +86,7 @@ console.log(ACTIONS_PATH + ' generated.'); const FORMATS_PATH = './docs/formats.md' const formats = jsdoc2md.renderSync({ - files: ['lib/common/formats.js'], + files: ['lib/common/formats.js', 'lib/register/format.js'], template: fs.readFileSync('scripts/handlebars/templates/formats.hbs').toString(), 'no-gfm': true, separators: true, diff --git a/scripts/handlebars/templates/formats.hbs b/scripts/handlebars/templates/formats.hbs index 0ce58050f..4f054677d 100644 --- a/scripts/handlebars/templates/formats.hbs +++ b/scripts/handlebars/templates/formats.hbs @@ -137,18 +137,71 @@ Not all formats use the `outputReferences` option because that file format might * [ios-swift/class.swift](#ios-swiftclassswift) * [flutter/class.dart](#flutterclassdart) +You can create custom formats that output references as well. See the [Custom format with output references](#custom-format-with-output-references) section. + ### Creating formats -You can create custom formats using the [`registerFormat`](api.md#registerformat) function. If you want to add configuration to your custom format, `this` is bound to the file object. Using this, you can access attributes on the file object with `this.myCustomAttribute` if the file object looks like: +You can create custom formats using the [`registerFormat`](api.md#registerformat) function or by directly including them in your [configuration](config.md). A format has a name and a formatter function, which takes an object as the argument and should return a string which is then written to a file. + +{{#module name="format"}} +{{>members}} +{{/module}} + +To use your custom format, you call it by name in the file configuration object: ```json { - "destination": "destination", - "format": "myCustomFormat", - "myCustomAttribute": "Hello world" + "source": ["tokens/**/*.json"], + "platforms": { + "css": { + "options": { + "showFileHeader": true + }, + "transformGroup": "css", + "files": [{ + "destination": "destination", + "format": "myCustomFormat", + "options": { + "showFileHeader": false + } + }] + } + } } ``` +It is recommended for any configuration needed for your custom format to use the `options` object. Style Dictionary will merge platform and file options so that in your Style Dictionary configuration you can specify options at a platform or file level. In the configuration above, the `options` object passed to the formatter would have `showFileHeader: false`. + +
+Note: to support legacy ways of defining custom formats, this in the formatter function is bound to the file object and when Style Dictionary calls the formatter function it passes 3 arguments: dictionary, platform, and file. Starting in 3.0 all data the formatter needs is in the first argument as shown above to make it easier to grab the arguments by name rather than by position. We recommend not using this or the positional arguments in your custom format. +
+ +#### Custom format with output references + +To take advantage of outputting references in your custom formats there are 2 helper methods in the `dictionary` argument passed to your formatter function: `usesReference(value)` and `getReference(value)`. Here is an example using those: + +```javascript +StyleDictionary.registerFormat({ + name: `es6WithReferences`, + formatter: function({dictionary}) { + return dictionary.allProperties.map(token => { + let value = JSON.stringify(token.value); + // the `dictionary` object now has `usesReference()` and + // `getReference()` methods. `usesReference()` will return true if + // the value has a reference in it. `getReference()` will return + // the reference to the whole token so that you can access its + // name or any other attributes. + if (dictionary.usesReference(token.original.value)) { + // Note: make sure to use `token.original.value` because + // `token.value` is already resolved at this point. + const reference = dictionary.getReference(token.original.value); + value = reference.name; + } + return `export const ${token.name} = ${value};` + }).join(`\n`) + } +}); +``` ### Using a template / templating engine to create a format @@ -182,7 +235,7 @@ const template = Handlebars.compile( fs.readFileSync('templates/MyTemplate.hbs') styleDictionary.registerFormat({ name: 'my/format', - formatter: function(dictionary, platform) { + formatter: function({dictionary, platform}) { return template({ properties: dictionary.properties, options: platform