@terrazzo/parser 0.7.2 → 0.8.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 (105) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.d.ts +707 -12
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4598 -12
  5. package/dist/index.js.map +1 -1
  6. package/package.json +11 -7
  7. package/rolldown.config.ts +24 -0
  8. package/src/build/index.ts +2 -2
  9. package/src/index.ts +76 -1
  10. package/src/parse/index.ts +72 -13
  11. package/src/parse/json.ts +35 -29
  12. package/src/parse/normalize.ts +4 -14
  13. package/src/parse/validate.ts +81 -68
  14. package/dist/build/index.d.ts +0 -20
  15. package/dist/build/index.d.ts.map +0 -1
  16. package/dist/build/index.js +0 -166
  17. package/dist/build/index.js.map +0 -1
  18. package/dist/config.d.ts +0 -8
  19. package/dist/config.d.ts.map +0 -1
  20. package/dist/config.js +0 -290
  21. package/dist/config.js.map +0 -1
  22. package/dist/lib/code-frame.d.ts +0 -31
  23. package/dist/lib/code-frame.d.ts.map +0 -1
  24. package/dist/lib/code-frame.js +0 -108
  25. package/dist/lib/code-frame.js.map +0 -1
  26. package/dist/lint/index.d.ts +0 -12
  27. package/dist/lint/index.d.ts.map +0 -1
  28. package/dist/lint/index.js +0 -105
  29. package/dist/lint/index.js.map +0 -1
  30. package/dist/lint/plugin-core/index.d.ts +0 -13
  31. package/dist/lint/plugin-core/index.d.ts.map +0 -1
  32. package/dist/lint/plugin-core/index.js +0 -40
  33. package/dist/lint/plugin-core/index.js.map +0 -1
  34. package/dist/lint/plugin-core/lib/docs.d.ts +0 -2
  35. package/dist/lint/plugin-core/lib/docs.d.ts.map +0 -1
  36. package/dist/lint/plugin-core/lib/docs.js +0 -4
  37. package/dist/lint/plugin-core/lib/docs.js.map +0 -1
  38. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +0 -40
  39. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts.map +0 -1
  40. package/dist/lint/plugin-core/rules/a11y-min-contrast.js +0 -58
  41. package/dist/lint/plugin-core/rules/a11y-min-contrast.js.map +0 -1
  42. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +0 -14
  43. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts.map +0 -1
  44. package/dist/lint/plugin-core/rules/a11y-min-font-size.js +0 -45
  45. package/dist/lint/plugin-core/rules/a11y-min-font-size.js.map +0 -1
  46. package/dist/lint/plugin-core/rules/colorspace.d.ts +0 -15
  47. package/dist/lint/plugin-core/rules/colorspace.d.ts.map +0 -1
  48. package/dist/lint/plugin-core/rules/colorspace.js +0 -85
  49. package/dist/lint/plugin-core/rules/colorspace.js.map +0 -1
  50. package/dist/lint/plugin-core/rules/consistent-naming.d.ts +0 -12
  51. package/dist/lint/plugin-core/rules/consistent-naming.d.ts.map +0 -1
  52. package/dist/lint/plugin-core/rules/consistent-naming.js +0 -49
  53. package/dist/lint/plugin-core/rules/consistent-naming.js.map +0 -1
  54. package/dist/lint/plugin-core/rules/descriptions.d.ts +0 -10
  55. package/dist/lint/plugin-core/rules/descriptions.d.ts.map +0 -1
  56. package/dist/lint/plugin-core/rules/descriptions.js +0 -32
  57. package/dist/lint/plugin-core/rules/descriptions.js.map +0 -1
  58. package/dist/lint/plugin-core/rules/duplicate-values.d.ts +0 -10
  59. package/dist/lint/plugin-core/rules/duplicate-values.d.ts.map +0 -1
  60. package/dist/lint/plugin-core/rules/duplicate-values.js +0 -65
  61. package/dist/lint/plugin-core/rules/duplicate-values.js.map +0 -1
  62. package/dist/lint/plugin-core/rules/max-gamut.d.ts +0 -15
  63. package/dist/lint/plugin-core/rules/max-gamut.d.ts.map +0 -1
  64. package/dist/lint/plugin-core/rules/max-gamut.js +0 -101
  65. package/dist/lint/plugin-core/rules/max-gamut.js.map +0 -1
  66. package/dist/lint/plugin-core/rules/required-children.d.ts +0 -19
  67. package/dist/lint/plugin-core/rules/required-children.d.ts.map +0 -1
  68. package/dist/lint/plugin-core/rules/required-children.js +0 -78
  69. package/dist/lint/plugin-core/rules/required-children.js.map +0 -1
  70. package/dist/lint/plugin-core/rules/required-modes.d.ts +0 -14
  71. package/dist/lint/plugin-core/rules/required-modes.d.ts.map +0 -1
  72. package/dist/lint/plugin-core/rules/required-modes.js +0 -52
  73. package/dist/lint/plugin-core/rules/required-modes.js.map +0 -1
  74. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +0 -11
  75. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts.map +0 -1
  76. package/dist/lint/plugin-core/rules/required-typography-properties.js +0 -38
  77. package/dist/lint/plugin-core/rules/required-typography-properties.js.map +0 -1
  78. package/dist/logger.d.ts +0 -77
  79. package/dist/logger.d.ts.map +0 -1
  80. package/dist/logger.js +0 -136
  81. package/dist/logger.js.map +0 -1
  82. package/dist/parse/alias.d.ts +0 -34
  83. package/dist/parse/alias.d.ts.map +0 -1
  84. package/dist/parse/alias.js +0 -302
  85. package/dist/parse/alias.js.map +0 -1
  86. package/dist/parse/index.d.ts +0 -36
  87. package/dist/parse/index.d.ts.map +0 -1
  88. package/dist/parse/index.js +0 -226
  89. package/dist/parse/index.js.map +0 -1
  90. package/dist/parse/json.d.ts +0 -52
  91. package/dist/parse/json.d.ts.map +0 -1
  92. package/dist/parse/json.js +0 -161
  93. package/dist/parse/json.js.map +0 -1
  94. package/dist/parse/normalize.d.ts +0 -24
  95. package/dist/parse/normalize.d.ts.map +0 -1
  96. package/dist/parse/normalize.js +0 -185
  97. package/dist/parse/normalize.js.map +0 -1
  98. package/dist/parse/validate.d.ts +0 -63
  99. package/dist/parse/validate.d.ts.map +0 -1
  100. package/dist/parse/validate.js +0 -798
  101. package/dist/parse/validate.js.map +0 -1
  102. package/dist/types.d.ts +0 -265
  103. package/dist/types.d.ts.map +0 -1
  104. package/dist/types.js +0 -2
  105. package/dist/types.js.map +0 -1
