diff --git a/.changeset/pretty-clocks-guess.md b/.changeset/pretty-clocks-guess.md new file mode 100644 index 000000000..889069696 --- /dev/null +++ b/.changeset/pretty-clocks-guess.md @@ -0,0 +1,26 @@ +--- +'style-dictionary': major +--- + +BREAKING: All of our hooks, parsers, preprocessors, transforms, formats, actions, fileHeaders and filters, support async functions as well now. This means that the formatHelpers -> fileHeader helper method is now asynchronous, to support async fileheader functions. + +```js +import StyleDictionary from 'style-dictionary'; + +const { fileHeader } = StyleDictionary.formatHelpers; + +StyleDictionary.registerFormat({ + name: 'custom/css', + // this can be async now, usually it is if you use fileHeader format helper, since that now always returns a Promise + formatter: async function ({ dictionary, file, options }) { + const { outputReferences } = options; + return ( + // this helper is now async! because the user-passed file.fileHeader might be an async function + (await fileHeader({ file })) + + ':root {\n' + + formattedVariables({ format: 'css', dictionary, outputReferences }) + + '\n}\n' + ); + }, +}); +``` diff --git a/__integration__/__snapshots__/android.test.snap.js b/__integration__/__snapshots__/android.test.snap.js index 58814e3d5..28f26cea7 100644 --- a/__integration__/__snapshots__/android.test.snap.js +++ b/__integration__/__snapshots__/android.test.snap.js @@ -1,7 +1,7 @@ /* @web/test-runner snapshot v1 */ export const snapshots = {}; -snapshots["android/resources should match snapshot"] = +snapshots["integration android android/resources should match snapshot"] = ` or \/\* style comments respectively. Default fallback is 'long'. * @param {Formatting} [options.formatting] - Custom formatting properties that define parts of a comment in code. The configurable strings are: prefix, lineSeparator, header, and footer. - * @returns {String} + * @returns {Promise} * @example * ```js * StyleDictionary.registerFormat({ @@ -50,7 +50,7 @@ const defaultFormatting = { * }); * ``` */ -export default function fileHeader({ file, commentStyle, formatting = {} }) { +export default async function fileHeader({ file, commentStyle, formatting = {} }) { // showFileHeader is true by default let showFileHeader = true; if (typeof file?.options?.showFileHeader !== 'undefined') { @@ -83,7 +83,9 @@ export default function fileHeader({ file, commentStyle, formatting = {} }) { footer = `${lineSeparator}-->`; } - return `${header}${fn(defaultHeader) + const headerContent = await fn(defaultHeader); + + return `${header}${headerContent .map(/** @param {string} line */ (line) => `${prefix}${line}`) .join(lineSeparator)}${footer}`; } diff --git a/lib/common/formats.js b/lib/common/formats.js index b890ab74f..6639a85ab 100644 --- a/lib/common/formats.js +++ b/lib/common/formats.js @@ -81,11 +81,12 @@ const formats = { * } * ``` */ - 'css/variables': function ({ dictionary, options = {}, file }) { + 'css/variables': async function ({ dictionary, options = {}, file }) { const selector = options.selector ? options.selector : `:root`; const { outputReferences, usesDtcg } = options; + const header = await fileHeader({ file }); return ( - fileHeader({ file }) + + header + `${selector} {\n` + formattedVariables({ format: 'css', dictionary, outputReferences, usesDtcg }) + `\n}\n` @@ -108,10 +109,11 @@ const formats = { * ) * ``` */ - 'scss/map-flat': function ({ dictionary, options, file }) { + 'scss/map-flat': async function ({ dictionary, options, file }) { const template = _template(scssMapFlat); const { allTokens } = dictionary; - return template({ allTokens, file, options, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'long' }); + return template({ allTokens, file, options, header }); }, /** @@ -140,14 +142,14 @@ const formats = { * ) * ``` */ - 'scss/map-deep': function ({ dictionary, options, file }) { + 'scss/map-deep': async function ({ dictionary, options, file }) { const mapTemplate = _template(scssMapDeep); - + const header = await fileHeader({ file, commentStyle: 'long' }); // Default the "themeable" option to true for backward compatibility. const { outputReferences, themeable = true, usesDtcg } = options; return ( '\n' + - fileHeader({ file, commentStyle: 'long' }) + + header + formattedVariables({ format: 'sass', dictionary, outputReferences, themeable, usesDtcg }) + '\n' + mapTemplate({ dictionary, file, options }) @@ -172,10 +174,11 @@ const formats = { * $color-background-alt: #eeeeee !default; * ``` */ - 'scss/variables': function ({ dictionary, options, file }) { + 'scss/variables': async function ({ dictionary, options, file }) { const { outputReferences, themeable = false, usesDtcg } = options; + const header = await fileHeader({ file, commentStyle: 'short' }); return ( - fileHeader({ file, commentStyle: 'short' }) + + header + formattedVariables({ format: 'sass', dictionary, outputReferences, themeable, usesDtcg }) + '\n' ); @@ -193,11 +196,9 @@ const formats = { * .icon.email:before { content:$content-icon-email; } * ``` */ - 'scss/icons': function ({ dictionary, options, file, platform }) { - return ( - fileHeader({ file, commentStyle: 'short' }) + - iconsWithPrefix('$', dictionary.allTokens, options, platform) - ); + 'scss/icons': async function ({ dictionary, options, file, platform }) { + const header = await fileHeader({ file, commentStyle: 'short' }); + return header + iconsWithPrefix('$', dictionary.allTokens, options, platform); }, /** @@ -215,12 +216,11 @@ const formats = { * \@color-background-alt: #eeeeee; * ``` */ - 'less/variables': function ({ dictionary, options, file }) { + 'less/variables': async function ({ dictionary, options, file }) { const { outputReferences, usesDtcg } = options; + const header = await fileHeader({ file, commentStyle: 'short' }); return ( - fileHeader({ file, commentStyle: 'short' }) + - formattedVariables({ format: 'less', dictionary, outputReferences, usesDtcg }) + - '\n' + header + formattedVariables({ format: 'less', dictionary, outputReferences, usesDtcg }) + '\n' ); }, @@ -236,11 +236,9 @@ const formats = { * .icon.email:before { content:\@content-icon-email; } * ``` */ - 'less/icons': function ({ dictionary, options, file, platform }) { - return ( - fileHeader({ file, commentStyle: 'short' }) + - iconsWithPrefix('@', dictionary.allTokens, options, platform) - ); + 'less/icons': async function ({ dictionary, options, file, platform }) { + const header = await fileHeader({ file, commentStyle: 'short' }); + return header + iconsWithPrefix('@', dictionary.allTokens, options, platform); }, /** @@ -255,10 +253,11 @@ const formats = { * $color-background-alt= #eeeeee; * ``` */ - 'stylus/variables': function ({ dictionary, options, file }) { + 'stylus/variables': async function ({ dictionary, options, file }) { const { outputReferences, usesDtcg } = options; + const header = await fileHeader({ file, commentStyle: 'short' }); return ( - fileHeader({ file, commentStyle: 'short' }) + + header + formattedVariables({ format: 'stylus', dictionary, outputReferences, usesDtcg }) + '\n' ); @@ -283,13 +282,9 @@ const formats = { * } * ``` */ - 'javascript/module': function ({ dictionary, file }) { - return ( - fileHeader({ file }) + - 'module.exports = ' + - JSON.stringify(dictionary.tokens, null, 2) + - ';\n' - ); + 'javascript/module': async function ({ dictionary, file }) { + const header = await fileHeader({ file }); + return header + 'module.exports = ' + JSON.stringify(dictionary.tokens, null, 2) + ';\n'; }, /** @@ -305,9 +300,10 @@ const formats = { *} *``` */ - 'javascript/module-flat': function ({ dictionary, file, options }) { + 'javascript/module-flat': async function ({ dictionary, file, options }) { + const header = await fileHeader({ file }); return ( - fileHeader({ file }) + + header + 'module.exports = ' + '{\n' + dictionary.allTokens @@ -342,9 +338,10 @@ const formats = { * } * ``` */ - 'javascript/object': function ({ dictionary, file }) { + 'javascript/object': async function ({ dictionary, file }) { + const header = await fileHeader({ file }); return ( - fileHeader({ file }) + + header + 'var ' + (file.name || '_styleDictionary') + ' = ' + @@ -384,10 +381,11 @@ const formats = { * })) * ``` */ - 'javascript/umd': function ({ dictionary, file }) { + 'javascript/umd': async function ({ dictionary, file }) { const name = file.name || '_styleDictionary'; + const header = await fileHeader({ file }); return ( - fileHeader({ file }) + + header + '(function(root, factory) {\n' + ' if (typeof module === "object" && module.exports) {\n' + ' module.exports = factory();\n' + @@ -443,9 +441,10 @@ const formats = { * export const ColorBackgroundAlt = '#fcfcfcfc'; * ``` */ - 'javascript/es6': function ({ dictionary, file, options }) { + 'javascript/es6': async function ({ dictionary, file, options }) { + const header = await fileHeader({ file }); return ( - fileHeader({ file }) + + header + dictionary.allTokens .map(function (token) { let to_ret = @@ -495,9 +494,10 @@ const formats = { * export const ColorBackgroundAlt : string; * ``` */ - 'typescript/es6-declarations': function ({ dictionary, file, options }) { + 'typescript/es6-declarations': async function ({ dictionary, file, options }) { + const header = await fileHeader({ file }); return ( - fileHeader({ file }) + + header + dictionary.allTokens .map(function (token) { let to_ret_token = ''; @@ -569,7 +569,7 @@ const formats = { * }); * ``` */ - 'typescript/module-declarations': function ({ dictionary, file, options }) { + 'typescript/module-declarations': async function ({ dictionary, file, options }) { const { moduleName = `tokens` } = options; /** * @param {Tokens} obj @@ -609,8 +609,9 @@ const formats = { [key: string]: any; }`; + const header = await fileHeader({ file }); const output = - fileHeader({ file }) + + header + `export default ${moduleName}; declare ${designTokenInterface} @@ -650,8 +651,9 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * 14sp * ``` */ - 'android/resources': function ({ dictionary, file, options }) { - return androidResources({ dictionary, file, fileHeader, options }); + 'android/resources': async function ({ dictionary, file, options }) { + const header = await fileHeader({ file, commentStyle: 'xml' }); + return androidResources({ dictionary, file, header, options }); }, /** @@ -679,9 +681,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * #ffe19d9c * ``` */ - 'android/colors': function ({ dictionary, options, file }) { + 'android/colors': async function ({ dictionary, options, file }) { const template = _template(androidColors); - return template({ dictionary, file, options, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'xml' }); + return template({ dictionary, file, options, header }); }, /** @@ -709,9 +712,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * 15.00dp * ``` */ - 'android/dimens': function ({ dictionary, options, file }) { + 'android/dimens': async function ({ dictionary, options, file }) { const template = _template(androidDimens); - return template({ dictionary, file, options, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'xml' }); + return template({ dictionary, file, options, header }); }, /** @@ -739,9 +743,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * 15.00sp * ``` */ - 'android/fontDimens': function ({ dictionary, options, file }) { + 'android/fontDimens': async function ({ dictionary, options, file }) { const template = _template(androidFontDimens); - return template({ dictionary, file, options, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'xml' }); + return template({ dictionary, file, options, header }); }, /** @@ -771,9 +776,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * 4000 * ``` */ - 'android/integers': function ({ dictionary, options, file }) { + 'android/integers': async function ({ dictionary, options, file }) { const template = _template(androidIntegers); - return template({ dictionary, file, options, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'xml' }); + return template({ dictionary, file, options, header }); }, /** @@ -802,9 +808,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * * ``` */ - 'android/strings': function ({ dictionary, options, file }) { + 'android/strings': async function ({ dictionary, options, file }) { const template = _template(androidStrings); - return template({ dictionary, file, options, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'xml' }); + return template({ dictionary, file, options, header }); }, // Compose templates @@ -831,7 +838,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * } * ``` */ - 'compose/object': function ({ dictionary, options, file }) { + 'compose/object': async function ({ dictionary, options, file }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const template = _template(composeObject); const { outputReferences, usesDtcg } = options; @@ -853,8 +860,8 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul } options = setComposeObjectProperties(options); - - return template({ allTokens: sortedTokens, file, options, formatProperty, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ allTokens: sortedTokens, file, options, formatProperty, header }); }, // iOS templates @@ -874,10 +881,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * #define SizeFontTiny 176.00f * ``` */ - 'ios/macros': function ({ dictionary, options, file }) { + 'ios/macros': async function ({ dictionary, options, file }) { const template = _template(macrosTemplate); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -888,10 +895,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Fix this template and add example and usage */ - 'ios/plist': function ({ dictionary, options, file }) { + 'ios/plist': async function ({ dictionary, options, file }) { const template = _template(plistTemplate); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'xml' }); + return template({ dictionary, options, file, header }); }, /** @@ -902,10 +909,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/singleton.m': function ({ dictionary, options, file }) { + 'ios/singleton.m': async function ({ dictionary, options, file }) { const template = _template(iosSingletonM); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -916,10 +923,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/singleton.h': function ({ dictionary, options, file }) { + 'ios/singleton.h': async function ({ dictionary, options, file }) { const template = _template(iosSingletonH); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -930,10 +937,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/static.h': function ({ dictionary, options, file }) { + 'ios/static.h': async function ({ dictionary, options, file }) { const template = _template(iosStaticH); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -944,10 +951,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/static.m': function ({ dictionary, options, file }) { + 'ios/static.m': async function ({ dictionary, options, file }) { const template = _template(iosStaticM); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -958,10 +965,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/colors.h': function ({ dictionary, options, file }) { + 'ios/colors.h': async function ({ dictionary, options, file }) { const template = _template(iosColorsH); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -972,10 +979,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/colors.m': function ({ dictionary, options, file }) { + 'ios/colors.m': async function ({ dictionary, options, file }) { const template = _template(iosColorsM); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -986,10 +993,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/strings.h': function ({ dictionary, options, file }) { + 'ios/strings.h': async function ({ dictionary, options, file }) { const template = _template(iosStringsH); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -1000,10 +1007,10 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * @param {FormatOpts} options * @todo Add example and usage */ - 'ios/strings.m': function ({ dictionary, options, file }) { + 'ios/strings.m': async function ({ dictionary, options, file }) { const template = _template(iosStringsM); - - return template({ dictionary, options, file, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ dictionary, options, file, header }); }, /** @@ -1025,7 +1032,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * } * ``` */ - 'ios-swift/class.swift': function ({ dictionary, options, file, platform }) { + 'ios-swift/class.swift': async function ({ dictionary, options, file, platform }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const template = _template(iosSwiftAny); const { outputReferences, usesDtcg } = options; @@ -1045,8 +1052,8 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul } else { sortedTokens = [...allTokens].sort(sortByName); } - - return template({ allTokens: sortedTokens, file, options, formatProperty, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ allTokens: sortedTokens, file, options, formatProperty, header }); }, /** @@ -1067,7 +1074,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * } * ``` */ - 'ios-swift/enum.swift': function ({ dictionary, options, file, platform }) { + 'ios-swift/enum.swift': async function ({ dictionary, options, file, platform }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const template = _template(iosSwiftAny); const { outputReferences, usesDtcg } = options; @@ -1087,7 +1094,8 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul } else { sortedTokens = [...allTokens].sort(sortByName); } - return template({ allTokens: sortedTokens, file, options, formatProperty, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ allTokens: sortedTokens, file, options, formatProperty, header }); }, /** @@ -1119,7 +1127,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * } * ``` */ - 'ios-swift/any.swift': function ({ dictionary, options, file, platform }) { + 'ios-swift/any.swift': async function ({ dictionary, options, file, platform }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const template = _template(iosSwiftAny); const { outputReferences, usesDtcg } = options; @@ -1139,7 +1147,8 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul } else { sortedTokens = [...allTokens].sort(sortByName); } - return template({ allTokens: sortedTokens, file, options, formatProperty, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ allTokens: sortedTokens, file, options, formatProperty, header }); }, // Css templates @@ -1346,7 +1355,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul * static const contentFontFamily1 = "NewJune"; * ``` */ - 'flutter/class.dart': function ({ dictionary, options, file }) { + 'flutter/class.dart': async function ({ dictionary, options, file }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const template = _template(flutterClassDart); const { outputReferences, usesDtcg } = options; @@ -1362,7 +1371,8 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul } else { sortedTokens = [...allTokens].sort(sortByName); } - return template({ allTokens: sortedTokens, file, options, formatProperty, fileHeader }); + const header = await fileHeader({ file, commentStyle: 'short' }); + return template({ allTokens: sortedTokens, file, options, formatProperty, header }); }, }; diff --git a/lib/common/templates/android/colors.template.js b/lib/common/templates/android/colors.template.js index bb3267262..7564fc06a 100644 --- a/lib/common/templates/android/colors.template.js +++ b/lib/common/templates/android/colors.template.js @@ -18,7 +18,7 @@ const tokens = dictionary.allTokens.filter(function(token) { return token.attributes.category === 'color'; }); %> -<%= fileHeader({file, commentStyle: 'xml'}) %> +<%= header %> <% tokens.forEach(function(token) { %><%= options.usesDtcg ? token.$value : token.value %><% if (token.comment) { %><% } %> diff --git a/lib/common/templates/android/dimens.template.js b/lib/common/templates/android/dimens.template.js index 7b5338f5e..b6a178bb2 100644 --- a/lib/common/templates/android/dimens.template.js +++ b/lib/common/templates/android/dimens.template.js @@ -17,7 +17,7 @@ export default `xml version="1.0" encoding="UTF-8"?> const tokens = dictionary.allTokens.filter(function(token) { return token.attributes.category === 'size' && token.attributes.type !== 'font' }); %> -<%= fileHeader({file, commentStyle: 'xml'}) %> +<%= header %> <% tokens.forEach(function(token) { %><%= options.usesDtcg ? token.$value : token.value %><% if (token.comment) { %><% } %> diff --git a/lib/common/templates/android/fontDimens.template.js b/lib/common/templates/android/fontDimens.template.js index 6da278f56..f646c7bff 100644 --- a/lib/common/templates/android/fontDimens.template.js +++ b/lib/common/templates/android/fontDimens.template.js @@ -17,7 +17,7 @@ export default ` const tokens = dictionary.allTokens.filter(function(token) { return token.attributes.category === 'size' && token.attributes.type === 'font'; }); %> -<%= fileHeader({file, commentStyle: 'xml'}) %> +<%= header %> <% tokens.forEach(function(token) { %><%= options.usesDtcg ? token.$value : token.value %><% if (token.comment) { %><% } %> diff --git a/lib/common/templates/android/integers.template.js b/lib/common/templates/android/integers.template.js index 1bbafe3fd..079aa7f24 100644 --- a/lib/common/templates/android/integers.template.js +++ b/lib/common/templates/android/integers.template.js @@ -17,7 +17,7 @@ export default ` var tokens = dictionary.allTokens.filter(function(token) { return token.attributes.category === 'time'; }); %> -<%= fileHeader({file, commentStyle: 'xml'}) %> +<%= header %> <% tokens.forEach(function(token) { %><%= options.usesDtcg ? token.$value : token.value %><% if (token.comment) { %><% } %> diff --git a/lib/common/templates/android/resources.template.js b/lib/common/templates/android/resources.template.js index 25ac45bb3..4b0ea7b49 100644 --- a/lib/common/templates/android/resources.template.js +++ b/lib/common/templates/android/resources.template.js @@ -26,12 +26,12 @@ import { usesReferences, getReferences } from 'style-dictionary/utils'; * @param {{ * dictionary: Dictionary; * file?: File; - * fileHeader?: FileHeader; + * header?: string; * options: Config * }} opts */ export default (opts) => { - const { file, fileHeader, dictionary, options } = opts; + const { file, header, dictionary, options } = opts; const resourceType = file?.resourceType || null; @@ -73,7 +73,7 @@ export default (opts) => { return ` -${fileHeader ? fileHeader({ file, commentStyle: 'xml' }) : ''} +${header} ${ /** @type {Token[]} */ (dictionary.allTokens) diff --git a/lib/common/templates/android/strings.template.js b/lib/common/templates/android/strings.template.js index baaaa3a38..5f6738b56 100644 --- a/lib/common/templates/android/strings.template.js +++ b/lib/common/templates/android/strings.template.js @@ -17,7 +17,7 @@ export default ` const tokens = dictionary.allTokens.filter(function(token) { return token.attributes.category === 'content'; }); %> -<%= fileHeader({file, commentStyle: 'xml'}) %> +<%= header %> <% tokens.forEach(function(token) { %><%= options.usesDtcg ? token.$value : token.value %><% if (token.comment) { %><% } %> diff --git a/lib/common/templates/compose/object.kt.template.js b/lib/common/templates/compose/object.kt.template.js index c927c5f6e..f2c020525 100644 --- a/lib/common/templates/compose/object.kt.template.js +++ b/lib/common/templates/compose/object.kt.template.js @@ -13,7 +13,7 @@ export default `<% // express or implied. See the License for the specific language governing // permissions and limitations under the License. %> -<%= fileHeader({file, commentStyle: 'short'}) %> +<%= header %> package <%= file.packageName %>; diff --git a/lib/common/templates/flutter/class.dart.template.js b/lib/common/templates/flutter/class.dart.template.js index 9e8db24fa..230f31979 100644 --- a/lib/common/templates/flutter/class.dart.template.js +++ b/lib/common/templates/flutter/class.dart.template.js @@ -13,7 +13,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({file, commentStyle: 'short'}) %> +<%= header %> import 'dart:ui'; diff --git a/lib/common/templates/ios-swift/any.swift.template.js b/lib/common/templates/ios-swift/any.swift.template.js index 9a48890cd..63eec91dc 100644 --- a/lib/common/templates/ios-swift/any.swift.template.js +++ b/lib/common/templates/ios-swift/any.swift.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({file, commentStyle: 'short'}) %> +<%= header %> <%= options.import.map(function(item) { return 'import ' + item }).join('\\n') diff --git a/lib/common/templates/ios/colors.h.template.js b/lib/common/templates/ios/colors.h.template.js index 51eae0e62..acd37195b 100644 --- a/lib/common/templates/ios/colors.h.template.js +++ b/lib/common/templates/ios/colors.h.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({file, commentStyle: 'short'}) %> +<%= header %> #import typedef NS_ENUM(NSInteger, <%= file.type %>) { diff --git a/lib/common/templates/ios/colors.m.template.js b/lib/common/templates/ios/colors.m.template.js index e2cf30054..c127ccfd7 100644 --- a/lib/common/templates/ios/colors.m.template.js +++ b/lib/common/templates/ios/colors.m.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({file, commentStyle: 'short'}) %> +<%= header %> #import "<%= file.className %>.h" @implementation <%= file.className %> diff --git a/lib/common/templates/ios/macros.template.js b/lib/common/templates/ios/macros.template.js index 2af7fa2dd..f6d47ebff 100644 --- a/lib/common/templates/ios/macros.template.js +++ b/lib/common/templates/ios/macros.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({file, commentStyle: 'short'}) %> +<%= header %> #import #import diff --git a/lib/common/templates/ios/plist.template.js b/lib/common/templates/ios/plist.template.js index 81bcc7462..2736610f4 100644 --- a/lib/common/templates/ios/plist.template.js +++ b/lib/common/templates/ios/plist.template.js @@ -21,7 +21,7 @@ const tokens = dictionary.allTokens.filter(function(token) { }); %> -<%= fileHeader({ file, commentStyle: 'xml' }) %> +<%= header %> <% tokens.forEach(function(token) { diff --git a/lib/common/templates/ios/singleton.h.template.js b/lib/common/templates/ios/singleton.h.template.js index f2b2a06b5..17f155478 100644 --- a/lib/common/templates/ios/singleton.h.template.js +++ b/lib/common/templates/ios/singleton.h.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({ file, commentStyle: 'short' })%> +<%= header %> #import #import diff --git a/lib/common/templates/ios/singleton.m.template.js b/lib/common/templates/ios/singleton.m.template.js index 310e979c4..614620fcf 100644 --- a/lib/common/templates/ios/singleton.m.template.js +++ b/lib/common/templates/ios/singleton.m.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({ file, commentStyle: 'short' })%> +<%= header %> #import "<%= file.className %>.h" @implementation <%= file.className %> diff --git a/lib/common/templates/ios/static.h.template.js b/lib/common/templates/ios/static.h.template.js index 3cd7e4d44..5c505fda0 100644 --- a/lib/common/templates/ios/static.h.template.js +++ b/lib/common/templates/ios/static.h.template.js @@ -16,7 +16,7 @@ export default `<% %> // <%= file.destination %> // -<%= fileHeader({ file, commentStyle: 'short' })%> +<%= header %> #import <% dictionary.allTokens.forEach(function(prop) { %> diff --git a/lib/common/templates/ios/static.m.template.js b/lib/common/templates/ios/static.m.template.js index 6df61b0f4..aa8fac8ee 100644 --- a/lib/common/templates/ios/static.m.template.js +++ b/lib/common/templates/ios/static.m.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({ file, commentStyle: 'short' }) %> +<%= header %> #import "<%= file.className %>.h" <% dictionary.allTokens.forEach(function(token) { %> diff --git a/lib/common/templates/ios/strings.h.template.js b/lib/common/templates/ios/strings.h.template.js index b34e44290..6a6ed8ed3 100644 --- a/lib/common/templates/ios/strings.h.template.js +++ b/lib/common/templates/ios/strings.h.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({ file, commentStyle: 'short' })%> +<%= header %> #import <% dictionary.allTokens.forEach(function(prop) { %> diff --git a/lib/common/templates/ios/strings.m.template.js b/lib/common/templates/ios/strings.m.template.js index 4eda91277..f26045232 100644 --- a/lib/common/templates/ios/strings.m.template.js +++ b/lib/common/templates/ios/strings.m.template.js @@ -16,7 +16,7 @@ export default `<% // // <%= file.destination %> // -<%= fileHeader({ file, commentStyle: 'short' }) %> +<%= header %> #import "<%= file.className %>.h" <% dictionary.allTokens.forEach(function(token) { %> diff --git a/lib/common/templates/scss/map-flat.template.js b/lib/common/templates/scss/map-flat.template.js index 2822e6988..2cc23232a 100644 --- a/lib/common/templates/scss/map-flat.template.js +++ b/lib/common/templates/scss/map-flat.template.js @@ -14,7 +14,7 @@ export default `<% // permissions and limitations under the License. %> -<%= fileHeader({file, commentStyle: 'long'}) %><% +<%= header %><% let output = ''; output += \`$\${file.mapName||'tokens'}: (\\n\`; output += allTokens.map(function(token){ diff --git a/lib/filterTokens.js b/lib/filterTokens.js index ba166cd6e..ed052c0ce 100644 --- a/lib/filterTokens.js +++ b/lib/filterTokens.js @@ -20,6 +20,16 @@ import isPlainObject from 'is-plain-obj'; * @typedef {import('../types/Config.d.ts').Config} Config */ +/** + * @param {Token[]} arr + * @param {Matcher} predicate + */ +async function asyncFilter(arr, predicate) { + return Promise.all(arr.map(predicate)).then((results) => + arr.filter((_, index) => results[index]), + ); +} + /** * Takes a nested object of tokens and filters them using the provided * function. @@ -29,13 +39,14 @@ import isPlainObject from 'is-plain-obj'; * returns `true` if the property should be included in the output or `false` * if the property should be excluded from the output. * @param {Config} options - * @returns {Tokens} tokens - A new object containing only the tokens + * @returns {Promise} tokens - A new object containing only the tokens * that matched the filter. */ -function filterTokenObject(tokens, filter, options) { +async function filterTokenObject(tokens, filter, options) { // Use reduce to generate a new object with the unwanted tokens filtered // out - return Object.entries(tokens ?? []).reduce((acc, [key, token]) => { + const result = await Object.entries(tokens ?? []).reduce(async (_acc, [key, token]) => { + const acc = await _acc; const tokenValue = options.usesDtcg ? token.$value : token.value; // If the token is not an object, we don't know what it is. We return it as-is. if (!isPlainObject(token)) { @@ -44,18 +55,20 @@ function filterTokenObject(tokens, filter, options) { // the filter function and either include it in the final `acc` object or // exclude it (by returning the `acc` object without it added). } else if (typeof tokenValue !== 'undefined') { - return filter(/** @type {Token} */ (token)) ? { ...acc, [key]: token } : acc; + const filtered = await asyncFilter(/** @type {Token[]} */ ([token]), filter); + return filtered.length === 0 ? acc : { ...acc, [key]: token }; // If we got here we have an object that is not a property. We'll assume // it's an object containing multiple tokens and recursively filter it // using the `filterTokenObject` function. } else { - const filtered = filterTokenObject(token, filter, options); + const filtered = await filterTokenObject(token, filter, options); // If the filtered object is not empty then add it to the final `acc` // object. If it is empty then every property inside of it was filtered // out, then exclude it entirely from the final `acc` object. return Object.entries(filtered || {}).length < 1 ? acc : { ...acc, [key]: filtered }; } }, {}); + return result; } /** @@ -67,20 +80,23 @@ function filterTokenObject(tokens, filter, options) { * and returns `true` if the token should be included in the output * or `false` if the token should be excluded from the output * @param {Config} [options] - * @returns {Dictionary} dictionary - A new dictionary containing only the + * @returns {Promise} dictionary - A new dictionary containing only the * tokens that matched the filter (or the original dictionary if no filter * function was provided). */ -export default function filterTokens(dictionary, filter, options = {}) { +export default async function filterTokens(dictionary, filter, options = {}) { if (!filter) { return dictionary; } else { if (typeof filter !== 'function') { throw new Error('filter is not a function'); } else { + const allTokens = await asyncFilter(dictionary.allTokens ?? [], filter); + const tokens = await filterTokenObject(dictionary.tokens, filter, options); + return { - allTokens: (dictionary.allTokens ?? []).filter(filter), - tokens: filterTokenObject(dictionary.tokens, filter, options), + allTokens, + tokens, }; } } diff --git a/lib/performActions.js b/lib/performActions.js index 80dab760d..ae2e92d9e 100644 --- a/lib/performActions.js +++ b/lib/performActions.js @@ -26,15 +26,15 @@ * @param {Dictionary} dictionary * @param {PlatformConfig} platform * @param {Config} options - * @returns */ -export default function performActions(dictionary, platform, options) { +export default async function performActions(dictionary, platform, options) { if (platform.actions) { - platform.actions.forEach(function (action) { - // ensure we've augmented action with the do/undo functions - if (typeof action !== 'string') { - action.do(dictionary, platform, options); - } - }); + return Promise.all( + platform.actions.map((action) => { + if (typeof action !== 'string' && typeof action.do === 'function') { + return action.do(dictionary, platform, options); + } + }), + ); } } diff --git a/lib/transform/object.js b/lib/transform/object.js index ad098b7e3..ecf34ed71 100644 --- a/lib/transform/object.js +++ b/lib/transform/object.js @@ -39,9 +39,9 @@ import tokenSetup from './tokenSetup.js'; * @param {{ transformedPropRefs?: string[], deferredPropValueTransforms?: string[] }} [ctx] * @param {string[]} [path] * @param {Record} [transformedObj] - * @returns {Tokens|TransformedTokens} + * @returns {Promise} */ -export default function transformObject( +export default async function transformObject( obj, config, options, @@ -96,7 +96,7 @@ export default function transformObject( // If we got here, the property hasn't been transformed yet and // does not use a value reference. Transform the property now and assign it. - const transformedToken = transformToken(token, config, options); + const transformedToken = await transformToken(token, config, options); // If a value transform returns undefined, it means the transform wants it to be deferred // e.g. due to a ref existing in a sibling prop that the transform relies on. // Example: { value: "#fff", darken: "{darken-amount}" } @@ -119,7 +119,7 @@ export default function transformObject( transformedPropRefs.push(pathName); } else if (isObj) { // objProp is not a token -> go deeper down the object tree - transformedObj[name] = transformObject( + transformedObj[name] = await transformObject( objProp, config, options, diff --git a/lib/transform/token.js b/lib/transform/token.js index aedefa201..629b292c4 100644 --- a/lib/transform/token.js +++ b/lib/transform/token.js @@ -28,9 +28,9 @@ import usesReferences from '../utils/references/usesReferences.js'; * @param {Token} token * @param {PlatformConfig} config * @param {Config} options - * @returns {Token|undefined} - A new property object with transforms applied. + * @returns {Promise} - A new property object with transforms applied. */ -export default function transformProperty(token, config, options) { +export default async function transformToken(token, config, options) { const to_ret = structuredClone(token); const transforms = /** @type {Omit[]} */ (config.transforms) || []; @@ -40,7 +40,7 @@ export default function transformProperty(token, config, options) { if (!transform.matcher || transform.matcher(to_ret)) { if (transform.type === 'name') { - to_ret.name = /** @type {Omit} */ (transform).transformer( + to_ret.name = await /** @type {Omit} */ (transform).transformer( to_ret, config, options, @@ -55,7 +55,7 @@ export default function transformProperty(token, config, options) { // Only transform non-referenced values (from original) // and transitive transforms if the value has been resolved if (!usesReferences(token.original.value, config) || transform.transitive) { - const transformedValue = transform.transformer(to_ret, config, options); + const transformedValue = await transform.transformer(to_ret, config, options); if (transformedValue === undefined) { return undefined; } @@ -67,7 +67,7 @@ export default function transformProperty(token, config, options) { to_ret.attributes = Object.assign( {}, to_ret.attributes, - transform.transformer(to_ret, config, options), + await transform.transformer(to_ret, config, options), ); } } diff --git a/types/Action.d.ts b/types/Action.d.ts index 594955284..a80e3a25d 100644 --- a/types/Action.d.ts +++ b/types/Action.d.ts @@ -17,8 +17,8 @@ import type { PlatformConfig, Config } from './Config.d.ts'; export interface Action { name: string; /** The action in the form of a function. */ - do(dictionary: Dictionary, config: PlatformConfig, options: Config): void; + do(dictionary: Dictionary, config: PlatformConfig, options: Config): void | Promise; /** A function that undoes the action. */ - undo?(dictionary: Dictionary, config: PlatformConfig, options: Config): void; + undo?(dictionary: Dictionary, config: PlatformConfig, options: Config): void | Promise; } diff --git a/types/File.d.ts b/types/File.d.ts index 605d97cdf..37d5a65e6 100644 --- a/types/File.d.ts +++ b/types/File.d.ts @@ -13,7 +13,7 @@ export interface FormattingOptions { separator?: string; } -export type FileHeader = (defaultMessage: string[]) => string[]; +export type FileHeader = (defaultMessage: string[]) => Promise | string[]; export interface File { className?: string; diff --git a/types/Filter.d.ts b/types/Filter.d.ts index 3d190d91b..42c85f443 100644 --- a/types/Filter.d.ts +++ b/types/Filter.d.ts @@ -17,4 +17,4 @@ export interface Filter { matcher: Matcher; } -export type Matcher = (token: TransformedToken) => boolean; +export type Matcher = (token: TransformedToken) => boolean | Promise; diff --git a/types/Format.d.ts b/types/Format.d.ts index f39133388..84d0458b7 100644 --- a/types/Format.d.ts +++ b/types/Format.d.ts @@ -39,7 +39,9 @@ export interface FormatterArguments { * The formatter function receives an overloaded object as its arguments and * it should return a string, which will be written to a file. */ -export type Formatter = ((arguments: FormatterArguments) => string) & { nested?: boolean }; +export type Formatter = ((arguments: FormatterArguments) => string | Promise) & { + nested?: boolean; +}; export interface Format { name: string; diff --git a/types/FormatHelpers.d.ts b/types/FormatHelpers.d.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/types/Parser.d.ts b/types/Parser.d.ts index 595c1b1e6..2070b7e9b 100644 --- a/types/Parser.d.ts +++ b/types/Parser.d.ts @@ -20,5 +20,5 @@ export interface ParserOptions { export interface Parser { pattern: RegExp; - parse: (options: ParserOptions) => DesignTokens; + parse: (options: ParserOptions) => DesignTokens | Promise; } diff --git a/types/Transform.d.ts b/types/Transform.d.ts index 92ce166e8..20a936ac2 100644 --- a/types/Transform.d.ts +++ b/types/Transform.d.ts @@ -20,7 +20,11 @@ interface BaseTransform { type: Type; matcher?: Matcher; transitive?: boolean; - transformer: (token: TransformedToken, config: PlatformConfig, options: Config) => Value; + transformer: ( + token: TransformedToken, + config: PlatformConfig, + options: Config, + ) => Promise | Value; } export type NameTransform = BaseTransform<'name', string>;