@terrazzo/parser 0.1.2 → 0.2.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 (118) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CONTRIBUTING.md +0 -12
  4. package/dist/build/index.d.ts +19 -0
  5. package/dist/build/index.js +165 -0
  6. package/dist/build/index.js.map +1 -0
  7. package/dist/config.d.ts +7 -0
  8. package/dist/config.js +269 -0
  9. package/dist/config.js.map +1 -0
  10. package/{index.d.ts → dist/index.d.ts} +1 -5
  11. package/dist/index.js +13 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/code-frame.d.ts +30 -0
  14. package/dist/lib/code-frame.js +108 -0
  15. package/dist/lib/code-frame.js.map +1 -0
  16. package/dist/lint/index.d.ts +11 -0
  17. package/dist/lint/index.js +102 -0
  18. package/dist/lint/index.js.map +1 -0
  19. package/dist/lint/plugin-core/index.d.ts +12 -0
  20. package/dist/lint/plugin-core/index.js +40 -0
  21. package/dist/lint/plugin-core/index.js.map +1 -0
  22. package/dist/lint/plugin-core/lib/docs.d.ts +1 -0
  23. package/dist/lint/plugin-core/lib/docs.js +4 -0
  24. package/dist/lint/plugin-core/lib/docs.js.map +1 -0
  25. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +39 -0
  26. package/dist/lint/plugin-core/rules/a11y-min-contrast.js +58 -0
  27. package/dist/lint/plugin-core/rules/a11y-min-contrast.js.map +1 -0
  28. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +13 -0
  29. package/dist/lint/plugin-core/rules/a11y-min-font-size.js +45 -0
  30. package/dist/lint/plugin-core/rules/a11y-min-font-size.js.map +1 -0
  31. package/dist/lint/plugin-core/rules/colorspace.d.ts +14 -0
  32. package/dist/lint/plugin-core/rules/colorspace.js +85 -0
  33. package/dist/lint/plugin-core/rules/colorspace.js.map +1 -0
  34. package/dist/lint/plugin-core/rules/consistent-naming.d.ts +11 -0
  35. package/dist/lint/plugin-core/rules/consistent-naming.js +49 -0
  36. package/dist/lint/plugin-core/rules/consistent-naming.js.map +1 -0
  37. package/dist/lint/plugin-core/rules/descriptions.d.ts +9 -0
  38. package/dist/lint/plugin-core/rules/descriptions.js +32 -0
  39. package/dist/lint/plugin-core/rules/descriptions.js.map +1 -0
  40. package/dist/lint/plugin-core/rules/duplicate-values.d.ts +9 -0
  41. package/dist/lint/plugin-core/rules/duplicate-values.js +65 -0
  42. package/dist/lint/plugin-core/rules/duplicate-values.js.map +1 -0
  43. package/dist/lint/plugin-core/rules/max-gamut.d.ts +14 -0
  44. package/dist/lint/plugin-core/rules/max-gamut.js +101 -0
  45. package/dist/lint/plugin-core/rules/max-gamut.js.map +1 -0
  46. package/dist/lint/plugin-core/rules/required-children.d.ts +18 -0
  47. package/dist/lint/plugin-core/rules/required-children.js +78 -0
  48. package/dist/lint/plugin-core/rules/required-children.js.map +1 -0
  49. package/dist/lint/plugin-core/rules/required-modes.d.ts +13 -0
  50. package/dist/lint/plugin-core/rules/required-modes.js +52 -0
  51. package/dist/lint/plugin-core/rules/required-modes.js.map +1 -0
  52. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +10 -0
  53. package/dist/lint/plugin-core/rules/required-typography-properties.js +38 -0
  54. package/dist/lint/plugin-core/rules/required-typography-properties.js.map +1 -0
  55. package/dist/logger.d.ts +76 -0
  56. package/dist/logger.js +123 -0
  57. package/dist/logger.js.map +1 -0
  58. package/dist/parse/alias.d.ts +51 -0
  59. package/dist/parse/alias.js +188 -0
  60. package/dist/parse/alias.js.map +1 -0
  61. package/dist/parse/index.d.ts +27 -0
  62. package/dist/parse/index.js +379 -0
  63. package/dist/parse/index.js.map +1 -0
  64. package/dist/parse/json.d.ts +36 -0
  65. package/dist/parse/json.js +88 -0
  66. package/dist/parse/json.js.map +1 -0
  67. package/dist/parse/normalize.d.ts +23 -0
  68. package/dist/parse/normalize.js +163 -0
  69. package/dist/parse/normalize.js.map +1 -0
  70. package/dist/parse/validate.d.ts +45 -0
  71. package/dist/parse/validate.js +601 -0
  72. package/dist/parse/validate.js.map +1 -0
  73. package/dist/types.d.ts +264 -0
  74. package/dist/types.js +2 -0
  75. package/dist/types.js.map +1 -0
  76. package/package.json +7 -7
  77. package/{build/index.js → src/build/index.ts} +47 -63
  78. package/src/config.ts +280 -0
  79. package/src/index.ts +18 -0
  80. package/{lib/code-frame.js → src/lib/code-frame.ts} +41 -8
  81. package/src/lint/index.ts +135 -0
  82. package/src/lint/plugin-core/index.ts +47 -0
  83. package/src/lint/plugin-core/lib/docs.ts +3 -0
  84. package/src/lint/plugin-core/rules/a11y-min-contrast.ts +91 -0
  85. package/src/lint/plugin-core/rules/a11y-min-font-size.ts +64 -0
  86. package/src/lint/plugin-core/rules/colorspace.ts +101 -0
  87. package/src/lint/plugin-core/rules/consistent-naming.ts +65 -0
  88. package/src/lint/plugin-core/rules/descriptions.ts +41 -0
  89. package/src/lint/plugin-core/rules/duplicate-values.ts +80 -0
  90. package/src/lint/plugin-core/rules/max-gamut.ts +121 -0
  91. package/src/lint/plugin-core/rules/required-children.ts +104 -0
  92. package/src/lint/plugin-core/rules/required-modes.ts +71 -0
  93. package/src/lint/plugin-core/rules/required-typography-properties.ts +53 -0
  94. package/{logger.js → src/logger.ts} +55 -16
  95. package/src/parse/alias.ts +224 -0
  96. package/src/parse/index.ts +457 -0
  97. package/src/parse/json.ts +106 -0
  98. package/{parse/normalize.js → src/parse/normalize.ts} +70 -23
  99. package/{parse/validate.js → src/parse/validate.ts} +159 -227
  100. package/src/types.ts +310 -0
  101. package/build/index.d.ts +0 -113
  102. package/config.d.ts +0 -64
  103. package/config.js +0 -206
  104. package/index.js +0 -35
  105. package/lib/code-frame.d.ts +0 -56
  106. package/lint/index.d.ts +0 -44
  107. package/lint/index.js +0 -59
  108. package/lint/plugin-core/index.d.ts +0 -3
  109. package/lint/plugin-core/index.js +0 -12
  110. package/lint/plugin-core/rules/duplicate-values.d.ts +0 -10
  111. package/lint/plugin-core/rules/duplicate-values.js +0 -68
  112. package/logger.d.ts +0 -71
  113. package/parse/index.d.ts +0 -45
  114. package/parse/index.js +0 -592
  115. package/parse/json.d.ts +0 -30
  116. package/parse/json.js +0 -94
  117. package/parse/normalize.d.ts +0 -3
  118. package/parse/validate.d.ts +0 -43
