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.
@@ -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 onto the internal dictionary object that is passed to formats and actions: `.usesReference()` and `.resolveReference()`. Also, it uses a new configuration on some formats: `keepReferences: true` to include variable references in the output.
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 (dictionary.usesReference(token.original.value)) {
36
- const refs = dictionary.getReferences(token.original.value);
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 `keepReferences` configuration.
53
- - `build/tokens.scss` This file is generated using the `scss/variables` built-in format with the new `keepReferences` configuration.
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
@@ -0,0 +1,6 @@
1
+ import _fs from 'node:fs';
2
+ import { setFs, fs } from './fs.js';
3
+
4
+ setFs(_fs);
5
+
6
+ export { fs, setFs };
@@ -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.2';
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 && dictionary.usesReference(prop.original.value)) {
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 = dictionary.getReferences(prop.original.value);
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 && dictionary.usesReference(a.original.value)) {
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 && dictionary.usesReference(b.original.value)) {
45
- const aRefs = dictionary.getReferences(a.original.value);
46
- const bRefs = dictionary.getReferences(b.original.value);
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
@@ -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
- const template = _template(androidResources);
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
- export default `<?xml version="1.0" encoding="UTF-8"?>
2
- <%
3
- //
4
- // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5
- //
6
- // Licensed under the Apache License, Version 2.0 (the "License").
7
- // You may not use this file except in compliance with the License.
8
- // A copy of the License is located at
9
- //
10
- // http://www.apache.org/licenses/LICENSE-2.0
11
- //
12
- // or in the "license" file accompanying this file. This file is distributed
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
- function propToType(prop) {
29
- if (resourceType) {
30
- return resourceType;
31
- }
32
- if (resourceMap[prop.attributes.category]) {
33
- return resourceMap[prop.attributes.category];
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
- return 'string';
36
- }
37
-
38
- function propToValue(prop) {
39
- if (file.options && file.options.outputReferences && dictionary.usesReference(prop.original.value)) {
40
- return \`@\${propToType(prop)}/\${dictionary.getReferences(prop.original.value)[0].name}\`;
41
- } else {
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
- <%= fileHeader({file, commentStyle: 'xml'}) %>
47
+
48
+ return `<?xml version="1.0" encoding="UTF-8"?>
49
+
50
+ ${fileHeader({ file, commentStyle: 'xml' })}
46
51
  <resources>
47
- <% dictionary.allTokens.forEach(function(prop) {
48
- %><<%= propToType(prop) %> name="<%= prop.name %>"><%= propToValue(prop) %></<%= propToType(prop) %>><% if (prop.comment) { %><!-- <%= prop.comment %> --><% } %>
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
+ };
@@ -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, usesReference, getReferences } = dictionary;
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 resolveReference from './resolveReference.js';
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 = resolveReference(pathName, this.tokens);
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 = resolveReference(pathName, this._tokens);
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.bind(this));
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.bind(this));
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
- this.getReferences(value[key], references);
72
+ getReferences(dictionary, value[key], references);
73
73
  }
74
74
  }
75
75
  }
@@ -11,7 +11,7 @@
11
11
  * and limitations under the License.
12
12
  */
13
13
 
14
- export default function resolveReference(path, obj) {
14
+ export default function getValueByPath(path, obj) {
15
15
  let ref = obj;
16
16
 
17
17
  if (!Array.isArray(path)) {
@@ -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 resolveReference from './references/resolveReference.js';
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
- updated_object = structuredClone(object); // This object will be edited
34
-
35
- regex = createReferenceRegex(options);
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
- function traverseObj(obj) {
46
- let key;
47
-
48
- for (key in obj) {
49
- if (!obj.hasOwnProperty(key)) {
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 (options.ignoreKeys && options.ignoreKeys.indexOf(key) !== -1) {
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 obj[key] === 'object') {
62
- traverseObj(obj[key]);
56
+ if (typeof value === 'object') {
57
+ traverseObj(value, fullObj, opts, current_context, foundCirc);
63
58
  } else {
64
- if (typeof obj[key] === 'string' && obj[key].indexOf('{') > -1) {
65
- obj[key] = compile_value(obj[key], [getName(current_context)]);
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 obj;
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.2",
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
- "index.js",
44
- "index-node.js",
44
+ "fs-node.js",
45
45
  "LICENSE",
46
46
  "NOTICE",
47
47
  "types"
@@ -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
@@ -354,5 +354,5 @@ declare namespace StyleDictionary {
354
354
  }
355
355
  }
356
356
 
357
- declare var StyleDictionary: StyleDictionary.Core;
358
- export = StyleDictionary;
357
+ declare const StyleDictionary: StyleDictionary.Core;
358
+ export default StyleDictionary;