@terrazzo/parser 0.3.5 → 0.5.0

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +41 -11
  2. package/dist/build/index.d.ts +1 -0
  3. package/dist/build/index.d.ts.map +1 -0
  4. package/dist/build/index.js +11 -10
  5. package/dist/build/index.js.map +1 -1
  6. package/dist/config.d.ts +1 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +42 -21
  9. package/dist/config.js.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/lib/code-frame.d.ts +1 -0
  13. package/dist/lib/code-frame.d.ts.map +1 -0
  14. package/dist/lint/index.d.ts +1 -0
  15. package/dist/lint/index.d.ts.map +1 -0
  16. package/dist/lint/index.js +8 -5
  17. package/dist/lint/index.js.map +1 -1
  18. package/dist/lint/plugin-core/index.d.ts +1 -0
  19. package/dist/lint/plugin-core/index.d.ts.map +1 -0
  20. package/dist/lint/plugin-core/lib/docs.d.ts +1 -0
  21. package/dist/lint/plugin-core/lib/docs.d.ts.map +1 -0
  22. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +1 -0
  23. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts.map +1 -0
  24. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +1 -0
  25. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts.map +1 -0
  26. package/dist/lint/plugin-core/rules/colorspace.d.ts +1 -0
  27. package/dist/lint/plugin-core/rules/colorspace.d.ts.map +1 -0
  28. package/dist/lint/plugin-core/rules/consistent-naming.d.ts +1 -0
  29. package/dist/lint/plugin-core/rules/consistent-naming.d.ts.map +1 -0
  30. package/dist/lint/plugin-core/rules/descriptions.d.ts +1 -0
  31. package/dist/lint/plugin-core/rules/descriptions.d.ts.map +1 -0
  32. package/dist/lint/plugin-core/rules/duplicate-values.d.ts +1 -0
  33. package/dist/lint/plugin-core/rules/duplicate-values.d.ts.map +1 -0
  34. package/dist/lint/plugin-core/rules/max-gamut.d.ts +1 -0
  35. package/dist/lint/plugin-core/rules/max-gamut.d.ts.map +1 -0
  36. package/dist/lint/plugin-core/rules/required-children.d.ts +1 -0
  37. package/dist/lint/plugin-core/rules/required-children.d.ts.map +1 -0
  38. package/dist/lint/plugin-core/rules/required-modes.d.ts +1 -0
  39. package/dist/lint/plugin-core/rules/required-modes.d.ts.map +1 -0
  40. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +1 -0
  41. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts.map +1 -0
  42. package/dist/logger.d.ts +4 -3
  43. package/dist/logger.d.ts.map +1 -0
  44. package/dist/logger.js +25 -14
  45. package/dist/logger.js.map +1 -1
  46. package/dist/parse/alias.d.ts +13 -3
  47. package/dist/parse/alias.d.ts.map +1 -0
  48. package/dist/parse/alias.js +94 -43
  49. package/dist/parse/alias.js.map +1 -1
  50. package/dist/parse/index.d.ts +1 -0
  51. package/dist/parse/index.d.ts.map +1 -0
  52. package/dist/parse/index.js +58 -25
  53. package/dist/parse/index.js.map +1 -1
  54. package/dist/parse/json.d.ts +3 -3
  55. package/dist/parse/json.d.ts.map +1 -0
  56. package/dist/parse/json.js +5 -7
  57. package/dist/parse/json.js.map +1 -1
  58. package/dist/parse/normalize.d.ts +1 -0
  59. package/dist/parse/normalize.d.ts.map +1 -0
  60. package/dist/parse/normalize.js +8 -6
  61. package/dist/parse/normalize.js.map +1 -1
  62. package/dist/parse/validate.d.ts +1 -0
  63. package/dist/parse/validate.d.ts.map +1 -0
  64. package/dist/parse/validate.js +194 -110
  65. package/dist/parse/validate.js.map +1 -1
  66. package/dist/types.d.ts +1 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/package.json +2 -2
  69. package/src/build/index.ts +11 -10
  70. package/src/config.ts +42 -22
  71. package/src/lint/index.ts +8 -6
  72. package/src/logger.ts +30 -20
  73. package/src/parse/alias.ts +114 -45
  74. package/src/parse/index.ts +59 -28
  75. package/src/parse/json.ts +6 -8
  76. package/src/parse/normalize.ts +8 -6
  77. package/src/parse/validate.ts +204 -110
