From 1c0ee01a92326f47f5de81b64066970cb3865040 Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Fri, 9 Sep 2022 16:01:17 +0200 Subject: [PATCH] fix: proper isObject checks where applicable (#23) --- .changeset/tiny-hornets-guess.md | 5 +++++ README.md | 19 +++++++++++++++++- src/mark-tokenset.js | 8 +++++--- src/trim-name.js | 4 +++- src/trim-value.js | 5 +++-- src/use-ref-value.js | 4 +++- src/utils/isObject.js | 7 +++++++ test/trim-value.test.js | 33 ++++++++++++++++++++++++++++++++ test/utils/isObject.test.js | 11 +++++++++++ 9 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 .changeset/tiny-hornets-guess.md create mode 100644 src/utils/isObject.js create mode 100644 test/utils/isObject.test.js diff --git a/.changeset/tiny-hornets-guess.md b/.changeset/tiny-hornets-guess.md new file mode 100644 index 0000000..4b03bcd --- /dev/null +++ b/.changeset/tiny-hornets-guess.md @@ -0,0 +1,5 @@ +--- +'@divriots/style-dictionary-to-figma': patch +--- + +Do proper isObject check (typeof null and Array are also 'object') where needed. Fixes bug with metadata props with type Array getting altered by trimValue to become Objects. diff --git a/README.md b/README.md index bac334a..54572a0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,24 @@ Used by Design Systems in [Backlight](https://backlight.dev) using design tokens ## Features -- Allows marking a category as a custom tokenset so that it will appear as a separate tokenset in Figma. This is useful if you want to combine many base tokens into a "global" set for example. +- **Mandatory action required**: Supports marking a category as a custom tokenset so that it will appear as a separate tokenset in Figma. This is useful if you want to combine many base tokens into a "global" set for example and a mandatory step for Figma Tokens Plugin currently, or your references will not work. You can configure it like so: + + ```json + { + "color": { + "tokenset": "global", + "primary": { + "base": { + "type": "color", + "value": "#14b8a6" + } + } + } + } + ``` + + `color.primary.base` token will appear under `global` tokenset now in the plugin. + - Trims `.value` from reference values as Figma Tokens plugin does not use this suffix. - Trims the `name` properties from tokens since Figma Tokens plugin uses this property to name its tokens, however, without a name property it creates its own naming/nesting by the object structure which is way nicer. - Use the reference values rather than its resolved values. Put `ignoreUseRefValue: true` as a sibling property to the value prop if you want to make an exception and keep it as a resolved value. diff --git a/src/mark-tokenset.js b/src/mark-tokenset.js index 40c6990..a2a73c5 100644 --- a/src/mark-tokenset.js +++ b/src/mark-tokenset.js @@ -1,3 +1,5 @@ +import { isObject } from './utils/isObject.js'; + /** * @typedef {import('./style-dictionary-to-figma.js').Obj} Obj */ @@ -9,14 +11,14 @@ export function markTokenset(obj) { const _obj = { ...obj }; Object.keys(_obj).forEach(key => { - if (typeof _obj[key] === 'object') { - // typeof check so we know it's an object + if (isObject(_obj[key])) { + // check so we know it's an object const nestedObj = /** @type {Obj} */ (_obj[key]); Object.keys(nestedObj).forEach(nestedKey => { if (nestedKey === 'tokenset') { // tokenset value may only be string const tokenset = /** @type {string} */ (nestedObj[nestedKey]); - if (typeof _obj[tokenset] !== 'object') { + if (!isObject(_obj[tokenset])) { _obj[tokenset] = {}; } /** @type {Obj} */ (_obj[tokenset])[key] = nestedObj; diff --git a/src/trim-name.js b/src/trim-name.js index cb4c946..8c555f7 100644 --- a/src/trim-name.js +++ b/src/trim-name.js @@ -1,3 +1,5 @@ +import { isObject } from './utils/isObject.js'; + /** * @typedef {import('./style-dictionary-to-figma.js').Obj} Obj */ @@ -12,7 +14,7 @@ export function trimName(obj) { Object.keys(newObj).forEach(key => { if (key === 'name') { delete newObj[key]; - } else if (typeof newObj[key] === 'object') { + } else if (isObject(newObj[key]) || Array.isArray(newObj[key])) { const newValue = trimName(/** @type {Obj} */ (newObj[key])); newObj[key] = Array.isArray(newObj[key]) ? Object.values(newValue) : newValue; } diff --git a/src/trim-value.js b/src/trim-value.js index c1b399e..58e95cf 100644 --- a/src/trim-value.js +++ b/src/trim-value.js @@ -1,3 +1,4 @@ +import { isObject } from './utils/isObject.js'; /** * @typedef {import('./style-dictionary-to-figma.js').Obj} Obj */ @@ -18,11 +19,11 @@ export function trimValue(obj, isValueObj = false) { if (matches && matches[1]) { newObj[key] = val.replace('.value', ''); } - } else if (typeof newObj[key] === 'object') { + } else if (isObject(newObj[key]) || Array.isArray(newObj[key])) { const newValue = trimValue(/** @type {Obj} */ (newObj[key]), true); newObj[key] = Array.isArray(newObj[key]) ? Object.values(newValue) : newValue; } - } else if (typeof newObj[key] === 'object') { + } else if (isObject(newObj[key])) { newObj[key] = trimValue(/** @type {Obj} */ (newObj[key])); } }); diff --git a/src/use-ref-value.js b/src/use-ref-value.js index 5b3783c..d26f2b0 100644 --- a/src/use-ref-value.js +++ b/src/use-ref-value.js @@ -1,3 +1,5 @@ +import { isObject } from './utils/isObject.js'; + /** * @typedef {import('./style-dictionary-to-figma.js').Obj} Obj * @typedef {string|Object|Array|number} Value @@ -35,7 +37,7 @@ export function useRefValue(obj) { if (isRefValue(originalValue)) { newObj.value = /** @type {Obj} */ (newObj[key]).value; } - } else if (typeof newObj[key] === 'object') { + } else if (isObject(newObj[key]) || Array.isArray(newObj[key])) { const newValue = useRefValue(/** @type {Obj} */ (newObj[key])); newObj[key] = Array.isArray(newObj[key]) ? Object.values(newValue) : newValue; } diff --git a/src/utils/isObject.js b/src/utils/isObject.js new file mode 100644 index 0000000..c04437f --- /dev/null +++ b/src/utils/isObject.js @@ -0,0 +1,7 @@ +/** + * @param {any} val + * @returns {boolean} + */ +export function isObject(val) { + return typeof val === 'object' && val !== null && !Array.isArray(val); +} diff --git a/test/trim-value.test.js b/test/trim-value.test.js index be05691..641b79c 100644 --- a/test/trim-value.test.js +++ b/test/trim-value.test.js @@ -139,4 +139,37 @@ describe('trim-value', () => { expect(trimmedObj).to.eql(expectedObj); }); + + it('keeps arbitrary metadata intact', () => { + const obj = { + core: { + color: { + primary: { + base: { + type: 'color', + value: '#14b8a6', + path: ['core', 'color', 'primary', 'base'], + }, + }, + }, + }, + }; + + const expectedObj = { + core: { + color: { + primary: { + base: { + type: 'color', + value: '#14b8a6', + path: ['core', 'color', 'primary', 'base'], + }, + }, + }, + }, + }; + + const transformedObj = trimValue(obj); + expect(transformedObj).to.eql(expectedObj); + }); }); diff --git a/test/utils/isObject.test.js b/test/utils/isObject.test.js new file mode 100644 index 0000000..27ef744 --- /dev/null +++ b/test/utils/isObject.test.js @@ -0,0 +1,11 @@ +import { expect } from '@esm-bundle/chai'; +import { isObject } from '../../src/utils/isObject.js'; + +describe('isObject utility', () => { + it('detects if something is an object', () => { + expect(isObject({})).to.be.true; + expect(isObject({ foo: 'bar', qux: { test: '1', testTwo: '2' } })).to.be.true; + expect(isObject(['foo', { test: '1', testTwo: '2' }])).to.be.false; + expect(isObject(null)).to.be.false; + }); +});