@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 +8 -0
- package/logger.js +6 -2
- package/package.json +3 -3
- package/parse/index.js +84 -11
- package/parse/normalize.js +1 -1
- package/parse/validate.d.ts +1 -1
- package/parse/validate.js +5 -5
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
|
-
//
|
|
29
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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], {
|
|
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
|
|
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}"
|
|
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.
|
|
445
|
-
message: `
|
|
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] =
|
|
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
|
-
|
|
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
|
}
|
package/parse/normalize.js
CHANGED
|
@@ -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 (
|
|
76
|
+
if (stop.position === undefined) {
|
|
77
77
|
stop.position = i / (token.$value.length - 1);
|
|
78
78
|
}
|
|
79
79
|
output.push(stop);
|
package/parse/validate.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface ValidateOptions {
|
|
|
12
12
|
logger: Logger;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
553
|
+
validateAliasSyntax($value, node, { logger, src });
|
|
554
554
|
return;
|
|
555
555
|
}
|
|
556
556
|
|