@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.
- package/CHANGELOG.md +62 -12
- package/dist/build/index.d.ts +1 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +13 -14
- package/dist/build/index.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -21
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/code-frame.d.ts +1 -0
- package/dist/lib/code-frame.d.ts.map +1 -0
- package/dist/lint/index.d.ts +1 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +8 -5
- package/dist/lint/index.js.map +1 -1
- package/dist/lint/plugin-core/index.d.ts +1 -0
- package/dist/lint/plugin-core/index.d.ts.map +1 -0
- package/dist/lint/plugin-core/lib/docs.d.ts +1 -0
- package/dist/lint/plugin-core/lib/docs.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +1 -0
- package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +1 -0
- package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/colorspace.d.ts +1 -0
- package/dist/lint/plugin-core/rules/colorspace.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/consistent-naming.d.ts +1 -0
- package/dist/lint/plugin-core/rules/consistent-naming.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/descriptions.d.ts +1 -0
- package/dist/lint/plugin-core/rules/descriptions.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/duplicate-values.d.ts +1 -0
- package/dist/lint/plugin-core/rules/duplicate-values.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/duplicate-values.js +1 -1
- package/dist/lint/plugin-core/rules/duplicate-values.js.map +1 -1
- package/dist/lint/plugin-core/rules/max-gamut.d.ts +1 -0
- package/dist/lint/plugin-core/rules/max-gamut.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/required-children.d.ts +1 -0
- package/dist/lint/plugin-core/rules/required-children.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/required-modes.d.ts +1 -0
- package/dist/lint/plugin-core/rules/required-modes.d.ts.map +1 -0
- package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +1 -0
- package/dist/lint/plugin-core/rules/required-typography-properties.d.ts.map +1 -0
- package/dist/logger.d.ts +4 -3
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +25 -14
- package/dist/logger.js.map +1 -1
- package/dist/parse/alias.d.ts +31 -48
- package/dist/parse/alias.d.ts.map +1 -0
- package/dist/parse/alias.js +281 -175
- package/dist/parse/alias.js.map +1 -1
- package/dist/parse/index.d.ts +1 -0
- package/dist/parse/index.d.ts.map +1 -0
- package/dist/parse/index.js +59 -70
- package/dist/parse/index.js.map +1 -1
- package/dist/parse/json.d.ts +3 -3
- package/dist/parse/json.d.ts.map +1 -0
- package/dist/parse/json.js +5 -7
- package/dist/parse/json.js.map +1 -1
- package/dist/parse/normalize.d.ts +1 -0
- package/dist/parse/normalize.d.ts.map +1 -0
- package/dist/parse/normalize.js +13 -7
- package/dist/parse/normalize.js.map +1 -1
- package/dist/parse/validate.d.ts +6 -0
- package/dist/parse/validate.d.ts.map +1 -0
- package/dist/parse/validate.js +205 -125
- package/dist/parse/validate.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/build/index.ts +13 -14
- package/src/config.ts +42 -22
- package/src/lint/index.ts +8 -6
- package/src/lint/plugin-core/rules/duplicate-values.ts +1 -1
- package/src/logger.ts +30 -20
- package/src/parse/alias.ts +330 -194
- package/src/parse/index.ts +59 -73
- package/src/parse/json.ts +6 -8
- package/src/parse/normalize.ts +14 -7
- package/src/parse/validate.ts +215 -128
package/src/parse/validate.ts
CHANGED
|
@@ -87,19 +87,26 @@ function validateMembersAs(
|
|
|
87
87
|
{ filename, src, logger }: ValidateOptions,
|
|
88
88
|
) {
|
|
89
89
|
const members = getObjMembers($value);
|
|
90
|
-
for (const
|
|
91
|
-
const { validator, required } =
|
|
92
|
-
if (!members[
|
|
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({
|
|
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
|
|
99
|
-
if (isMaybeAlias(
|
|
100
|
-
validateAliasSyntax(
|
|
105
|
+
const memberValue = members[name];
|
|
106
|
+
if (isMaybeAlias(memberValue)) {
|
|
107
|
+
validateAliasSyntax(memberValue, node, { filename, src, logger });
|
|
101
108
|
} else {
|
|
102
|
-
validator(
|
|
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({
|
|
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({
|
|
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'
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
|
217
|
-
} else if (
|
|
218
|
-
|
|
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".'
|
|
277
|
+
logger.error({ ...baseMessage, message: 'Missing required property "value".' });
|
|
245
278
|
}
|
|
246
279
|
if (!unit) {
|
|
247
|
-
logger.error({ message: 'Missing required property "unit".'
|
|
280
|
+
logger.error({ ...baseMessage, message: 'Missing required property "unit".' });
|
|
248
281
|
}
|
|
249
282
|
if (value!.type !== 'Number') {
|
|
250
|
-
logger.error({
|
|
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}
|
|
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'
|
|
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)}
|
|
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".'
|
|
329
|
+
logger.error({ ...baseMessage, message: 'Missing required property "value".' });
|
|
291
330
|
}
|
|
292
331
|
if (!unit) {
|
|
293
|
-
logger.error({ message: 'Missing required property "unit".'
|
|
332
|
+
logger.error({ ...baseMessage, message: 'Missing required property "unit".' });
|
|
294
333
|
}
|
|
295
334
|
if (value?.type !== 'Number') {
|
|
296
|
-
logger.error({
|
|
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({
|
|
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}
|
|
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'
|
|
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)}
|
|
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'
|
|
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)}
|
|
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({
|
|
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({
|
|
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({
|
|
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}"
|
|
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}
|
|
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}
|
|
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({
|
|
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'
|
|
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'
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
785
|
-
|
|
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:
|
|
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
|
}
|