@@ -34,7 +34,12 @@ export const COMPOSITE_TYPE_VALUES = {
34
34
  },
35
35
  };
36
36
 
37
- /** Resolve alias */
37
+ /**
38
+ * Resolve alias
39
+ * Note: to save work during resolution (and reduce memory), this will inject
40
+ * `aliasedBy` while it traverses. Resolution isn’t normally associated with
41
+ * mutation, but for our usecase it is preferable.
42
+ */
38
43
  export function resolveAlias(
39
44
  alias: string,
40
45
  {
@@ -52,19 +57,34 @@ export function resolveAlias(
52
57
  node: AnyNode;
53
58
  scanned?: string[];
54
59
  },
55
- ) {
60
+ ): { id: string; chain: string[] } {
56
61
  const { id } = parseAlias(alias);
57
62
  if (!tokens[id]) {
58
- logger.error({ message: `Alias "${alias}" not found.`, filename, src, node });
63
+ logger.error({ group: 'parser', label: 'alias', message: `Alias "${alias}" not found.`, filename, src, node });
59
64
  }
60
65
  if (scanned.includes(id)) {
61
- logger.error({ message: `Circular alias detected from "${alias}".`, filename, src, node });
66
+ logger.error({
67
+ group: 'parser',
68
+ label: 'alias',
69
+ message: `Circular alias detected from "${alias}".`,
70
+ filename,
71
+ src,
72
+ node,
73
+ });
62
74
  }
63
75
  const token = tokens[id]!;
64
- if (!isAlias(token.$value)) {
65
- return id;
76
+ // important: use originalValue to trace the full alias path correctly
77
+ if (!isAlias(token.originalValue.$value)) {
78
+ return { id, chain: scanned.concat(id) };
66
79
  }
67
- return resolveAlias(token.$value as string, { tokens, logger, filename, node, src, scanned: [...scanned, id] });
80
+ return resolveAlias(token.originalValue.$value as string, {
81
+ tokens,
82
+ logger,
83
+ filename,
84
+ node,
85
+ src,
86
+ scanned: scanned.concat(id),
87
+ });
68
88
  }
69
89
 
70
90
  /** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
@@ -76,8 +96,37 @@ export function applyAliases(
76
96
  filename,
77
97
  src,
78
98
  node,
79
- }: { tokens: Record<string, TokenNormalized>; logger: Logger; filename?: URL; src: string; node: AnyNode },
99
+ skipReverseAlias,
100
+ }: {
101
+ tokens: Record<string, TokenNormalized>;
102
+ logger: Logger;
103
+ filename?: URL;
104
+ src: string;
105
+ node: AnyNode;
106
+ skipReverseAlias?: boolean;
107
+ },
80
108
  ) {
109
+ /**
110
+ * Add reverse alias lookups. Note this intentionally mutates the root
111
+ * references in `tokens` to save work. This is essential to doing everything
112
+ * in one pass while still being accurate.
113
+ */
114
+ function addReverseAliases(resolvedID: string, ids: string[]) {
115
+ if (skipReverseAlias || !tokens[resolvedID]) {
116
+ return;
117
+ }
118
+
119
+ // populate entire chain of aliases, so we can redeclare tokens when needed
120
+ if (!tokens[resolvedID]!.aliasedBy) {
121
+ tokens[resolvedID]!.aliasedBy = [];
122
+ }
123
+ for (const link of [token.id, ...ids]) {
124
+ if (link !== resolvedID && !tokens[resolvedID]!.aliasedBy!.includes(link)) {
125
+ tokens[resolvedID]!.aliasedBy.push(link);
126
+ }
127
+ }
128
+ }
129
+
81
130
  const $valueNode =
82
131
  (node.type === 'Object' && node.members.find((m) => (m.name as StringNode).value === '$value')?.value) || node;
83
132
  const expectedAliasTypes =
@@ -85,29 +134,28 @@ export function applyAliases(
85
134
 
86
135
  // handle simple aliases
87
136
  if (isAlias(token.$value)) {
88
- const aliasOfID = resolveAlias(token.$value as string, { tokens, logger, filename, node, src });
137
+ const { id: deepAliasID, chain } = resolveAlias(token.$value as string, { tokens, logger, filename, node, src });
89
138
  const { mode: aliasMode } = parseAlias(token.$value as string);
90
- const aliasOf = tokens[aliasOfID]!;
91
- token.aliasOf = aliasOfID;
92
- token.$value = aliasOf!.mode[aliasMode!]?.$value || aliasOf.$value;
93
- if (token.$type && token.$type !== aliasOf.$type) {
139
+ const resolvedToken = tokens[deepAliasID]!;
140
+
141
+ // resolve value in root token, and add aliasOf
142
+ token.aliasOf = deepAliasID;
143
+ token.aliasChain = chain;
144
+ token.$value = resolvedToken!.mode[aliasMode!]?.$value || resolvedToken.$value;
145
+
146
+ addReverseAliases(deepAliasID, chain);
147
+
148
+ if (token.$type && token.$type !== resolvedToken.$type) {
94
149
  logger.error({
95
- message: `Invalid alias: expected $type: "${token.$type}", received $type: "${aliasOf.$type}".`,
150
+ group: 'parser',
151
+ label: 'alias',
152
+ message: `Invalid alias: expected $type: "${token.$type}", received $type: "${resolvedToken.$type}".`,
96
153
  node: $valueNode,
97
154
  filename,
98
155
  src,
99
156
  });
100
157
  }
101
- token.$type = aliasOf.$type;
102
-
103
- // also update aliased token’s .aliasedBy value
104
- if (!tokens[aliasOfID]!.aliasedBy) {
105
- tokens[aliasOfID]!.aliasedBy = [];
106
- }
107
- if (!tokens[aliasOfID]!.aliasedBy.includes(token.id)) {
108
- tokens[aliasOfID]!.aliasedBy.push(token.id);
109
- tokens[aliasOfID]!.aliasedBy.sort((a, b) => a.localeCompare(b, 'en-us', { numeric: true })); // sort to improve downstream plugins’ determinism
110
- }
158
+ token.$type = resolvedToken.$type;
111
159
  }
112
160
 
113
161
  // handle aliases within array values (e.g. cubicBezier, gradient)
@@ -120,12 +168,12 @@ export function applyAliases(
120
168
  token.partialAliasOf = [];
121
169
  }
122
170
  // @ts-ignore
123
- const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, filename, node, src });
171
+ const { id: deepAliasID } = resolveAlias(token.$value[i], { tokens, logger, filename, node, src });
124
172
  // @ts-ignore
125
173
  const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[i]);
126
174
  token.partialAliasOf![i] = aliasID;
127
175
  // @ts-ignore
128
- token.$value[i] = (aliasMode && tokens[aliasOfID].mode[aliasMode]?.$value) || tokens[aliasOfID].$value;
176
+ token.$value[i] = (aliasMode && tokens[deepAliasID].mode[aliasMode]?.$value) || tokens[deepAliasID].$value;
129
177
  } else if (typeof token.$value[i] === 'object') {
130
178
  for (const [property, subvalue] of Object.entries(token.$value[i]!)) {
131
179
  if (isAlias(subvalue)) {
@@ -135,14 +183,19 @@ export function applyAliases(
135
183
  if (!token.partialAliasOf[i]) {
136
184
  token.partialAliasOf[i] = {};
137
185
  }
138
- const aliasOfID = resolveAlias(subvalue, { tokens, logger, filename, node, src });
139
- const { id: aliasID, mode: aliasMode } = parseAlias(subvalue);
140
- const aliasToken = tokens[aliasOfID]!;
186
+ const { id: deepAliasID, chain } = resolveAlias(subvalue, { tokens, logger, filename, node, src });
187
+ const { id: shallowAliasID, mode: aliasMode } = parseAlias(subvalue);
188
+ const resolvedToken = tokens[deepAliasID]!;
189
+
190
+ addReverseAliases(deepAliasID, chain);
191
+
141
192
  const possibleTypes: string[] = expectedAliasTypes?.[property as keyof typeof expectedAliasTypes] || [];
142
- if (possibleTypes.length && !possibleTypes.includes(aliasToken.$type)) {
193
+ if (possibleTypes.length && !possibleTypes.includes(resolvedToken.$type)) {
143
194
  const elementNode = ($valueNode as ArrayNode).elements[i]!.value;
144
195
  logger.error({
145
- message: `Invalid alias: expected $type: "${possibleTypes.join('" or "')}", received $type: "${aliasToken.$type}".`,
196
+ group: 'parser',
197
+ label: 'alias',
198
+ message: `Invalid alias: expected $type: "${possibleTypes.join('" or "')}", received $type: "${resolvedToken.$type}".`,
146
199
  node: (elementNode as ObjectNode).members.find((m) => (m.name as StringNode).value === property)!.value,
147
200
  filename,
148
201
  src,
@@ -150,9 +203,9 @@ export function applyAliases(
150
203
  }
151
204
 
152
205
  // @ts-ignore
153
- token.partialAliasOf[i][property] = aliasID; // also keep the shallow alias here, too!
206
+ token.partialAliasOf[i][property] = shallowAliasID; // also keep the shallow alias here, too!
154
207
  // @ts-ignore
155
- token.$value[i][property] = (aliasMode && aliasToken.mode[aliasMode]?.$value) || aliasToken.$value;
208
+ token.$value[i][property] = (aliasMode && resolvedToken.mode[aliasMode]?.$value) || resolvedToken.$value;
156
209
  }
157
210
  }
158
211
  }
@@ -169,16 +222,21 @@ export function applyAliases(
169
222
  if (!token.partialAliasOf) {
170
223
  token.partialAliasOf = {};
171
224
  }
172
- const aliasOfID = resolveAlias(subvalue, { tokens, logger, filename, node, src });
173
- const { id: aliasID, mode: aliasMode } = parseAlias(subvalue);
225
+ const { id: deepAliasID, chain } = resolveAlias(subvalue, { tokens, logger, filename, node, src });
226
+ const { id: shallowAliasID, mode: aliasMode } = parseAlias(subvalue);
227
+
228
+ addReverseAliases(deepAliasID, chain);
229
+
174
230
  // @ts-ignore
175
- token.partialAliasOf[property] = aliasID; // keep the shallow alias!
176
- const aliasToken = tokens[aliasOfID];
231
+ token.partialAliasOf[property] = shallowAliasID; // keep the shallow alias!
232
+ const resolvedToken = tokens[deepAliasID];
177
233
  // @ts-ignore
178
- if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken!.$type)) {
234
+ if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(resolvedToken!.$type)) {
179
235
  logger.error({
236
+ group: 'parser',
237
+ label: 'alias',
180
238
  // @ts-ignore
181
- message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
239
+ message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${resolvedToken.$type}".`,
182
240
  // @ts-ignore
183
241
  node: $valueNode.members.find((m) => m.name.value === property).value,
184
242
  filename,
@@ -186,7 +244,7 @@ export function applyAliases(
186
244
  });
187
245
  }
188
246
  // @ts-ignore
189
- token.$value[property] = aliasToken.mode[aliasMode]?.$value || aliasToken.$value;
247
+ token.$value[property] = resolvedToken.mode[aliasMode]?.$value || resolvedToken.$value;
190
248
  }
191
249
 
192
250
  // strokeStyle has an array within an object
@@ -197,7 +255,16 @@ export function applyAliases(
197
255
  // @ts-ignore
198
256
  if (isAlias(token.$value[property][i])) {
199
257
  // @ts-ignore
200
- const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, filename, node, src });
258
+ const { id: deepAliasID, chain } = resolveAlias(token.$value[property][i], {
259
+ tokens,
260
+ logger,
261
+ filename,
262
+ node,
263
+ src,
264
+ });
265
+
266
+ addReverseAliases(deepAliasID, chain);
267
+
201
268
  if (!token.partialAliasOf) {
202
269
  token.partialAliasOf = {};
203
270
  }
@@ -210,21 +277,23 @@ export function applyAliases(
210
277
  const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[property][i]);
211
278
  // @ts-ignore
212
279
  token.partialAliasOf[property][i] = aliasID; // keep the shallow alias!
213
- const aliasToken = tokens[aliasOfID];
280
+ const resolvedToken = tokens[deepAliasID];
214
281
  // @ts-ignore
215
- if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
282
+ if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(resolvedToken.$type)) {
216
283
  // @ts-ignore
217
284
  const arrayNode = $valueNode.members.find((m) => m.name.value === property).value;
218
285
  logger.error({
286
+ group: 'parser',
287
+ label: 'alias',
219
288
  // @ts-ignore
220
- message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
289
+ message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${resolvedToken.$type}".`,
221
290
  node: arrayNode.elements[i],
222
291
  filename,
223
292
  src,
224
293
  });
225
294
  }
226
295
  // @ts-ignore
227
- token.$value[property][i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
296
+ token.$value[property][i] = tokens[deepAliasID].mode[aliasMode]?.$value || tokens[deepAliasID].$value;
228
297
  }
229
298
  }
230
299
  }
@@ -105,6 +105,8 @@ export default async function parse(
105
105
  const totalStart = performance.now();
106
106
 
107
107
  // 5. Resolve aliases and populate groups
108
+ const aliasesStart = performance.now();
109
+ let aliasCount = 0;
108
110
  for (const [id, token] of Object.entries(tokens)) {
109
111
  if (!Object.hasOwn(tokens, id)) {
110
112
  continue;
@@ -116,13 +118,21 @@ export default async function parse(
116
118
  node: token.source.node,
117
119
  logger,
118
120
  });
121
+ token.mode['.']!.$type = token.$type;
119
122
  token.mode['.']!.$value = token.$value;
120
123
  if (token.aliasOf) {
121
124
  token.mode['.']!.aliasOf = token.aliasOf;
122
125
  }
126
+ if (token.aliasChain) {
127
+ token.mode['.']!.aliasChain = token.aliasChain;
128
+ }
129
+ if (token.aliasedBy) {
130
+ token.mode['.']!.aliasedBy = token.aliasedBy;
131
+ }
123
132
  if (token.partialAliasOf) {
124
133
  token.mode['.']!.partialAliasOf = token.partialAliasOf;
125
134
  }
135
+ aliasCount++;
126
136
  const { group: parentGroup } = splitID(id);
127
137
  if (parentGroup) {
128
138
  for (const siblingID of Object.keys(tokens)) {
@@ -133,14 +143,16 @@ export default async function parse(
133
143
  }
134
144
  }
135
145
  }
136
-
137
- // 6. resolve mode aliases
138
- const modesStart = performance.now();
139
146
  logger.debug({
140
- message: 'Start mode resolution',
147
+ message: `Resolved ${aliasCount} aliases`,
141
148
  group: 'parser',
142
- label: 'modes',
149
+ label: 'alias',
150
+ timing: performance.now() - aliasesStart,
143
151
  });
152
+
153
+ // 7. resolve mode aliases
154
+ const modesStart = performance.now();
155
+ let modeAliasCount = 0;
144
156
  for (const [id, token] of Object.entries(tokens)) {
145
157
  if (!Object.hasOwn(tokens, id)) {
146
158
  continue;
@@ -149,18 +161,20 @@ export default async function parse(
149
161
  if (mode === '.') {
150
162
  continue; // skip shadow of root value
151
163
  }
164
+ modeAliasCount++;
152
165
  applyAliases(modeValue, {
153
166
  tokens,
154
167
  node: modeValue.source.node,
155
168
  logger,
156
169
  src: _sources[token.source.loc!]?.src as string,
170
+ skipReverseAlias: true,
157
171
  });
158
172
  }
159
173
  }
160
174
  logger.debug({
175
+ message: `Resolved ${modeAliasCount} mode aliases`,
161
176
  group: 'parser',
162
- label: 'modes',
163
- message: 'Finish token modes',
177
+ label: 'alias',
164
178
  timing: performance.now() - modesStart,
165
179
  });
166
180
 
@@ -175,6 +189,7 @@ export default async function parse(
175
189
  const { errorCount } = logger.stats();
176
190
  if (errorCount > 0) {
177
191
  logger.error({
192
+ group: 'parser',
178
193
  message: `Parser encountered ${errorCount} ${pluralize(errorCount, 'error', 'errors')}. Exiting.`,
179
194
  });
180
195
  }
@@ -207,20 +222,19 @@ async function parseSingle(
207
222
  ): Promise<{ tokens: Record<string, Token>; document: DocumentNode; src?: string }> {
208
223
  // 1. Build AST
209
224
  const startParsing = performance.now();
210
- logger.debug({ group: 'parser', label: 'parse', message: 'Start JSON parsing' });
211
225
  const { src, document } = toMomoa(input, { filename, logger, continueOnError, yamlToMomoa });
212
226
  logger.debug({
213
227
  group: 'parser',
214
- label: 'parse',
228
+ label: 'json',
215
229
  message: 'Finish JSON parsing',
216
230
  timing: performance.now() - startParsing,
217
231
  });
218
232
  const tokens: Record<string, TokenNormalized> = {};
219
233
 
220
234
  // 2. Walk AST to validate tokens
235
+ let tokenCount = 0;
221
236
  const startValidate = performance.now();
222
237
  const $typeInheritance: Record<string, Token['$type']> = {};
223
- logger.debug({ message: 'Start token validation', group: 'parser', label: 'validate' });
224
238
  traverse(document, {
225
239
  enter(node, parent, subpath) {
226
240
  // if $type appears at root of tokens.json, collect it
@@ -237,19 +251,20 @@ async function parseSingle(
237
251
  const token = validateTokenNode(node, { filename, src, config, logger, parent, subpath, $typeInheritance });
238
252
  if (token) {
239
253
  tokens[token.id] = token;
254
+ tokenCount++;
240
255
  }
241
256
  }
242
257
  },
243
258
  });
244
- logger.debug({ message: 'Finish token validation', group: 'parser', label: 'validate', timing: startValidate });
245
-
246
- // 3. normalize values
247
- const normalizeStart = performance.now();
248
259
  logger.debug({
249
- message: 'Start token normalization',
260
+ message: `Validated ${tokenCount} tokens`,
250
261
  group: 'parser',
251
- label: 'normalize',
262
+ label: 'validate',
263
+ timing: performance.now() - startValidate,
252
264
  });
265
+
266
+ // 3. normalize values
267
+ const normalizeStart = performance.now();
253
268
  for (const [id, token] of Object.entries(tokens)) {
254
269
  try {
255
270
  tokens[id]!.$value = normalize(token);
@@ -259,7 +274,15 @@ async function parseSingle(
259
274
  if (members.$value) {
260
275
  node = members.$value as ObjectNode;
261
276
  }
262
- logger.error({ message: (err as Error).message, filename, src, node, continueOnError });
277
+ logger.error({
278
+ group: 'parser',
279
+ label: 'normalize',
280
+ message: (err as Error).message,
281
+ filename,
282
+ src,
283
+ node,
284
+ continueOnError,
285
+ });
263
286
  }
264
287
  for (const [mode, modeValue] of Object.entries(token.mode)) {
265
288
  if (mode === '.') {
@@ -273,31 +296,39 @@ async function parseSingle(
273
296
  if (members.$value) {
274
297
  node = members.$value as ObjectNode;
275
298
  }
276
- logger.error({ message: (err as Error).message, filename, src, node: modeValue.source.node, continueOnError });
299
+ logger.error({
300
+ group: 'parser',
301
+ label: 'normalize',
302
+ message: (err as Error).message,
303
+ filename,
304
+ src,
305
+ node: modeValue.source.node,
306
+ continueOnError,
307
+ });
277
308
  }
278
309
  }
279
310
  }
311
+ logger.debug({
312
+ message: `Normalized ${tokenCount} tokens`,
313
+ group: 'parser',
314
+ label: 'normalize',
315
+ timing: performance.now() - normalizeStart,
316
+ });
280
317
 
281
318
  // 4. Execute lint runner with loaded plugins
282
319
  if (!skipLint && config?.plugins?.length) {
283
320
  const lintStart = performance.now();
284
- logger.debug({ message: 'Start token linting', group: 'parser', label: 'validate' });
285
321
  await lintRunner({ tokens, src, config, logger });
286
322
  logger.debug({
287
- message: 'Finish token linting',
323
+ message: `Linted ${tokenCount} tokens`,
288
324
  group: 'parser',
289
- label: 'validate',
325
+ label: 'lint',
290
326
  timing: performance.now() - lintStart,
291
327
  });
328
+ } else {
329
+ logger.debug({ message: 'Linting skipped', group: 'parser', label: 'lint' });
292
330
  }
293
331
 
294
- logger.debug({
295
- message: 'Finish token normalization',
296
- group: 'parser',
297
- label: 'normalize',
298
- timing: performance.now() - normalizeStart,
299
- });
300
-
301
332
  return {
302
333
  tokens,
303
334
  document,
package/src/parse/json.ts CHANGED
@@ -55,18 +55,15 @@ export function getObjMembers(node: ObjectNode): Record<string | number, ValueNo
55
55
  }
56
56
 
57
57
  /**
58
- * Inject members to ObjectNode and return a clone
58
+ * Inject members to ObjectNode
59
59
  * @param {ObjectNode} node
60
60
  * @param {MemberNode[]} members
61
- * @return {ObjectNode}
62
61
  */
63
- export function injectObjMembers(node: ObjectNode, members: MemberNode[] = []): ObjectNode {
62
+ export function injectObjMembers(node: ObjectNode, members: MemberNode[] = []) {
64
63
  if (node.type !== 'Object') {
65
- return node;
64
+ return;
66
65
  }
67
- const newNode = structuredClone(node);
68
- newNode.members.push(...members);
69
- return newNode;
66
+ node.members.push(...members);
70
67
  }
71
68
 
72
69
  /**
@@ -176,11 +173,12 @@ export function toMomoa(
176
173
  try {
177
174
  document = yamlToMomoa(input); // if string, but not JSON, attempt YAML
178
175
  } catch (err) {
179
- logger.error({ message: String(err), filename, src: input, continueOnError });
176
+ logger.error({ group: 'parser', label: 'json', message: String(err), filename, src: input, continueOnError });
180
177
  }
181
178
  } else {
182
179
  logger.error({
183
180
  group: 'parser',
181
+ label: 'yaml',
184
182
  message: `Install \`yaml-to-momoa\` package to parse YAML, and pass in as option, e.g.:
185
183
 
186
184
  import { parse } from '@terrazzo/parser';
@@ -110,7 +110,7 @@ export default function normalizeValue<T extends Token>(token: T): T['$value'] {
110
110
  }
111
111
  const output: GradientValueNormalized = [];
112
112
  for (let i = 0; i < token.$value.length; i++) {
113
- const stop = { ...(token.$value[i] as GradientStopNormalized) };
113
+ const stop = structuredClone(token.$value[i] as GradientStopNormalized);
114
114
  stop.color = normalizeValue({ $type: 'color', $value: stop.color! });
115
115
  if (stop.position === undefined) {
116
116
  stop.position = i / (token.$value.length - 1);
@@ -166,22 +166,24 @@ export default function normalizeValue<T extends Token>(token: T): T['$value'] {
166
166
  return token.$value;
167
167
  }
168
168
  const output: TypographyValueNormalized = {};
169
- for (const k in token.$value) {
169
+ for (const [k, $value] of Object.entries(token.$value)) {
170
170
  switch (k) {
171
171
  case 'fontSize':
172
172
  case 'letterSpacing': {
173
- output[k] = normalizeValue({ $type: 'dimension', $value: token.$value[k] as DimensionValue });
173
+ output[k] = normalizeValue({ $type: 'dimension', $value: $value as DimensionValue });
174
174
  break;
175
175
  }
176
176
  case 'lineHeight': {
177
177
  output[k] = normalizeValue({
178
178
  $type: typeof token.$value === 'number' ? 'number' : 'dimension',
179
- $value: token.$value[k] as any,
179
+ $value: $value as any,
180
180
  });
181
181
  break;
182
182
  }
183
- default:
184
- output[k] = token.$value[k];
183
+ default: {
184
+ output[k] = $value;
185
+ break;
186
+ }
185
187
  }
186
188
  }
187
189
  return output;