@terrazzo/parser 0.0.18 → 0.0.19

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @terrazzo/parser
2
2
 
3
+ ## 0.0.19
4
+
5
+ ### Patch Changes
6
+
7
+ - [#313](https://github.com/terrazzoapp/terrazzo/pull/313) [`1408594`](https://github.com/terrazzoapp/terrazzo/commit/1408594de029f57137c936dc2ff9ab949f039215) Thanks [@drwpow](https://github.com/drwpow)! - Fix bug in gradient position aliasing
8
+
9
+ - [#313](https://github.com/terrazzoapp/terrazzo/pull/313) [`1408594`](https://github.com/terrazzoapp/terrazzo/commit/1408594de029f57137c936dc2ff9ab949f039215) Thanks [@drwpow](https://github.com/drwpow)! - Improve alias type validation
10
+
3
11
  ## 0.0.18
4
12
 
5
13
  ### Patch Changes
package/logger.js CHANGED
@@ -25,8 +25,12 @@ export function formatMessage(entry, severity) {
25
25
  }
26
26
  if (entry.src) {
27
27
  const start = entry.node?.loc?.start;
28
- // note: strip "file://" protocol, but not href
29
- message = `${message}\n\n${entry.filename ? `${entry.filename.href.replace(/^file:\/\//, '')}:${start?.line ?? 0}:${start?.column ?? 0}\n\n` : ''}${codeFrameColumns(entry.src, { start }, { highlightCode: false })}`;
28
+ // strip "file://" protocol, but not href
29
+ const loc = entry.filename
30
+ ? `${entry.filename?.href.replace(/^file:\/\//, '')}:${start?.line ?? 0}:${start?.column ?? 0}\n\n`
31
+ : '';
32
+ const codeFrame = codeFrameColumns(entry.src, { start }, { highlightCode: false });
33
+ message = `${message}\n\n${loc}${codeFrame}`;
30
34
  }
31
35
  return message;
32
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrazzo/parser",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "description": "Parser/validator for the Design Tokens Community Group (DTCG) standard.",
5
5
  "type": "module",
6
6
  "author": {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@humanwhocodes/momoa": "^3.2.1",
33
+ "@humanwhocodes/momoa": "^3.2.2",
34
34
  "@types/babel__code-frame": "^7.0.6",
35
35
  "@types/culori": "^2.1.1",
36
36
  "culori": "^4.0.1",
@@ -38,7 +38,7 @@
38
38
  "merge-anything": "^5.1.7",
39
39
  "picocolors": "^1.1.0",
40
40
  "wildcard-match": "^5.1.3",
41
- "yaml": "^2.5.1",
41
+ "yaml": "^2.6.0",
42
42
  "@terrazzo/token-tools": "^0.0.9"
43
43
  },
44
44
  "devDependencies": {
package/parse/index.js CHANGED
@@ -131,7 +131,12 @@ export default async function parse(
131
131
  if (mode === '.') {
132
132
  continue; // skip shadow of root value
133
133
  }
134
- applyAliases(tokens[id].mode[mode], { tokens, node: tokens[id].mode[mode].source.node, logger });
134
+ applyAliases(tokens[id].mode[mode], {
135
+ tokens,
136
+ node: tokens[id].mode[mode].source.node,
137
+ logger,
138
+ src: sources[tokens[id].source.loc]?.src,
139
+ });
135
140
  }
136
141
  }
137
142
  logger.debug({
@@ -419,10 +424,10 @@ export function maybeJSONString(input) {
419
424
  export function resolveAlias(alias, { tokens, logger, filename, src, node, scanned = [] }) {
420
425
  const { id } = parseAlias(alias);
421
426
  if (!tokens[id]) {
422
- logger.error({ message: `Alias "${alias}" not found`, filename, src, node });
427
+ logger.error({ message: `Alias "${alias}" not found.`, filename, src, node });
423
428
  }
424
429
  if (scanned.includes(id)) {
425
- logger.error({ message: `Circular alias detected from "${alias}"`, filename, src, node });
430
+ logger.error({ message: `Circular alias detected from "${alias}".`, filename, src, node });
426
431
  }
427
432
  const token = tokens[id];
428
433
  if (!isAlias(token.$value)) {
@@ -431,8 +436,43 @@ export function resolveAlias(alias, { tokens, logger, filename, src, node, scann
431
436
  return resolveAlias(token.$value, { tokens, logger, filename, node, src, scanned: [...scanned, id] });
432
437
  }
433
438
 
439
+ /** Throw error if resolved alias for composite properties doesn’t match expected $type. */
440
+ const COMPOSITE_TYPE_VALUES = {
441
+ border: {
442
+ color: ['color'],
443
+ width: ['dimension'],
444
+ strokeStyle: ['strokeStyle'],
445
+ },
446
+ gradient: {
447
+ color: ['color'],
448
+ position: ['number'],
449
+ },
450
+ shadow: {
451
+ color: ['color'],
452
+ position: ['dimension'],
453
+ },
454
+ strokeStyle: {
455
+ dashArray: ['dimension'],
456
+ },
457
+ transition: {
458
+ duration: ['duration'],
459
+ delay: ['duration'],
460
+ timingFunction: ['cubicBezier'],
461
+ },
462
+ typography: {
463
+ fontFamily: ['fontFamily'],
464
+ fontSize: ['dimension'],
465
+ fontWeight: ['fontWeight'],
466
+ letterSpacing: ['dimension'],
467
+ lineHeight: ['dimension', 'number'],
468
+ },
469
+ };
470
+
434
471
  /** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
435
472
  function applyAliases(token, { tokens, logger, filename, src, node }) {
473
+ const $valueNode = (node.type === 'Object' && node.members.find((m) => m.name.value === '$value')?.value) || node;
474
+ const expectedAliasTypes = COMPOSITE_TYPE_VALUES[token.$type];
475
+
436
476
  // handle simple aliases
437
477
  if (isAlias(token.$value)) {
438
478
  const aliasOfID = resolveAlias(token.$value, { tokens, logger, filename, node, src });
@@ -441,15 +481,16 @@ function applyAliases(token, { tokens, logger, filename, src, node }) {
441
481
  token.aliasOf = aliasOfID;
442
482
  token.$value = aliasOf.mode[aliasMode]?.$value || aliasOf.$value;
443
483
  if (token.$type && token.$type !== aliasOf.$type) {
444
- logger.warn({
445
- message: `Token ${token.id} has $type "${token.$type}" but aliased ${aliasOfID} of $type "${aliasOf.$type}"`,
446
- node,
484
+ logger.error({
485
+ message: `Invalid alias: expected $type: "${token.$type}", received $type: "${aliasOf.$type}".`,
486
+ node: $valueNode,
487
+ filename,
488
+ src,
447
489
  });
448
- token.$type = aliasOf.$type;
449
- } else {
450
- token.$type = aliasOf.$type;
451
490
  }
491
+ token.$type = aliasOf.$type;
452
492
  }
493
+
453
494
  // handle aliases within array values (e.g. cubicBezier, gradient)
454
495
  else if (Array.isArray(token.$value)) {
455
496
  // some arrays are primitives, some are objects. handle both
@@ -473,8 +514,20 @@ function applyAliases(token, { tokens, logger, filename, src, node }) {
473
514
  }
474
515
  const aliasOfID = resolveAlias(token.$value[i][property], { tokens, logger, filename, node, src });
475
516
  const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[i][property]);
517
+ const aliasToken = tokens[aliasOfID];
518
+
519
+ if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
520
+ const elementNode = $valueNode.elements[i].value;
521
+ logger.error({
522
+ message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
523
+ node: elementNode.members.find((m) => m.name.value === property).value,
524
+ filename,
525
+ src,
526
+ });
527
+ }
528
+
476
529
  token.partialAliasOf[i][property] = aliasID; // also keep the shallow alias here, too!
477
- token.$value[i][property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
530
+ token.$value[i][property] = aliasToken.mode[aliasMode]?.$value || aliasToken.$value;
478
531
  }
479
532
  }
480
533
  }
@@ -494,8 +547,18 @@ function applyAliases(token, { tokens, logger, filename, src, node }) {
494
547
  const aliasOfID = resolveAlias(token.$value[property], { tokens, logger, filename, node, src });
495
548
  const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[property]);
496
549
  token.partialAliasOf[property] = aliasID; // keep the shallow alias!
497
- token.$value[property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
550
+ const aliasToken = tokens[aliasOfID];
551
+ if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
552
+ logger.error({
553
+ message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
554
+ node: $valueNode.members.find((m) => m.name.value === property).value,
555
+ filename,
556
+ src,
557
+ });
558
+ }
559
+ token.$value[property] = aliasToken.mode[aliasMode]?.$value || aliasToken.$value;
498
560
  }
561
+
499
562
  // strokeStyle has an array within an object
500
563
  else if (Array.isArray(token.$value[property])) {
501
564
  for (let i = 0; i < token.$value[property].length; i++) {
@@ -509,6 +572,16 @@ function applyAliases(token, { tokens, logger, filename, src, node }) {
509
572
  }
510
573
  const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[property][i]);
511
574
  token.partialAliasOf[property][i] = aliasID; // keep the shallow alias!
575
+ const aliasToken = tokens[aliasOfID];
576
+ if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
577
+ const arrayNode = $valueNode.members.find((m) => m.name.value === property).value;
578
+ logger.error({
579
+ message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
580
+ node: arrayNode.elements[i],
581
+ filename,
582
+ src,
583
+ });
584
+ }
512
585
  token.$value[property][i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
513
586
  }
514
587
  }
@@ -73,7 +73,7 @@ export default function normalizeValue(token) {
73
73
  for (let i = 0; i < token.$value.length; i++) {
74
74
  const stop = { ...token.$value[i] };
75
75
  stop.color = normalizeValue({ $type: 'color', $value: stop.color });
76
- if (typeof stop.position !== 'number') {
76
+ if (stop.position === undefined) {
77
77
  stop.position = i / (token.$value.length - 1);
78
78
  }
79
79
  output.push(stop);
@@ -12,7 +12,7 @@ export interface ValidateOptions {
12
12
  logger: Logger;
13
13
  }
14
14
 
15
- export function validateAlias($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
15
+ export function validateAliasSyntax($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
16
16
 
17
17
  export function validateBorder($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
18
18
 
package/parse/validate.js CHANGED
@@ -92,7 +92,7 @@ function validateMembersAs($value, properties, node, { filename, src, logger })
92
92
  }
93
93
  const value = members[property];
94
94
  if (isMaybeAlias(value)) {
95
- validateAlias(value, node, { filename, src, logger });
95
+ validateAliasSyntax(value, node, { filename, src, logger });
96
96
  } else {
97
97
  validator(value, node, { filename, src, logger });
98
98
  }
@@ -100,13 +100,13 @@ function validateMembersAs($value, properties, node, { filename, src, logger })
100
100
  }
101
101
 
102
102
  /**
103
- * Verify an Alias token is valid
103
+ * Verify an Alias $value is formatted correctly
104
104
  * @param {ValueNode} $value
105
105
  * @param {AnyNode} node
106
106
  * @param {ValidateOptions} options
107
107
  * @return {void}
108
108
  */
109
- export function validateAlias($value, node, { filename, src, logger }) {
109
+ export function validateAliasSyntax($value, node, { filename, src, logger }) {
110
110
  if ($value.type !== 'String' || !isAlias($value.value)) {
111
111
  logger.error({ message: `Invalid alias: ${print($value)}`, filename, node: $value, src });
112
112
  }
@@ -476,7 +476,7 @@ export function validateStrokeStyle($value, node, { filename, src, logger }) {
476
476
  for (const element of dashArray.elements) {
477
477
  if (element.value.type === 'String' && element.value.value !== '') {
478
478
  if (isMaybeAlias(element.value)) {
479
- validateAlias(element.value, node, { logger, src });
479
+ validateAliasSyntax(element.value, node, { logger, src });
480
480
  } else {
481
481
  validateDimension(element.value, node, { logger, src });
482
482
  }
@@ -550,7 +550,7 @@ export default function validate(node, { filename, src, logger }) {
550
550
  // If top-level value is a valid alias, this is valid (no need for $type)
551
551
  // ⚠️ Important: ALL Object and Array nodes below will need to check for aliases within!
552
552
  if (isMaybeAlias($value)) {
553
- validateAlias($value, node, { logger, src });
553
+ validateAliasSyntax($value, node, { logger, src });
554
554
  return;
555
555
  }
556
556