diff --git a/lib/StyleDictionary.js b/lib/StyleDictionary.js index e613ad6ca..a602dc7da 100644 --- a/lib/StyleDictionary.js +++ b/lib/StyleDictionary.js @@ -49,10 +49,6 @@ import cleanActions from './cleanActions.js'; const PROPERTY_VALUE_COLLISIONS = GroupMessages.GROUP.PropertyValueCollisions; const PROPERTY_REFERENCE_WARNINGS = GroupMessages.GROUP.PropertyReferenceWarnings; -// const TEMPLATE_DEPRECATION_WARNINGS = GroupMessages.GROUP.TemplateDeprecationWarnings; -// const REGISTER_TEMPLATE_DEPRECATION_WARNINGS = GroupMessages.GROUP.RegisterTemplateDeprecationWarnings; -// const SASS_MAP_FORMAT_DEPRECATION_WARNINGS = GroupMessages.GROUP.SassMapFormatDeprecationWarnings; - /** * Style Dictionary module * diff --git a/lib/common/formatHelpers/getTypeScriptType.js b/lib/common/formatHelpers/getTypeScriptType.js index 3c81768d9..40ad40e9b 100644 --- a/lib/common/formatHelpers/getTypeScriptType.js +++ b/lib/common/formatHelpers/getTypeScriptType.js @@ -10,7 +10,6 @@ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ -import { unique } from '../../utils/es6_.js'; /** * Given some value, returns a basic valid TypeScript type for that value. @@ -72,11 +71,13 @@ function getArrayType(passedArray) { if (passedArray.every((v) => getTypeScriptType(v) === firstValueType)) { return firstValueType + '[]'; } else { - return `(${unique( - passedArray.map((item, index) => { - const isLast = passedArray.length === index + 1; - return `${getTypeScriptType(item)}${!isLast ? ' | ' : ''}`; - }), + return `(${Array.from( + new Set( + passedArray.map((item, index) => { + const isLast = passedArray.length === index + 1; + return `${getTypeScriptType(item)}${!isLast ? ' | ' : ''}`; + }), + ), ).join('')})[]`; } } diff --git a/lib/common/transforms.js b/lib/common/transforms.js index abb3e917f..373dda09e 100644 --- a/lib/common/transforms.js +++ b/lib/common/transforms.js @@ -13,9 +13,16 @@ import Color from 'tinycolor2'; import path from '@bundled-es-modules/path-browserify'; -import { kebabCase, camelCase, snakeCase, upperFirst } from '../utils/es6_.js'; +import { + camelCaseTransformMerge, + snakeCase, + paramCase as kebabCase, + camelCase as _camelCase, +} from 'change-case'; import convertToBase64 from '../utils/convertToBase64.js'; +const camelCase = (str) => _camelCase(str, { transform: camelCaseTransformMerge }); + const UNICODE_PATTERN = /&#x([^;]+);/g; function isColor(token) { @@ -281,6 +288,9 @@ export default { 'name/cti/pascal': { type: 'name', transformer: function (token, options) { + const upperFirst = function (str) { + return str ? str[0].toUpperCase() + str.substr(1) : ''; + }; return upperFirst(camelCase([options.prefix].concat(token.path).join(' '))); }, }, diff --git a/lib/filterTokens.js b/lib/filterTokens.js index ce9e390ac..2ba4305c9 100644 --- a/lib/filterTokens.js +++ b/lib/filterTokens.js @@ -10,14 +10,13 @@ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ - -import { reduce, isObject, filter as filterUtil, assign, isEmpty } from './utils/es6_.js'; +import { isPlainObject } from 'is-plain-object'; /** * Takes a nested object of tokens and filters them using the provided * function. * - * @param {Object} tokens + * @param {Object|undefined|null} tokens * @param {Function} filter - A function that receives a property object and * returns `true` if the property should be included in the output or `false` * if the property should be excluded from the output. @@ -27,45 +26,26 @@ import { reduce, isObject, filter as filterUtil, assign, isEmpty } from './utils function filterTokenObject(tokens, filter) { // Use reduce to generate a new object with the unwanted tokens filtered // out - return reduce( - tokens, - (result, value, key) => { - // If the value is not an object, we don't know what it is. We return it as-is. - if (!isObject(value)) { - return result; - // If the value has a `value` member we know it's a property, pass it to - // the filter function and either include it in the final `result` object or - // exclude it (by returning the `result` object without it added). - } else if (typeof value.value !== 'undefined') { - return filter(value) ? assign(result, { [key]: value }) : result; - // If we got here we have an object that is not a property. We'll assume - // it's an object containing multiple tokens and recursively filter it - // using the `filterTokenObject` function. - } else { - const filtered = filterTokenObject(value, filter); - // If the filtered object is not empty then add it to the final `result` - // object. If it is empty then every property inside of it was filtered - // out, then exclude it entirely from the final `result` object. - return isEmpty(filtered) ? result : assign(result, { [key]: filtered }); - } - }, - {}, - ); -} - -/** - * Takes an array of tokens and filters them using the provided function. - * - * @param {Object[]} tokens - * @param {Function} filter - A function that receives a property object and - * returns `true` if the property should be included in the output or `false` - * if the property should be excluded from the output. - * @returns {Object[]} tokens - A new array containing only the tokens - * that matched the filter. - */ -function filterTokenArray(tokens, filter) { - // Go lodash! - return filterUtil(tokens, filter); + return Object.entries(tokens ?? []).reduce((acc, [key, value]) => { + // If the value is not an object, we don't know what it is. We return it as-is. + if (!isPlainObject(value)) { + return acc; + // If the value has a `value` member we know it's a property, pass it to + // the filter function and either include it in the final `acc` object or + // exclude it (by returning the `acc` object without it added). + } else if (typeof value.value !== 'undefined') { + return filter(value) ? { ...acc, [key]: value } : acc; + // If we got here we have an object that is not a property. We'll assume + // it's an object containing multiple tokens and recursively filter it + // using the `filterTokenObject` function. + } else { + const filtered = filterTokenObject(value, filter); + // 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 }; + } + }, {}); } /** @@ -84,9 +64,13 @@ export default function filterTokens(dictionary, filter) { if (!filter) { return dictionary; } else { - return { - allTokens: filterTokenArray(dictionary.allTokens, filter), - tokens: filterTokenObject(dictionary.tokens, filter), - }; + if (typeof filter !== 'function') { + throw new Error('filter is not a function'); + } else { + return { + allTokens: (dictionary.allTokens ?? []).filter(filter), + tokens: filterTokenObject(dictionary.tokens, filter), + }; + } } } diff --git a/lib/register/transformGroup.js b/lib/register/transformGroup.js index 479396b63..beee04b56 100644 --- a/lib/register/transformGroup.js +++ b/lib/register/transformGroup.js @@ -11,8 +11,6 @@ * and limitations under the License. */ -import { isArray } from '../utils/es6_.js'; - /** * Add a custom transformGroup to the Style Dictionary, which is a * group of transforms. @@ -36,7 +34,7 @@ import { isArray } from '../utils/es6_.js'; */ export default function registerTransformGroup(options) { if (typeof options.name !== 'string') throw new Error('transform name must be a string'); - if (!isArray(options.transforms)) + if (!Array.isArray(options.transforms)) throw new Error('transforms must be an array of registered value transforms'); options.transforms.forEach( diff --git a/lib/transform/config.js b/lib/transform/config.js index 8ed4265cd..ab53dd4a3 100644 --- a/lib/transform/config.js +++ b/lib/transform/config.js @@ -11,11 +11,10 @@ * and limitations under the License. */ -import { clone, matches } from '../utils/es6_.js'; +import { isPlainObject } from 'is-plain-object'; import deepExtend from '../utils/deepExtend.js'; import GroupMessages from '../utils/groupMessages.js'; -const TEMPLATE_DEPRECATION_WARNINGS = GroupMessages.GROUP.TemplateDeprecationWarnings; const MISSING_TRANSFORM_ERRORS = GroupMessages.GROUP.MissingRegisterTransformErrors; /** @@ -29,7 +28,7 @@ const MISSING_TRANSFORM_ERRORS = GroupMessages.GROUP.MissingRegisterTransformErr * @returns {Object} */ export default function transformConfig(platformConfig, dictionary, platformName) { - const to_ret = clone(platformConfig); + const to_ret = { ...platformConfig }; // structuredClone not suitable due to config being able to contain Function() etc. to_ret.log = platformConfig.log ?? dictionary.log; // The platform can define either a transformGroup or an array @@ -123,6 +122,21 @@ None of ${transform_warnings} match the name of a registered transform. throw new Error("Can't find filter: " + file.filter); } } else if (typeof file.filter === 'object') { + // Recursively go over the object keys of filter object and + // return a filter Function that filters tokens + // by the specified object keys. + const matchFn = function (inputObj, testObj) { + if (isPlainObject(testObj)) { + return Object.keys(testObj).every((key) => matchFn(inputObj[key], testObj[key])); + } else { + return inputObj == testObj; + } + }; + const matches = function (matchObj) { + let cloneObj = { ...matchObj }; // shallow clone, structuredClone not suitable because obj can contain "Function()" + let matchesFn = (inputObj) => matchFn(inputObj, cloneObj); + return matchesFn; + }; ext.filter = matches(file.filter); } else if (typeof file.filter === 'function') { ext.filter = file.filter; diff --git a/lib/transform/object.js b/lib/transform/object.js index 1b0c296b9..641fe606f 100644 --- a/lib/transform/object.js +++ b/lib/transform/object.js @@ -11,7 +11,7 @@ * and limitations under the License. */ -import { isPlainObject, pull } from '../utils/es6_.js'; +import { isPlainObject } from 'is-plain-object'; import usesValueReference from '../utils/references/usesReference.js'; import getName from '../utils/references/getName.js'; import transformToken from './token.js'; @@ -26,7 +26,7 @@ import tokenSetup from './tokenSetup.js'; * @private * @param {Object} obj * @param {Object} options - * @param {Object} [transformationContext={}] + * @param {Object} [ctx={}] * @param {Array} [path=[]] * @param {Object} [transformedObj={}] * @returns {Object} @@ -34,13 +34,12 @@ import tokenSetup from './tokenSetup.js'; export default function transformObject( obj, options, - transformationContext = {}, + { transformedPropRefs = [], deferredPropValueTransforms = [] } = {}, path, transformedObj, ) { transformedObj = transformedObj || {}; path = path || []; - const { transformedPropRefs = [], deferredPropValueTransforms = [] } = transformationContext; for (const name in obj) { if (!obj.hasOwnProperty(name)) { @@ -87,8 +86,15 @@ 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. transformedObj[name] = transformToken(setupProperty, options); - // Remove the property path from the deferred transform list - pull(deferredPropValueTransforms, pathName); + + // Remove the property path from the deferred transform list, starting from end of array + for (let i = deferredPropValueTransforms.length - 1; i >= 0; i--) { + if (deferredPropValueTransforms[i] === pathName) { + // Important to use .splice and mutate the original array all the way up + deferredPropValueTransforms.splice(i, 1); + } + } + // Add the property path to the transformed list so we don't transform it again. transformedPropRefs.push(pathName); } else if (isObj) { @@ -96,7 +102,7 @@ export default function transformObject( transformedObj[name] = transformObject( objProp, options, - transformationContext, + { transformedPropRefs, deferredPropValueTransforms }, path, transformedObj[name], ); diff --git a/lib/transform/token.js b/lib/transform/token.js index 8d1da2ef3..4a107469e 100644 --- a/lib/transform/token.js +++ b/lib/transform/token.js @@ -11,7 +11,6 @@ * and limitations under the License. */ -import { clone } from '../utils/es6_.js'; import usesReference from '../utils/references/usesReference.js'; /** @@ -23,7 +22,7 @@ import usesReference from '../utils/references/usesReference.js'; * @returns {Object} - A new property object with transforms applied. */ export default function transformProperty(property, options) { - const to_ret = clone(property); + const to_ret = structuredClone(property); const transforms = options.transforms; for (let i = 0; i < transforms.length; i++) { diff --git a/lib/transform/tokenSetup.js b/lib/transform/tokenSetup.js index 8bf6e6c48..178a43699 100644 --- a/lib/transform/tokenSetup.js +++ b/lib/transform/tokenSetup.js @@ -11,8 +11,8 @@ * and limitations under the License. */ +import { isPlainObject } from 'is-plain-object'; import deepExtend from '../utils/deepExtend.js'; -import { isPlainObject, isString, isArray, clone } from '../utils/es6_.js'; /** * Takes a token object, a leaf node in a tokens object, and @@ -27,8 +27,8 @@ import { isPlainObject, isString, isArray, clone } from '../utils/es6_.js'; */ export default function tokenSetup(token, name, path) { if (!token && !isPlainObject(token)) throw new Error('Property object must be an object'); - if (!name || !isString(name)) throw new Error('Name must be a string'); - if (!path || !isArray(path)) throw new Error('Path must be an array'); + if (!name || !(typeof name === 'string')) throw new Error('Name must be a string'); + if (!path || !Array.isArray(path)) throw new Error('Path must be an array'); let to_ret = token; @@ -49,7 +49,7 @@ export default function tokenSetup(token, name, path) { to_ret.attributes = to_ret.attributes || {}; // An array of the path down the object tree; we will use it to build readable names // like color_font_base - to_ret.path = clone(path); + to_ret.path = structuredClone(path); } return to_ret; diff --git a/lib/utils/deepExtend.js b/lib/utils/deepExtend.js index 174c526e3..bd15ac809 100644 --- a/lib/utils/deepExtend.js +++ b/lib/utils/deepExtend.js @@ -11,7 +11,7 @@ * and limitations under the License. */ -import { isPlainObject, isArray } from './es6_.js'; +import { isPlainObject } from 'is-plain-object'; /** * TODO: see if we can use deepmerge instead of maintaining our own utility @@ -26,15 +26,7 @@ import { isPlainObject, isArray } from './es6_.js'; export default function deepExtend(objects, collision, path) { if (objects == null) return {}; - var src, - copyIsArray, - copy, - name, - options, - clone, - target = objects[0] || {}, - i = 1, - length = objects.length; + let target = objects[0] || {}; path = path || []; @@ -43,33 +35,36 @@ export default function deepExtend(objects, collision, path) { target = {}; } - for (; i < length; i++) { + for (let i = 1; i < objects.length; i++) { + const options = objects[i]; // Only deal with non-null/undefined values - if ((options = objects[i]) != null) { + if (options != null) { // Extend the base object - for (name in options) { + for (const name in options) { // Not everything extends from Object in browser context, so bind from Object just in case if (!Object.hasOwnProperty.bind(options)(name)) continue; if (name === '__proto__') continue; - src = target[name]; - copy = options[name]; + const src = target[name]; + const copy = options[name]; // Prevent never-ending loop if (target === copy) { continue; } + let copyIsArray = Array.isArray(copy); // Recurse if we're merging plain objects or arrays - if (copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { + if (copy && (isPlainObject(copy) || copyIsArray)) { + let clone; if (copyIsArray) { copyIsArray = false; - clone = src && isArray(src) ? src : []; + clone = src && Array.isArray(src) ? src : []; } else { clone = src && isPlainObject(src) ? src : {}; } - var nextPath = path.slice(0); + const nextPath = path.slice(0); nextPath.push(name); // Never move original objects, clone them diff --git a/lib/utils/es6_.js b/lib/utils/es6_.js deleted file mode 100644 index f6b6a2bed..000000000 --- a/lib/utils/es6_.js +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with - * the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -import * as ChangeCase from 'change-case'; - -export const reduce = function (obj, f, accumulator_init) { - return Object.keys(obj || {}).reduce((accumulator, key) => { - let value = obj[key]; - return f(accumulator, value, key, obj); - }, accumulator_init); -}; - -export const forEach = function (obj, f) { - Object.keys(obj || {}).forEach((key) => { - let value = obj[key]; - f(value, key); - }); -}; - -// Note: This is a crappy version to a certain extent... don't use with Strings, for example... -export const clone = function (object) { - return Object.assign(new object.constructor(), object); -}; - -export const cloneDeep = function (obj) { - if (obj === null || obj === undefined || typeof obj !== 'object') { - return obj; - } - - if (obj instanceof Array) { - return obj.reduce((arr, item, i) => { - arr[i] = cloneDeep(item); - return arr; - }, []); - } - - if (obj instanceof Object) { - return Object.keys(obj || {}).reduce((cpObj, key) => { - cpObj[key] = cloneDeep(obj[key]); - return cpObj; - }, {}); - } -}; - -export const isObject = function (value) { - const type = typeof value; - return value != null && (type === 'object' || type === 'function'); -}; - -export const isString = function (obj) { - return typeof obj === 'string' || obj instanceof String; -}; - -export const isArray = function (obj) { - return Array.isArray(obj); -}; - -export const isEmpty = function (obj) { - return [Object, Array].includes((obj || {}).constructor) && !Object.entries(obj || {}).length; -}; - -export function isPlainObject(value) { - if ( - typeof value !== 'object' || - value === null || - Object.prototype.toString.call(value) !== '[object Object]' - ) { - return false; - } - if (Object.getPrototypeOf(value) === null) { - return true; - } - let proto = value; - while (Object.getPrototypeOf(proto) !== null) { - proto = Object.getPrototypeOf(proto); - } - return Object.getPrototypeOf(value) === proto; -} - -export const filter = function (arr, filter) { - if (typeof filter !== 'function') { - throw 'filter is not a function'; - } - if (typeof arr === 'undefined') { - return []; - } - return arr.filter(filter); -}; - -export const assign = function () { - let args = Array.prototype.slice.call(arguments); - args.unshift({}); - return Object.assign(...args); -}; - -export const pull = function (arr, ...removeList) { - var removeSet = new Set(removeList); - for (let i = arr.length - 1; i >= 0; i--) { - if (removeSet.has(arr[i])) { - arr.splice(i, 1); - } - } -}; - -export const unique = function (arr) { - return [...new Set(arr)]; -}; - -export const upperFirst = function (str) { - return str ? str[0].toUpperCase() + str.substr(1) : ''; -}; - -const matchFn = function (inputObj, testObj) { - if (isObject(testObj)) { - return Object.keys(testObj).every((key) => matchFn(inputObj[key], testObj[key])); - } else { - return inputObj == testObj; - } -}; - -export const matches = function (matchObj) { - let cloneObj = cloneDeep(matchObj); - let matchesFn = (inputObj) => matchFn(inputObj, cloneObj); - return matchesFn; -}; - -const DEFAULT_OPTIONS = { - transform: ChangeCase.camelCaseTransformMerge, -}; -export const changeDefaultCaseTransform = function (caseFunction, default_options) { - return (caseToChange, options) => - caseFunction(caseToChange, Object.assign({}, DEFAULT_OPTIONS, default_options, options)); -}; - -// aliases -export const each = forEach; -export const forIn = forEach; -export const camelCase = changeDefaultCaseTransform(ChangeCase.camelCase); -export const snakeCase = ChangeCase.snakeCase; -export const kebabCase = ChangeCase.paramCase; diff --git a/lib/utils/flattenTokens.js b/lib/utils/flattenTokens.js index 1cdae2d8b..d7b64d9b2 100644 --- a/lib/utils/flattenTokens.js +++ b/lib/utils/flattenTokens.js @@ -11,7 +11,7 @@ * and limitations under the License. */ -import { isPlainObject } from './es6_.js'; +import { isPlainObject } from 'is-plain-object'; /** * Takes an plain javascript object and will make a flat array of all the leaf nodes. diff --git a/lib/utils/references/getName.js b/lib/utils/references/getName.js index 7711421b1..a4b14b7ac 100644 --- a/lib/utils/references/getName.js +++ b/lib/utils/references/getName.js @@ -22,8 +22,8 @@ import defaults from './defaults.js'; * @returns {string} - The paths name */ export default function getName(path, opts = {}) { - const options = Object.assign({}, defaults, opts); - if (!path || !(path instanceof Array)) { + const options = { ...defaults, ...opts }; + if (!Array.isArray(path)) { throw new Error('Getting name for path failed. Path must be an array'); } return path.join(options.separator); diff --git a/lib/utils/resolveObject.js b/lib/utils/resolveObject.js index 57d0d9406..e345be12a 100644 --- a/lib/utils/resolveObject.js +++ b/lib/utils/resolveObject.js @@ -11,7 +11,6 @@ * and limitations under the License. */ -import { cloneDeep } from './es6_.js'; import GroupMessages from './groupMessages.js'; import usesReference from './references/usesReference.js'; import getName from './references/getName.js'; @@ -31,7 +30,7 @@ let updated_object, regex, options; export default function resolveObject(object, opts) { options = Object.assign({}, defaults, opts); - updated_object = cloneDeep(object); // This object will be edited + updated_object = structuredClone(object); // This object will be edited regex = createReferenceRegex(options);