@@ -1,13 +1,22 @@
1
- import { print } from '@humanwhocodes/momoa';
1
+ import {
2
+ type AnyNode,
3
+ type MemberNode,
4
+ type ObjectNode,
5
+ type StringNode,
6
+ type ValueNode,
7
+ print,
8
+ } from '@humanwhocodes/momoa';
2
9
  import { isAlias } from '@terrazzo/token-tools';
10
+ import type Logger from '../logger.js';
3
11
  import { getObjMembers } from './json.js';
4
12
 
5
13
  const listFormat = new Intl.ListFormat('en-us', { type: 'disjunction' });
6
14
 
7
- /** @typedef {import("@humanwhocodes/momoa").AnyNode} AnyNode */
8
- /** @typedef {import("@humanwhocodes/momoa").ObjectNode} ObjectNode */
9
- /** @typedef {import("@humanwhocodes/momoa").ValueNode} ValueNode */
10
- /** @typedef {import("@babel/code-frame").SourceLocation} SourceLocation */
15
+ export interface ValidateOptions {
16
+ filename?: URL;
17
+ src: string;
18
+ logger: Logger;
19
+ }
11
20
 
12
21
  export const VALID_COLORSPACES = new Set([
13
22
  'adobe-rgb',
@@ -60,30 +69,24 @@ export const STROKE_STYLE_VALUES = new Set([
60
69
  ]);
61
70
  export const STROKE_STYLE_LINE_CAP_VALUES = new Set(['round', 'butt', 'square']);
62
71
 
63
- /**
64
- * Distinct from isAlias() in that this accepts malformed aliases
65
- * @param {AnyNode} node
66
- * @return {boolean}
67
- */
68
- function isMaybeAlias(node) {
72
+ /** Distinct from isAlias() in that this accepts malformed aliases */
73
+ function isMaybeAlias(node: AnyNode) {
69
74
  if (node?.type === 'String') {
70
75
  return node.value.startsWith('{');
71
76
  }
72
77
  return false;
73
78
  }
74
79
 
75
- /**
76
- * Assert object members match given types
77
- * @param {ObjectNode} $value
78
- * @param {Record<string, { validator: typeof validateAlias; required?: boolean }>} properties
79
- * @param {AnyNode} node
80
- * @param {ValidateOptions} options
81
- * @return {void}
82
- */
83
- function validateMembersAs($value, properties, node, { filename, src, logger }) {
80
+ /** Assert object members match given types */
81
+ function validateMembersAs(
82
+ $value: ObjectNode,
83
+ properties: Record<string, { validator: typeof validateAliasSyntax; required?: boolean }>,
84
+ node: AnyNode,
85
+ { filename, src, logger }: ValidateOptions,
86
+ ) {
84
87
  const members = getObjMembers($value);
85
88
  for (const property in properties) {
86
- const { validator, required } = properties[property];
89
+ const { validator, required } = properties[property]!;
87
90
  if (!members[property]) {
88
91
  if (required) {
89
92
  logger.error({ message: `Missing required property "${property}"`, filename, node: $value, src });
@@ -99,51 +102,33 @@ function validateMembersAs($value, properties, node, { filename, src, logger })
99
102
  }
100
103
  }
101
104
 
102
- /**
103
- * Verify an Alias $value is formatted correctly
104
- * @param {ValueNode} $value
105
- * @param {AnyNode} node
106
- * @param {ValidateOptions} options
107
- * @return {void}
108
- */
109
- export function validateAliasSyntax($value, node, { filename, src, logger }) {
105
+ /** Verify an Alias $value is formatted correctly */
106
+ export function validateAliasSyntax($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
110
107
  if ($value.type !== 'String' || !isAlias($value.value)) {
111
108
  logger.error({ message: `Invalid alias: ${print($value)}`, filename, node: $value, src });
112
109
  }
113
110
  }
114
111
 
115
- /**
116
- * Verify a Border token is valid
117
- * @param {ValueNode} $value
118
- * @param {AnyNode} node
119
- * @param {ValidateOptions} options
120
- * @return {void}
121
- */
122
- export function validateBorder($value, node, { filename, src, logger }) {
112
+ /** Verify a Border token is valid */
113
+ export function validateBorder($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
123
114
  if ($value.type !== 'Object') {
124
115
  logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
125
- return;
116
+ } else {
117
+ validateMembersAs(
118
+ $value,
119
+ {
120
+ color: { validator: validateColor, required: true },
121
+ style: { validator: validateStrokeStyle, required: true },
122
+ width: { validator: validateDimension, required: true },
123
+ },
124
+ node,
125
+ { filename, src, logger },
126
+ );
126
127
  }
127
- validateMembersAs(
128
- $value,
129
- {
130
- color: { validator: validateColor, required: true },
131
- style: { validator: validateStrokeStyle, required: true },
132
- width: { validator: validateDimension, required: true },
133
- },
134
- node,
135
- { filename, src, logger },
136
- );
137
128
  }
138
129
 
139
- /**
140
- * Verify a Color token is valid
141
- * @param {ValueNode} $value
142
- * @param {AnyNode} node
143
- * @param {ValidateOptions} options
144
- * @return {void}
145
- */
146
- export function validateColor($value, node, { filename, src, logger }) {
130
+ /** Verify a Color token is valid */
131
+ export function validateColor($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
147
132
  if ($value.type === 'String') {
148
133
  // TODO: enable when object notation is finalized
149
134
  // logger.warn({
@@ -164,34 +149,35 @@ export function validateColor($value, node, { filename, src, logger }) {
164
149
  if (v.type !== 'String') {
165
150
  logger.error({ message: `Expected string, received ${print(v)}`, filename, node: v, src });
166
151
  }
167
- if (!VALID_COLORSPACES.has(v.value)) {
152
+ if (!VALID_COLORSPACES.has((v as StringNode).value)) {
168
153
  logger.error({ message: `Unsupported colorspace ${print(v)}`, filename, node: v, src });
169
154
  }
170
155
  },
171
156
  required: true,
172
157
  },
173
158
  channels: {
174
- validator: (v, node) => {
159
+ validator: (v) => {
175
160
  if (v.type !== 'Array') {
176
161
  logger.error({ message: `Expected array, received ${print(v)}`, filename, node: v, src });
177
- }
178
- if (v.elements?.length !== 3) {
179
- logger.error({
180
- message: `Expected 3 channels, received ${v.elements?.length ?? 0}`,
181
- filename,
182
- node: v,
183
- src,
184
- });
185
- }
186
- for (const element of v.elements) {
187
- if (element.value.type !== 'Number') {
162
+ } else {
163
+ if (v.elements?.length !== 3) {
188
164
  logger.error({
189
- message: `Expected number, received ${print(element.value)}`,
165
+ message: `Expected 3 channels, received ${v.elements?.length ?? 0}`,
190
166
  filename,
191
- node: element,
167
+ node: v,
192
168
  src,
193
169
  });
194
170
  }
171
+ for (const element of v.elements) {
172
+ if (element.value.type !== 'Number') {
173
+ logger.error({
174
+ message: `Expected number, received ${print(element.value)}`,
175
+ filename,
176
+ node: element,
177
+ src,
178
+ });
179
+ }
180
+ }
195
181
  }
196
182
  },
197
183
  required: true,
@@ -222,14 +208,8 @@ export function validateColor($value, node, { filename, src, logger }) {
222
208
  }
223
209
  }
224
210
 
225
- /**
226
- * Verify a Cubic Bézier token is valid
227
- * @param {ValueNode} $value
228
- * @param {AnyNode} node
229
- * @param {ValidateOptions} options
230
- * @return {void}
231
- */
232
- export function validateCubicBezier($value, node, { filename, src, logger }) {
211
+ /** Verify a Cubic Bézier token is valid */
212
+ export function validateCubicBezier($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
233
213
  if ($value.type !== 'Array') {
234
214
  logger.error({ message: `Expected array of strings, received ${print($value)}`, filename, node: $value, src });
235
215
  } else if (
@@ -251,14 +231,8 @@ export function validateCubicBezier($value, node, { filename, src, logger }) {
251
231
  }
252
232
  }
253
233
 
254
- /**
255
- * Verify a Dimension token is valid
256
- * @param {ValueNode} $value
257
- * @param {AnyNode} node
258
- * @param {ValidateOptions} options
259
- * @return {void}
260
- */
261
- export function validateDimension($value, node, { filename, src, logger }) {
234
+ /** Verify a Dimension token is valid */
235
+ export function validateDimension($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
262
236
  if ($value.type === 'Number' && $value.value === 0) {
263
237
  return; // `0` is a valid number
264
238
  }
@@ -270,12 +244,12 @@ export function validateDimension($value, node, { filename, src, logger }) {
270
244
  if (!unit) {
271
245
  logger.error({ message: 'Missing required property "unit".', filename, node: $value, src });
272
246
  }
273
- if (value.type !== 'Number') {
274
- logger.error({ message: `Expected number, received ${value.type}`, filename, node: value, src });
247
+ if (value!.type !== 'Number') {
248
+ logger.error({ message: `Expected number, received ${value!.type}`, filename, node: value, src });
275
249
  }
276
- if (!['px', 'em', 'rem'].includes(unit.value)) {
250
+ if (!['px', 'em', 'rem'].includes((unit as StringNode).value)) {
277
251
  logger.error({
278
- message: `Expected unit "px", "em", or "rem", received ${print(unit)}`,
252
+ message: `Expected unit "px", "em", or "rem", received ${print(unit as StringNode)}`,
279
253
  filename,
280
254
  node: unit,
281
255
  src,
@@ -287,30 +261,24 @@ export function validateDimension($value, node, { filename, src, logger }) {
287
261
  if ($value.type !== 'String') {
288
262
  logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
289
263
  }
290
- const value = $value.value.match(/^-?[0-9.]+/)?.[0];
291
- const unit = $value.value.replace(value, '');
292
- if ($value.value === '') {
264
+ const value = ($value as StringNode).value.match(/^-?[0-9.]+/)?.[0];
265
+ const unit = ($value as StringNode).value.replace(value!, '');
266
+ if (($value as StringNode).value === '') {
293
267
  logger.error({ message: 'Expected dimension, received empty string', filename, node: $value, src });
294
268
  } else if (!['px', 'em', 'rem'].includes(unit)) {
295
269
  logger.error({
296
- message: `Expected unit "px", "em", or "rem", received ${JSON.stringify(unit || $value.value)}`,
270
+ message: `Expected unit "px", "em", or "rem", received ${JSON.stringify(unit || ($value as StringNode).value)}`,
297
271
  filename,
298
272
  node: $value,
299
273
  src,
300
274
  });
301
- } else if (!Number.isFinite(Number.parseFloat(value))) {
275
+ } else if (!Number.isFinite(Number.parseFloat(value!))) {
302
276
  logger.error({ message: `Expected dimension with units, received ${print($value)}`, filename, node: $value, src });
303
277
  }
304
278
  }
305
279
 
306
- /**
307
- * Verify a Duration token is valid
308
- * @param {ValueNode} $value
309
- * @param {AnyNode} node
310
- * @param {ValidateOptions} options
311
- * @return {void}
312
- */
313
- export function validateDuration($value, node, { filename, src, logger }) {
280
+ /** Verify a Duration token is valid */
281
+ export function validateDuration($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
314
282
  if ($value.type === 'Number' && $value.value === 0) {
315
283
  return; // `0` is a valid number
316
284
  }
@@ -322,11 +290,11 @@ export function validateDuration($value, node, { filename, src, logger }) {
322
290
  if (!unit) {
323
291
  logger.error({ message: 'Missing required property "unit".', filename, node: $value, src });
324
292
  }
325
- if (value.type !== 'Number') {
326
- logger.error({ message: `Expected number, received ${value.type}`, filename, node: value, src });
293
+ if (value?.type !== 'Number') {
294
+ logger.error({ message: `Expected number, received ${value?.type}`, filename, node: value, src });
327
295
  }
328
- if (!['ms', 's'].includes(unit.value)) {
329
- logger.error({ message: `Expected unit "ms" or "s", received ${print(unit)}`, filename, node: unit, src });
296
+ if (!['ms', 's'].includes((unit as StringNode).value)) {
297
+ logger.error({ message: `Expected unit "ms" or "s", received ${print(unit!)}`, filename, node: unit, src });
330
298
  }
331
299
  return;
332
300
  }
@@ -334,13 +302,13 @@ export function validateDuration($value, node, { filename, src, logger }) {
334
302
  if ($value.type !== 'String') {
335
303
  logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
336
304
  }
337
- const value = $value.value.match(/^-?[0-9.]+/)?.[0];
338
- const unit = $value.value.replace(value, '');
339
- if ($value.value === '') {
305
+ const value = ($value as StringNode).value.match(/^-?[0-9.]+/)?.[0]!;
306
+ const unit = ($value as StringNode).value.replace(value, '');
307
+ if (($value as StringNode).value === '') {
340
308
  logger.error({ message: 'Expected duration, received empty string', filename, node: $value, src });
341
309
  } else if (!['ms', 's'].includes(unit)) {
342
310
  logger.error({
343
- message: `Expected unit "ms" or "s", received ${JSON.stringify(unit || $value.value)}`,
311
+ message: `Expected unit "ms" or "s", received ${JSON.stringify(unit || ($value as StringNode).value)}`,
344
312
  filename,
345
313
  node: $value,
346
314
  src,
@@ -350,14 +318,8 @@ export function validateDuration($value, node, { filename, src, logger }) {
350
318
  }
351
319
  }
352
320
 
353
- /**
354
- * Verify a Font Family token is valid
355
- * @param {ValueNode} $value
356
- * @param {AnyNode} node
357
- * @param {ValidateOptions} options
358
- * @return {void}
359
- */
360
- export function validateFontFamily($value, node, { filename, src, logger }) {
321
+ /** Verify a Font Family token is valid */
322
+ export function validateFontFamily($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
361
323
  if ($value.type !== 'String' && $value.type !== 'Array') {
362
324
  logger.error({
363
325
  message: `Expected string or array of strings, received ${$value.type}`,
@@ -379,14 +341,8 @@ export function validateFontFamily($value, node, { filename, src, logger }) {
379
341
  }
380
342
  }
381
343
 
382
- /**
383
- * Verify a Font Weight token is valid
384
- * @param {ValueNode} $value
385
- * @param {AnyNode} node
386
- * @param {ValidateOptions} options
387
- * @return {void}
388
- */
389
- export function validateFontWeight($value, node, { filename, src, logger }) {
344
+ /** Verify a Font Weight token is valid */
345
+ export function validateFontWeight($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
390
346
  if ($value.type !== 'String' && $value.type !== 'Number') {
391
347
  logger.error({
392
348
  message: `Expected a font weight name or number 0–1000, received ${$value.type}`,
@@ -408,14 +364,8 @@ export function validateFontWeight($value, node, { filename, src, logger }) {
408
364
  }
409
365
  }
410
366
 
411
- /**
412
- * Verify a Gradient token is valid
413
- * @param {ValueNode} $value
414
- * @param {AnyNode} node
415
- * @param {ValidateOptions} options
416
- * @return {void}
417
- */
418
- export function validateGradient($value, node, { filename, src, logger }) {
367
+ /** Verify a Gradient token is valid */
368
+ export function validateGradient($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
419
369
  if ($value.type !== 'Array') {
420
370
  logger.error({
421
371
  message: `Expected array of gradient stops, received ${$value.type}`,
@@ -423,78 +373,68 @@ export function validateGradient($value, node, { filename, src, logger }) {
423
373
  node: $value,
424
374
  src,
425
375
  });
426
- return;
427
- }
428
- for (let i = 0; i < $value.elements.length; i++) {
429
- const element = $value.elements[i];
430
- if (element.value.type !== 'Object') {
431
- logger.error({
432
- message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
433
- filename,
434
- node: element,
435
- src,
436
- });
437
- break;
376
+ } else {
377
+ for (let i = 0; i < $value.elements.length; i++) {
378
+ const element = $value.elements[i]!;
379
+ if (element.value.type !== 'Object') {
380
+ logger.error({
381
+ message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
382
+ filename,
383
+ node: element,
384
+ src,
385
+ });
386
+ break;
387
+ }
388
+ validateMembersAs(
389
+ element.value,
390
+ {
391
+ color: { validator: validateColor, required: true },
392
+ position: { validator: validateNumber, required: true },
393
+ },
394
+ element,
395
+ { filename, src, logger },
396
+ );
438
397
  }
439
- validateMembersAs(
440
- element.value,
441
- {
442
- color: { validator: validateColor, required: true },
443
- position: { validator: validateNumber, required: true },
444
- },
445
- element,
446
- { filename, src, logger },
447
- );
448
398
  }
449
399
  }
450
400
 
451
- /**
452
- * Verify a Number token is valid
453
- * @param {ValueNode} $value
454
- * @param {AnyNode} node
455
- * @param {ValidateOptions} options
456
- * @return {void}
457
- */
458
- export function validateNumber($value, node, { filename, src, logger }) {
401
+ /** Verify a Number token is valid */
402
+ export function validateNumber($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
459
403
  if ($value.type !== 'Number') {
460
404
  logger.error({ message: `Expected number, received ${$value.type}`, filename, node: $value, src });
461
405
  }
462
406
  }
463
407
 
464
- /**
465
- * Verify a Shadow token’s value is valid
466
- * @param {ValueNode} $value
467
- * @param {AnyNode} node
468
- * @param {ValidateOptions} options
469
- * @return {void}
470
- */
471
- export function validateShadowLayer($value, node, { filename, src, logger }) {
408
+ /** Verify a Boolean token is valid */
409
+ export function validateBoolean($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
410
+ if ($value.type !== 'Boolean') {
411
+ logger.error({ message: `Expected boolean, received ${$value.type}`, filename, node: $value, src });
412
+ }
413
+ }
414
+
415
+ /** Verify a Shadow token’s value is valid */
416
+ export function validateShadowLayer($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
472
417
  if ($value.type !== 'Object') {
473
418
  logger.error({ message: `Expected Object, received ${$value.type}`, filename, node: $value, src });
474
- return;
419
+ } else {
420
+ validateMembersAs(
421
+ $value,
422
+ {
423
+ color: { validator: validateColor, required: true },
424
+ offsetX: { validator: validateDimension, required: true },
425
+ offsetY: { validator: validateDimension, required: true },
426
+ blur: { validator: validateDimension },
427
+ spread: { validator: validateDimension },
428
+ inset: { validator: validateBoolean },
429
+ },
430
+ node,
431
+ { filename, src, logger },
432
+ );
475
433
  }
476
- validateMembersAs(
477
- $value,
478
- {
479
- color: { validator: validateColor, required: true },
480
- offsetX: { validator: validateDimension, required: true },
481
- offsetY: { validator: validateDimension, required: true },
482
- blur: { validator: validateDimension },
483
- spread: { validator: validateDimension },
484
- },
485
- node,
486
- { filename, src, logger },
487
- );
488
434
  }
489
435
 
490
- /**
491
- * Verify a Stroke Style token is valid.
492
- * @param {ValueNode} $value
493
- * @param {AnyNode} node
494
- * @param {ValidateOptions} options
495
- * @return {void}
496
- */
497
- export function validateStrokeStyle($value, node, { filename, src, logger }) {
436
+ /** Verify a Stroke Style token is valid. */
437
+ export function validateStrokeStyle($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
498
438
  // note: strokeStyle’s values are NOT aliasable (unless by string, but that breaks validations)
499
439
  if ($value.type === 'String') {
500
440
  if (!STROKE_STYLE_VALUES.has($value.value)) {
@@ -517,7 +457,7 @@ export function validateStrokeStyle($value, node, { filename, src, logger }) {
517
457
  const { lineCap, dashArray } = strokeMembers;
518
458
  if (lineCap?.type !== 'String' || !STROKE_STYLE_LINE_CAP_VALUES.has(lineCap.value)) {
519
459
  logger.error({
520
- message: `Unknown lineCap value ${print(lineCap)}. Expected one of: ${listFormat.format([
460
+ message: `Unknown lineCap value ${print(lineCap!)}. Expected one of: ${listFormat.format([
521
461
  ...STROKE_STYLE_LINE_CAP_VALUES,
522
462
  ])}.`,
523
463
  });
@@ -540,62 +480,55 @@ export function validateStrokeStyle($value, node, { filename, src, logger }) {
540
480
  }
541
481
  }
542
482
  } else {
543
- logger.error({ message: `Expected array of strings, received ${dashArray.type}`, filename, node: $value, src });
483
+ logger.error({ message: `Expected array of strings, received ${dashArray!.type}`, filename, node: $value, src });
544
484
  }
545
485
  } else {
546
486
  logger.error({ message: `Expected string or object, received ${$value.type}`, filename, node: $value, src });
547
487
  }
548
488
  }
549
489
 
550
- /**
551
- * Verify a Transition token is valid
552
- * @param {ValueNode} $value
553
- * @param {AnyNode} node
554
- * @param {ValidateOptions} options
555
- * @return {void}
556
- */
557
- export function validateTransition($value, node, { filename, src, logger }) {
490
+ /** Verify a Transition token is valid */
491
+ export function validateTransition($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions) {
558
492
  if ($value.type !== 'Object') {
559
493
  logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
560
- return;
494
+ } else {
495
+ validateMembersAs(
496
+ $value,
497
+ {
498
+ duration: { validator: validateDuration, required: true },
499
+ delay: { validator: validateDuration, required: false }, // note: spec says delay is required, but Terrazzo makes delay optional
500
+ timingFunction: { validator: validateCubicBezier, required: true },
501
+ },
502
+ node,
503
+ { filename, src, logger },
504
+ );
561
505
  }
562
- validateMembersAs(
563
- $value,
564
- {
565
- duration: { validator: validateDuration, required: true },
566
- delay: { validator: validateDuration, required: false }, // note: spec says delay is required, but Terrazzo makes delay optional
567
- timingFunction: { validator: validateCubicBezier, required: true },
568
- },
569
- node,
570
- { filename, src, logger },
571
- );
572
506
  }
573
507
 
574
508
  /**
575
- * Validate a MemberNode (the entire token object, plus its key in the parent object) to see if it’s a valid DTCG token or not.
576
- * Keeping the parent key really helps in debug messages.
577
- * @param {AnyNode} node
578
- * @param {ValidateOptions} options
579
- * @return {void}
509
+ * Validate a MemberNode (the entire token object, plus its key in the parent
510
+ * object) to see if it’s a valid DTCG token or not. Keeping the parent key
511
+ * really helps in debug messages.
580
512
  */
581
- export default function validate(node, { filename, src, logger }) {
513
+ export default function validate(node: MemberNode, { filename, src, logger }: ValidateOptions) {
582
514
  if (node.type !== 'Member' && node.type !== 'Object') {
583
515
  logger.error({
584
- message: `Expected Object, received ${JSON.stringify(node.type)}`,
516
+ message: `Expected Object, received ${JSON.stringify(
517
+ // @ts-ignore Yes, TypeScript, this SHOULD be unexpected. This is why we’re validating.
518
+ node.type,
519
+ )}`,
585
520
  filename,
586
521
  node,
587
522
  src,
588
523
  });
589
- return;
590
524
  }
591
525
 
592
526
  const rootMembers = node.value.type === 'Object' ? getObjMembers(node.value) : {};
593
- const $value = rootMembers.$value;
594
- const $type = rootMembers.$type;
527
+ const $value = rootMembers.$value as ValueNode;
528
+ const $type = rootMembers.$type as StringNode;
595
529
 
596
530
  if (!$value) {
597
531
  logger.error({ message: 'Token missing $value', filename, node, src });
598
- return;
599
532
  }
600
533
  // If top-level value is a valid alias, this is valid (no need for $type)
601
534
  // ⚠️ Important: ALL Object and Array nodes below will need to check for aliases within!
@@ -606,7 +539,6 @@ export default function validate(node, { filename, src, logger }) {
606
539
 
607
540
  if (!$type) {
608
541
  logger.error({ message: 'Token missing $type', filename, node, src });
609
- return;
610
542
  }
611
543
 
612
544
  switch ($type.value) {