@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.
Files changed (53) hide show
  1. package/dist/index.d.ts +39 -6
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +578 -512
  4. package/dist/index.js.map +1 -1
  5. package/package.json +3 -3
  6. package/src/build/index.ts +0 -209
  7. package/src/config.ts +0 -304
  8. package/src/index.ts +0 -95
  9. package/src/lib/code-frame.ts +0 -177
  10. package/src/lib/momoa.ts +0 -10
  11. package/src/lib/resolver-utils.ts +0 -35
  12. package/src/lint/index.ts +0 -142
  13. package/src/lint/plugin-core/index.ts +0 -103
  14. package/src/lint/plugin-core/lib/docs.ts +0 -3
  15. package/src/lint/plugin-core/rules/a11y-min-contrast.ts +0 -91
  16. package/src/lint/plugin-core/rules/a11y-min-font-size.ts +0 -66
  17. package/src/lint/plugin-core/rules/colorspace.ts +0 -108
  18. package/src/lint/plugin-core/rules/consistent-naming.ts +0 -65
  19. package/src/lint/plugin-core/rules/descriptions.ts +0 -43
  20. package/src/lint/plugin-core/rules/duplicate-values.ts +0 -85
  21. package/src/lint/plugin-core/rules/max-gamut.ts +0 -144
  22. package/src/lint/plugin-core/rules/required-children.ts +0 -106
  23. package/src/lint/plugin-core/rules/required-modes.ts +0 -75
  24. package/src/lint/plugin-core/rules/required-type.ts +0 -28
  25. package/src/lint/plugin-core/rules/required-typography-properties.ts +0 -65
  26. package/src/lint/plugin-core/rules/valid-boolean.ts +0 -41
  27. package/src/lint/plugin-core/rules/valid-border.ts +0 -57
  28. package/src/lint/plugin-core/rules/valid-color.ts +0 -265
  29. package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +0 -83
  30. package/src/lint/plugin-core/rules/valid-dimension.ts +0 -199
  31. package/src/lint/plugin-core/rules/valid-duration.ts +0 -123
  32. package/src/lint/plugin-core/rules/valid-font-family.ts +0 -68
  33. package/src/lint/plugin-core/rules/valid-font-weight.ts +0 -89
  34. package/src/lint/plugin-core/rules/valid-gradient.ts +0 -79
  35. package/src/lint/plugin-core/rules/valid-link.ts +0 -41
  36. package/src/lint/plugin-core/rules/valid-number.ts +0 -63
  37. package/src/lint/plugin-core/rules/valid-shadow.ts +0 -67
  38. package/src/lint/plugin-core/rules/valid-string.ts +0 -41
  39. package/src/lint/plugin-core/rules/valid-stroke-style.ts +0 -104
  40. package/src/lint/plugin-core/rules/valid-transition.ts +0 -61
  41. package/src/lint/plugin-core/rules/valid-typography.ts +0 -67
  42. package/src/logger.ts +0 -213
  43. package/src/parse/index.ts +0 -124
  44. package/src/parse/load.ts +0 -172
  45. package/src/parse/normalize.ts +0 -163
  46. package/src/parse/process.ts +0 -251
  47. package/src/parse/token.ts +0 -553
  48. package/src/resolver/create-synthetic-resolver.ts +0 -86
  49. package/src/resolver/index.ts +0 -7
  50. package/src/resolver/load.ts +0 -215
  51. package/src/resolver/normalize.ts +0 -133
  52. package/src/resolver/validate.ts +0 -375
  53. package/src/types.ts +0 -468
@@ -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
- }