@terrazzo/parser 2.0.0-alpha.2 → 2.0.0-alpha.3
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 +159 -40
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +711 -58
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/config.ts +1 -2
- package/src/index.ts +2 -0
- 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 +36 -2
- package/src/parse/load.ts +10 -6
- package/src/parse/token.ts +6 -1
- package/src/resolver/index.ts +7 -0
- package/src/resolver/load.ts +161 -0
- package/src/resolver/normalize.ts +99 -0
- package/src/resolver/validate.ts +359 -0
- package/src/types.ts +104 -34
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import type { DocumentNode, ObjectNode } 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: 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: 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 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: 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(node: ObjectNode, isInline = false, { src }: ValidateResolverOptions): LogEntry[] {
|
|
281
|
+
const errors: LogEntry[] = [];
|
|
282
|
+
const entry = { group: 'parser', label: 'resolver', src } as const;
|
|
283
|
+
let hasName = !isInline;
|
|
284
|
+
let hasType = !isInline;
|
|
285
|
+
let hasContexts = false;
|
|
286
|
+
for (const member of node.members) {
|
|
287
|
+
if (member.name.type !== 'String') {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
switch (member.name.value) {
|
|
291
|
+
case 'name': {
|
|
292
|
+
hasName = true;
|
|
293
|
+
if (member.value.type !== 'String') {
|
|
294
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case 'description': {
|
|
299
|
+
if (member.value.type !== 'String') {
|
|
300
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case 'type': {
|
|
305
|
+
hasType = true;
|
|
306
|
+
if (member.value.type !== 'String') {
|
|
307
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.STRING, node: member.value });
|
|
308
|
+
} else if (member.value.value !== 'modifier') {
|
|
309
|
+
errors.push({ ...entry, message: '"type" must be "modifier".' });
|
|
310
|
+
}
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case 'contexts': {
|
|
314
|
+
hasContexts = true;
|
|
315
|
+
if (member.value.type !== 'Object') {
|
|
316
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.OBJECT, node: member.value });
|
|
317
|
+
} else if (member.value.members.length === 0) {
|
|
318
|
+
errors.push({ ...entry, message: `"contexts" can’t be empty object.`, node: member.value });
|
|
319
|
+
} else {
|
|
320
|
+
for (const context of member.value.members) {
|
|
321
|
+
if (context.value.type !== 'Array') {
|
|
322
|
+
errors.push({ ...entry, message: MESSAGE_EXPECTED.ARRAY, node: context.value });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
case '$defs':
|
|
329
|
+
case '$extensions':
|
|
330
|
+
if (member.value.type !== 'Object') {
|
|
331
|
+
errors.push({ ...entry, message: `Expected object`, node: member.value });
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
case '$ref': {
|
|
335
|
+
if (member.value.type !== 'String') {
|
|
336
|
+
errors.push({ ...entry, message: `Expected string`, node: member.value });
|
|
337
|
+
}
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
default: {
|
|
341
|
+
errors.push({ ...entry, message: `Unknown key ${JSON.stringify(member.name.value)}`, node: member.name });
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// handle required keys
|
|
348
|
+
if (!hasName) {
|
|
349
|
+
errors.push({ ...entry, message: `Missing "name".`, node });
|
|
350
|
+
}
|
|
351
|
+
if (!hasType) {
|
|
352
|
+
errors.push({ ...entry, message: `"type": "modifier" missing.`, node });
|
|
353
|
+
}
|
|
354
|
+
if (!hasContexts) {
|
|
355
|
+
errors.push({ ...entry, message: `Missing "contexts".`, node });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return errors;
|
|
359
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
Group,
|
|
4
|
+
TokenNormalized,
|
|
5
|
+
TokenNormalizedSet,
|
|
6
|
+
TokenTransformed,
|
|
7
|
+
TokenTransformedBase,
|
|
8
|
+
} from '@terrazzo/token-tools';
|
|
3
9
|
import type ytm from 'yaml-to-momoa';
|
|
4
10
|
import type Logger from './logger.js';
|
|
5
11
|
|
|
12
|
+
// Export some types as a convenience, because they originally came from this package
|
|
13
|
+
export type {
|
|
14
|
+
Group,
|
|
15
|
+
TokenNormalized,
|
|
16
|
+
TokenNormalizedSet,
|
|
17
|
+
TokenTransformed,
|
|
18
|
+
TokenTransformedBase,
|
|
19
|
+
} from '@terrazzo/token-tools';
|
|
20
|
+
|
|
6
21
|
export interface PluginHookContext {
|
|
7
22
|
logger: Logger;
|
|
8
23
|
}
|
|
@@ -231,7 +246,7 @@ export interface LintRuleMetaData<
|
|
|
231
246
|
docs?: LintRuleDocs & LintRuleMetaDataDocs;
|
|
232
247
|
/**
|
|
233
248
|
* A map of messages which the rule can report. The key is the messageId, and
|
|
234
|
-
* the string is the
|
|
249
|
+
* the string is the parameterized error string.
|
|
235
250
|
*/
|
|
236
251
|
messages?: Record<MessageIds, string>;
|
|
237
252
|
/**
|
|
@@ -270,6 +285,12 @@ export interface OutputFileExpanded extends OutputFile {
|
|
|
270
285
|
export interface ParseOptions {
|
|
271
286
|
logger?: Logger;
|
|
272
287
|
config: ConfigInit;
|
|
288
|
+
/**
|
|
289
|
+
* Handle requests to loading remote files, either from a remote URL or on the filesystem.
|
|
290
|
+
* - Remote requests will have an "https:' protocol
|
|
291
|
+
* - Filesystem files will have a "file:" protocol
|
|
292
|
+
*/
|
|
293
|
+
req?: (src: URL, origin: URL) => Promise<string>;
|
|
273
294
|
/**
|
|
274
295
|
* Skip lint step
|
|
275
296
|
* @default false
|
|
@@ -310,42 +331,95 @@ export interface Plugin {
|
|
|
310
331
|
buildEnd?(options: BuildEndHookOptions): Promise<void>;
|
|
311
332
|
}
|
|
312
333
|
|
|
313
|
-
interface
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
334
|
+
export interface ReferenceObject {
|
|
335
|
+
$ref: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface Resolver<
|
|
339
|
+
Inputs extends Record<string, string[]> = Record<string, string[]>,
|
|
340
|
+
Input = Record<keyof Inputs, Inputs[keyof Inputs][number]>,
|
|
341
|
+
> {
|
|
342
|
+
/** Supply values to modifiers to produce a final tokens set */
|
|
343
|
+
apply: (input: Partial<Input>) => TokenNormalizedSet;
|
|
344
|
+
/** List all possible valid input combinations. Ignores default values, as they would duplicate some other permutations. */
|
|
345
|
+
permutations: Input[];
|
|
346
|
+
/** The original resolver document, simplified */
|
|
347
|
+
source: ResolverSourceNormalized;
|
|
348
|
+
/** Helper function for permutations—see if a particular input is valid. Automatically applies default values. */
|
|
349
|
+
isValidInput: (input: Input) => boolean;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface ResolverSource {
|
|
353
|
+
/** Human-friendly name of this resolver */
|
|
354
|
+
name?: string;
|
|
355
|
+
/** DTCG version */
|
|
356
|
+
version: '2025.10';
|
|
357
|
+
/** Description of this resolver */
|
|
358
|
+
description?: string;
|
|
359
|
+
/** Mapping of sets */
|
|
360
|
+
sets?: Record<string, ResolverSet>;
|
|
361
|
+
/** Mapping of modifiers */
|
|
362
|
+
modifiers?: Record<string, ResolverModifier>;
|
|
363
|
+
resolutionOrder: (ResolverSetInline | ResolverModifierInline | ReferenceObject)[];
|
|
364
|
+
$extensions?: Record<string, unknown>;
|
|
365
|
+
$defs?: Record<string, unknown>;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Resolver where all tokens are loaded and flattened in-memory, so only the final merging is left */
|
|
369
|
+
export interface ResolverSourceNormalized {
|
|
370
|
+
name: string | undefined;
|
|
371
|
+
version: '2025.10';
|
|
372
|
+
description: string | undefined;
|
|
373
|
+
sets: Record<string, ResolverSet> | undefined;
|
|
374
|
+
modifiers: Record<string, ResolverModifier> | undefined;
|
|
318
375
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
376
|
+
* Array of all sets and modifiers that have been converted to inline,
|
|
377
|
+
* regardless of original declaration. In a normalized resolver, only a single
|
|
378
|
+
* pass over the resolutionOrder array is needed.
|
|
321
379
|
*/
|
|
322
|
-
|
|
323
|
-
/** The original token. */
|
|
324
|
-
token: TokenNormalized;
|
|
325
|
-
/** Arbitrary metadata set by plugins. */
|
|
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 };
|
|
333
|
-
};
|
|
380
|
+
resolutionOrder: (ResolverSetNormalized | ResolverModifierNormalized)[];
|
|
334
381
|
}
|
|
335
382
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
383
|
+
export interface ResolverModifier<Context extends string = string> {
|
|
384
|
+
description?: string;
|
|
385
|
+
contexts: Record<Context, (Group | ReferenceObject)[]>;
|
|
386
|
+
default?: Context;
|
|
387
|
+
$extensions?: Record<string, unknown>;
|
|
388
|
+
$defs?: Record<string, unknown>;
|
|
340
389
|
}
|
|
341
390
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
type: '
|
|
345
|
-
|
|
391
|
+
export type ResolverModifierInline<Context extends string = string> = ResolverModifier<Context> & {
|
|
392
|
+
name: string;
|
|
393
|
+
type: 'modifier';
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
export interface ResolverModifierNormalized {
|
|
397
|
+
name: string;
|
|
398
|
+
type: 'modifier';
|
|
399
|
+
description: string | undefined;
|
|
400
|
+
contexts: Record<string, Group[]>;
|
|
401
|
+
default: string | undefined;
|
|
402
|
+
$extensions: Record<string, unknown> | undefined;
|
|
403
|
+
$defs: Record<string, unknown> | undefined;
|
|
346
404
|
}
|
|
347
405
|
|
|
348
|
-
export
|
|
406
|
+
export interface ResolverSet {
|
|
407
|
+
description?: string;
|
|
408
|
+
sources: (Group | ReferenceObject)[];
|
|
409
|
+
$extensions?: Record<string, unknown>;
|
|
410
|
+
$defs?: Record<string, unknown>;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export type ResolverSetInline = ResolverSet & { name: string; type: 'set' };
|
|
414
|
+
|
|
415
|
+
export interface ResolverSetNormalized {
|
|
416
|
+
name: string;
|
|
417
|
+
type: 'set';
|
|
418
|
+
description: string | undefined;
|
|
419
|
+
sources: Group[];
|
|
420
|
+
$extensions: Record<string, unknown> | undefined;
|
|
421
|
+
$defs: Record<string, unknown> | undefined;
|
|
422
|
+
}
|
|
349
423
|
|
|
350
424
|
export interface TransformParams {
|
|
351
425
|
/** ID of an existing format */
|
|
@@ -382,7 +456,3 @@ export interface TransformHookOptions {
|
|
|
382
456
|
/** Momoa documents */
|
|
383
457
|
sources: InputSource[];
|
|
384
458
|
}
|
|
385
|
-
|
|
386
|
-
export interface ReferenceObject {
|
|
387
|
-
$ref: string;
|
|
388
|
-
}
|