From 402e088cd384ba21b9d16901eb53f1e9b1457889 Mon Sep 17 00:00:00 2001 From: TrevorBurnham Date: Sat, 6 Nov 2021 18:24:36 -0400 Subject: [PATCH] feat(formats): add support for Microsoft's JSONC format Resolves #698 --- __tests__/__configs/test.jsonc | 98 ++++++++++++++++++++++++++ __tests__/__json_files/shallow/4.jsonc | 7 ++ __tests__/extend.test.js | 5 ++ __tests__/utils/combineJSON.test.js | 6 ++ docs/README.md | 2 +- docs/config.md | 1 + docs/tokens.md | 1 + lib/extend.js | 1 + lib/utils/combineJSON.js | 1 + lib/utils/jsonc.js | 14 ++++ package-lock.json | 15 +++- package.json | 2 + types/index.d.ts | 2 +- 13 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 __tests__/__configs/test.jsonc create mode 100644 __tests__/__json_files/shallow/4.jsonc create mode 100644 lib/utils/jsonc.js diff --git a/__tests__/__configs/test.jsonc b/__tests__/__configs/test.jsonc new file mode 100644 index 000000000..4dea5a66a --- /dev/null +++ b/__tests__/__configs/test.jsonc @@ -0,0 +1,98 @@ +{ + // some comment + "source": ["test/properties/**/*.json"], + "platforms": { + "web": { + "transformGroup": "web", + "prefix": "smop", + "buildPath": "test/output/web/", + "files": [ + { + "destination": "_icons.css", + "format": "scss/icons" + }, + { + "destination": "_variables.css", + "format": "scss/variables" + }, + { + "destination": "_styles.js", + "format": "javascript/module" + } + ] + }, + "scss": { + "transformGroup": "scss", + "prefix": "smop", + "buildPath": "test/output/scss/", + "files": [ + { + "destination": "_icons.scss", + "format": "scss/icons" + }, + { + "destination": "_variables.scss", + "format": "scss/variables" + } + ] + }, + "less": { + "transformGroup": "less", + "prefix": "smop", + "buildPath": "test/output/less/", + "files": [ + { + "destination": "_icons.less", + "format": "less/icons" + }, + { + "destination": "_variables.less", + "format": "less/variables" + } + ] + }, + "android": { + "transformGroup": "android", + "buildPath": "test/output/", + "files": [ + { + "destination": "android/colors.xml", + "template": "android/colors" + }, + { + "destination": "android/font_dimen.xml", + "template": "android/fontDimens" + }, + { + "destination": "android/dimens.xml", + "template": "android/dimens" + } + ], + "actions": ["android/copyImages"] + }, + "ios": { + "transformGroup": "ios", + "buildPath": "test/output/ios/", + "files": [ + { + "destination": "style_dictionary.plist", + "template": "ios/plist" + }, + { + "destination": "style_dictionary.h", + "template": "ios/macros" + } + ] + }, + "react-native": { + "transformGroup": "react-native", + "buildPath": "__tests__/__output/react-native/", + "files": [ + { + "destination": "style_dictionary.js", + "format": "javascript/es6" + } + ] + } + } +} diff --git a/__tests__/__json_files/shallow/4.jsonc b/__tests__/__json_files/shallow/4.jsonc new file mode 100644 index 000000000..7f4ace054 --- /dev/null +++ b/__tests__/__json_files/shallow/4.jsonc @@ -0,0 +1,7 @@ +{ + "jsonCA": 5, + // some comment + "d": { + "jsonCe": 1 + } +} diff --git a/__tests__/extend.test.js b/__tests__/extend.test.js index fda698d13..80c796e11 100644 --- a/__tests__/extend.test.js +++ b/__tests__/extend.test.js @@ -228,6 +228,11 @@ describe('extend', () => { expect(StyleDictionaryExtended).toHaveProperty('platforms.web'); }); + it('should accept a string as a path to a JSONC file', function() { + var StyleDictionaryExtended = StyleDictionary.extend(__dirname + '/__configs/test.jsonc'); + expect(StyleDictionaryExtended).toHaveProperty('platforms.web'); + }); + it('should allow for chained extends and not mutate the original', function() { var StyleDictionary1 = StyleDictionary.extend({ foo: 'bar' diff --git a/__tests__/utils/combineJSON.test.js b/__tests__/utils/combineJSON.test.js index 44532b259..69fdee9c5 100644 --- a/__tests__/utils/combineJSON.test.js +++ b/__tests__/utils/combineJSON.test.js @@ -75,6 +75,12 @@ describe('utils', () => { expect(test.d).toHaveProperty('json5e', 1); }); + it('should support jsonc', () => { + var test = combineJSON(["__tests__/__json_files/shallow/*.jsonc"]); + expect(test).toHaveProperty('jsonCA', 5); + expect(test.d).toHaveProperty('jsonCe', 1); + }); + describe('custom parsers', () => { it('should support yaml.parse', () => { const parsers = [{ diff --git a/docs/README.md b/docs/README.md index e2c57b4b3..0c4e4ced3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,7 +23,7 @@ When you are managing user experiences, it can be quite challenging to keep styl ## The Basics __A style dictionary consists of:__ -1. [Design tokens](tokens.md), organized in JSON, JSON5, or JS files +1. [Design tokens](tokens.md), organized in JSON, JSONC, JSON5, or JS files 1. Static assets (e.g. fonts, icons, images, sounds, etc.), organized into folders 1. [Configuration](config.md), defining the [transformation](transforms.md) and [formatting](formats.md) of the tokens and assets for each output platform diff --git a/docs/config.md b/docs/config.md index ab185e647..1b358e3ce 100644 --- a/docs/config.md +++ b/docs/config.md @@ -38,6 +38,7 @@ Here is an example configuration: Style Dictionary supports configuration files in these file formats: * JSON +* JSONC * JSON5 * Javascript (CommonJS) diff --git a/docs/tokens.md b/docs/tokens.md index 7c2c64c5b..5f184da07 100644 --- a/docs/tokens.md +++ b/docs/tokens.md @@ -128,6 +128,7 @@ See more in the advanced [referencing-aliasing example](https://github.com/amzn/ Design token files can included inline in the configuration, or be written in separate files. Style Dictionary supports these languages for design token files: * JSON +* [JSONC](https://code.visualstudio.com/docs/languages/json#_json-with-comments) * [JSON5](https://json5.org) * CommonJS modules * Potentially any language with [custom parsers](#customfileparsers) diff --git a/lib/extend.js b/lib/extend.js index dd2113366..0a8ed7acf 100644 --- a/lib/extend.js +++ b/lib/extend.js @@ -12,6 +12,7 @@ */ require('json5/lib/register'); +require.extensions[".jsonc"] = require("./utils/jsonc").register; var combineJSON = require('./utils/combineJSON'), deepExtend = require('./utils/deepExtend'), diff --git a/lib/utils/combineJSON.js b/lib/utils/combineJSON.js index adfbb0a40..1b7329eef 100644 --- a/lib/utils/combineJSON.js +++ b/lib/utils/combineJSON.js @@ -12,6 +12,7 @@ */ require('json5/lib/register'); +require.extensions[".jsonc"] = require("./jsonc").register; var glob = require('glob'), deepExtend = require('./deepExtend'), diff --git a/lib/utils/jsonc.js b/lib/utils/jsonc.js new file mode 100644 index 000000000..6f562bb22 --- /dev/null +++ b/lib/utils/jsonc.js @@ -0,0 +1,14 @@ +const fs = require("fs"); +const jsonc = require("jsonc-parser"); + +module.exports = { + register(module, filename) { + const content = fs.readFileSync(filename, "utf8"); + try { + module.exports = jsonc.parse(content); + } catch (err) { + err.message = filename + ": " + err.message; + throw err; + } + }, +}; diff --git a/package-lock.json b/package-lock.json index 2b4e0ff30..302ed8807 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "style-dictionary", - "version": "3.0.2", + "version": "3.0.3", "license": "Apache-2.0", "dependencies": { "chalk": "^4.0.0", @@ -37,6 +37,7 @@ "jsdoc-escape-at": "^1.0.1", "jsdoc-to-markdown": "^7.0.1", "json5-jest": "^1.0.1", + "jsonc-parser": "^3.0.0", "less": "^3.11.2", "lint-staged": "^10.2.7", "node-sass": "^6.0.1", @@ -9905,6 +9906,12 @@ "json5": "lib/cli.js" } }, + "node_modules/jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "dev": true + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -24084,6 +24091,12 @@ } } }, + "jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "dev": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", diff --git a/package.json b/package.json index b2b354775..ee1f1222e 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ ], "transform": { "^.+\\.json5$": "json5-jest", + "^.+\\.jsonc$": "json5-jest", "^.+\\.jsx?$": "babel-jest" } }, @@ -141,6 +142,7 @@ "jsdoc-escape-at": "^1.0.1", "jsdoc-to-markdown": "^7.0.1", "json5-jest": "^1.0.1", + "jsonc-parser": "^3.0.0", "less": "^3.11.2", "lint-staged": "^10.2.7", "node-sass": "^6.0.1", diff --git a/types/index.d.ts b/types/index.d.ts index f38ae1828..bacd22f70 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -186,7 +186,7 @@ declare namespace StyleDictionary { /** * Adds a custom parser to parse style dictionary files. This allows you to modify * the design token data before it gets to Style Dictionary or write your - * token files in a language other than JSON, JSON5, or CommonJS modules. + * token files in a language other than JSON, JSONC, JSON5, or CommonJS modules. * * @param {Regex} parser.pattern - A file path regular expression to match which files this parser should be be used on. This is similar to how webpack loaders work. `/\.json$/` will match any file ending in '.json', for example. * @param {Function} parser.parse - Function to parse the file contents. Takes 1 argument, which is an object with 2 attributes: contents which is the string of the file contents and filePath. The function should return a plain Javascript object.