@terrazzo/parser 2.0.0-alpha.7 → 2.0.0-beta.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/dist/index.d.ts +39 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +578 -512
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/build/index.ts +0 -209
- package/src/config.ts +0 -304
- package/src/index.ts +0 -95
- package/src/lib/code-frame.ts +0 -177
- package/src/lib/momoa.ts +0 -10
- package/src/lib/resolver-utils.ts +0 -35
- package/src/lint/index.ts +0 -142
- package/src/lint/plugin-core/index.ts +0 -103
- package/src/lint/plugin-core/lib/docs.ts +0 -3
- package/src/lint/plugin-core/rules/a11y-min-contrast.ts +0 -91
- package/src/lint/plugin-core/rules/a11y-min-font-size.ts +0 -66
- package/src/lint/plugin-core/rules/colorspace.ts +0 -108
- package/src/lint/plugin-core/rules/consistent-naming.ts +0 -65
- package/src/lint/plugin-core/rules/descriptions.ts +0 -43
- package/src/lint/plugin-core/rules/duplicate-values.ts +0 -85
- package/src/lint/plugin-core/rules/max-gamut.ts +0 -144
- package/src/lint/plugin-core/rules/required-children.ts +0 -106
- package/src/lint/plugin-core/rules/required-modes.ts +0 -75
- package/src/lint/plugin-core/rules/required-type.ts +0 -28
- package/src/lint/plugin-core/rules/required-typography-properties.ts +0 -65
- package/src/lint/plugin-core/rules/valid-boolean.ts +0 -41
- package/src/lint/plugin-core/rules/valid-border.ts +0 -57
- package/src/lint/plugin-core/rules/valid-color.ts +0 -265
- package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +0 -83
- package/src/lint/plugin-core/rules/valid-dimension.ts +0 -199
- package/src/lint/plugin-core/rules/valid-duration.ts +0 -123
- package/src/lint/plugin-core/rules/valid-font-family.ts +0 -68
- package/src/lint/plugin-core/rules/valid-font-weight.ts +0 -89
- package/src/lint/plugin-core/rules/valid-gradient.ts +0 -79
- package/src/lint/plugin-core/rules/valid-link.ts +0 -41
- package/src/lint/plugin-core/rules/valid-number.ts +0 -63
- package/src/lint/plugin-core/rules/valid-shadow.ts +0 -67
- package/src/lint/plugin-core/rules/valid-string.ts +0 -41
- package/src/lint/plugin-core/rules/valid-stroke-style.ts +0 -104
- package/src/lint/plugin-core/rules/valid-transition.ts +0 -61
- package/src/lint/plugin-core/rules/valid-typography.ts +0 -67
- package/src/logger.ts +0 -213
- package/src/parse/index.ts +0 -124
- package/src/parse/load.ts +0 -172
- package/src/parse/normalize.ts +0 -163
- package/src/parse/process.ts +0 -251
- package/src/parse/token.ts +0 -553
- package/src/resolver/create-synthetic-resolver.ts +0 -86
- package/src/resolver/index.ts +0 -7
- package/src/resolver/load.ts +0 -215
- package/src/resolver/normalize.ts +0 -133
- package/src/resolver/validate.ts +0 -375
- package/src/types.ts +0 -468
package/src/resolver/validate.ts
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { getObjMember, getObjMembers } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import type { LogEntry, default as Logger } from '../logger.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Determine whether this is likely a resolver
|
|
7
|
-
* We use terms the word “likely” because this occurs before validation. Since
|
|
8
|
-
* we may be dealing with a doc _intended_ to be a resolver, but may be lacking
|
|
9
|
-
* some critical information, how can we determine intent? There’s a bit of
|
|
10
|
-
* guesswork here, but we try and find a reasonable edge case where we sniff out
|
|
11
|
-
* invalid DTCG syntax that a resolver doc would have.
|
|
12
|
-
*/
|
|
13
|
-
export function isLikelyResolver(doc: momoa.DocumentNode): boolean {
|
|
14
|
-
if (doc.body.type !== 'Object') {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
// This is a resolver if…
|
|
18
|
-
for (const member of doc.body.members) {
|
|
19
|
-
if (member.name.type !== 'String') {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
switch (member.name.value) {
|
|
23
|
-
case 'name':
|
|
24
|
-
case 'description':
|
|
25
|
-
case 'version': {
|
|
26
|
-
// 1. name, description, or version are a string
|
|
27
|
-
if (member.value.type === 'String') {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
case 'sets':
|
|
33
|
-
case 'modifiers': {
|
|
34
|
-
if (member.value.type !== 'Object') {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
// 2. sets.description or modifiers.description is a string
|
|
38
|
-
if (getObjMember(member.value, 'description')?.type === 'String') {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
// 3. sets.sources is an array
|
|
42
|
-
if (member.name.value === 'sets' && getObjMember(member.value, 'sources')?.type === 'Array') {
|
|
43
|
-
return true;
|
|
44
|
-
} else if (member.name.value === 'modifiers') {
|
|
45
|
-
const contexts = getObjMember(member.value, 'contexts');
|
|
46
|
-
if (contexts?.type === 'Object' && contexts.members.some((m) => m.value.type === 'Array')) {
|
|
47
|
-
// 4. contexts[key] is an array
|
|
48
|
-
// (note: modifiers.contexts as an object is technically valid token format! We need to check for the array)
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
case 'resolutionOrder': {
|
|
55
|
-
// 4. resolutionOrder is an array
|
|
56
|
-
if (member.value.type === 'Array') {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface ValidateResolverOptions {
|
|
68
|
-
logger: Logger;
|
|
69
|
-
src: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const MESSAGE_EXPECTED = {
|
|
73
|
-
STRING: 'Expected string.',
|
|
74
|
-
OBJECT: 'Expected object.',
|
|
75
|
-
ARRAY: 'Expected array.',
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Validate a resolver document.
|
|
80
|
-
* There’s a ton of boilerplate here, only to surface detailed code frames. Is there a better abstraction?
|
|
81
|
-
*/
|
|
82
|
-
export function validateResolver(node: momoa.DocumentNode, { logger, src }: ValidateResolverOptions) {
|
|
83
|
-
const entry = { group: 'parser', label: 'resolver', src } as const;
|
|
84
|
-
if (node.body.type !== 'Object') {
|
|
85
|
-
logger.error({ ...entry, message: MESSAGE_EXPECTED.OBJECT, node });
|
|
86
|
-
}
|
|
87
|
-
const errors: LogEntry[] = [];
|
|
88
|
-
|
|
89
|
-
let hasVersion = false;
|
|
90
|
-
let hasResolutionOrder = false;
|
|
91
|
-
|
|
92
|
-
for (const member of (node.body as momoa.ObjectNode).members) {
|
|
93
|
-
if (member.name.type !== 'String') {
|
|
94
|
-
continue; // IDK, don’t ask
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
switch (member.name.value) {
|
|
98
|
-
case 'name':
|
|
99
|
-
case 'description': {
|
|
100
|
-
if (member.value.type !== 'String') {
|
|
101
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING });
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
case 'version': {
|
|
107
|
-
hasVersion = true;
|
|
108
|
-
if (member.value.type !== 'String' || member.value.value !== '2025.10') {
|
|
109
|
-
errors.push({ ...entry, message: `Expected "version" to be "2025.10".`, node: member.value });
|
|
110
|
-
}
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
case 'sets':
|
|
115
|
-
case 'modifiers': {
|
|
116
|
-
if (member.value.type !== 'Object') {
|
|
117
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.OBJECT, node: member.value });
|
|
118
|
-
} else {
|
|
119
|
-
for (const item of member.value.members) {
|
|
120
|
-
if (item.value.type !== 'Object') {
|
|
121
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.OBJECT, node: item.value });
|
|
122
|
-
} else {
|
|
123
|
-
const validator = member.name.value === 'sets' ? validateSet : validateModifier;
|
|
124
|
-
errors.push(...validator(item.value, false, { logger, src }));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
case 'resolutionOrder': {
|
|
132
|
-
hasResolutionOrder = true;
|
|
133
|
-
if (member.value.type !== 'Array') {
|
|
134
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.ARRAY, node: member.value });
|
|
135
|
-
} else if (member.value.elements.length === 0) {
|
|
136
|
-
errors.push({ ...entry, message: `"resolutionOrder" can’t be empty array.`, node: member.value });
|
|
137
|
-
} else {
|
|
138
|
-
for (const item of member.value.elements) {
|
|
139
|
-
if (item.value.type !== 'Object') {
|
|
140
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.OBJECT, node: item.value });
|
|
141
|
-
} else {
|
|
142
|
-
const itemMembers = getObjMembers(item.value);
|
|
143
|
-
if (itemMembers.$ref?.type === 'String') {
|
|
144
|
-
continue; // we can’t validate this just yet, assume it’s correct
|
|
145
|
-
}
|
|
146
|
-
// Validate "type"
|
|
147
|
-
if (itemMembers.type?.type === 'String') {
|
|
148
|
-
if (itemMembers.type.value === 'set') {
|
|
149
|
-
validateSet(item.value, true, { logger, src });
|
|
150
|
-
} else if (itemMembers.type.value === 'modifier') {
|
|
151
|
-
validateModifier(item.value, true, { logger, src });
|
|
152
|
-
} else {
|
|
153
|
-
errors.push({
|
|
154
|
-
...entry,
|
|
155
|
-
message: `Unknown type ${JSON.stringify(itemMembers.type.value)}`,
|
|
156
|
-
node: itemMembers.type,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// validate sets & modifiers if they’re missing "type"
|
|
161
|
-
if (itemMembers.sources?.type === 'Array') {
|
|
162
|
-
validateSet(item.value, true, { logger, src });
|
|
163
|
-
} else if (itemMembers.contexts?.type === 'Object') {
|
|
164
|
-
validateModifier(item.value, true, { logger, src });
|
|
165
|
-
} else if (itemMembers.name?.type === 'String' || itemMembers.description?.type === 'String') {
|
|
166
|
-
validateSet(item.value, true, { logger, src }); // if this has a "name" or "description", guess set
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
case '$defs':
|
|
174
|
-
case '$extensions':
|
|
175
|
-
if (member.value.type !== 'Object') {
|
|
176
|
-
errors.push({ ...entry, message: `Expected object`, node: member.value });
|
|
177
|
-
}
|
|
178
|
-
break;
|
|
179
|
-
case '$schema':
|
|
180
|
-
case '$ref': {
|
|
181
|
-
if (member.value.type !== 'String') {
|
|
182
|
-
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
183
|
-
}
|
|
184
|
-
break;
|
|
185
|
-
}
|
|
186
|
-
default: {
|
|
187
|
-
errors.push({ ...entry, message: `Unknown key ${JSON.stringify(member.name.value)}`, node: member.name, src });
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// handle required keys
|
|
194
|
-
if (!hasVersion) {
|
|
195
|
-
errors.push({ ...entry, message: `Missing "version".`, node, src });
|
|
196
|
-
}
|
|
197
|
-
if (!hasResolutionOrder) {
|
|
198
|
-
errors.push({ ...entry, message: `Missing "resolutionOrder".`, node, src });
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (errors.length) {
|
|
202
|
-
logger.error(...errors);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function validateSet(node: momoa.ObjectNode, isInline = false, { src }: ValidateResolverOptions): LogEntry[] {
|
|
207
|
-
const entry = { group: 'parser', label: 'resolver', src } as const;
|
|
208
|
-
const errors: LogEntry[] = [];
|
|
209
|
-
let hasName = !isInline;
|
|
210
|
-
let hasType = !isInline;
|
|
211
|
-
let hasSources = false;
|
|
212
|
-
for (const member of node.members) {
|
|
213
|
-
if (member.name.type !== 'String') {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
switch (member.name.value) {
|
|
217
|
-
case 'name': {
|
|
218
|
-
hasName = true;
|
|
219
|
-
if (member.value.type !== 'String') {
|
|
220
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
221
|
-
}
|
|
222
|
-
break;
|
|
223
|
-
}
|
|
224
|
-
case 'description': {
|
|
225
|
-
if (member.value.type !== 'String') {
|
|
226
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
}
|
|
230
|
-
case 'type': {
|
|
231
|
-
hasType = true;
|
|
232
|
-
if (member.value.type !== 'String') {
|
|
233
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
234
|
-
} else if (member.value.value !== 'set') {
|
|
235
|
-
errors.push({ ...entry, message: '"type" must be "set".' });
|
|
236
|
-
}
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
case 'sources': {
|
|
240
|
-
hasSources = true;
|
|
241
|
-
if (member.value.type !== 'Array') {
|
|
242
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.ARRAY, node: member.value });
|
|
243
|
-
} else if (member.value.elements.length === 0) {
|
|
244
|
-
errors.push({ ...entry, message: `"sources" can’t be empty array.`, node: member.value });
|
|
245
|
-
}
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
case '$defs':
|
|
249
|
-
case '$extensions':
|
|
250
|
-
if (member.value.type !== 'Object') {
|
|
251
|
-
errors.push({ ...entry, message: `Expected object`, node: member.value });
|
|
252
|
-
}
|
|
253
|
-
break;
|
|
254
|
-
case '$ref': {
|
|
255
|
-
if (member.value.type !== 'String') {
|
|
256
|
-
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
257
|
-
}
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
default: {
|
|
261
|
-
errors.push({ ...entry, message: `Unknown key ${JSON.stringify(member.name.value)}`, node: member.name });
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// handle required keys
|
|
268
|
-
if (!hasName) {
|
|
269
|
-
errors.push({ ...entry, message: `Missing "name".`, node });
|
|
270
|
-
}
|
|
271
|
-
if (!hasType) {
|
|
272
|
-
errors.push({ ...entry, message: `"type": "set" missing.`, node });
|
|
273
|
-
}
|
|
274
|
-
if (!hasSources) {
|
|
275
|
-
errors.push({ ...entry, message: `Missing "sources".`, node });
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return errors;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export function validateModifier(
|
|
282
|
-
node: momoa.ObjectNode,
|
|
283
|
-
isInline = false,
|
|
284
|
-
{ src }: ValidateResolverOptions,
|
|
285
|
-
): LogEntry[] {
|
|
286
|
-
const errors: LogEntry[] = [];
|
|
287
|
-
const entry = { group: 'parser', label: 'resolver', src } as const;
|
|
288
|
-
let hasName = !isInline;
|
|
289
|
-
let hasType = !isInline;
|
|
290
|
-
let hasContexts = false;
|
|
291
|
-
for (const member of node.members) {
|
|
292
|
-
if (member.name.type !== 'String') {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
switch (member.name.value) {
|
|
296
|
-
case 'name': {
|
|
297
|
-
hasName = true;
|
|
298
|
-
if (member.value.type !== 'String') {
|
|
299
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
300
|
-
}
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
case 'description': {
|
|
304
|
-
if (member.value.type !== 'String') {
|
|
305
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
306
|
-
}
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
case 'type': {
|
|
310
|
-
hasType = true;
|
|
311
|
-
if (member.value.type !== 'String') {
|
|
312
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
313
|
-
} else if (member.value.value !== 'modifier') {
|
|
314
|
-
errors.push({ ...entry, message: '"type" must be "modifier".' });
|
|
315
|
-
}
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
case 'contexts': {
|
|
319
|
-
hasContexts = true;
|
|
320
|
-
if (member.value.type !== 'Object') {
|
|
321
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.OBJECT, node: member.value });
|
|
322
|
-
} else if (member.value.members.length === 0) {
|
|
323
|
-
errors.push({ ...entry, message: `"contexts" can’t be empty object.`, node: member.value });
|
|
324
|
-
} else {
|
|
325
|
-
for (const context of member.value.members) {
|
|
326
|
-
if (context.value.type !== 'Array') {
|
|
327
|
-
errors.push({ ...entry, message: MESSAGE_EXPECTED.ARRAY, node: context.value });
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
break;
|
|
332
|
-
}
|
|
333
|
-
case 'default': {
|
|
334
|
-
if (member.value.type !== 'String') {
|
|
335
|
-
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
336
|
-
} else {
|
|
337
|
-
const contexts = getObjMember(node, 'contexts') as momoa.ObjectNode | undefined;
|
|
338
|
-
if (!contexts || !getObjMember(contexts, member.value.value)) {
|
|
339
|
-
errors.push({ ...entry, message: 'Invalid default context', node: member.value });
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
case '$defs':
|
|
345
|
-
case '$extensions':
|
|
346
|
-
if (member.value.type !== 'Object') {
|
|
347
|
-
errors.push({ ...entry, message: `Expected object`, node: member.value });
|
|
348
|
-
}
|
|
349
|
-
break;
|
|
350
|
-
case '$ref': {
|
|
351
|
-
if (member.value.type !== 'String') {
|
|
352
|
-
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
353
|
-
}
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
default: {
|
|
357
|
-
errors.push({ ...entry, message: `Unknown key ${JSON.stringify(member.name.value)}`, node: member.name });
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// handle required keys
|
|
364
|
-
if (!hasName) {
|
|
365
|
-
errors.push({ ...entry, message: `Missing "name".`, node });
|
|
366
|
-
}
|
|
367
|
-
if (!hasType) {
|
|
368
|
-
errors.push({ ...entry, message: `"type": "modifier" missing.`, node });
|
|
369
|
-
}
|
|
370
|
-
if (!hasContexts) {
|
|
371
|
-
errors.push({ ...entry, message: `Missing "contexts".`, node });
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return errors;
|
|
375
|
-
}
|