@terrazzo/parser 2.0.0-alpha.2 → 2.0.0-alpha.4
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 +5 -12
- package/README.md +1 -1
- package/dist/index.d.ts +181 -57
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +877 -122
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/build/index.ts +2 -2
- package/src/config.ts +1 -2
- package/src/index.ts +2 -0
- package/src/lib/resolver-utils.ts +35 -0
- package/src/lint/index.ts +4 -3
- package/src/lint/plugin-core/index.ts +3 -4
- package/src/lint/plugin-core/lib/docs.ts +1 -1
- package/src/lint/plugin-core/rules/{no-type-on-alias.ts → required-type.ts} +5 -6
- package/src/parse/index.ts +51 -4
- package/src/parse/load.ts +25 -111
- package/src/parse/process.ts +124 -0
- package/src/parse/token.ts +12 -7
- package/src/resolver/create-synthetic-resolver.ts +86 -0
- package/src/resolver/index.ts +7 -0
- package/src/resolver/load.ts +216 -0
- package/src/resolver/normalize.ts +106 -0
- package/src/resolver/validate.ts +363 -0
- package/src/types.ts +113 -44
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import type * 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.name.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 '$ref': {
|
|
180
|
+
if (member.value.type !== 'String') {
|
|
181
|
+
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
default: {
|
|
186
|
+
errors.push({ ...entry, message: `Unknown key ${JSON.stringify(member.name.value)}`, node: member.name, src });
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// handle required keys
|
|
193
|
+
if (!hasVersion) {
|
|
194
|
+
errors.push({ ...entry, message: `Missing "version".`, node, src });
|
|
195
|
+
}
|
|
196
|
+
if (!hasResolutionOrder) {
|
|
197
|
+
errors.push({ ...entry, message: `Missing "resolutionOrder".`, node, src });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (errors.length) {
|
|
201
|
+
logger.error(...errors);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function validateSet(node: momoa.ObjectNode, isInline = false, { src }: ValidateResolverOptions): LogEntry[] {
|
|
206
|
+
const entry = { group: 'parser', label: 'resolver', src } as const;
|
|
207
|
+
const errors: LogEntry[] = [];
|
|
208
|
+
let hasName = !isInline;
|
|
209
|
+
let hasType = !isInline;
|
|
210
|
+
let hasSources = false;
|
|
211
|
+
for (const member of node.members) {
|
|
212
|
+
if (member.name.type !== 'String') {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
switch (member.name.value) {
|
|
216
|
+
case 'name': {
|
|
217
|
+
hasName = true;
|
|
218
|
+
if (member.value.type !== 'String') {
|
|
219
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 'description': {
|
|
224
|
+
if (member.value.type !== 'String') {
|
|
225
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
case 'type': {
|
|
230
|
+
hasType = true;
|
|
231
|
+
if (member.value.type !== 'String') {
|
|
232
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
233
|
+
} else if (member.value.value !== 'set') {
|
|
234
|
+
errors.push({ ...entry, message: '"type" must be "set".' });
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case 'sources': {
|
|
239
|
+
hasSources = true;
|
|
240
|
+
if (member.value.type !== 'Array') {
|
|
241
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.ARRAY, node: member.value });
|
|
242
|
+
} else if (member.value.elements.length === 0) {
|
|
243
|
+
errors.push({ ...entry, message: `"sources" can’t be empty array.`, node: member.value });
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case '$defs':
|
|
248
|
+
case '$extensions':
|
|
249
|
+
if (member.value.type !== 'Object') {
|
|
250
|
+
errors.push({ ...entry, message: `Expected object`, node: member.value });
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
case '$ref': {
|
|
254
|
+
if (member.value.type !== 'String') {
|
|
255
|
+
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
default: {
|
|
260
|
+
errors.push({ ...entry, message: `Unknown key ${JSON.stringify(member.name.value)}`, node: member.name });
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// handle required keys
|
|
267
|
+
if (!hasName) {
|
|
268
|
+
errors.push({ ...entry, message: `Missing "name".`, node });
|
|
269
|
+
}
|
|
270
|
+
if (!hasType) {
|
|
271
|
+
errors.push({ ...entry, message: `"type": "set" missing.`, node });
|
|
272
|
+
}
|
|
273
|
+
if (!hasSources) {
|
|
274
|
+
errors.push({ ...entry, message: `Missing "sources".`, node });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return errors;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function validateModifier(
|
|
281
|
+
node: momoa.ObjectNode,
|
|
282
|
+
isInline = false,
|
|
283
|
+
{ src }: ValidateResolverOptions,
|
|
284
|
+
): LogEntry[] {
|
|
285
|
+
const errors: LogEntry[] = [];
|
|
286
|
+
const entry = { group: 'parser', label: 'resolver', src } as const;
|
|
287
|
+
let hasName = !isInline;
|
|
288
|
+
let hasType = !isInline;
|
|
289
|
+
let hasContexts = false;
|
|
290
|
+
for (const member of node.members) {
|
|
291
|
+
if (member.name.type !== 'String') {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
switch (member.name.value) {
|
|
295
|
+
case 'name': {
|
|
296
|
+
hasName = true;
|
|
297
|
+
if (member.value.type !== 'String') {
|
|
298
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'description': {
|
|
303
|
+
if (member.value.type !== 'String') {
|
|
304
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'type': {
|
|
309
|
+
hasType = true;
|
|
310
|
+
if (member.value.type !== 'String') {
|
|
311
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
312
|
+
} else if (member.value.value !== 'modifier') {
|
|
313
|
+
errors.push({ ...entry, message: '"type" must be "modifier".' });
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case 'contexts': {
|
|
318
|
+
hasContexts = true;
|
|
319
|
+
if (member.value.type !== 'Object') {
|
|
320
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.OBJECT, node: member.value });
|
|
321
|
+
} else if (member.value.members.length === 0) {
|
|
322
|
+
errors.push({ ...entry, message: `"contexts" can’t be empty object.`, node: member.value });
|
|
323
|
+
} else {
|
|
324
|
+
for (const context of member.value.members) {
|
|
325
|
+
if (context.value.type !== 'Array') {
|
|
326
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.ARRAY, node: context.value });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case '$defs':
|
|
333
|
+
case '$extensions':
|
|
334
|
+
if (member.value.type !== 'Object') {
|
|
335
|
+
errors.push({ ...entry, message: `Expected object`, node: member.value });
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
case '$ref': {
|
|
339
|
+
if (member.value.type !== 'String') {
|
|
340
|
+
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
default: {
|
|
345
|
+
errors.push({ ...entry, message: `Unknown key ${JSON.stringify(member.name.value)}`, node: member.name });
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// handle required keys
|
|
352
|
+
if (!hasName) {
|
|
353
|
+
errors.push({ ...entry, message: `Missing "name".`, node });
|
|
354
|
+
}
|
|
355
|
+
if (!hasType) {
|
|
356
|
+
errors.push({ ...entry, message: `"type": "modifier" missing.`, node });
|
|
357
|
+
}
|
|
358
|
+
if (!hasContexts) {
|
|
359
|
+
errors.push({ ...entry, message: `Missing "contexts".`, node });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return errors;
|
|
363
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import type {
|
|
2
|
+
import type { InputSourceWithDocument } from '@terrazzo/json-schema-tools';
|
|
3
|
+
import type {
|
|
4
|
+
Group,
|
|
5
|
+
TokenNormalized,
|
|
6
|
+
TokenNormalizedSet,
|
|
7
|
+
TokenTransformed,
|
|
8
|
+
TokenTransformedBase,
|
|
9
|
+
} from '@terrazzo/token-tools';
|
|
3
10
|
import type ytm from 'yaml-to-momoa';
|
|
4
11
|
import type Logger from './logger.js';
|
|
5
12
|
|
|
13
|
+
// Export some types as a convenience, because they originally came from this package
|
|
14
|
+
export type {
|
|
15
|
+
Group,
|
|
16
|
+
TokenNormalized,
|
|
17
|
+
TokenNormalizedSet,
|
|
18
|
+
TokenTransformed,
|
|
19
|
+
TokenTransformedBase,
|
|
20
|
+
} from '@terrazzo/token-tools';
|
|
21
|
+
|
|
6
22
|
export interface PluginHookContext {
|
|
7
23
|
logger: Logger;
|
|
8
24
|
}
|
|
@@ -15,7 +31,7 @@ export interface BuildHookOptions {
|
|
|
15
31
|
/** Query transformed values */
|
|
16
32
|
getTransforms(params: TransformParams): TokenTransformed[];
|
|
17
33
|
/** Momoa documents */
|
|
18
|
-
sources:
|
|
34
|
+
sources: InputSourceWithDocument[];
|
|
19
35
|
outputFile: (
|
|
20
36
|
/** Filename to output (relative to outDir) */
|
|
21
37
|
filename: string,
|
|
@@ -36,7 +52,7 @@ export interface BuildEndHookOptions {
|
|
|
36
52
|
/** Query transformed values */
|
|
37
53
|
getTransforms(params: TransformParams): TokenTransformed[];
|
|
38
54
|
/** Momoa documents */
|
|
39
|
-
sources:
|
|
55
|
+
sources: InputSourceWithDocument[];
|
|
40
56
|
/** Final files to be written */
|
|
41
57
|
outputFiles: OutputFileExpanded[];
|
|
42
58
|
}
|
|
@@ -132,12 +148,6 @@ export interface ConfigOptions {
|
|
|
132
148
|
cwd: URL;
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
export interface InputSource {
|
|
136
|
-
filename?: URL;
|
|
137
|
-
src: any;
|
|
138
|
-
document: momoa.DocumentNode;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
151
|
export interface LintNotice {
|
|
142
152
|
/** Lint message shown to the user */
|
|
143
153
|
message: string;
|
|
@@ -211,7 +221,7 @@ export interface LintRuleContext<MessageIds extends string, LintRuleOptions exte
|
|
|
211
221
|
* All source files present in this run. To find the original source, match a
|
|
212
222
|
* token’s `source.loc` filename to one of the source’s `filename`s.
|
|
213
223
|
*/
|
|
214
|
-
sources:
|
|
224
|
+
sources: InputSourceWithDocument[];
|
|
215
225
|
/** Source file location. */
|
|
216
226
|
filename?: URL;
|
|
217
227
|
/** ID:Token map of all tokens. */
|
|
@@ -231,7 +241,7 @@ export interface LintRuleMetaData<
|
|
|
231
241
|
docs?: LintRuleDocs & LintRuleMetaDataDocs;
|
|
232
242
|
/**
|
|
233
243
|
* A map of messages which the rule can report. The key is the messageId, and
|
|
234
|
-
* the string is the
|
|
244
|
+
* the string is the parameterized error string.
|
|
235
245
|
*/
|
|
236
246
|
messages?: Record<MessageIds, string>;
|
|
237
247
|
/**
|
|
@@ -270,6 +280,12 @@ export interface OutputFileExpanded extends OutputFile {
|
|
|
270
280
|
export interface ParseOptions {
|
|
271
281
|
logger?: Logger;
|
|
272
282
|
config: ConfigInit;
|
|
283
|
+
/**
|
|
284
|
+
* Handle requests to loading remote files, either from a remote URL or on the filesystem.
|
|
285
|
+
* - Remote requests will have an "https:' protocol
|
|
286
|
+
* - Filesystem files will have a "file:" protocol
|
|
287
|
+
*/
|
|
288
|
+
req?: (src: URL, origin: URL) => Promise<string>;
|
|
273
289
|
/**
|
|
274
290
|
* Skip lint step
|
|
275
291
|
* @default false
|
|
@@ -288,7 +304,7 @@ export interface ParseOptions {
|
|
|
288
304
|
*/
|
|
289
305
|
transform?: TransformVisitors;
|
|
290
306
|
/** (internal cache; do not use) */
|
|
291
|
-
_sources?: Record<string,
|
|
307
|
+
_sources?: Record<string, InputSourceWithDocument>;
|
|
292
308
|
}
|
|
293
309
|
|
|
294
310
|
export interface Plugin {
|
|
@@ -310,42 +326,99 @@ export interface Plugin {
|
|
|
310
326
|
buildEnd?(options: BuildEndHookOptions): Promise<void>;
|
|
311
327
|
}
|
|
312
328
|
|
|
313
|
-
interface
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
329
|
+
export interface ReferenceObject {
|
|
330
|
+
$ref: string;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface Resolver<
|
|
334
|
+
Inputs extends Record<string, string[]> = Record<string, string[]>,
|
|
335
|
+
Input = Record<keyof Inputs, Inputs[keyof Inputs][number]>,
|
|
336
|
+
> {
|
|
337
|
+
/** Supply values to modifiers to produce a final tokens set */
|
|
338
|
+
apply: (input: Partial<Input>) => TokenNormalizedSet;
|
|
339
|
+
/** List all possible valid input combinations. Ignores default values, as they would duplicate some other permutations. */
|
|
340
|
+
listPermutations: () => Input[];
|
|
341
|
+
/** The original resolver document, simplified */
|
|
342
|
+
source: ResolverSourceNormalized;
|
|
343
|
+
/** Helper function for permutations—see if a particular input is valid. Automatically applies default values. */
|
|
344
|
+
isValidInput: (input: Input) => boolean;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export interface ResolverSource {
|
|
348
|
+
/** Human-friendly name of this resolver */
|
|
349
|
+
name?: string;
|
|
350
|
+
/** DTCG version */
|
|
351
|
+
version: '2025.10';
|
|
352
|
+
/** Description of this resolver */
|
|
353
|
+
description?: string;
|
|
354
|
+
/** Mapping of sets */
|
|
355
|
+
sets?: Record<string, ResolverSet>;
|
|
356
|
+
/** Mapping of modifiers */
|
|
357
|
+
modifiers?: Record<string, ResolverModifier>;
|
|
358
|
+
resolutionOrder: (ResolverSetInline | ResolverModifierInline | ReferenceObject)[];
|
|
359
|
+
$extensions?: Record<string, unknown>;
|
|
360
|
+
$defs?: Record<string, unknown>;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Resolver where all tokens are loaded and flattened in-memory, so only the final merging is left */
|
|
364
|
+
export interface ResolverSourceNormalized {
|
|
365
|
+
name: string | undefined;
|
|
366
|
+
version: '2025.10';
|
|
367
|
+
description: string | undefined;
|
|
368
|
+
sets: Record<string, ResolverSet> | undefined;
|
|
369
|
+
modifiers: Record<string, ResolverModifier> | undefined;
|
|
318
370
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
371
|
+
* Array of all sets and modifiers that have been converted to inline,
|
|
372
|
+
* regardless of original declaration. In a normalized resolver, only a single
|
|
373
|
+
* pass over the resolutionOrder array is needed.
|
|
321
374
|
*/
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
meta?: Record<string | number | symbol, unknown> & {
|
|
327
|
-
/**
|
|
328
|
-
* Metadata for the token-listing plugin. Plugins can
|
|
329
|
-
* set this to be the name of a token as it appears in code,
|
|
330
|
-
* and the token-listing plugin will pick it up and use it.
|
|
331
|
-
*/
|
|
332
|
-
'token-listing'?: { name: string | undefined };
|
|
375
|
+
resolutionOrder: (ResolverSetNormalized | ResolverModifierNormalized)[];
|
|
376
|
+
_source: {
|
|
377
|
+
filename?: URL;
|
|
378
|
+
node: momoa.DocumentNode;
|
|
333
379
|
};
|
|
334
380
|
}
|
|
335
381
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
382
|
+
export interface ResolverModifier<Context extends string = string> {
|
|
383
|
+
description?: string;
|
|
384
|
+
contexts: Record<Context, (Group | ReferenceObject)[]>;
|
|
385
|
+
default?: Context;
|
|
386
|
+
$extensions?: Record<string, unknown>;
|
|
387
|
+
$defs?: Record<string, unknown>;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export type ResolverModifierInline<Context extends string = string> = ResolverModifier<Context> & {
|
|
391
|
+
name: string;
|
|
392
|
+
type: 'modifier';
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export interface ResolverModifierNormalized {
|
|
396
|
+
name: string;
|
|
397
|
+
type: 'modifier';
|
|
398
|
+
description: string | undefined;
|
|
399
|
+
contexts: Record<string, Group[]>;
|
|
400
|
+
default: string | undefined;
|
|
401
|
+
$extensions: Record<string, unknown> | undefined;
|
|
402
|
+
$defs: Record<string, unknown> | undefined;
|
|
340
403
|
}
|
|
341
404
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
405
|
+
export interface ResolverSet {
|
|
406
|
+
description?: string;
|
|
407
|
+
sources: (Group | ReferenceObject)[];
|
|
408
|
+
$extensions?: Record<string, unknown>;
|
|
409
|
+
$defs?: Record<string, unknown>;
|
|
346
410
|
}
|
|
347
411
|
|
|
348
|
-
export type
|
|
412
|
+
export type ResolverSetInline = ResolverSet & { name: string; type: 'set' };
|
|
413
|
+
|
|
414
|
+
export interface ResolverSetNormalized {
|
|
415
|
+
name: string;
|
|
416
|
+
type: 'set';
|
|
417
|
+
description: string | undefined;
|
|
418
|
+
sources: Group[];
|
|
419
|
+
$extensions: Record<string, unknown> | undefined;
|
|
420
|
+
$defs: Record<string, unknown> | undefined;
|
|
421
|
+
}
|
|
349
422
|
|
|
350
423
|
export interface TransformParams {
|
|
351
424
|
/** ID of an existing format */
|
|
@@ -380,9 +453,5 @@ export interface TransformHookOptions {
|
|
|
380
453
|
},
|
|
381
454
|
): void;
|
|
382
455
|
/** Momoa documents */
|
|
383
|
-
sources:
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export interface ReferenceObject {
|
|
387
|
-
$ref: string;
|
|
456
|
+
sources: InputSourceWithDocument[];
|
|
388
457
|
}
|