-
Notifications
You must be signed in to change notification settings - Fork 530
/
getReferences.js
115 lines (104 loc) · 4.44 KB
/
getReferences.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*
* 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.ts').GetReferencesOptions} GetReferencesOptions
* @typedef {import('../../StyleDictionary.js').default} Dictionary
* @typedef {import('../../../types/DesignToken.ts').TransformedTokens} Tokens
* @typedef {import('../../../types/DesignToken.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;
}