style-dictionary 4.0.0-prerelease.2 → 4.0.0-prerelease.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/advanced/variables-in-outputs/README.md +7 -5
- package/fs-node.js +6 -0
- package/lib/StyleDictionary.js +1 -1
- package/lib/common/formatHelpers/createPropertyFormatter.js +4 -2
- package/lib/common/formatHelpers/sortByReference.js +6 -4
- package/lib/common/formats.js +1 -2
- package/lib/common/templates/android/resources.template.js +56 -45
- package/lib/register/format.js +0 -2
- package/lib/utils/createDictionary.js +0 -6
- package/lib/utils/createFormatArgs.js +1 -3
- package/lib/utils/index.js +19 -0
- package/lib/utils/references/getReferences.js +8 -8
- package/lib/utils/references/{resolveReference.js → getValueByPath.js} +1 -1
- package/lib/utils/references/resolveReferences.js +161 -0
- package/lib/utils/resolveObject.js +28 -129
- package/package.json +5 -5
- package/types/Dictionary.d.ts +0 -2
- package/types/index.d.ts +2 -2
|
@@ -14,7 +14,7 @@ At this point, you can run `npm run build`. This command will generate the outpu
|
|
|
14
14
|
|
|
15
15
|
#### How does it work
|
|
16
16
|
|
|
17
|
-
The "build" command uses the `sd.config.js` file as the Style Dictionary configuration. It is configured to use JSON files in the `tokens/` directory as the source files. It adds a custom format directly in the configuration (as opposed to using the `.registerFormat()` method) that uses 2 new methods added
|
|
17
|
+
The "build" command uses the `sd.config.js` file as the Style Dictionary configuration. It is configured to use JSON files in the `tokens/` directory as the source files. It adds a custom format directly in the configuration (as opposed to using the `.registerFormat()` method) that uses 2 new methods added as exposed utilities: `usesReference()` and `getReferences()`. Also, it uses a new configuration on some formats: `outputReferences: true` to include variable references in the output.
|
|
18
18
|
|
|
19
19
|
#### What to look at
|
|
20
20
|
|
|
@@ -23,6 +23,8 @@ The `sd.config.js` file has everything you need to see. The tokens included in t
|
|
|
23
23
|
Here is an example that shows how to get an alias's name within a custom format:
|
|
24
24
|
|
|
25
25
|
```javascript
|
|
26
|
+
import { usesReference, getReferences } from 'style-dictionary/utils';
|
|
27
|
+
|
|
26
28
|
//...
|
|
27
29
|
function({ dictionary }) {
|
|
28
30
|
return dictionary.allTokens.map(token => {
|
|
@@ -32,8 +34,8 @@ function({ dictionary }) {
|
|
|
32
34
|
// the value has a reference in it. `getReferences()` will return
|
|
33
35
|
// an array of references to the whole tokens so that you can access their
|
|
34
36
|
// names or any other attributes.
|
|
35
|
-
if (
|
|
36
|
-
const refs =
|
|
37
|
+
if (usesReference(token.original.value)) {
|
|
38
|
+
const refs = getReferences(dictionary, token.original.value);
|
|
37
39
|
refs.forEach(ref => {
|
|
38
40
|
value = value.replace(ref.value, function() {
|
|
39
41
|
return `${ref.name}`;
|
|
@@ -49,5 +51,5 @@ The `build/` directory is where all the files are being built to. After Style Di
|
|
|
49
51
|
|
|
50
52
|
- `build/tokens.js` This file is generated from the custom format in this example. Tokens that are references to other tokens use the variable name instead of raw value.
|
|
51
53
|
- `build/tokens.json` This file does not use variable references to show that other outputs work as intended.
|
|
52
|
-
- `build/tokens.css` This file is generated using the `css/variables` built-in format with the new `
|
|
53
|
-
- `build/tokens.scss` This file is generated using the `scss/variables` built-in format with the new `
|
|
54
|
+
- `build/tokens.css` This file is generated using the `css/variables` built-in format with the new `outputReferences` configuration.
|
|
55
|
+
- `build/tokens.scss` This file is generated using the `scss/variables` built-in format with the new `outputReferences` configuration.
|
package/fs-node.js
ADDED
package/lib/StyleDictionary.js
CHANGED
|
@@ -67,7 +67,7 @@ export default class StyleDictionary {
|
|
|
67
67
|
// Placeholder is transformed on prepublish -> see scripts/inject-version.js
|
|
68
68
|
// Another option might be import pkg from './package.json' with { "type": "json" } which would work in both browser and node, but support is not there yet.
|
|
69
69
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#browser_compatibility
|
|
70
|
-
static VERSION = '4.0.0-prerelease.
|
|
70
|
+
static VERSION = '4.0.0-prerelease.4';
|
|
71
71
|
static formatHelpers = formatHelpers;
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* and limitations under the License.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { usesReference, getReferences } from 'style-dictionary/utils';
|
|
15
|
+
|
|
14
16
|
const defaultFormatting = {
|
|
15
17
|
prefix: '',
|
|
16
18
|
commentStyle: 'long',
|
|
@@ -159,10 +161,10 @@ export default function createPropertyFormatter({
|
|
|
159
161
|
* This will see if there are references and if there are, replace
|
|
160
162
|
* the resolved value with the reference's name.
|
|
161
163
|
*/
|
|
162
|
-
if (outputReferences &&
|
|
164
|
+
if (outputReferences && usesReference(prop.original.value)) {
|
|
163
165
|
// Formats that use this function expect `value` to be a string
|
|
164
166
|
// or else you will get '[object Object]' in the output
|
|
165
|
-
const refs =
|
|
167
|
+
const refs = getReferences(dictionary, prop.original.value);
|
|
166
168
|
|
|
167
169
|
// original can either be an object value, which requires transitive value transformation in web CSS formats
|
|
168
170
|
// or a different (primitive) type, meaning it can be stringified.
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* and limitations under the License.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { usesReference, getReferences } from 'style-dictionary/utils';
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* A function that returns a sorting function to be used with Array.sort that
|
|
16
18
|
* will sort the allTokens array based on references. This is to make sure
|
|
@@ -39,11 +41,11 @@ export default function sortByReference(dictionary) {
|
|
|
39
41
|
|
|
40
42
|
// If token a uses a reference and token b doesn't, b might come before a
|
|
41
43
|
// read on..
|
|
42
|
-
if (a.original &&
|
|
44
|
+
if (a.original && usesReference(a.original.value)) {
|
|
43
45
|
// Both a and b have references, we need to see if the reference each other
|
|
44
|
-
if (b.original &&
|
|
45
|
-
const aRefs =
|
|
46
|
-
const bRefs =
|
|
46
|
+
if (b.original && usesReference(b.original.value)) {
|
|
47
|
+
const aRefs = getReferences(dictionary, a.original.value);
|
|
48
|
+
const bRefs = getReferences(dictionary, b.original.value);
|
|
47
49
|
|
|
48
50
|
aRefs.forEach((aRef) => {
|
|
49
51
|
// a references b, we want b to come first
|
package/lib/common/formats.js
CHANGED
|
@@ -611,8 +611,7 @@ declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), nul
|
|
|
611
611
|
* ```
|
|
612
612
|
*/
|
|
613
613
|
'android/resources': function ({ dictionary, options, file }) {
|
|
614
|
-
|
|
615
|
-
return template({ dictionary, file, options, fileHeader });
|
|
614
|
+
return androidResources({ dictionary, file, options, fileHeader });
|
|
616
615
|
},
|
|
617
616
|
|
|
618
617
|
/**
|
|
@@ -1,50 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
14
|
-
// express or implied. See the License for the specific language governing
|
|
15
|
-
// permissions and limitations under the License.
|
|
16
|
-
|
|
17
|
-
var resourceType = file.resourceType || null;
|
|
18
|
-
|
|
19
|
-
var resourceMap = file.resourceMap || {
|
|
20
|
-
size: 'dimen',
|
|
21
|
-
color: 'color',
|
|
22
|
-
string: 'string',
|
|
23
|
-
content: 'string',
|
|
24
|
-
time: 'integer',
|
|
25
|
-
number: 'integer'
|
|
26
|
-
};
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
|
|
5
|
+
* the License. A copy of the License is located at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
|
11
|
+
* and limitations under the License.
|
|
12
|
+
*/
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
14
|
+
import { usesReference, getReferences } from 'style-dictionary/utils';
|
|
15
|
+
|
|
16
|
+
export default (opts) => {
|
|
17
|
+
const { file, fileHeader, dictionary } = opts;
|
|
18
|
+
|
|
19
|
+
const resourceType = file.resourceType || null;
|
|
20
|
+
|
|
21
|
+
const resourceMap = file.resourceMap || {
|
|
22
|
+
size: 'dimen',
|
|
23
|
+
color: 'color',
|
|
24
|
+
string: 'string',
|
|
25
|
+
content: 'string',
|
|
26
|
+
time: 'integer',
|
|
27
|
+
number: 'integer',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function propToType(prop) {
|
|
31
|
+
if (resourceType) {
|
|
32
|
+
return resourceType;
|
|
33
|
+
}
|
|
34
|
+
if (resourceMap[prop.attributes.category]) {
|
|
35
|
+
return resourceMap[prop.attributes.category];
|
|
36
|
+
}
|
|
37
|
+
return 'string';
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return prop.value;
|
|
39
|
+
|
|
40
|
+
function propToValue(prop) {
|
|
41
|
+
if (file.options && file.options.outputReferences && usesReference(prop.original.value)) {
|
|
42
|
+
return `@${propToType(prop)}/${getReferences(dictionary, prop.original.value)[0].name}`;
|
|
43
|
+
} else {
|
|
44
|
+
return prop.value;
|
|
45
|
+
}
|
|
43
46
|
}
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
|
|
48
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
49
|
+
|
|
50
|
+
${fileHeader({ file, commentStyle: 'xml' })}
|
|
46
51
|
<resources>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
${dictionary.allTokens
|
|
53
|
+
.map(
|
|
54
|
+
(prop) =>
|
|
55
|
+
`<${propToType(prop)} name="${prop.name}">${propToValue(prop)}</${propToType(prop)}>${
|
|
56
|
+
prop.comment ? `<!-- ${prop.comment} -->` : ''
|
|
57
|
+
}`,
|
|
58
|
+
)
|
|
59
|
+
.reduce((acc, curr) => acc + `${curr}\n `, '')}
|
|
50
60
|
</resources>`;
|
|
61
|
+
};
|
package/lib/register/format.js
CHANGED
|
@@ -24,8 +24,6 @@
|
|
|
24
24
|
* @param {Object} args.dictionary - The transformed and resolved dictionary object
|
|
25
25
|
* @param {Object} args.dictionary.tokens - Object structure of the tokens that has been transformed and references resolved.
|
|
26
26
|
* @param {Array} args.dictionary.allTokens - Flattened array of all the tokens. This makes it easy to output a list, like a list of SCSS variables.
|
|
27
|
-
* @param {function(value): Boolean} args.dictionary.usesReference - Use this function to see if a token's value uses a reference. This is the same function style dictionary uses internally to detect a reference.
|
|
28
|
-
* @param {function(value): Value} args.dictionary.getReferences - Use this function to get the tokens that it references. You can use this to output a reference in your custom format. For example: `dictionary.getReferences(token.original.value) // returns an array of the referenced token objects`
|
|
29
27
|
* @param {Object} args.platform - The platform configuration this format is being called in.
|
|
30
28
|
* @param {Object} args.file - The file configuration this format is being called in.
|
|
31
29
|
* @param {Object} args.options - Merged options object that combines platform level configuration and file level configuration. File options take precedence.
|
|
@@ -12,16 +12,12 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import flattenTokens from './flattenTokens.js';
|
|
15
|
-
import getReferences from './references/getReferences.js';
|
|
16
|
-
import usesReference from './references/usesReference.js';
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
*
|
|
20
18
|
* @typedef Dictionary
|
|
21
19
|
* @property {Object} $tokens
|
|
22
20
|
* @property {Array} allTokens
|
|
23
|
-
* @property {Dictionary.getReferences} getReferences
|
|
24
|
-
* @property {Dictionary.usesReference} usesReference
|
|
25
21
|
*/
|
|
26
22
|
|
|
27
23
|
/**
|
|
@@ -35,7 +31,5 @@ export default function createDictionary({ tokens }) {
|
|
|
35
31
|
return {
|
|
36
32
|
tokens,
|
|
37
33
|
allTokens,
|
|
38
|
-
getReferences,
|
|
39
|
-
usesReference,
|
|
40
34
|
};
|
|
41
35
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import deepExtend from './deepExtend.js';
|
|
15
15
|
|
|
16
16
|
export default function createFormatArgs({ dictionary, platform, file = {} }) {
|
|
17
|
-
const { allTokens, tokens
|
|
17
|
+
const { allTokens, tokens } = dictionary;
|
|
18
18
|
// This will merge platform and file-level configuration
|
|
19
19
|
// where the file configuration takes precedence
|
|
20
20
|
const { options } = platform;
|
|
@@ -22,8 +22,6 @@ export default function createFormatArgs({ dictionary, platform, file = {} }) {
|
|
|
22
22
|
|
|
23
23
|
return {
|
|
24
24
|
dictionary,
|
|
25
|
-
usesReference,
|
|
26
|
-
getReferences,
|
|
27
25
|
allTokens,
|
|
28
26
|
tokens,
|
|
29
27
|
platform,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
|
|
5
|
+
* the License. A copy of the License is located at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
|
11
|
+
* and limitations under the License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import usesReference from './references/usesReference.js';
|
|
15
|
+
import getReferences from './references/getReferences.js';
|
|
16
|
+
import { resolveReferences } from './references/resolveReferences.js';
|
|
17
|
+
|
|
18
|
+
// Public style-dictionary/utils API
|
|
19
|
+
export { usesReference, getReferences, resolveReferences };
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import getPath from './getPathFromName.js';
|
|
15
15
|
import createReferenceRegex from './createReferenceRegex.js';
|
|
16
|
-
import
|
|
16
|
+
import getValueByPath from './getValueByPath.js';
|
|
17
17
|
import GroupMessages from '../groupMessages.js';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -27,12 +27,12 @@ import GroupMessages from '../groupMessages.js';
|
|
|
27
27
|
* ```
|
|
28
28
|
*
|
|
29
29
|
* @memberof Dictionary
|
|
30
|
+
* @param {Object} dictionary the dictionary to search in
|
|
30
31
|
* @param {string} value the value that contains a reference
|
|
31
32
|
* @param {object[]} references array of token's references because a token's value can contain multiple references due to string interpolation
|
|
32
33
|
* @returns {any}
|
|
33
34
|
*/
|
|
34
|
-
export default function getReferences(value, references = []) {
|
|
35
|
-
// `this` is the dictionary object passed to formats and actions
|
|
35
|
+
export default function getReferences(dictionary, value, references = []) {
|
|
36
36
|
const regex = createReferenceRegex({});
|
|
37
37
|
|
|
38
38
|
// this will update the references array with the referenced tokens it finds.
|
|
@@ -43,11 +43,11 @@ export default function getReferences(value, references = []) {
|
|
|
43
43
|
// Find what the value is referencing
|
|
44
44
|
const pathName = getPath(variable);
|
|
45
45
|
|
|
46
|
-
let ref =
|
|
46
|
+
let ref = getValueByPath(pathName, dictionary.tokens);
|
|
47
47
|
|
|
48
48
|
if (!ref) {
|
|
49
49
|
// fall back on _tokens as it is unfiltered
|
|
50
|
-
ref =
|
|
50
|
+
ref = getValueByPath(pathName, dictionary._tokens);
|
|
51
51
|
// and warn the user about this
|
|
52
52
|
GroupMessages.add(GroupMessages.GROUP.FilteredOutputReferences, variable);
|
|
53
53
|
}
|
|
@@ -56,7 +56,7 @@ export default function getReferences(value, references = []) {
|
|
|
56
56
|
|
|
57
57
|
if (typeof value === 'string') {
|
|
58
58
|
// function inside .replace runs multiple times if there are multiple matches
|
|
59
|
-
value.replace(regex, findReference
|
|
59
|
+
value.replace(regex, findReference);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// If the token's value is an object, run the replace reference
|
|
@@ -65,11 +65,11 @@ export default function getReferences(value, references = []) {
|
|
|
65
65
|
if (typeof value === 'object') {
|
|
66
66
|
for (const key in value) {
|
|
67
67
|
if (value.hasOwnProperty(key) && typeof value[key] === 'string') {
|
|
68
|
-
value[key].replace(regex, findReference
|
|
68
|
+
value[key].replace(regex, findReference);
|
|
69
69
|
}
|
|
70
70
|
// if it is an object, we go further down the rabbit hole
|
|
71
71
|
if (value.hasOwnProperty(key) && typeof value[key] === 'object') {
|
|
72
|
-
|
|
72
|
+
getReferences(dictionary, value[key], references);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
|
|
5
|
+
* the License. A copy of the License is located at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
+
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
|
11
|
+
* and limitations under the License.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import GroupMessages from '../groupMessages.js';
|
|
15
|
+
import getPathFromName from './getPathFromName.js';
|
|
16
|
+
import getName from './getName.js';
|
|
17
|
+
import getValueByPath from './getValueByPath.js';
|
|
18
|
+
import usesReference from './usesReference.js';
|
|
19
|
+
import createReferenceRegex from './createReferenceRegex.js';
|
|
20
|
+
import defaults from './defaults.js';
|
|
21
|
+
|
|
22
|
+
const PROPERTY_REFERENCE_WARNINGS = GroupMessages.GROUP.PropertyReferenceWarnings;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Utility to resolve references inside a string value
|
|
26
|
+
* @param {string} value
|
|
27
|
+
* @param {Object} dictionary
|
|
28
|
+
* @param {Object} opts
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function resolveReferences(
|
|
32
|
+
value,
|
|
33
|
+
dictionary,
|
|
34
|
+
{
|
|
35
|
+
regex,
|
|
36
|
+
ignorePaths = [],
|
|
37
|
+
current_context = [],
|
|
38
|
+
separator = defaults.separator,
|
|
39
|
+
opening_character = defaults.opening_character,
|
|
40
|
+
closing_character = defaults.closing_character,
|
|
41
|
+
// for internal usage
|
|
42
|
+
stack = [],
|
|
43
|
+
foundCirc = {},
|
|
44
|
+
firstIteration = true,
|
|
45
|
+
} = {},
|
|
46
|
+
) {
|
|
47
|
+
const reg = regex ?? createReferenceRegex({ opening_character, closing_character, separator });
|
|
48
|
+
let to_ret = value;
|
|
49
|
+
let ref;
|
|
50
|
+
|
|
51
|
+
// When we know the current context:
|
|
52
|
+
// the key associated with the value that we are resolving the reference for
|
|
53
|
+
// Then we can push this to the stack to improve our circular reference warnings
|
|
54
|
+
// by starting them with the key
|
|
55
|
+
if (firstIteration && current_context.length > 0) {
|
|
56
|
+
stack.push(getName(current_context));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Replace the reference inline, but don't replace the whole string because
|
|
60
|
+
// references can be part of the value such as "1px solid {color.border.light}"
|
|
61
|
+
value.replace(reg, function (match, variable) {
|
|
62
|
+
variable = variable.trim();
|
|
63
|
+
|
|
64
|
+
// Find what the value is referencing
|
|
65
|
+
const pathName = getPathFromName(variable, { separator });
|
|
66
|
+
|
|
67
|
+
const refHasValue = pathName[pathName.length - 1] === 'value';
|
|
68
|
+
|
|
69
|
+
if (refHasValue && ignorePaths.indexOf(variable) !== -1) {
|
|
70
|
+
return value;
|
|
71
|
+
} else if (!refHasValue && ignorePaths.indexOf(`${variable}.value`) !== -1) {
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
stack.push(variable);
|
|
76
|
+
|
|
77
|
+
ref = getValueByPath(pathName, dictionary);
|
|
78
|
+
|
|
79
|
+
// If the reference doesn't end in 'value'
|
|
80
|
+
// and
|
|
81
|
+
// the reference points to someplace that has a `value` attribute
|
|
82
|
+
// we should take the '.value' of the reference
|
|
83
|
+
// per the W3C draft spec where references do not have .value
|
|
84
|
+
// https://design-tokens.github.io/community-group/format/#aliases-references
|
|
85
|
+
if (!refHasValue && ref && ref.hasOwnProperty('value')) {
|
|
86
|
+
ref = ref.value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (typeof ref !== 'undefined') {
|
|
90
|
+
if (typeof ref === 'string' || typeof ref === 'number') {
|
|
91
|
+
to_ret = value.replace(match, ref);
|
|
92
|
+
|
|
93
|
+
// Recursive, therefore we can compute multi-layer variables like a = b, b = c, eventually a = c
|
|
94
|
+
if (usesReference(to_ret, reg)) {
|
|
95
|
+
const reference = to_ret.slice(1, -1);
|
|
96
|
+
|
|
97
|
+
// Compare to found circular references
|
|
98
|
+
if (foundCirc.hasOwnProperty(reference)) {
|
|
99
|
+
// If the current reference is a member of a circular reference, do nothing
|
|
100
|
+
} else if (stack.indexOf(reference) !== -1) {
|
|
101
|
+
// If the current stack already contains the current reference, we found a new circular reference
|
|
102
|
+
// chop down only the circular part, save it to our circular reference info, and spit out an error
|
|
103
|
+
|
|
104
|
+
// Get the position of the existing reference in the stack
|
|
105
|
+
const stackIndexReference = stack.indexOf(reference);
|
|
106
|
+
|
|
107
|
+
// Get the portion of the stack that starts at the circular reference and brings you through until the end
|
|
108
|
+
const circStack = stack.slice(stackIndexReference);
|
|
109
|
+
|
|
110
|
+
// For all the references in this list, add them to the list of references that end up in a circular reference
|
|
111
|
+
circStack.forEach(function (key) {
|
|
112
|
+
foundCirc[key] = true;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Add our found circular reference to the end of the cycle
|
|
116
|
+
circStack.push(reference);
|
|
117
|
+
|
|
118
|
+
// Add circ reference info to our list of warning messages
|
|
119
|
+
GroupMessages.add(
|
|
120
|
+
PROPERTY_REFERENCE_WARNINGS,
|
|
121
|
+
'Circular definition cycle: ' + circStack.join(', '),
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
to_ret = resolveReferences(to_ret, dictionary, {
|
|
125
|
+
regex: reg,
|
|
126
|
+
ignorePaths,
|
|
127
|
+
current_context,
|
|
128
|
+
separator,
|
|
129
|
+
stack,
|
|
130
|
+
foundCirc,
|
|
131
|
+
firstIteration: false,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// if evaluated value is a number and equal to the reference, we want to keep the type
|
|
136
|
+
if (typeof ref === 'number' && ref.toString() === to_ret) {
|
|
137
|
+
to_ret = ref;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// if evaluated value is not a string or number, we want to keep the type
|
|
141
|
+
to_ret = ref;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// User might have passed current_context option which is path (arr) pointing to key
|
|
145
|
+
// that this value is associated with, helpful for debugging
|
|
146
|
+
const context = getName(current_context, { separator });
|
|
147
|
+
GroupMessages.add(
|
|
148
|
+
PROPERTY_REFERENCE_WARNINGS,
|
|
149
|
+
`Reference doesn't exist:${
|
|
150
|
+
context ? ` ${context}` : ''
|
|
151
|
+
} tries to reference ${variable}, which is not defined`,
|
|
152
|
+
);
|
|
153
|
+
to_ret = ref;
|
|
154
|
+
}
|
|
155
|
+
stack.pop(variable);
|
|
156
|
+
|
|
157
|
+
return to_ret;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return to_ret;
|
|
161
|
+
}
|
|
@@ -11,162 +11,61 @@
|
|
|
11
11
|
* and limitations under the License.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import GroupMessages from './groupMessages.js';
|
|
15
|
-
import usesReference from './references/usesReference.js';
|
|
16
|
-
import getName from './references/getName.js';
|
|
17
|
-
import getPath from './references/getPathFromName.js';
|
|
18
14
|
import createReferenceRegex from './references/createReferenceRegex.js';
|
|
19
|
-
import
|
|
15
|
+
import { resolveReferences } from './references/resolveReferences.js';
|
|
20
16
|
|
|
21
|
-
const PROPERTY_REFERENCE_WARNINGS = GroupMessages.GROUP.PropertyReferenceWarnings;
|
|
22
|
-
|
|
23
|
-
let current_context = []; // To maintain the context to be able to test for circular definitions
|
|
24
17
|
const defaults = {
|
|
25
18
|
ignoreKeys: ['original'],
|
|
26
|
-
ignorePaths: [],
|
|
27
19
|
};
|
|
28
|
-
let updated_object, regex, options;
|
|
29
|
-
|
|
30
|
-
export default function resolveObject(object, opts) {
|
|
31
|
-
options = Object.assign({}, defaults, opts);
|
|
32
20
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
21
|
+
export default function resolveObject(object, _opts = {}) {
|
|
22
|
+
const foundCirc = {};
|
|
23
|
+
const opts = { ...defaults, ..._opts };
|
|
24
|
+
const current_context = [];
|
|
25
|
+
const clone = structuredClone(object); // This object will be edited
|
|
26
|
+
opts.regex = createReferenceRegex(opts);
|
|
36
27
|
|
|
37
28
|
if (typeof object === 'object') {
|
|
38
|
-
current_context
|
|
39
|
-
return traverseObj(updated_object);
|
|
29
|
+
return traverseObj(clone, clone, opts, current_context, foundCirc);
|
|
40
30
|
} else {
|
|
41
31
|
throw new Error('Please pass an object in');
|
|
42
32
|
}
|
|
43
33
|
}
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
35
|
+
/**
|
|
36
|
+
* @param {Object} slice - slice within the full object
|
|
37
|
+
* @param {Object} fullObj - the full object
|
|
38
|
+
* @param {Object} opts - options such as regex, ignoreKeys, ignorePaths, etc.
|
|
39
|
+
* @param {string[]} current_context - keeping track of the token group context that we're in
|
|
40
|
+
*/
|
|
41
|
+
function traverseObj(slice, fullObj, opts, current_context, foundCirc) {
|
|
42
|
+
for (const key in slice) {
|
|
43
|
+
if (!slice.hasOwnProperty(key)) {
|
|
50
44
|
continue;
|
|
51
45
|
}
|
|
46
|
+
const value = slice[key];
|
|
52
47
|
|
|
53
48
|
// We want to check for ignoredKeys, this is to
|
|
54
49
|
// skip over attributes that should not be
|
|
55
50
|
// mutated, like a copy of the original property.
|
|
56
|
-
if (
|
|
51
|
+
if (opts.ignoreKeys && opts.ignoreKeys.indexOf(key) !== -1) {
|
|
57
52
|
continue;
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
current_context.push(key);
|
|
61
|
-
if (typeof
|
|
62
|
-
traverseObj(
|
|
56
|
+
if (typeof value === 'object') {
|
|
57
|
+
traverseObj(value, fullObj, opts, current_context, foundCirc);
|
|
63
58
|
} else {
|
|
64
|
-
if (typeof
|
|
65
|
-
|
|
59
|
+
if (typeof value === 'string' && value.indexOf('{') > -1) {
|
|
60
|
+
slice[key] = resolveReferences(value, fullObj, {
|
|
61
|
+
...opts,
|
|
62
|
+
current_context,
|
|
63
|
+
foundCirc,
|
|
64
|
+
});
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
current_context.pop();
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let foundCirc = {};
|
|
75
|
-
function compile_value(value, stack) {
|
|
76
|
-
let to_ret = value,
|
|
77
|
-
ref;
|
|
78
|
-
|
|
79
|
-
// Replace the reference inline, but don't replace the whole string because
|
|
80
|
-
// references can be part of the value such as "1px solid {color.border.light}"
|
|
81
|
-
value.replace(regex, function (match, variable) {
|
|
82
|
-
variable = variable.trim();
|
|
83
|
-
|
|
84
|
-
// Find what the value is referencing
|
|
85
|
-
const pathName = getPath(variable, options);
|
|
86
|
-
const context = getName(current_context, options);
|
|
87
|
-
const refHasValue = pathName[pathName.length - 1] === 'value';
|
|
88
|
-
|
|
89
|
-
if (refHasValue && options.ignorePaths.indexOf(variable) !== -1) {
|
|
90
|
-
return value;
|
|
91
|
-
} else if (!refHasValue && options.ignorePaths.indexOf(`${variable}.value`) !== -1) {
|
|
92
|
-
return value;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
stack.push(variable);
|
|
96
|
-
|
|
97
|
-
ref = resolveReference(pathName, updated_object);
|
|
98
|
-
|
|
99
|
-
// If the reference doesn't end in 'value'
|
|
100
|
-
// and
|
|
101
|
-
// the reference points to someplace that has a `value` attribute
|
|
102
|
-
// we should take the '.value' of the reference
|
|
103
|
-
// per the W3C draft spec where references do not have .value
|
|
104
|
-
// https://design-tokens.github.io/community-group/format/#aliases-references
|
|
105
|
-
if (!refHasValue && ref && ref.hasOwnProperty('value')) {
|
|
106
|
-
ref = ref.value;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (typeof ref !== 'undefined') {
|
|
110
|
-
if (typeof ref === 'string' || typeof ref === 'number') {
|
|
111
|
-
to_ret = value.replace(match, ref);
|
|
112
|
-
|
|
113
|
-
// Recursive, therefore we can compute multi-layer variables like a = b, b = c, eventually a = c
|
|
114
|
-
if (usesReference(to_ret, regex)) {
|
|
115
|
-
const reference = to_ret.slice(1, -1);
|
|
116
|
-
|
|
117
|
-
// Compare to found circular references
|
|
118
|
-
if (foundCirc.hasOwnProperty(reference)) {
|
|
119
|
-
// If the current reference is a member of a circular reference, do nothing
|
|
120
|
-
} else if (stack.indexOf(reference) !== -1) {
|
|
121
|
-
// If the current stack already contains the current reference, we found a new circular reference
|
|
122
|
-
// chop down only the circular part, save it to our circular reference info, and spit out an error
|
|
123
|
-
|
|
124
|
-
// Get the position of the existing reference in the stack
|
|
125
|
-
const stackIndexReference = stack.indexOf(reference);
|
|
126
|
-
|
|
127
|
-
// Get the portion of the stack that starts at the circular reference and brings you through until the end
|
|
128
|
-
const circStack = stack.slice(stackIndexReference);
|
|
129
|
-
|
|
130
|
-
// For all the references in this list, add them to the list of references that end up in a circular reference
|
|
131
|
-
circStack.forEach(function (key) {
|
|
132
|
-
foundCirc[key] = true;
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Add our found circular reference to the end of the cycle
|
|
136
|
-
circStack.push(reference);
|
|
137
|
-
|
|
138
|
-
// Add circ reference info to our list of warning messages
|
|
139
|
-
GroupMessages.add(
|
|
140
|
-
PROPERTY_REFERENCE_WARNINGS,
|
|
141
|
-
'Circular definition cycle: ' + circStack.join(', '),
|
|
142
|
-
);
|
|
143
|
-
} else {
|
|
144
|
-
to_ret = compile_value(to_ret, stack);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
// if evaluated value is a number and equal to the reference, we want to keep the type
|
|
148
|
-
if (typeof ref === 'number' && ref.toString() === to_ret) {
|
|
149
|
-
to_ret = ref;
|
|
150
|
-
}
|
|
151
|
-
} else {
|
|
152
|
-
// if evaluated value is not a string or number, we want to keep the type
|
|
153
|
-
to_ret = ref;
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
GroupMessages.add(
|
|
157
|
-
PROPERTY_REFERENCE_WARNINGS,
|
|
158
|
-
"Reference doesn't exist: " +
|
|
159
|
-
context +
|
|
160
|
-
' tries to reference ' +
|
|
161
|
-
variable +
|
|
162
|
-
', which is not defined',
|
|
163
|
-
);
|
|
164
|
-
to_ret = ref;
|
|
165
|
-
}
|
|
166
|
-
stack.pop(variable);
|
|
167
|
-
|
|
168
|
-
return to_ret;
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
return to_ret;
|
|
70
|
+
return fullObj;
|
|
172
71
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "style-dictionary",
|
|
3
|
-
"version": "4.0.0-prerelease.
|
|
3
|
+
"version": "4.0.0-prerelease.4",
|
|
4
4
|
"description": "Style once, use everywhere. A build system for creating cross-platform styles.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"style dictionary",
|
|
@@ -27,10 +27,11 @@
|
|
|
27
27
|
"./fs": {
|
|
28
28
|
"node": "./fs-node.js",
|
|
29
29
|
"default": "./fs.js"
|
|
30
|
-
}
|
|
30
|
+
},
|
|
31
|
+
"./utils": "./lib/utils/index.js"
|
|
31
32
|
},
|
|
32
33
|
"bin": {
|
|
33
|
-
"style-dictionary": "./bin/style-dictionary"
|
|
34
|
+
"style-dictionary": "./bin/style-dictionary.js"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
36
37
|
"node": ">=18.0.0"
|
|
@@ -40,8 +41,7 @@
|
|
|
40
41
|
"lib",
|
|
41
42
|
"examples",
|
|
42
43
|
"fs.js",
|
|
43
|
-
"
|
|
44
|
-
"index-node.js",
|
|
44
|
+
"fs-node.js",
|
|
45
45
|
"LICENSE",
|
|
46
46
|
"NOTICE",
|
|
47
47
|
"types"
|
package/types/Dictionary.d.ts
CHANGED
|
@@ -16,6 +16,4 @@ import { TransformedToken, TransformedTokens } from './TransformedToken';
|
|
|
16
16
|
export interface Dictionary {
|
|
17
17
|
allTokens: TransformedToken[];
|
|
18
18
|
tokens: TransformedTokens;
|
|
19
|
-
usesReference: (value: any) => boolean;
|
|
20
|
-
getReferences: (value: any) => TransformedToken[];
|
|
21
19
|
}
|
package/types/index.d.ts
CHANGED