style-dictionary
Version:
Style once, use everywhere. A build system for creating cross-platform styles.
116 lines (104 loc) • 4.55 kB
JavaScript
/*
* 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 getPathFromName from './getPathFromName.js';
import createReferenceRegex from './createReferenceRegex.js';
import getValueByPath from './getValueByPath.js';
import GroupMessages from '../groupMessages.js';
import defaults from './defaults.js';
const FILTER_WARNINGS = GroupMessages.GROUP.FilteredOutputReferences;
/**
* @typedef {import('../../../types/Config.d.ts').GetReferencesOptions} GetReferencesOptions
* @typedef {import('../../StyleDictionary.js').default} Dictionary
* @typedef {import('../../../types/DesignToken.d.ts').TransformedTokens} Tokens
* @typedef {import('../../../types/DesignToken.d.ts').TransformedToken} Token
*/
/**
* This is a helper function that is added to the dictionary object that
* is passed to formats and actions. It will resolve a reference giving
* you access to the token (not just the value) that a value references.
* This allows formats to have variable references in the output. For example:
*
* ```css
* --color-background-base: var(--color-core-white);
* ```
*
* @param {string|Object<string, string|any>} value the value that contains a reference
* @param {Tokens} tokens the dictionary to search in
* @param {GetReferencesOptions} [opts]
* @param {Token[]} [references] array of token's references because a token's value can contain multiple references due to string interpolation
* @returns {Token[]}
*/
export function getReferences(value, tokens, opts = {}, references = []) {
const { usesDtcg, separator, warnImmediately = true, unfilteredTokens } = opts;
const regex = createReferenceRegex(opts);
/**
* this will update the references array with the referenced tokens it finds.
* @param {string} _
* @param {string} variable
*/
function findReference(_, variable) {
// remove 'value' to access the whole token object
variable = variable.trim().replace(`.${usesDtcg ? '$' : ''}value`, '');
// Find what the value is referencing
const pathName = getPathFromName(variable, separator ?? defaults.separator);
let ref = getValueByPath(pathName, tokens);
let unfilteredWarning;
if (ref === undefined && unfilteredTokens) {
// warn the user about this
if (warnImmediately) {
unfilteredWarning = `Filtered out token references were found: ${variable}`;
} else {
// we collect the warning and warn later in the process
GroupMessages.add(FILTER_WARNINGS, variable);
}
// fall back on unfilteredTokens as it is unfiltered
ref = getValueByPath(pathName, unfilteredTokens);
}
if (ref !== undefined) {
references.push({ ...ref, ref: pathName });
// not undefined anymore which means that if unfilteredWarning was set earlier,
// the missing ref is due to it being filtered out
if (unfilteredWarning) {
console.warn(unfilteredWarning);
}
} else {
const errMessage = `Tries to reference ${variable}, which is not defined.`;
if (warnImmediately) {
throw new Error(errMessage);
}
}
return '';
}
if (typeof value === 'string') {
// function inside .replace runs multiple times if there are multiple matches
// TODO: we don't need the replace's return value, consider using something else here
value.replace(regex, findReference);
}
// If the token's value is an object, run the replace reference
// on each value within that object. This mirrors the `usesReferences`
// function which iterates over the object to see if there is a reference
if (typeof value === 'object') {
for (const key in value) {
if (Object.hasOwn(value, key)) {
if (typeof value[key] === 'string') {
value[key].replace(regex, findReference);
}
// if it is an object, we go further down the rabbit hole
if (typeof value[key] === 'object') {
getReferences(value[key], tokens, opts, references);
}
}
}
}
return references;
}