@@ -1,798 +0,0 @@
1
- import { evaluate, print, } from '@humanwhocodes/momoa';
2
- import { isAlias, isTokenMatch, splitID } from '@terrazzo/token-tools';
3
- import { getObjMembers, injectObjMembers } from './json.js';
4
- const listFormat = new Intl.ListFormat('en-us', { type: 'disjunction' });
5
- export const VALID_COLORSPACES = new Set([
6
- 'adobe-rgb',
7
- 'display-p3',
8
- 'hsl',
9
- 'hwb',
10
- 'lab',
11
- 'lch',
12
- 'oklab',
13
- 'oklch',
14
- 'prophoto',
15
- 'rec2020',
16
- 'srgb',
17
- 'srgb-linear',
18
- 'xyz',
19
- 'xyz-d50',
20
- 'xyz-d65',
21
- ]);
22
- export const FONT_WEIGHT_VALUES = new Set([
23
- 'thin',
24
- 'hairline',
25
- 'extra-light',
26
- 'ultra-light',
27
- 'light',
28
- 'normal',
29
- 'regular',
30
- 'book',
31
- 'medium',
32
- 'semi-bold',
33
- 'demi-bold',
34
- 'bold',
35
- 'extra-bold',
36
- 'ultra-bold',
37
- 'black',
38
- 'heavy',
39
- 'extra-black',
40
- 'ultra-black',
41
- ]);
42
- export const STROKE_STYLE_VALUES = new Set([
43
- 'solid',
44
- 'dashed',
45
- 'dotted',
46
- 'double',
47
- 'groove',
48
- 'ridge',
49
- 'outset',
50
- 'inset',
51
- ]);
52
- export const STROKE_STYLE_LINE_CAP_VALUES = new Set(['round', 'butt', 'square']);
53
- /** Distinct from isAlias() in that this accepts malformed aliases */
54
- function isMaybeAlias(node) {
55
- if (node?.type === 'String') {
56
- return node.value.startsWith('{');
57
- }
58
- return false;
59
- }
60
- /** Assert object members match given types */
61
- function validateMembersAs($value, properties, node, { filename, src, logger }) {
62
- const members = getObjMembers($value);
63
- for (const [name, value] of Object.entries(properties)) {
64
- const { validator, required } = value;
65
- if (!members[name]) {
66
- if (required) {
67
- logger.error({
68
- group: 'parser',
69
- label: 'validate',
70
- message: `Missing required property "${name}"`,
71
- filename,
72
- node: $value,
73
- src,
74
- });
75
- }
76
- continue;
77
- }
78
- const memberValue = members[name];
79
- if (isMaybeAlias(memberValue)) {
80
- validateAliasSyntax(memberValue, node, { filename, src, logger });
81
- }
82
- else {
83
- validator(memberValue, node, { filename, src, logger });
84
- }
85
- }
86
- }
87
- /** Verify an Alias $value is formatted correctly */
88
- export function validateAliasSyntax($value, _node, { filename, src, logger }) {
89
- if ($value.type !== 'String' || !isAlias($value.value)) {
90
- logger.error({
91
- group: 'parser',
92
- label: 'validate',
93
- message: `Invalid alias: ${print($value)}`,
94
- filename,
95
- node: $value,
96
- src,
97
- });
98
- }
99
- }
100
- /** Verify a Border token is valid */
101
- export function validateBorder($value, node, { filename, src, logger }) {
102
- if ($value.type !== 'Object') {
103
- logger.error({
104
- group: 'parser',
105
- label: 'validate',
106
- message: `Expected object, received ${$value.type}`,
107
- filename,
108
- node: $value,
109
- src,
110
- });
111
- }
112
- else {
113
- validateMembersAs($value, {
114
- color: { validator: validateColor, required: true },
115
- style: { validator: validateStrokeStyle, required: true },
116
- width: { validator: validateDimension, required: true },
117
- }, node, { filename, src, logger });
118
- }
119
- }
120
- /** Verify a Color token is valid */
121
- export function validateColor($value, node, { filename, src, logger }) {
122
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
123
- if ($value.type === 'String') {
124
- // TODO: enable when object notation is finalized
125
- // logger.warn({
126
- // filename,
127
- // message: 'String colors are no longer recommended; please use the object notation instead.',
128
- // node: $value,
129
- // src,
130
- // });
131
- if ($value.value === '') {
132
- logger.error({ ...baseMessage, message: 'Expected color, received empty string' });
133
- }
134
- }
135
- else if ($value.type === 'Object') {
136
- // allow "channels" but raise warning. also rename as a workaround (mutating the AST is a bad idea in general, but this is safe)
137
- const channelMemberI = $value.members.findIndex((m) => m.name.type === 'String' && m.name.value === 'channels');
138
- if (channelMemberI !== -1) {
139
- logger.warn({ ...baseMessage, message: '"channels" is deprecated; rename "channels" to "components"' });
140
- $value.members[channelMemberI].name.value = 'components';
141
- }
142
- validateMembersAs($value, {
143
- colorSpace: {
144
- validator: (v) => {
145
- if (v.type !== 'String') {
146
- logger.error({
147
- ...baseMessage,
148
- message: `Expected string, received ${print(v)}`,
149
- node: v,
150
- });
151
- }
152
- if (!VALID_COLORSPACES.has(v.value)) {
153
- logger.error({
154
- ...baseMessage,
155
- message: `Unsupported colorspace ${print(v)}`,
156
- node: v,
157
- });
158
- }
159
- },
160
- required: true,
161
- },
162
- components: {
163
- validator: (v) => {
164
- if (v.type !== 'Array') {
165
- logger.error({
166
- ...baseMessage,
167
- message: `Expected array, received ${print(v)}`,
168
- node: v,
169
- });
170
- }
171
- else {
172
- // note: in the future, length will change depending on colorSpace, e.g. CMYK
173
- // but in the current spec it’s 3 for now.
174
- if (v.elements?.length !== 3) {
175
- logger.error({
176
- ...baseMessage,
177
- message: `Expected 3 components, received ${v.elements?.length ?? 0}`,
178
- node: v,
179
- });
180
- }
181
- for (const element of v.elements) {
182
- if (element.value.type !== 'Number') {
183
- logger.error({
184
- ...baseMessage,
185
- message: `Expected number, received ${print(element.value)}`,
186
- node: element,
187
- });
188
- }
189
- }
190
- }
191
- },
192
- required: true,
193
- },
194
- hex: {
195
- validator: (v) => {
196
- if (v.type !== 'String' ||
197
- // this is a weird one—with the RegEx we test, it will work for
198
- // lengths of 3, 4, 6, and 8 (but not 5 or 7). So we check length
199
- // here, to keep the RegEx simple and readable. The "+ 1" is just
200
- // accounting for the '#' prefix character.
201
- v.value.length === 5 + 1 ||
202
- v.value.length === 7 + 1 ||
203
- !/^#[a-f0-9]{3,8}$/i.test(v.value)) {
204
- logger.error({
205
- ...baseMessage,
206
- message: `Invalid hex color ${print(v)}`,
207
- node: v,
208
- });
209
- }
210
- },
211
- },
212
- alpha: { validator: validateNumber },
213
- }, node, { filename, src, logger });
214
- }
215
- else {
216
- logger.error({
217
- ...baseMessage,
218
- message: `Expected object, received ${$value.type}`,
219
- node: $value,
220
- });
221
- }
222
- }
223
- /** Verify a Cubic Bézier token is valid */
224
- export function validateCubicBezier($value, _node, { filename, src, logger }) {
225
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
226
- if ($value.type !== 'Array') {
227
- logger.error({ ...baseMessage, message: `Expected array of numbers, received ${print($value)}` });
228
- }
229
- else if (!$value.elements.every((e) => e.value.type === 'Number')) {
230
- logger.error({ ...baseMessage, message: 'Expected an array of 4 numbers, received some non-numbers' });
231
- }
232
- else if ($value.elements.length !== 4) {
233
- logger.error({ ...baseMessage, message: `Expected an array of 4 numbers, received ${$value.elements.length}` });
234
- }
235
- }
236
- /** Verify a Dimension token is valid */
237
- export function validateDimension($value, _node, { filename, src, logger }) {
238
- if ($value.type === 'Number' && $value.value === 0) {
239
- return; // `0` is a valid number
240
- }
241
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
242
- // Give priority to object notation because it’s a faster code path
243
- if ($value.type === 'Object') {
244
- const { value, unit } = getObjMembers($value);
245
- if (!value) {
246
- logger.error({ ...baseMessage, message: 'Missing required property "value".' });
247
- }
248
- if (!unit) {
249
- logger.error({ ...baseMessage, message: 'Missing required property "unit".' });
250
- }
251
- if (value.type !== 'Number') {
252
- logger.error({
253
- ...baseMessage,
254
- message: `Expected number, received ${value.type}`,
255
- node: value,
256
- });
257
- }
258
- if (!['px', 'em', 'rem'].includes(unit.value)) {
259
- logger.error({
260
- ...baseMessage,
261
- message: `Expected unit "px", "em", or "rem", received ${print(unit)}`,
262
- node: unit,
263
- });
264
- }
265
- return;
266
- }
267
- // Backwards compat: string
268
- if ($value.type !== 'String') {
269
- logger.error({ ...baseMessage, message: `Expected string, received ${$value.type}` });
270
- }
271
- const value = $value.value.match(/^-?[0-9.]+/)?.[0];
272
- const unit = $value.value.replace(value, '');
273
- if ($value.value === '') {
274
- logger.error({ ...baseMessage, message: 'Expected dimension, received empty string' });
275
- }
276
- else if (!['px', 'em', 'rem'].includes(unit)) {
277
- logger.error({
278
- ...baseMessage,
279
- message: `Expected unit "px", "em", or "rem", received ${JSON.stringify(unit || $value.value)}`,
280
- });
281
- }
282
- else if (!Number.isFinite(Number.parseFloat(value))) {
283
- logger.error({ ...baseMessage, message: `Expected dimension with units, received ${print($value)}` });
284
- }
285
- }
286
- /** Verify a Duration token is valid */
287
- export function validateDuration($value, _node, { filename, src, logger }) {
288
- if ($value.type === 'Number' && $value.value === 0) {
289
- return; // `0` is a valid number
290
- }
291
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
292
- // Give priority to object notation because it’s a faster code path
293
- if ($value.type === 'Object') {
294
- const { value, unit } = getObjMembers($value);
295
- if (!value) {
296
- logger.error({ ...baseMessage, message: 'Missing required property "value".' });
297
- }
298
- if (!unit) {
299
- logger.error({ ...baseMessage, message: 'Missing required property "unit".' });
300
- }
301
- if (value?.type !== 'Number') {
302
- logger.error({
303
- ...baseMessage,
304
- message: `Expected number, received ${value?.type}`,
305
- node: value,
306
- });
307
- }
308
- if (!['ms', 's'].includes(unit.value)) {
309
- logger.error({
310
- ...baseMessage,
311
- message: `Expected unit "ms" or "s", received ${print(unit)}`,
312
- node: unit,
313
- });
314
- }
315
- return;
316
- }
317
- // Backwards compat: string
318
- if ($value.type !== 'String') {
319
- logger.error({ ...baseMessage, message: `Expected string, received ${$value.type}` });
320
- }
321
- const value = $value.value.match(/^-?[0-9.]+/)?.[0];
322
- const unit = $value.value.replace(value, '');
323
- if ($value.value === '') {
324
- logger.error({ ...baseMessage, message: 'Expected duration, received empty string' });
325
- }
326
- else if (!['ms', 's'].includes(unit)) {
327
- logger.error({
328
- ...baseMessage,
329
- message: `Expected unit "ms" or "s", received ${JSON.stringify(unit || $value.value)}`,
330
- });
331
- }
332
- else if (!Number.isFinite(Number.parseFloat(value))) {
333
- logger.error({ ...baseMessage, message: `Expected duration with units, received ${print($value)}` });
334
- }
335
- }
336
- /** Verify a Font Family token is valid */
337
- export function validateFontFamily($value, _node, { filename, src, logger }) {
338
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
339
- if ($value.type !== 'String' && $value.type !== 'Array') {
340
- logger.error({ ...baseMessage, message: `Expected string or array of strings, received ${$value.type}` });
341
- }
342
- if ($value.type === 'String' && $value.value === '') {
343
- logger.error({ ...baseMessage, message: 'Expected font family name, received empty string' });
344
- }
345
- if ($value.type === 'Array' && !$value.elements.every((e) => e.value.type === 'String' && e.value.value !== '')) {
346
- logger.error({
347
- ...baseMessage,
348
- message: 'Expected an array of strings, received some non-strings or empty strings',
349
- });
350
- }
351
- }
352
- /** Verify a Font Weight token is valid */
353
- export function validateFontWeight($value, _node, { filename, src, logger }) {
354
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
355
- if ($value.type !== 'String' && $value.type !== 'Number') {
356
- logger.error({ ...baseMessage, message: `Expected a font weight name or number 0–1000, received ${$value.type}` });
357
- }
358
- if ($value.type === 'String' && !FONT_WEIGHT_VALUES.has($value.value)) {
359
- logger.error({
360
- ...baseMessage,
361
- message: `Unknown font weight ${print($value)}. Expected one of: ${listFormat.format([...FONT_WEIGHT_VALUES])}.`,
362
- });
363
- }
364
- if ($value.type === 'Number' && ($value.value < 0 || $value.value > 1000)) {
365
- logger.error({ ...baseMessage, message: `Expected number 0–1000, received ${print($value)}` });
366
- }
367
- }
368
- /** Verify a Gradient token is valid */
369
- export function validateGradient($value, _node, { filename, src, logger }) {
370
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
371
- if ($value.type !== 'Array') {
372
- logger.error({ ...baseMessage, message: `Expected array of gradient stops, received ${$value.type}` });
373
- }
374
- else {
375
- for (let i = 0; i < $value.elements.length; i++) {
376
- const element = $value.elements[i];
377
- if (element.value.type !== 'Object') {
378
- logger.error({
379
- ...baseMessage,
380
- message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
381
- node: element,
382
- });
383
- break;
384
- }
385
- validateMembersAs(element.value, {
386
- color: { validator: validateColor, required: true },
387
- position: { validator: validateNumber, required: true },
388
- }, element, { filename, src, logger });
389
- }
390
- }
391
- }
392
- /** Verify a Number token is valid */
393
- export function validateNumber($value, _node, { filename, src, logger }) {
394
- if ($value.type !== 'Number') {
395
- logger.error({
396
- group: 'parser',
397
- label: 'validate',
398
- message: `Expected number, received ${$value.type}`,
399
- filename,
400
- node: $value,
401
- src,
402
- });
403
- }
404
- }
405
- /** Verify a Boolean token is valid */
406
- export function validateBoolean($value, _node, { filename, src, logger }) {
407
- if ($value.type !== 'Boolean') {
408
- logger.error({
409
- group: 'parser',
410
- label: 'validate',
411
- message: `Expected boolean, received ${$value.type}`,
412
- filename,
413
- node: $value,
414
- src,
415
- });
416
- }
417
- }
418
- /** Verify a Shadow token’s value is valid */
419
- export function validateShadowLayer($value, node, { filename, src, logger }) {
420
- if ($value.type !== 'Object') {
421
- logger.error({
422
- group: 'parser',
423
- label: 'validate',
424
- message: `Expected Object, received ${$value.type}`,
425
- filename,
426
- node: $value,
427
- src,
428
- });
429
- }
430
- else {
431
- validateMembersAs($value, {
432
- color: { validator: validateColor, required: true },
433
- offsetX: { validator: validateDimension, required: true },
434
- offsetY: { validator: validateDimension, required: true },
435
- blur: { validator: validateDimension },
436
- spread: { validator: validateDimension },
437
- inset: { validator: validateBoolean },
438
- }, node, { filename, src, logger });
439
- }
440
- }
441
- /** Verify a Stroke Style token is valid. */
442
- export function validateStrokeStyle($value, node, { filename, src, logger }) {
443
- const baseMessage = { group: 'parser', label: 'validate', filename, node: $value, src };
444
- // note: strokeStyle’s values are NOT aliasable (unless by string, but that breaks validations)
445
- if ($value.type === 'String') {
446
- if (!STROKE_STYLE_VALUES.has($value.value)) {
447
- logger.error({
448
- ...baseMessage,
449
- message: `Unknown stroke style ${print($value)}. Expected one of: ${listFormat.format([
450
- ...STROKE_STYLE_VALUES,
451
- ])}.`,
452
- });
453
- }
454
- }
455
- else if ($value.type === 'Object') {
456
- const strokeMembers = getObjMembers($value);
457
- for (const property of ['dashArray', 'lineCap']) {
458
- if (!strokeMembers[property]) {
459
- logger.error({ ...baseMessage, message: `Missing required property "${property}"` });
460
- }
461
- }
462
- const { lineCap, dashArray } = strokeMembers;
463
- if (lineCap?.type !== 'String' || !STROKE_STYLE_LINE_CAP_VALUES.has(lineCap.value)) {
464
- logger.error({
465
- ...baseMessage,
466
- message: `Unknown lineCap value ${print(lineCap)}. Expected one of: ${listFormat.format([
467
- ...STROKE_STYLE_LINE_CAP_VALUES,
468
- ])}.`,
469
- node,
470
- });
471
- }
472
- if (dashArray?.type === 'Array') {
473
- for (const element of dashArray.elements) {
474
- if (element.value.type === 'String' && element.value.value !== '') {
475
- if (isMaybeAlias(element.value)) {
476
- validateAliasSyntax(element.value, node, { logger, src });
477
- }
478
- else {
479
- validateDimension(element.value, node, { logger, src });
480
- }
481
- }
482
- else {
483
- logger.error({
484
- ...baseMessage,
485
- message: 'Expected array of strings, recieved some non-strings or empty strings.',
486
- node: element,
487
- });
488
- }
489
- }
490
- }
491
- else {
492
- logger.error({ ...baseMessage, message: `Expected array of strings, received ${dashArray.type}` });
493
- }
494
- }
495
- else {
496
- logger.error({ ...baseMessage, message: `Expected string or object, received ${$value.type}` });
497
- }
498
- }
499
- /** Verify a Transition token is valid */
500
- export function validateTransition($value, node, { filename, src, logger }) {
501
- if ($value.type !== 'Object') {
502
- logger.error({
503
- group: 'parser',
504
- label: 'validate',
505
- message: `Expected object, received ${$value.type}`,
506
- filename,
507
- node: $value,
508
- src,
509
- });
510
- }
511
- else {
512
- validateMembersAs($value, {
513
- duration: { validator: validateDuration, required: true },
514
- delay: { validator: validateDuration, required: false }, // note: spec says delay is required, but Terrazzo makes delay optional
515
- timingFunction: { validator: validateCubicBezier, required: true },
516
- }, node, { filename, src, logger });
517
- }
518
- }
519
- /**
520
- * Validate a MemberNode (the entire token object, plus its key in the parent
521
- * object) to see if it’s a valid DTCG token or not. Keeping the parent key
522
- * really helps in debug messages.
523
- */
524
- export function validateTokenMemberNode(node, { filename, src, logger }) {
525
- const baseMessage = { group: 'parser', label: 'validate', filename, node, src };
526
- if (node.type !== 'Member' && node.type !== 'Object') {
527
- logger.error({
528
- ...baseMessage,
529
- message: `Expected Object, received ${JSON.stringify(
530
- // @ts-ignore Yes, TypeScript, this SHOULD be unexpected. This is why we’re validating.
531
- node.type)}`,
532
- });
533
- }
534
- const rootMembers = node.value.type === 'Object' ? getObjMembers(node.value) : {};
535
- const $value = rootMembers.$value;
536
- const $type = rootMembers.$type;
537
- if (!$value) {
538
- logger.error({ ...baseMessage, message: 'Token missing $value' });
539
- }
540
- // If top-level value is a valid alias, this is valid (no need for $type)
541
- // ⚠️ Important: ALL Object and Array nodes below will need to check for aliases within!
542
- if (isMaybeAlias($value)) {
543
- validateAliasSyntax($value, node, { logger, src });
544
- return;
545
- }
546
- if (!$type) {
547
- logger.error({ ...baseMessage, message: 'Token missing $type' });
548
- }
549
- switch ($type.value) {
550
- case 'color': {
551
- validateColor($value, node, { logger, src });
552
- break;
553
- }
554
- case 'cubicBezier': {
555
- validateCubicBezier($value, node, { logger, src });
556
- break;
557
- }
558
- case 'dimension': {
559
- validateDimension($value, node, { logger, src });
560
- break;
561
- }
562
- case 'duration': {
563
- validateDuration($value, node, { logger, src });
564
- break;
565
- }
566
- case 'fontFamily': {
567
- validateFontFamily($value, node, { logger, src });
568
- break;
569
- }
570
- case 'fontWeight': {
571
- validateFontWeight($value, node, { logger, src });
572
- break;
573
- }
574
- case 'number': {
575
- validateNumber($value, node, { logger, src });
576
- break;
577
- }
578
- case 'shadow': {
579
- if ($value.type === 'Object') {
580
- validateShadowLayer($value, node, { logger, src });
581
- }
582
- else if ($value.type === 'Array') {
583
- for (const element of $value.elements) {
584
- validateShadowLayer(element.value, $value, { logger, src });
585
- }
586
- }
587
- else {
588
- logger.error({
589
- ...baseMessage,
590
- message: `Expected shadow object or array of shadow objects, received ${$value.type}`,
591
- node: $value,
592
- });
593
- }
594
- break;
595
- }
596
- // extensions
597
- case 'boolean': {
598
- if ($value.type !== 'Boolean') {
599
- logger.error({
600
- ...baseMessage,
601
- message: `Expected boolean, received ${$value.type}`,
602
- node: $value,
603
- });
604
- }
605
- break;
606
- }
607
- case 'link': {
608
- if ($value.type !== 'String') {
609
- logger.error({
610
- ...baseMessage,
611
- message: `Expected string, received ${$value.type}`,
612
- node: $value,
613
- });
614
- }
615
- else if ($value.value === '') {
616
- logger.error({
617
- ...baseMessage,
618
- message: 'Expected URL, received empty string',
619
- node: $value,
620
- });
621
- }
622
- break;
623
- }
624
- case 'string': {
625
- if ($value.type !== 'String') {
626
- logger.error({
627
- ...baseMessage,
628
- message: `Expected string, received ${$value.type}`,
629
- node: $value,
630
- });
631
- }
632
- break;
633
- }
634
- // composite types
635
- case 'border': {
636
- validateBorder($value, node, { filename, src, logger });
637
- break;
638
- }
639
- case 'gradient': {
640
- validateGradient($value, node, { filename, src, logger });
641
- break;
642
- }
643
- case 'strokeStyle': {
644
- validateStrokeStyle($value, node, { filename, src, logger });
645
- break;
646
- }
647
- case 'transition': {
648
- validateTransition($value, node, { filename, src, logger });
649
- break;
650
- }
651
- case 'typography': {
652
- if ($value.type !== 'Object') {
653
- logger.error({
654
- ...baseMessage,
655
- message: `Expected object, received ${$value.type}`,
656
- node: $value,
657
- });
658
- break;
659
- }
660
- if ($value.members.length === 0) {
661
- logger.error({
662
- ...baseMessage,
663
- message: 'Empty typography token. Must contain at least 1 property.',
664
- node: $value,
665
- });
666
- }
667
- validateMembersAs($value, {
668
- fontFamily: { validator: validateFontFamily },
669
- fontWeight: { validator: validateFontWeight },
670
- }, node, { filename, src, logger });
671
- break;
672
- }
673
- default: {
674
- // noop
675
- break;
676
- }
677
- }
678
- }
679
- /**
680
- * Validate does a little more than validate; it also converts to TokenNormalized
681
- * and sets up the basic data structure. But aliases are unresolved, and we need
682
- * a 2nd normalization pass afterward.
683
- */
684
- export default function validateTokenNode(node, { config, filename, logger, parent, src, subpath, $typeInheritance }) {
685
- // const start = performance.now();
686
- // don’t validate $value
687
- if (subpath.includes('$value') || node.value.type !== 'Object') {
688
- return;
689
- }
690
- const members = getObjMembers(node.value);
691
- // keep track of $types
692
- if ($typeInheritance && members.$type && members.$type.type === 'String' && !members.$value) {
693
- // @ts-ignore
694
- $typeInheritance[subpath.join('.') || '.'] = node.value.members.find((m) => m.name.value === '$type');
695
- }
696
- // don’t validate $extensions or $defs
697
- if (!members.$value || subpath.includes('$extensions') || subpath.includes('$deps')) {
698
- return;
699
- }
700
- const id = subpath.join('.');
701
- if (!subpath.includes('.$value') && members.value) {
702
- logger.warn({
703
- group: 'parser',
704
- label: 'validate',
705
- message: `Group ${id} has "value". Did you mean "$value"?`,
706
- filename,
707
- node,
708
- src,
709
- });
710
- }
711
- const extensions = members.$extensions ? getObjMembers(members.$extensions) : undefined;
712
- const sourceNode = structuredClone(node);
713
- // get parent type by taking the closest-scoped $type (length === closer)
714
- let parent$type;
715
- let longestPath = '';
716
- for (const [k, v] of Object.entries($typeInheritance ?? {})) {
717
- if (k === '.' || id.startsWith(k)) {
718
- if (k.length > longestPath.length) {
719
- parent$type = v;
720
- longestPath = k;
721
- }
722
- }
723
- }
724
- if (parent$type && !members.$type) {
725
- injectObjMembers(
726
- // @ts-ignore
727
- sourceNode.value, [parent$type]);
728
- }
729
- validateTokenMemberNode(sourceNode, { filename, src, logger });
730
- // All tokens must be valid, so we want to validate it up till this
731
- // point. However, if we are ignoring this token (or respecting
732
- // $deprecated, we can omit it from the output.
733
- const $deprecated = members.$deprecated && evaluate(members.$deprecated);
734
- if ((config.ignore.deprecated && $deprecated) || (config.ignore.tokens && isTokenMatch(id, config.ignore.tokens))) {
735
- return;
736
- }
737
- const group = { id: splitID(id).group, tokens: [] };
738
- if (parent$type) {
739
- group.$type =
740
- // @ts-ignore
741
- parent$type.value.value;
742
- }
743
- // note: this will also include sibling tokens, so be selective about only accessing group-specific properties
744
- const groupMembers = getObjMembers(
745
- // @ts-ignore
746
- parent);
747
- if (groupMembers.$description) {
748
- group.$description = evaluate(groupMembers.$description);
749
- }
750
- if (groupMembers.$extensions) {
751
- group.$extensions = evaluate(groupMembers.$extensions);
752
- }
753
- const token = {
754
- // @ts-ignore
755
- $type: members.$type?.value ?? parent$type?.value.value,
756
- // @ts-ignore
757
- $value: evaluate(members.$value),
758
- id,
759
- // @ts-ignore
760
- mode: {},
761
- // @ts-ignore
762
- originalValue: evaluate(node.value),
763
- group,
764
- source: {
765
- loc: filename ? filename.href : undefined,
766
- // @ts-ignore
767
- node: sourceNode.value,
768
- },
769
- };
770
- // @ts-ignore
771
- if (members.$description?.value) {
772
- // @ts-ignore
773
- token.$description = members.$description.value;
774
- }
775
- // handle modes
776
- // note that circular refs are avoided here, such as not duplicating `modes`
777
- const modeValues = extensions?.mode ? getObjMembers(extensions.mode) : {};
778
- for (const mode of ['.', ...Object.keys(modeValues)]) {
779
- const modeValue = mode === '.' ? token.$value : evaluate(modeValues[mode]);
780
- token.mode[mode] = {
781
- $value: modeValue,
782
- originalValue: modeValue,
783
- source: {
784
- loc: filename ? filename.href : undefined,
785
- // @ts-ignore
786
- node: modeValues[mode],
787
- },
788
- };
789
- }
790
- // logger.debug({
791
- // message: `${token.id}: validateTokenNode`,
792
- // group: 'parser', label: 'validate',
793
- // label: 'validate',
794
- // timing: performance.now() - start,
795
- // });
796
- return token;
797
- }
798
- //# sourceMappingURL=validate.js.map