@terrazzo/parser 0.4.0 → 0.6.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 (80) hide show
  1. package/CHANGELOG.md +62 -12
  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 +13 -14
  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/duplicate-values.js +1 -1
  35. package/dist/lint/plugin-core/rules/duplicate-values.js.map +1 -1
  36. package/dist/lint/plugin-core/rules/max-gamut.d.ts +1 -0
  37. package/dist/lint/plugin-core/rules/max-gamut.d.ts.map +1 -0
  38. package/dist/lint/plugin-core/rules/required-children.d.ts +1 -0
  39. package/dist/lint/plugin-core/rules/required-children.d.ts.map +1 -0
  40. package/dist/lint/plugin-core/rules/required-modes.d.ts +1 -0
  41. package/dist/lint/plugin-core/rules/required-modes.d.ts.map +1 -0
  42. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +1 -0
  43. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts.map +1 -0
  44. package/dist/logger.d.ts +4 -3
  45. package/dist/logger.d.ts.map +1 -0
  46. package/dist/logger.js +25 -14
  47. package/dist/logger.js.map +1 -1
  48. package/dist/parse/alias.d.ts +31 -48
  49. package/dist/parse/alias.d.ts.map +1 -0
  50. package/dist/parse/alias.js +281 -175
  51. package/dist/parse/alias.js.map +1 -1
  52. package/dist/parse/index.d.ts +1 -0
  53. package/dist/parse/index.d.ts.map +1 -0
  54. package/dist/parse/index.js +59 -70
  55. package/dist/parse/index.js.map +1 -1
  56. package/dist/parse/json.d.ts +3 -3
  57. package/dist/parse/json.d.ts.map +1 -0
  58. package/dist/parse/json.js +5 -7
  59. package/dist/parse/json.js.map +1 -1
  60. package/dist/parse/normalize.d.ts +1 -0
  61. package/dist/parse/normalize.d.ts.map +1 -0
  62. package/dist/parse/normalize.js +13 -7
  63. package/dist/parse/normalize.js.map +1 -1
  64. package/dist/parse/validate.d.ts +6 -0
  65. package/dist/parse/validate.d.ts.map +1 -0
  66. package/dist/parse/validate.js +205 -125
  67. package/dist/parse/validate.js.map +1 -1
  68. package/dist/types.d.ts +1 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/package.json +2 -2
  71. package/src/build/index.ts +13 -14
  72. package/src/config.ts +42 -22
  73. package/src/lint/index.ts +8 -6
  74. package/src/lint/plugin-core/rules/duplicate-values.ts +1 -1
  75. package/src/logger.ts +30 -20
  76. package/src/parse/alias.ts +330 -194
  77. package/src/parse/index.ts +59 -73
  78. package/src/parse/json.ts +6 -8
  79. package/src/parse/normalize.ts +14 -7
  80. package/src/parse/validate.ts +215 -128
@@ -87,19 +87,26 @@ function validateMembersAs(
87
87
  { filename, src, logger }: ValidateOptions,
88
88
  ) {
89
89
  const members = getObjMembers($value);
90
- for (const property in properties) {
91
- const { validator, required } = properties[property]!;
92
- if (!members[property]) {
90
+ for (const [name, value] of Object.entries(properties)) {
91
+ const { validator, required } = value;
92
+ if (!members[name]) {
93
93
  if (required) {
94
- logger.error({ message: `Missing required property "${property}"`, filename, node: $value, src });
94
+ logger.error({
95
+ group: 'parser',
96
+ label: 'validate',
97
+ message: `Missing required property "${name}"`,
98
+ filename,
99
+ node: $value,
100
+ src,
101
+ });
95
102
  }
96
103
  continue;
97
104
  }
98
- const value = members[property];
99
- if (isMaybeAlias(value)) {
100
- validateAliasSyntax(value, node, { filename, src, logger });
105
+ const memberValue = members[name];
106
+ if (isMaybeAlias(memberValue)) {
107
+ validateAliasSyntax(memberValue, node, { filename, src, logger });
101
108
  } else {
102
- validator(value, node, { filename, src, logger });
109
+ validator(memberValue, node, { filename, src, logger });
103
110
  }
104
111
  }
105
112
  }
@@ -107,14 +114,28 @@ function validateMembersAs(
107
114
  /** Verify an Alias $value is formatted correctly */
108
115
  export function validateAliasSyntax($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
109
116
  if ($value.type !== 'String' || !isAlias($value.value)) {
110
- logger.error({ message: `Invalid alias: ${print($value)}`, filename, node: $value, src });
117
+ logger.error({
118
+ group: 'parser',
119
+ label: 'validate',
120
+ message: `Invalid alias: ${print($value)}`,
121
+ filename,
122
+ node: $value,
123
+ src,
124
+ });
111
125
  }
112
126
  }
113
127
 
114
128
  /** Verify a Border token is valid */
115
129
  export function validateBorder($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
116
130
  if ($value.type !== 'Object') {
117
- logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
131
+ logger.error({
132
+ group: 'parser',
133
+ label: 'validate',
134
+ message: `Expected object, received ${$value.type}`,
135
+ filename,
136
+ node: $value,
137
+ src,
138
+ });
118
139
  } else {
119
140
  validateMembersAs(
120
141
  $value,
@@ -131,6 +152,7 @@ export function validateBorder($value: ValueNode, node: AnyNode, { filename, src
131
152
 
132
153
  /** Verify a Color token is valid */
133
154
  export function validateColor($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
155
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
134
156
  if ($value.type === 'String') {
135
157
  // TODO: enable when object notation is finalized
136
158
  // logger.warn({
@@ -140,7 +162,7 @@ export function validateColor($value: ValueNode, node: AnyNode, { filename, src,
140
162
  // src,
141
163
  // });
142
164
  if ($value.value === '') {
143
- logger.error({ message: 'Expected color, received empty string', filename, node: $value, src });
165
+ logger.error({ ...baseMessage, message: 'Expected color, received empty string' });
144
166
  }
145
167
  } else if ($value.type === 'Object') {
146
168
  validateMembersAs(
@@ -149,10 +171,18 @@ export function validateColor($value: ValueNode, node: AnyNode, { filename, src,
149
171
  colorSpace: {
150
172
  validator: (v) => {
151
173
  if (v.type !== 'String') {
152
- logger.error({ message: `Expected string, received ${print(v)}`, filename, node: v, src });
174
+ logger.error({
175
+ ...baseMessage,
176
+ message: `Expected string, received ${print(v)}`,
177
+ node: v,
178
+ });
153
179
  }
154
180
  if (!VALID_COLORSPACES.has((v as StringNode).value)) {
155
- logger.error({ message: `Unsupported colorspace ${print(v)}`, filename, node: v, src });
181
+ logger.error({
182
+ ...baseMessage,
183
+ message: `Unsupported colorspace ${print(v)}`,
184
+ node: v,
185
+ });
156
186
  }
157
187
  },
158
188
  required: true,
@@ -160,23 +190,25 @@ export function validateColor($value: ValueNode, node: AnyNode, { filename, src,
160
190
  channels: {
161
191
  validator: (v) => {
162
192
  if (v.type !== 'Array') {
163
- logger.error({ message: `Expected array, received ${print(v)}`, filename, node: v, src });
193
+ logger.error({
194
+ ...baseMessage,
195
+ message: `Expected array, received ${print(v)}`,
196
+ node: v,
197
+ });
164
198
  } else {
165
199
  if (v.elements?.length !== 3) {
166
200
  logger.error({
201
+ ...baseMessage,
167
202
  message: `Expected 3 channels, received ${v.elements?.length ?? 0}`,
168
- filename,
169
203
  node: v,
170
- src,
171
204
  });
172
205
  }
173
206
  for (const element of v.elements) {
174
207
  if (element.value.type !== 'Number') {
175
208
  logger.error({
209
+ ...baseMessage,
176
210
  message: `Expected number, received ${print(element.value)}`,
177
- filename,
178
211
  node: element,
179
- src,
180
212
  });
181
213
  }
182
214
  }
@@ -196,7 +228,11 @@ export function validateColor($value: ValueNode, node: AnyNode, { filename, src,
196
228
  v.value.length === 7 + 1 ||
197
229
  !/^#[a-f0-9]{3,8}$/i.test(v.value)
198
230
  ) {
199
- logger.error({ message: `Invalid hex color ${print(v)}`, filename, node: v, src });
231
+ logger.error({
232
+ ...baseMessage,
233
+ message: `Invalid hex color ${print(v)}`,
234
+ node: v,
235
+ });
200
236
  }
201
237
  },
202
238
  },
@@ -206,30 +242,23 @@ export function validateColor($value: ValueNode, node: AnyNode, { filename, src,
206
242
  { filename, src, logger },
207
243
  );
208
244
  } else {
209
- logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
245
+ logger.error({
246
+ ...baseMessage,
247
+ message: `Expected object, received ${$value.type}`,
248
+ node: $value,
249
+ });
210
250
  }
211
251
  }
212
252
 
213
253
  /** Verify a Cubic Bézier token is valid */
214
254
  export function validateCubicBezier($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
255
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
215
256
  if ($value.type !== 'Array') {
216
- logger.error({ message: `Expected array of strings, received ${print($value)}`, filename, node: $value, src });
217
- } else if (
218
- !$value.elements.every((e) => e.value.type === 'Number' || (e.value.type === 'String' && isAlias(e.value.value)))
219
- ) {
220
- logger.error({
221
- message: 'Expected an array of 4 numbers, received some non-numbers',
222
- filename,
223
- node: $value,
224
- src,
225
- });
257
+ logger.error({ ...baseMessage, message: `Expected array of numbers, received ${print($value)}` });
258
+ } else if (!$value.elements.every((e) => e.value.type === 'Number')) {
259
+ logger.error({ ...baseMessage, message: 'Expected an array of 4 numbers, received some non-numbers' });
226
260
  } else if ($value.elements.length !== 4) {
227
- logger.error({
228
- message: `Expected an array of 4 numbers, received ${$value.elements.length}`,
229
- filename,
230
- node: $value,
231
- src,
232
- });
261
+ logger.error({ ...baseMessage, message: `Expected an array of 4 numbers, received ${$value.elements.length}` });
233
262
  }
234
263
  }
235
264
 
@@ -238,44 +267,50 @@ export function validateDimension($value: ValueNode, _node: AnyNode, { filename,
238
267
  if ($value.type === 'Number' && $value.value === 0) {
239
268
  return; // `0` is a valid number
240
269
  }
270
+
271
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
272
+
273
+ // Give priority to object notation because it’s a faster code path
241
274
  if ($value.type === 'Object') {
242
275
  const { value, unit } = getObjMembers($value);
243
276
  if (!value) {
244
- logger.error({ message: 'Missing required property "value".', filename, node: $value, src });
277
+ logger.error({ ...baseMessage, message: 'Missing required property "value".' });
245
278
  }
246
279
  if (!unit) {
247
- logger.error({ message: 'Missing required property "unit".', filename, node: $value, src });
280
+ logger.error({ ...baseMessage, message: 'Missing required property "unit".' });
248
281
  }
249
282
  if (value!.type !== 'Number') {
250
- logger.error({ message: `Expected number, received ${value!.type}`, filename, node: value, src });
283
+ logger.error({
284
+ ...baseMessage,
285
+ message: `Expected number, received ${value!.type}`,
286
+ node: value,
287
+ });
251
288
  }
252
289
  if (!['px', 'em', 'rem'].includes((unit as StringNode).value)) {
253
290
  logger.error({
291
+ ...baseMessage,
254
292
  message: `Expected unit "px", "em", or "rem", received ${print(unit as StringNode)}`,
255
- filename,
256
293
  node: unit,
257
- src,
258
294
  });
259
295
  }
260
296
  return;
261
297
  }
298
+
262
299
  // Backwards compat: string
263
300
  if ($value.type !== 'String') {
264
- logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
301
+ logger.error({ ...baseMessage, message: `Expected string, received ${$value.type}` });
265
302
  }
266
303
  const value = ($value as StringNode).value.match(/^-?[0-9.]+/)?.[0];
267
304
  const unit = ($value as StringNode).value.replace(value!, '');
268
305
  if (($value as StringNode).value === '') {
269
- logger.error({ message: 'Expected dimension, received empty string', filename, node: $value, src });
306
+ logger.error({ ...baseMessage, message: 'Expected dimension, received empty string' });
270
307
  } else if (!['px', 'em', 'rem'].includes(unit)) {
271
308
  logger.error({
309
+ ...baseMessage,
272
310
  message: `Expected unit "px", "em", or "rem", received ${JSON.stringify(unit || ($value as StringNode).value)}`,
273
- filename,
274
- node: $value,
275
- src,
276
311
  });
277
312
  } else if (!Number.isFinite(Number.parseFloat(value!))) {
278
- logger.error({ message: `Expected dimension with units, received ${print($value)}`, filename, node: $value, src });
313
+ logger.error({ ...baseMessage, message: `Expected dimension with units, received ${print($value)}` });
279
314
  }
280
315
  }
281
316
 
@@ -284,106 +319,101 @@ export function validateDuration($value: ValueNode, _node: AnyNode, { filename,
284
319
  if ($value.type === 'Number' && $value.value === 0) {
285
320
  return; // `0` is a valid number
286
321
  }
322
+
323
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
324
+
325
+ // Give priority to object notation because it’s a faster code path
287
326
  if ($value.type === 'Object') {
288
327
  const { value, unit } = getObjMembers($value);
289
328
  if (!value) {
290
- logger.error({ message: 'Missing required property "value".', filename, node: $value, src });
329
+ logger.error({ ...baseMessage, message: 'Missing required property "value".' });
291
330
  }
292
331
  if (!unit) {
293
- logger.error({ message: 'Missing required property "unit".', filename, node: $value, src });
332
+ logger.error({ ...baseMessage, message: 'Missing required property "unit".' });
294
333
  }
295
334
  if (value?.type !== 'Number') {
296
- logger.error({ message: `Expected number, received ${value?.type}`, filename, node: value, src });
335
+ logger.error({
336
+ ...baseMessage,
337
+ message: `Expected number, received ${value?.type}`,
338
+ node: value,
339
+ });
297
340
  }
298
341
  if (!['ms', 's'].includes((unit as StringNode).value)) {
299
- logger.error({ message: `Expected unit "ms" or "s", received ${print(unit!)}`, filename, node: unit, src });
342
+ logger.error({
343
+ ...baseMessage,
344
+ message: `Expected unit "ms" or "s", received ${print(unit!)}`,
345
+ node: unit,
346
+ });
300
347
  }
301
348
  return;
302
349
  }
350
+
303
351
  // Backwards compat: string
304
352
  if ($value.type !== 'String') {
305
- logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
353
+ logger.error({ ...baseMessage, message: `Expected string, received ${$value.type}` });
306
354
  }
307
355
  const value = ($value as StringNode).value.match(/^-?[0-9.]+/)?.[0]!;
308
356
  const unit = ($value as StringNode).value.replace(value, '');
309
357
  if (($value as StringNode).value === '') {
310
- logger.error({ message: 'Expected duration, received empty string', filename, node: $value, src });
358
+ logger.error({ ...baseMessage, message: 'Expected duration, received empty string' });
311
359
  } else if (!['ms', 's'].includes(unit)) {
312
360
  logger.error({
361
+ ...baseMessage,
313
362
  message: `Expected unit "ms" or "s", received ${JSON.stringify(unit || ($value as StringNode).value)}`,
314
- filename,
315
- node: $value,
316
- src,
317
363
  });
318
364
  } else if (!Number.isFinite(Number.parseFloat(value))) {
319
- logger.error({ message: `Expected duration with units, received ${print($value)}`, filename, node: $value, src });
365
+ logger.error({ ...baseMessage, message: `Expected duration with units, received ${print($value)}` });
320
366
  }
321
367
  }
322
368
 
323
369
  /** Verify a Font Family token is valid */
324
370
  export function validateFontFamily($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
371
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
325
372
  if ($value.type !== 'String' && $value.type !== 'Array') {
326
- logger.error({
327
- message: `Expected string or array of strings, received ${$value.type}`,
328
- filename,
329
- node: $value,
330
- src,
331
- });
373
+ logger.error({ ...baseMessage, message: `Expected string or array of strings, received ${$value.type}` });
332
374
  }
333
375
  if ($value.type === 'String' && $value.value === '') {
334
- logger.error({ message: 'Expected font family name, received empty string', filename, node: $value, src });
376
+ logger.error({ ...baseMessage, message: 'Expected font family name, received empty string' });
335
377
  }
336
378
  if ($value.type === 'Array' && !$value.elements.every((e) => e.value.type === 'String' && e.value.value !== '')) {
337
379
  logger.error({
380
+ ...baseMessage,
338
381
  message: 'Expected an array of strings, received some non-strings or empty strings',
339
- filename,
340
- node: $value,
341
- src,
342
382
  });
343
383
  }
344
384
  }
345
385
 
346
386
  /** Verify a Font Weight token is valid */
347
387
  export function validateFontWeight($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
388
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
348
389
  if ($value.type !== 'String' && $value.type !== 'Number') {
349
- logger.error({
350
- message: `Expected a font weight name or number 0–1000, received ${$value.type}`,
351
- filename,
352
- node: $value,
353
- src,
354
- });
390
+ logger.error({ ...baseMessage, message: `Expected a font weight name or number 0–1000, received ${$value.type}` });
355
391
  }
356
392
  if ($value.type === 'String' && !FONT_WEIGHT_VALUES.has($value.value)) {
357
393
  logger.error({
394
+ ...baseMessage,
358
395
  message: `Unknown font weight ${print($value)}. Expected one of: ${listFormat.format([...FONT_WEIGHT_VALUES])}.`,
359
- filename,
360
- node: $value,
361
- src,
362
396
  });
363
397
  }
364
398
  if ($value.type === 'Number' && ($value.value < 0 || $value.value > 1000)) {
365
- logger.error({ message: `Expected number 0–1000, received ${print($value)}`, filename, node: $value, src });
399
+ logger.error({ ...baseMessage, message: `Expected number 0–1000, received ${print($value)}` });
366
400
  }
367
401
  }
368
402
 
369
403
  /** Verify a Gradient token is valid */
370
404
  export function validateGradient($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
405
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
406
+
371
407
  if ($value.type !== 'Array') {
372
- logger.error({
373
- message: `Expected array of gradient stops, received ${$value.type}`,
374
- filename,
375
- node: $value,
376
- src,
377
- });
408
+ logger.error({ ...baseMessage, message: `Expected array of gradient stops, received ${$value.type}` });
378
409
  } else {
379
410
  for (let i = 0; i < $value.elements.length; i++) {
380
411
  const element = $value.elements[i]!;
381
412
  if (element.value.type !== 'Object') {
382
413
  logger.error({
414
+ ...baseMessage,
383
415
  message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
384
- filename,
385
416
  node: element,
386
- src,
387
417
  });
388
418
  break;
389
419
  }
@@ -403,21 +433,42 @@ export function validateGradient($value: ValueNode, _node: AnyNode, { filename,
403
433
  /** Verify a Number token is valid */
404
434
  export function validateNumber($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
405
435
  if ($value.type !== 'Number') {
406
- logger.error({ message: `Expected number, received ${$value.type}`, filename, node: $value, src });
436
+ logger.error({
437
+ group: 'parser',
438
+ label: 'validate',
439
+ message: `Expected number, received ${$value.type}`,
440
+ filename,
441
+ node: $value,
442
+ src,
443
+ });
407
444
  }
408
445
  }
409
446
 
410
447
  /** Verify a Boolean token is valid */
411
448
  export function validateBoolean($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
412
449
  if ($value.type !== 'Boolean') {
413
- logger.error({ message: `Expected boolean, received ${$value.type}`, filename, node: $value, src });
450
+ logger.error({
451
+ group: 'parser',
452
+ label: 'validate',
453
+ message: `Expected boolean, received ${$value.type}`,
454
+ filename,
455
+ node: $value,
456
+ src,
457
+ });
414
458
  }
415
459
  }
416
460
 
417
461
  /** Verify a Shadow token’s value is valid */
418
462
  export function validateShadowLayer($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
419
463
  if ($value.type !== 'Object') {
420
- logger.error({ message: `Expected Object, received ${$value.type}`, filename, node: $value, src });
464
+ logger.error({
465
+ group: 'parser',
466
+ label: 'validate',
467
+ message: `Expected Object, received ${$value.type}`,
468
+ filename,
469
+ node: $value,
470
+ src,
471
+ });
421
472
  } else {
422
473
  validateMembersAs(
423
474
  $value,
@@ -437,31 +488,33 @@ export function validateShadowLayer($value: ValueNode, node: AnyNode, { filename
437
488
 
438
489
  /** Verify a Stroke Style token is valid. */
439
490
  export function validateStrokeStyle($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
491
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
492
+
440
493
  // note: strokeStyle’s values are NOT aliasable (unless by string, but that breaks validations)
441
494
  if ($value.type === 'String') {
442
495
  if (!STROKE_STYLE_VALUES.has($value.value)) {
443
496
  logger.error({
497
+ ...baseMessage,
444
498
  message: `Unknown stroke style ${print($value)}. Expected one of: ${listFormat.format([
445
499
  ...STROKE_STYLE_VALUES,
446
500
  ])}.`,
447
- filename,
448
- node: $value,
449
- src,
450
501
  });
451
502
  }
452
503
  } else if ($value.type === 'Object') {
453
504
  const strokeMembers = getObjMembers($value);
454
505
  for (const property of ['dashArray', 'lineCap']) {
455
506
  if (!strokeMembers[property]) {
456
- logger.error({ message: `Missing required property "${property}"`, filename, node: $value, src });
507
+ logger.error({ ...baseMessage, message: `Missing required property "${property}"` });
457
508
  }
458
509
  }
459
510
  const { lineCap, dashArray } = strokeMembers;
460
511
  if (lineCap?.type !== 'String' || !STROKE_STYLE_LINE_CAP_VALUES.has(lineCap.value)) {
461
512
  logger.error({
513
+ ...baseMessage,
462
514
  message: `Unknown lineCap value ${print(lineCap!)}. Expected one of: ${listFormat.format([
463
515
  ...STROKE_STYLE_LINE_CAP_VALUES,
464
516
  ])}.`,
517
+ node,
465
518
  });
466
519
  }
467
520
  if (dashArray?.type === 'Array') {
@@ -474,25 +527,31 @@ export function validateStrokeStyle($value: ValueNode, node: AnyNode, { filename
474
527
  }
475
528
  } else {
476
529
  logger.error({
530
+ ...baseMessage,
477
531
  message: 'Expected array of strings, recieved some non-strings or empty strings.',
478
- filename,
479
532
  node: element,
480
- src,
481
533
  });
482
534
  }
483
535
  }
484
536
  } else {
485
- logger.error({ message: `Expected array of strings, received ${dashArray!.type}`, filename, node: $value, src });
537
+ logger.error({ ...baseMessage, message: `Expected array of strings, received ${dashArray!.type}` });
486
538
  }
487
539
  } else {
488
- logger.error({ message: `Expected string or object, received ${$value.type}`, filename, node: $value, src });
540
+ logger.error({ ...baseMessage, message: `Expected string or object, received ${$value.type}` });
489
541
  }
490
542
  }
491
543
 
492
544
  /** Verify a Transition token is valid */
493
545
  export function validateTransition($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
494
546
  if ($value.type !== 'Object') {
495
- logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
547
+ logger.error({
548
+ group: 'parser',
549
+ label: 'validate',
550
+ message: `Expected object, received ${$value.type}`,
551
+ filename,
552
+ node: $value,
553
+ src,
554
+ });
496
555
  } else {
497
556
  validateMembersAs(
498
557
  $value,
@@ -513,15 +572,15 @@ export function validateTransition($value: ValueNode, node: AnyNode, { filename,
513
572
  * really helps in debug messages.
514
573
  */
515
574
  export function validateTokenMemberNode(node: MemberNode, { filename, src, logger }: ValidateOptions) {
575
+ const baseMessage = { group: 'parser' as const, label: 'validate', filename, node, src };
576
+
516
577
  if (node.type !== 'Member' && node.type !== 'Object') {
517
578
  logger.error({
579
+ ...baseMessage,
518
580
  message: `Expected Object, received ${JSON.stringify(
519
581
  // @ts-ignore Yes, TypeScript, this SHOULD be unexpected. This is why we’re validating.
520
582
  node.type,
521
583
  )}`,
522
- filename,
523
- node,
524
- src,
525
584
  });
526
585
  }
527
586
 
@@ -530,7 +589,7 @@ export function validateTokenMemberNode(node: MemberNode, { filename, src, logge
530
589
  const $type = rootMembers.$type as StringNode;
531
590
 
532
591
  if (!$value) {
533
- logger.error({ message: 'Token missing $value', filename, node, src });
592
+ logger.error({ ...baseMessage, message: 'Token missing $value' });
534
593
  }
535
594
  // If top-level value is a valid alias, this is valid (no need for $type)
536
595
  // ⚠️ Important: ALL Object and Array nodes below will need to check for aliases within!
@@ -540,7 +599,7 @@ export function validateTokenMemberNode(node: MemberNode, { filename, src, logge
540
599
  }
541
600
 
542
601
  if (!$type) {
543
- logger.error({ message: 'Token missing $type', filename, node, src });
602
+ logger.error({ ...baseMessage, message: 'Token missing $type' });
544
603
  }
545
604
 
546
605
  switch ($type.value) {
@@ -581,10 +640,9 @@ export function validateTokenMemberNode(node: MemberNode, { filename, src, logge
581
640
  }
582
641
  } else {
583
642
  logger.error({
643
+ ...baseMessage,
584
644
  message: `Expected shadow object or array of shadow objects, received ${$value.type}`,
585
- filename,
586
645
  node: $value,
587
- src,
588
646
  });
589
647
  }
590
648
  break;
@@ -593,21 +651,37 @@ export function validateTokenMemberNode(node: MemberNode, { filename, src, logge
593
651
  // extensions
594
652
  case 'boolean': {
595
653
  if ($value.type !== 'Boolean') {
596
- logger.error({ message: `Expected boolean, received ${$value.type}`, filename, node: $value, src });
654
+ logger.error({
655
+ ...baseMessage,
656
+ message: `Expected boolean, received ${$value.type}`,
657
+ node: $value,
658
+ });
597
659
  }
598
660
  break;
599
661
  }
600
662
  case 'link': {
601
663
  if ($value.type !== 'String') {
602
- logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
664
+ logger.error({
665
+ ...baseMessage,
666
+ message: `Expected string, received ${$value.type}`,
667
+ node: $value,
668
+ });
603
669
  } else if ($value.value === '') {
604
- logger.error({ message: 'Expected URL, received empty string', filename, node: $value, src });
670
+ logger.error({
671
+ ...baseMessage,
672
+ message: 'Expected URL, received empty string',
673
+ node: $value,
674
+ });
605
675
  }
606
676
  break;
607
677
  }
608
678
  case 'string': {
609
679
  if ($value.type !== 'String') {
610
- logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
680
+ logger.error({
681
+ ...baseMessage,
682
+ message: `Expected string, received ${$value.type}`,
683
+ node: $value,
684
+ });
611
685
  }
612
686
  break;
613
687
  }
@@ -631,15 +705,18 @@ export function validateTokenMemberNode(node: MemberNode, { filename, src, logge
631
705
  }
632
706
  case 'typography': {
633
707
  if ($value.type !== 'Object') {
634
- logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
708
+ logger.error({
709
+ ...baseMessage,
710
+ message: `Expected object, received ${$value.type}`,
711
+ node: $value,
712
+ });
635
713
  break;
636
714
  }
637
715
  if ($value.members.length === 0) {
638
716
  logger.error({
717
+ ...baseMessage,
639
718
  message: 'Empty typography token. Must contain at least 1 property.',
640
- filename,
641
719
  node: $value,
642
- src,
643
720
  });
644
721
  }
645
722
  validateMembersAs(
@@ -671,10 +748,17 @@ export interface ValidateTokenNodeOptions {
671
748
  $typeInheritance?: Record<string, Token['$type']>;
672
749
  }
673
750
 
751
+ /**
752
+ * Validate does a little more than validate; it also converts to TokenNormalized
753
+ * and sets up the basic data structure. But aliases are unresolved, and we need
754
+ * a 2nd normalization pass afterward.
755
+ */
674
756
  export default function validateTokenNode(
675
757
  node: MemberNode,
676
758
  { config, filename, logger, parent, src, subpath, $typeInheritance }: ValidateTokenNodeOptions,
677
759
  ): TokenNormalized | undefined {
760
+ // const start = performance.now();
761
+
678
762
  // don’t validate $value
679
763
  if (subpath.includes('$value') || node.value.type !== 'Object') {
680
764
  return;
@@ -696,7 +780,14 @@ export default function validateTokenNode(
696
780
  const id = subpath.join('.');
697
781
 
698
782
  if (!subpath.includes('.$value') && members.value) {
699
- logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, filename, node, src });
783
+ logger.warn({
784
+ group: 'parser',
785
+ label: 'validate',
786
+ message: `Group ${id} has "value". Did you mean "$value"?`,
787
+ filename,
788
+ node,
789
+ src,
790
+ });
700
791
  }
701
792
 
702
793
  const extensions = members.$extensions ? getObjMembers(members.$extensions as ObjectNode) : undefined;
@@ -714,7 +805,7 @@ export default function validateTokenNode(
714
805
  }
715
806
  }
716
807
  if (parent$type && !members.$type) {
717
- sourceNode.value = injectObjMembers(
808
+ injectObjMembers(
718
809
  // @ts-ignore
719
810
  sourceNode.value,
720
811
  [parent$type],
@@ -773,29 +864,25 @@ export default function validateTokenNode(
773
864
 
774
865
  // handle modes
775
866
  // note that circular refs are avoided here, such as not duplicating `modes`
776
- const modeValues = extensions?.mode
777
- ? getObjMembers(
778
- // @ts-ignore
779
- extensions.mode,
780
- )
781
- : {};
867
+ const modeValues = extensions?.mode ? getObjMembers(extensions.mode as any) : {};
782
868
  for (const mode of ['.', ...Object.keys(modeValues)]) {
869
+ const modeValue = mode === '.' ? token.$value : (evaluate((modeValues as any)[mode]) as any);
783
870
  token.mode[mode] = {
784
- id: token.id,
785
- // @ts-ignore
786
- $type: token.$type,
787
- // @ts-ignore
788
- $value: mode === '.' ? token.$value : evaluate(modeValues[mode]),
871
+ $value: modeValue,
872
+ originalValue: modeValue,
789
873
  source: {
790
874
  loc: filename ? filename.href : undefined,
791
875
  // @ts-ignore
792
- node: mode === '.' ? structuredClone(token.source.node) : modeValues[mode],
876
+ node: modeValues[mode],
793
877
  },
794
878
  };
795
- if (token.$description) {
796
- token.mode[mode]!.$description = token.$description;
797
- }
798
879
  }
799
880
 
881
+ // logger.debug({
882
+ // message: `${token.id}: validateTokenNode`,
883
+ // group: 'parser', label: 'validate',
884
+ // label: 'validate',
885
+ // timing: performance.now() - start,
886
+ // });
800
887
  return token;
801
888
  }