@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/dist/index.js
CHANGED
|
@@ -230,29 +230,35 @@ function validateTransformParams({ params, logger, pluginName }) {
|
|
|
230
230
|
message: "setTransform() value expected object of strings, received some non-string values"
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
|
+
const FALLBACK_PERMUTATION_ID = JSON.stringify({ tzMode: "*" });
|
|
233
234
|
/** Run build stage */
|
|
234
235
|
async function build(tokens, { resolver, sources, logger = new Logger(), config }) {
|
|
235
236
|
const formats = {};
|
|
236
237
|
const result = { outputFiles: [] };
|
|
237
|
-
function getTransforms(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const modeMatcher = params.mode ? wcmatch(params.mode) : null;
|
|
247
|
-
return (formats[params.format] ?? []).filter((token) => {
|
|
248
|
-
if (params.$type) {
|
|
249
|
-
if (typeof params.$type === "string" && token.token.$type !== params.$type) return false;
|
|
250
|
-
else if (Array.isArray(params.$type) && !params.$type.some(($type) => token.token.$type === $type)) return false;
|
|
238
|
+
function getTransforms(plugin) {
|
|
239
|
+
return function getTransforms$1(params) {
|
|
240
|
+
if (!params?.format) {
|
|
241
|
+
logger.warn({
|
|
242
|
+
group: "plugin",
|
|
243
|
+
label: plugin,
|
|
244
|
+
message: "\"format\" missing from getTransforms(), no tokens returned."
|
|
245
|
+
});
|
|
246
|
+
return [];
|
|
251
247
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
248
|
+
const tokenMatcher = params.id && params.id !== "*" ? wcmatch(params.id) : null;
|
|
249
|
+
const modeMatcher = params.mode ? wcmatch(params.mode) : null;
|
|
250
|
+
const permutationID = params.input ? resolver.getPermutationID(params.input) : JSON.stringify({ tzMode: "*" });
|
|
251
|
+
return (formats[params.format]?.[permutationID] ?? []).filter((token) => {
|
|
252
|
+
if (params.$type) {
|
|
253
|
+
if (typeof params.$type === "string" && token.token.$type !== params.$type) return false;
|
|
254
|
+
else if (Array.isArray(params.$type) && !params.$type.some(($type) => token.token.$type === $type)) return false;
|
|
255
|
+
}
|
|
256
|
+
if (tokenMatcher && !tokenMatcher(token.token.id)) return false;
|
|
257
|
+
if (params.input && token.permutationID !== resolver.getPermutationID(params.input)) return false;
|
|
258
|
+
if (modeMatcher && !modeMatcher(token.mode)) return false;
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
};
|
|
256
262
|
}
|
|
257
263
|
let transformsLocked = false;
|
|
258
264
|
const startTransform = performance.now();
|
|
@@ -260,7 +266,7 @@ async function build(tokens, { resolver, sources, logger = new Logger(), config
|
|
|
260
266
|
context: { logger },
|
|
261
267
|
tokens,
|
|
262
268
|
sources,
|
|
263
|
-
getTransforms,
|
|
269
|
+
getTransforms: getTransforms(plugin.name),
|
|
264
270
|
setTransform(id, params) {
|
|
265
271
|
if (transformsLocked) {
|
|
266
272
|
logger.warn({
|
|
@@ -271,6 +277,7 @@ async function build(tokens, { resolver, sources, logger = new Logger(), config
|
|
|
271
277
|
return;
|
|
272
278
|
}
|
|
273
279
|
const token = tokens[id];
|
|
280
|
+
const permutationID = params.input ? resolver.getPermutationID(params.input) : FALLBACK_PERMUTATION_ID;
|
|
274
281
|
const cleanValue = typeof params.value === "string" ? params.value : { ...params.value };
|
|
275
282
|
validateTransformParams({
|
|
276
283
|
logger,
|
|
@@ -280,19 +287,27 @@ async function build(tokens, { resolver, sources, logger = new Logger(), config
|
|
|
280
287
|
},
|
|
281
288
|
pluginName: plugin.name
|
|
282
289
|
});
|
|
283
|
-
if (!formats[params.format]) formats[params.format] =
|
|
284
|
-
|
|
285
|
-
|
|
290
|
+
if (!formats[params.format]) formats[params.format] = {};
|
|
291
|
+
if (!formats[params.format][permutationID]) formats[params.format][permutationID] = [];
|
|
292
|
+
let foundTokenI = -1;
|
|
293
|
+
if (params.mode) foundTokenI = formats[params.format][permutationID].findIndex((t) => id === t.id && (!params.localID || params.localID === t.localID) && params.mode === t.mode);
|
|
294
|
+
else if (params.input) {
|
|
295
|
+
if (!formats[params.format][permutationID]) formats[params.format][permutationID] = [];
|
|
296
|
+
foundTokenI = formats[params.format][permutationID].findIndex((t) => id === t.id && (!params.localID || params.localID === t.localID) && permutationID === t.permutationID);
|
|
297
|
+
} else foundTokenI = formats[params.format][permutationID].findIndex((t) => id === t.id && (!params.localID || params.localID === t.localID));
|
|
298
|
+
if (foundTokenI === -1) formats[params.format][permutationID].push({
|
|
286
299
|
...params,
|
|
287
300
|
id,
|
|
288
301
|
value: cleanValue,
|
|
289
302
|
type: typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE,
|
|
290
303
|
mode: params.mode || ".",
|
|
291
|
-
token: structuredClone(token)
|
|
304
|
+
token: structuredClone(token),
|
|
305
|
+
permutationID,
|
|
306
|
+
input: JSON.parse(permutationID)
|
|
292
307
|
});
|
|
293
308
|
else {
|
|
294
|
-
formats[params.format][foundTokenI].value = cleanValue;
|
|
295
|
-
formats[params.format][foundTokenI].type = typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE;
|
|
309
|
+
formats[params.format][permutationID][foundTokenI].value = cleanValue;
|
|
310
|
+
formats[params.format][permutationID][foundTokenI].type = typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE;
|
|
296
311
|
}
|
|
297
312
|
},
|
|
298
313
|
resolver
|
|
@@ -312,7 +327,7 @@ async function build(tokens, { resolver, sources, logger = new Logger(), config
|
|
|
312
327
|
context: { logger },
|
|
313
328
|
tokens,
|
|
314
329
|
sources,
|
|
315
|
-
getTransforms,
|
|
330
|
+
getTransforms: getTransforms(plugin.name),
|
|
316
331
|
resolver,
|
|
317
332
|
outputFile(filename, contents) {
|
|
318
333
|
const resolved = new URL(filename, config.outDir);
|
|
@@ -341,7 +356,7 @@ async function build(tokens, { resolver, sources, logger = new Logger(), config
|
|
|
341
356
|
await Promise.all(config.plugins.map(async (plugin) => plugin.buildEnd?.({
|
|
342
357
|
context: { logger },
|
|
343
358
|
tokens,
|
|
344
|
-
getTransforms,
|
|
359
|
+
getTransforms: getTransforms(plugin.name),
|
|
345
360
|
sources,
|
|
346
361
|
outputFiles: structuredClone(result.outputFiles)
|
|
347
362
|
})));
|
|
@@ -2150,6 +2165,7 @@ function normalizeTokens({ rawConfig, config, logger, cwd }) {
|
|
|
2150
2165
|
});
|
|
2151
2166
|
}
|
|
2152
2167
|
}
|
|
2168
|
+
config.alphabetize = rawConfig.alphabetize ?? true;
|
|
2153
2169
|
}
|
|
2154
2170
|
/** Normalize config.outDir */
|
|
2155
2171
|
function normalizeOutDir({ config, cwd, logger }) {
|
|
@@ -2423,449 +2439,66 @@ function filterResolverPaths(path) {
|
|
|
2423
2439
|
}
|
|
2424
2440
|
return path;
|
|
2425
2441
|
}
|
|
2426
|
-
/**
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
return JSON.stringify(Object.fromEntries(Object.entries(input).sort((a, b) => a[0].localeCompare(b[0], "en-us", { numeric: true }))));
|
|
2442
|
+
/** Make a deterministic string from an object */
|
|
2443
|
+
function getPermutationID(input) {
|
|
2444
|
+
const keys = Object.keys(input).sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
2445
|
+
return JSON.stringify(Object.fromEntries(keys.map((k) => [k, input[k]])));
|
|
2431
2446
|
}
|
|
2432
2447
|
|
|
2433
2448
|
//#endregion
|
|
2434
|
-
//#region src/
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
* We use terms the word “likely” because this occurs before validation. Since
|
|
2438
|
-
* we may be dealing with a doc _intended_ to be a resolver, but may be lacking
|
|
2439
|
-
* some critical information, how can we determine intent? There’s a bit of
|
|
2440
|
-
* guesswork here, but we try and find a reasonable edge case where we sniff out
|
|
2441
|
-
* invalid DTCG syntax that a resolver doc would have.
|
|
2442
|
-
*/
|
|
2443
|
-
function isLikelyResolver(doc) {
|
|
2444
|
-
if (doc.body.type !== "Object") return false;
|
|
2445
|
-
for (const member of doc.body.members) {
|
|
2446
|
-
if (member.name.type !== "String") continue;
|
|
2447
|
-
switch (member.name.value) {
|
|
2448
|
-
case "name":
|
|
2449
|
-
case "description":
|
|
2450
|
-
case "version":
|
|
2451
|
-
if (member.value.type === "String") return true;
|
|
2452
|
-
break;
|
|
2453
|
-
case "sets":
|
|
2454
|
-
case "modifiers":
|
|
2455
|
-
if (member.value.type !== "Object") continue;
|
|
2456
|
-
if (getObjMember(member.value, "description")?.type === "String") return true;
|
|
2457
|
-
if (member.name.value === "sets" && getObjMember(member.value, "sources")?.type === "Array") return true;
|
|
2458
|
-
else if (member.name.value === "modifiers") {
|
|
2459
|
-
const contexts = getObjMember(member.value, "contexts");
|
|
2460
|
-
if (contexts?.type === "Object" && contexts.members.some((m) => m.value.type === "Array")) return true;
|
|
2461
|
-
}
|
|
2462
|
-
break;
|
|
2463
|
-
case "resolutionOrder":
|
|
2464
|
-
if (member.value.type === "Array") return true;
|
|
2465
|
-
break;
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
return false;
|
|
2449
|
+
//#region src/parse/assert.ts
|
|
2450
|
+
function assert(value, logger, entry) {
|
|
2451
|
+
if (!value) logger.error(entry);
|
|
2469
2452
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2453
|
+
function assertStringNode(value, logger, entry) {
|
|
2454
|
+
assert(value?.type === "String", logger, entry);
|
|
2455
|
+
}
|
|
2456
|
+
function assertObjectNode(value, logger, entry) {
|
|
2457
|
+
assert(value?.type === "Object", logger, entry);
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
//#endregion
|
|
2461
|
+
//#region src/parse/normalize.ts
|
|
2475
2462
|
/**
|
|
2476
|
-
*
|
|
2477
|
-
*
|
|
2463
|
+
* Normalize token value.
|
|
2464
|
+
* The reason for the “any” typing is this aligns various user-provided inputs to the type
|
|
2478
2465
|
*/
|
|
2479
|
-
function
|
|
2466
|
+
function normalize(token, { logger, src }) {
|
|
2480
2467
|
const entry = {
|
|
2481
2468
|
group: "parser",
|
|
2482
|
-
label: "
|
|
2469
|
+
label: "init",
|
|
2483
2470
|
src
|
|
2484
2471
|
};
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
if (member.value.type !== "Object") errors.push({
|
|
2514
|
-
...entry,
|
|
2515
|
-
message: MESSAGE_EXPECTED.OBJECT,
|
|
2516
|
-
node: member.value
|
|
2517
|
-
});
|
|
2518
|
-
else for (const item of member.value.members) if (item.value.type !== "Object") errors.push({
|
|
2519
|
-
...entry,
|
|
2520
|
-
message: MESSAGE_EXPECTED.OBJECT,
|
|
2521
|
-
node: item.value
|
|
2522
|
-
});
|
|
2523
|
-
else {
|
|
2524
|
-
const validator = member.name.value === "sets" ? validateSet : validateModifier;
|
|
2525
|
-
errors.push(...validator(item.value, false, {
|
|
2526
|
-
logger,
|
|
2527
|
-
src
|
|
2528
|
-
}));
|
|
2529
|
-
}
|
|
2530
|
-
break;
|
|
2531
|
-
case "resolutionOrder":
|
|
2532
|
-
hasResolutionOrder = true;
|
|
2533
|
-
if (member.value.type !== "Array") errors.push({
|
|
2534
|
-
...entry,
|
|
2535
|
-
message: MESSAGE_EXPECTED.ARRAY,
|
|
2536
|
-
node: member.value
|
|
2537
|
-
});
|
|
2538
|
-
else if (member.value.elements.length === 0) errors.push({
|
|
2539
|
-
...entry,
|
|
2540
|
-
message: `"resolutionOrder" can’t be empty array.`,
|
|
2541
|
-
node: member.value
|
|
2542
|
-
});
|
|
2543
|
-
else for (const item of member.value.elements) if (item.value.type !== "Object") errors.push({
|
|
2544
|
-
...entry,
|
|
2545
|
-
message: MESSAGE_EXPECTED.OBJECT,
|
|
2546
|
-
node: item.value
|
|
2547
|
-
});
|
|
2548
|
-
else {
|
|
2549
|
-
const itemMembers = getObjMembers(item.value);
|
|
2550
|
-
if (itemMembers.$ref?.type === "String") continue;
|
|
2551
|
-
if (itemMembers.type?.type === "String") if (itemMembers.type.value === "set") validateSet(item.value, true, {
|
|
2552
|
-
logger,
|
|
2553
|
-
src
|
|
2554
|
-
});
|
|
2555
|
-
else if (itemMembers.type.value === "modifier") validateModifier(item.value, true, {
|
|
2556
|
-
logger,
|
|
2557
|
-
src
|
|
2558
|
-
});
|
|
2559
|
-
else errors.push({
|
|
2560
|
-
...entry,
|
|
2561
|
-
message: `Unknown type ${JSON.stringify(itemMembers.type.value)}`,
|
|
2562
|
-
node: itemMembers.type
|
|
2563
|
-
});
|
|
2564
|
-
if (itemMembers.sources?.type === "Array") validateSet(item.value, true, {
|
|
2565
|
-
logger,
|
|
2566
|
-
src
|
|
2567
|
-
});
|
|
2568
|
-
else if (itemMembers.contexts?.type === "Object") validateModifier(item.value, true, {
|
|
2569
|
-
logger,
|
|
2570
|
-
src
|
|
2571
|
-
});
|
|
2572
|
-
else if (itemMembers.name?.type === "String" || itemMembers.description?.type === "String") validateSet(item.value, true, {
|
|
2573
|
-
logger,
|
|
2574
|
-
src
|
|
2575
|
-
});
|
|
2576
|
-
}
|
|
2577
|
-
break;
|
|
2578
|
-
case "$defs":
|
|
2579
|
-
case "$extensions":
|
|
2580
|
-
if (member.value.type !== "Object") errors.push({
|
|
2581
|
-
...entry,
|
|
2582
|
-
message: `Expected object`,
|
|
2583
|
-
node: member.value
|
|
2584
|
-
});
|
|
2585
|
-
break;
|
|
2586
|
-
case "$schema":
|
|
2587
|
-
case "$ref":
|
|
2588
|
-
if (member.value.type !== "String") errors.push({
|
|
2589
|
-
...entry,
|
|
2590
|
-
message: `Expected string`,
|
|
2591
|
-
node: member.value
|
|
2592
|
-
});
|
|
2593
|
-
break;
|
|
2594
|
-
default:
|
|
2595
|
-
errors.push({
|
|
2596
|
-
...entry,
|
|
2597
|
-
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
2598
|
-
node: member.name,
|
|
2599
|
-
src
|
|
2600
|
-
});
|
|
2601
|
-
break;
|
|
2472
|
+
function normalizeFontFamily(value) {
|
|
2473
|
+
return typeof value === "string" ? [value] : value;
|
|
2474
|
+
}
|
|
2475
|
+
function normalizeFontWeight(value) {
|
|
2476
|
+
return typeof value === "string" && FONT_WEIGHTS[value] || value;
|
|
2477
|
+
}
|
|
2478
|
+
function normalizeColor(value, node) {
|
|
2479
|
+
if (typeof value === "string" && !isAlias(value)) {
|
|
2480
|
+
logger.warn({
|
|
2481
|
+
...entry,
|
|
2482
|
+
node,
|
|
2483
|
+
message: `${token.id}: string colors will be deprecated in a future version. Please update to object notation`
|
|
2484
|
+
});
|
|
2485
|
+
try {
|
|
2486
|
+
return parseColor(value);
|
|
2487
|
+
} catch {
|
|
2488
|
+
return {
|
|
2489
|
+
colorSpace: "srgb",
|
|
2490
|
+
components: [
|
|
2491
|
+
0,
|
|
2492
|
+
0,
|
|
2493
|
+
0
|
|
2494
|
+
],
|
|
2495
|
+
alpha: 1
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
} else if (value && typeof value === "object") {
|
|
2499
|
+
if (value.alpha === void 0) value.alpha = 1;
|
|
2602
2500
|
}
|
|
2603
|
-
|
|
2604
|
-
if (!hasVersion) errors.push({
|
|
2605
|
-
...entry,
|
|
2606
|
-
message: `Missing "version".`,
|
|
2607
|
-
node,
|
|
2608
|
-
src
|
|
2609
|
-
});
|
|
2610
|
-
if (!hasResolutionOrder) errors.push({
|
|
2611
|
-
...entry,
|
|
2612
|
-
message: `Missing "resolutionOrder".`,
|
|
2613
|
-
node,
|
|
2614
|
-
src
|
|
2615
|
-
});
|
|
2616
|
-
if (errors.length) logger.error(...errors);
|
|
2617
|
-
}
|
|
2618
|
-
function validateSet(node, isInline = false, { src }) {
|
|
2619
|
-
const entry = {
|
|
2620
|
-
group: "parser",
|
|
2621
|
-
label: "resolver",
|
|
2622
|
-
src
|
|
2623
|
-
};
|
|
2624
|
-
const errors = [];
|
|
2625
|
-
let hasName = !isInline;
|
|
2626
|
-
let hasType = !isInline;
|
|
2627
|
-
let hasSources = false;
|
|
2628
|
-
for (const member of node.members) {
|
|
2629
|
-
if (member.name.type !== "String") continue;
|
|
2630
|
-
switch (member.name.value) {
|
|
2631
|
-
case "name":
|
|
2632
|
-
hasName = true;
|
|
2633
|
-
if (member.value.type !== "String") errors.push({
|
|
2634
|
-
...entry,
|
|
2635
|
-
message: MESSAGE_EXPECTED.STRING,
|
|
2636
|
-
node: member.value
|
|
2637
|
-
});
|
|
2638
|
-
break;
|
|
2639
|
-
case "description":
|
|
2640
|
-
if (member.value.type !== "String") errors.push({
|
|
2641
|
-
...entry,
|
|
2642
|
-
message: MESSAGE_EXPECTED.STRING,
|
|
2643
|
-
node: member.value
|
|
2644
|
-
});
|
|
2645
|
-
break;
|
|
2646
|
-
case "type":
|
|
2647
|
-
hasType = true;
|
|
2648
|
-
if (member.value.type !== "String") errors.push({
|
|
2649
|
-
...entry,
|
|
2650
|
-
message: MESSAGE_EXPECTED.STRING,
|
|
2651
|
-
node: member.value
|
|
2652
|
-
});
|
|
2653
|
-
else if (member.value.value !== "set") errors.push({
|
|
2654
|
-
...entry,
|
|
2655
|
-
message: "\"type\" must be \"set\"."
|
|
2656
|
-
});
|
|
2657
|
-
break;
|
|
2658
|
-
case "sources":
|
|
2659
|
-
hasSources = true;
|
|
2660
|
-
if (member.value.type !== "Array") errors.push({
|
|
2661
|
-
...entry,
|
|
2662
|
-
message: MESSAGE_EXPECTED.ARRAY,
|
|
2663
|
-
node: member.value
|
|
2664
|
-
});
|
|
2665
|
-
else if (member.value.elements.length === 0) errors.push({
|
|
2666
|
-
...entry,
|
|
2667
|
-
message: `"sources" can’t be empty array.`,
|
|
2668
|
-
node: member.value
|
|
2669
|
-
});
|
|
2670
|
-
break;
|
|
2671
|
-
case "$defs":
|
|
2672
|
-
case "$extensions":
|
|
2673
|
-
if (member.value.type !== "Object") errors.push({
|
|
2674
|
-
...entry,
|
|
2675
|
-
message: `Expected object`,
|
|
2676
|
-
node: member.value
|
|
2677
|
-
});
|
|
2678
|
-
break;
|
|
2679
|
-
case "$ref":
|
|
2680
|
-
if (member.value.type !== "String") errors.push({
|
|
2681
|
-
...entry,
|
|
2682
|
-
message: `Expected string`,
|
|
2683
|
-
node: member.value
|
|
2684
|
-
});
|
|
2685
|
-
break;
|
|
2686
|
-
default:
|
|
2687
|
-
errors.push({
|
|
2688
|
-
...entry,
|
|
2689
|
-
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
2690
|
-
node: member.name
|
|
2691
|
-
});
|
|
2692
|
-
break;
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
if (!hasName) errors.push({
|
|
2696
|
-
...entry,
|
|
2697
|
-
message: `Missing "name".`,
|
|
2698
|
-
node
|
|
2699
|
-
});
|
|
2700
|
-
if (!hasType) errors.push({
|
|
2701
|
-
...entry,
|
|
2702
|
-
message: `"type": "set" missing.`,
|
|
2703
|
-
node
|
|
2704
|
-
});
|
|
2705
|
-
if (!hasSources) errors.push({
|
|
2706
|
-
...entry,
|
|
2707
|
-
message: `Missing "sources".`,
|
|
2708
|
-
node
|
|
2709
|
-
});
|
|
2710
|
-
return errors;
|
|
2711
|
-
}
|
|
2712
|
-
function validateModifier(node, isInline = false, { src }) {
|
|
2713
|
-
const errors = [];
|
|
2714
|
-
const entry = {
|
|
2715
|
-
group: "parser",
|
|
2716
|
-
label: "resolver",
|
|
2717
|
-
src
|
|
2718
|
-
};
|
|
2719
|
-
let hasName = !isInline;
|
|
2720
|
-
let hasType = !isInline;
|
|
2721
|
-
let hasContexts = false;
|
|
2722
|
-
for (const member of node.members) {
|
|
2723
|
-
if (member.name.type !== "String") continue;
|
|
2724
|
-
switch (member.name.value) {
|
|
2725
|
-
case "name":
|
|
2726
|
-
hasName = true;
|
|
2727
|
-
if (member.value.type !== "String") errors.push({
|
|
2728
|
-
...entry,
|
|
2729
|
-
message: MESSAGE_EXPECTED.STRING,
|
|
2730
|
-
node: member.value
|
|
2731
|
-
});
|
|
2732
|
-
break;
|
|
2733
|
-
case "description":
|
|
2734
|
-
if (member.value.type !== "String") errors.push({
|
|
2735
|
-
...entry,
|
|
2736
|
-
message: MESSAGE_EXPECTED.STRING,
|
|
2737
|
-
node: member.value
|
|
2738
|
-
});
|
|
2739
|
-
break;
|
|
2740
|
-
case "type":
|
|
2741
|
-
hasType = true;
|
|
2742
|
-
if (member.value.type !== "String") errors.push({
|
|
2743
|
-
...entry,
|
|
2744
|
-
message: MESSAGE_EXPECTED.STRING,
|
|
2745
|
-
node: member.value
|
|
2746
|
-
});
|
|
2747
|
-
else if (member.value.value !== "modifier") errors.push({
|
|
2748
|
-
...entry,
|
|
2749
|
-
message: "\"type\" must be \"modifier\"."
|
|
2750
|
-
});
|
|
2751
|
-
break;
|
|
2752
|
-
case "contexts":
|
|
2753
|
-
hasContexts = true;
|
|
2754
|
-
if (member.value.type !== "Object") errors.push({
|
|
2755
|
-
...entry,
|
|
2756
|
-
message: MESSAGE_EXPECTED.OBJECT,
|
|
2757
|
-
node: member.value
|
|
2758
|
-
});
|
|
2759
|
-
else if (member.value.members.length === 0) errors.push({
|
|
2760
|
-
...entry,
|
|
2761
|
-
message: `"contexts" can’t be empty object.`,
|
|
2762
|
-
node: member.value
|
|
2763
|
-
});
|
|
2764
|
-
else for (const context of member.value.members) if (context.value.type !== "Array") errors.push({
|
|
2765
|
-
...entry,
|
|
2766
|
-
message: MESSAGE_EXPECTED.ARRAY,
|
|
2767
|
-
node: context.value
|
|
2768
|
-
});
|
|
2769
|
-
break;
|
|
2770
|
-
case "default":
|
|
2771
|
-
if (member.value.type !== "String") errors.push({
|
|
2772
|
-
...entry,
|
|
2773
|
-
message: `Expected string`,
|
|
2774
|
-
node: member.value
|
|
2775
|
-
});
|
|
2776
|
-
else {
|
|
2777
|
-
const contexts = getObjMember(node, "contexts");
|
|
2778
|
-
if (!contexts || !getObjMember(contexts, member.value.value)) errors.push({
|
|
2779
|
-
...entry,
|
|
2780
|
-
message: "Invalid default context",
|
|
2781
|
-
node: member.value
|
|
2782
|
-
});
|
|
2783
|
-
}
|
|
2784
|
-
break;
|
|
2785
|
-
case "$defs":
|
|
2786
|
-
case "$extensions":
|
|
2787
|
-
if (member.value.type !== "Object") errors.push({
|
|
2788
|
-
...entry,
|
|
2789
|
-
message: `Expected object`,
|
|
2790
|
-
node: member.value
|
|
2791
|
-
});
|
|
2792
|
-
break;
|
|
2793
|
-
case "$ref":
|
|
2794
|
-
if (member.value.type !== "String") errors.push({
|
|
2795
|
-
...entry,
|
|
2796
|
-
message: `Expected string`,
|
|
2797
|
-
node: member.value
|
|
2798
|
-
});
|
|
2799
|
-
break;
|
|
2800
|
-
default:
|
|
2801
|
-
errors.push({
|
|
2802
|
-
...entry,
|
|
2803
|
-
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
2804
|
-
node: member.name
|
|
2805
|
-
});
|
|
2806
|
-
break;
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
if (!hasName) errors.push({
|
|
2810
|
-
...entry,
|
|
2811
|
-
message: `Missing "name".`,
|
|
2812
|
-
node
|
|
2813
|
-
});
|
|
2814
|
-
if (!hasType) errors.push({
|
|
2815
|
-
...entry,
|
|
2816
|
-
message: `"type": "modifier" missing.`,
|
|
2817
|
-
node
|
|
2818
|
-
});
|
|
2819
|
-
if (!hasContexts) errors.push({
|
|
2820
|
-
...entry,
|
|
2821
|
-
message: `Missing "contexts".`,
|
|
2822
|
-
node
|
|
2823
|
-
});
|
|
2824
|
-
return errors;
|
|
2825
|
-
}
|
|
2826
|
-
|
|
2827
|
-
//#endregion
|
|
2828
|
-
//#region src/parse/normalize.ts
|
|
2829
|
-
/**
|
|
2830
|
-
* Normalize token value.
|
|
2831
|
-
* The reason for the “any” typing is this aligns various user-provided inputs to the type
|
|
2832
|
-
*/
|
|
2833
|
-
function normalize(token, { logger, src }) {
|
|
2834
|
-
const entry = {
|
|
2835
|
-
group: "parser",
|
|
2836
|
-
label: "init",
|
|
2837
|
-
src
|
|
2838
|
-
};
|
|
2839
|
-
function normalizeFontFamily(value) {
|
|
2840
|
-
return typeof value === "string" ? [value] : value;
|
|
2841
|
-
}
|
|
2842
|
-
function normalizeFontWeight(value) {
|
|
2843
|
-
return typeof value === "string" && FONT_WEIGHTS[value] || value;
|
|
2844
|
-
}
|
|
2845
|
-
function normalizeColor(value, node) {
|
|
2846
|
-
if (typeof value === "string" && !isAlias(value)) {
|
|
2847
|
-
logger.warn({
|
|
2848
|
-
...entry,
|
|
2849
|
-
node,
|
|
2850
|
-
message: `${token.id}: string colors will be deprecated in a future version. Please update to object notation`
|
|
2851
|
-
});
|
|
2852
|
-
try {
|
|
2853
|
-
return parseColor(value);
|
|
2854
|
-
} catch {
|
|
2855
|
-
return {
|
|
2856
|
-
colorSpace: "srgb",
|
|
2857
|
-
components: [
|
|
2858
|
-
0,
|
|
2859
|
-
0,
|
|
2860
|
-
0
|
|
2861
|
-
],
|
|
2862
|
-
alpha: 1
|
|
2863
|
-
};
|
|
2864
|
-
}
|
|
2865
|
-
} else if (value && typeof value === "object") {
|
|
2866
|
-
if (value.alpha === void 0) value.alpha = 1;
|
|
2867
|
-
}
|
|
2868
|
-
return value;
|
|
2501
|
+
return value;
|
|
2869
2502
|
}
|
|
2870
2503
|
switch (token.$type) {
|
|
2871
2504
|
case "color":
|
|
@@ -2951,7 +2584,7 @@ function aliasToTokenRef(alias, mode) {
|
|
|
2951
2584
|
function tokenFromNode(node, { groups, path, source, ignore }) {
|
|
2952
2585
|
if (!(node.type === "Object" && !!getObjMember(node, "$value") && !path.includes("$extensions"))) return;
|
|
2953
2586
|
const jsonID = encodeFragment(path);
|
|
2954
|
-
const id = path.join(".");
|
|
2587
|
+
const id = path.join(".").replace(/\.\$root$/, "");
|
|
2955
2588
|
const originalToken = momoa.evaluate(node);
|
|
2956
2589
|
const group = groups[encodeFragment(path.slice(0, -1))];
|
|
2957
2590
|
if (group?.tokens && !group.tokens.includes(id)) group.tokens.push(id);
|
|
@@ -3176,7 +2809,7 @@ function refToTokenID($ref) {
|
|
|
3176
2809
|
if (typeof path !== "string") return;
|
|
3177
2810
|
const { subpath } = parseRef(path);
|
|
3178
2811
|
if (subpath?.[0] === "$defs") subpath.splice(0, 2);
|
|
3179
|
-
return subpath?.length && subpath.join(".").replace(/\.(\$value|\$extensions).*$/, "") || void 0;
|
|
2812
|
+
return subpath?.length && subpath.join(".").replace(/\.(\$root|\$value|\$extensions).*$/, "") || void 0;
|
|
3180
2813
|
}
|
|
3181
2814
|
const EXPECTED_NESTED_ALIAS = {
|
|
3182
2815
|
border: {
|
|
@@ -3304,7 +2937,7 @@ function resolveAliases(tokens, { logger, refMap, sources }) {
|
|
|
3304
2937
|
|
|
3305
2938
|
//#endregion
|
|
3306
2939
|
//#region src/parse/process.ts
|
|
3307
|
-
function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
2940
|
+
function processTokens(rootSource, { config, logger, sourceByFilename, isResolver }) {
|
|
3308
2941
|
const entry = {
|
|
3309
2942
|
group: "parser",
|
|
3310
2943
|
label: "init"
|
|
@@ -3312,13 +2945,19 @@ function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
|
3312
2945
|
const refMap = {};
|
|
3313
2946
|
function resolveRef(node, chain) {
|
|
3314
2947
|
const { subpath } = parseRef(node.value);
|
|
3315
|
-
|
|
2948
|
+
assert(subpath, logger, {
|
|
3316
2949
|
...entry,
|
|
3317
2950
|
message: "Can’t resolve $ref",
|
|
3318
2951
|
node,
|
|
3319
2952
|
src: rootSource.src
|
|
3320
2953
|
});
|
|
3321
2954
|
const next = findNode(rootSource.document, subpath);
|
|
2955
|
+
assert(next, logger, {
|
|
2956
|
+
...entry,
|
|
2957
|
+
message: "Can't find $ref",
|
|
2958
|
+
node,
|
|
2959
|
+
src: rootSource.src
|
|
2960
|
+
});
|
|
3322
2961
|
if (next?.type === "Object") {
|
|
3323
2962
|
const next$ref = getObjMember(next, "$ref");
|
|
3324
2963
|
if (next$ref && next$ref.type === "String") {
|
|
@@ -3339,7 +2978,7 @@ function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
|
3339
2978
|
if (rawPath.includes("$extensions") || node.type !== "Object") return;
|
|
3340
2979
|
const $ref = node.type === "Object" ? getObjMember(node, "$ref") : void 0;
|
|
3341
2980
|
if (!$ref) return;
|
|
3342
|
-
|
|
2981
|
+
assertStringNode($ref, logger, {
|
|
3343
2982
|
...entry,
|
|
3344
2983
|
message: "Invalid $ref. Expected string.",
|
|
3345
2984
|
node: $ref,
|
|
@@ -3363,10 +3002,9 @@ function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
|
3363
3002
|
});
|
|
3364
3003
|
function flatten$extends(node, chain) {
|
|
3365
3004
|
const memberKeys = node.members.map((m) => m.name.type === "String" && m.name.value).filter(Boolean);
|
|
3366
|
-
let extended;
|
|
3367
3005
|
if (memberKeys.includes("$extends")) {
|
|
3368
3006
|
const $extends = getObjMember(node, "$extends");
|
|
3369
|
-
|
|
3007
|
+
assertStringNode($extends, logger, {
|
|
3370
3008
|
...entry,
|
|
3371
3009
|
message: "$extends must be a string",
|
|
3372
3010
|
node: $extends,
|
|
@@ -3379,7 +3017,7 @@ function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
|
3379
3017
|
src: rootSource.src
|
|
3380
3018
|
});
|
|
3381
3019
|
const next = isAlias($extends.value) ? aliasToGroupRef($extends.value) : void 0;
|
|
3382
|
-
|
|
3020
|
+
assert(next, logger, {
|
|
3383
3021
|
...entry,
|
|
3384
3022
|
message: "$extends must be a valid alias",
|
|
3385
3023
|
node: $extends,
|
|
@@ -3392,14 +3030,14 @@ function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
|
3392
3030
|
src: rootSource.src
|
|
3393
3031
|
});
|
|
3394
3032
|
chain.push(next.$ref);
|
|
3395
|
-
extended = findNode(rootSource.document, parseRef(next.$ref).subpath ?? []);
|
|
3396
|
-
|
|
3033
|
+
const extended = findNode(rootSource.document, parseRef(next.$ref).subpath ?? []);
|
|
3034
|
+
assert(extended, logger, {
|
|
3397
3035
|
...entry,
|
|
3398
3036
|
message: "Could not resolve $extends",
|
|
3399
3037
|
node: $extends,
|
|
3400
3038
|
src: rootSource.src
|
|
3401
3039
|
});
|
|
3402
|
-
|
|
3040
|
+
assertObjectNode(extended, logger, {
|
|
3403
3041
|
...entry,
|
|
3404
3042
|
message: "$extends must resolve to a group of tokens",
|
|
3405
3043
|
node
|
|
@@ -3422,7 +3060,6 @@ function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
|
3422
3060
|
const tokens = {};
|
|
3423
3061
|
const tokenIDs = [];
|
|
3424
3062
|
const groups = {};
|
|
3425
|
-
const isResolver = isLikelyResolver(rootSource.document);
|
|
3426
3063
|
traverse(rootSource.document, { enter(node, _parent, rawPath) {
|
|
3427
3064
|
if (node.type !== "Object") return;
|
|
3428
3065
|
groupFromNode(node, {
|
|
@@ -3495,6 +3132,7 @@ function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
|
3495
3132
|
message: "Normalized values",
|
|
3496
3133
|
timing: performance.now() - normalizeStart
|
|
3497
3134
|
});
|
|
3135
|
+
if (config.alphabetize === false) return tokens;
|
|
3498
3136
|
const sortStart = performance.now();
|
|
3499
3137
|
const tokensSorted = {};
|
|
3500
3138
|
tokenIDs.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
@@ -3599,36 +3237,440 @@ function findObject(dict, path, logger) {
|
|
|
3599
3237
|
}
|
|
3600
3238
|
|
|
3601
3239
|
//#endregion
|
|
3602
|
-
//#region src/resolver/
|
|
3603
|
-
/**
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3240
|
+
//#region src/resolver/validate.ts
|
|
3241
|
+
/**
|
|
3242
|
+
* Determine whether this is likely a resolver
|
|
3243
|
+
* We use terms the word “likely” because this occurs before validation. Since
|
|
3244
|
+
* we may be dealing with a doc _intended_ to be a resolver, but may be lacking
|
|
3245
|
+
* some critical information, how can we determine intent? There’s a bit of
|
|
3246
|
+
* guesswork here, but we try and find a reasonable edge case where we sniff out
|
|
3247
|
+
* invalid DTCG syntax that a resolver doc would have.
|
|
3248
|
+
*/
|
|
3249
|
+
function isLikelyResolver(doc) {
|
|
3250
|
+
if (doc.body.type !== "Object") return false;
|
|
3251
|
+
for (const member of doc.body.members) {
|
|
3252
|
+
if (member.name.type !== "String") continue;
|
|
3253
|
+
switch (member.name.value) {
|
|
3254
|
+
case "name":
|
|
3255
|
+
case "description":
|
|
3256
|
+
case "version":
|
|
3257
|
+
if (member.value.type === "String") return true;
|
|
3258
|
+
break;
|
|
3259
|
+
case "sets":
|
|
3260
|
+
case "modifiers":
|
|
3261
|
+
if (member.value.type !== "Object") continue;
|
|
3262
|
+
if (getObjMember(member.value, "description")?.type === "String") return true;
|
|
3263
|
+
if (member.name.value === "sets" && getObjMember(member.value, "sources")?.type === "Array") return true;
|
|
3264
|
+
else if (member.name.value === "modifiers") {
|
|
3265
|
+
const contexts = getObjMember(member.value, "contexts");
|
|
3266
|
+
if (contexts?.type === "Object" && contexts.members.some((m) => m.value.type === "Array")) return true;
|
|
3267
|
+
}
|
|
3268
|
+
break;
|
|
3269
|
+
case "resolutionOrder":
|
|
3270
|
+
if (member.value.type === "Array") return true;
|
|
3271
|
+
break;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
return false;
|
|
3275
|
+
}
|
|
3276
|
+
const MESSAGE_EXPECTED = {
|
|
3277
|
+
STRING: "Expected string.",
|
|
3278
|
+
OBJECT: "Expected object.",
|
|
3279
|
+
ARRAY: "Expected array."
|
|
3280
|
+
};
|
|
3281
|
+
/**
|
|
3282
|
+
* Validate a resolver document.
|
|
3283
|
+
* There’s a ton of boilerplate here, only to surface detailed code frames. Is there a better abstraction?
|
|
3284
|
+
*/
|
|
3285
|
+
function validateResolver(node, { logger, src }) {
|
|
3286
|
+
const entry = {
|
|
3287
|
+
group: "parser",
|
|
3288
|
+
label: "resolver",
|
|
3289
|
+
src
|
|
3290
|
+
};
|
|
3291
|
+
if (node.body.type !== "Object") logger.error({
|
|
3292
|
+
...entry,
|
|
3293
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3294
|
+
node
|
|
3295
|
+
});
|
|
3296
|
+
const errors = [];
|
|
3297
|
+
let hasVersion = false;
|
|
3298
|
+
let hasResolutionOrder = false;
|
|
3299
|
+
for (const member of node.body.members) {
|
|
3300
|
+
if (member.name.type !== "String") continue;
|
|
3301
|
+
switch (member.name.value) {
|
|
3302
|
+
case "name":
|
|
3303
|
+
case "description":
|
|
3304
|
+
if (member.value.type !== "String") errors.push({
|
|
3305
|
+
...entry,
|
|
3306
|
+
message: MESSAGE_EXPECTED.STRING
|
|
3307
|
+
});
|
|
3308
|
+
break;
|
|
3309
|
+
case "version":
|
|
3310
|
+
hasVersion = true;
|
|
3311
|
+
if (member.value.type !== "String" || member.value.value !== "2025.10") errors.push({
|
|
3312
|
+
...entry,
|
|
3313
|
+
message: `Expected "version" to be "2025.10".`,
|
|
3314
|
+
node: member.value
|
|
3315
|
+
});
|
|
3316
|
+
break;
|
|
3317
|
+
case "sets":
|
|
3318
|
+
case "modifiers":
|
|
3319
|
+
if (member.value.type !== "Object") errors.push({
|
|
3320
|
+
...entry,
|
|
3321
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3322
|
+
node: member.value
|
|
3323
|
+
});
|
|
3324
|
+
else for (const item of member.value.members) if (item.value.type !== "Object") errors.push({
|
|
3325
|
+
...entry,
|
|
3326
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3327
|
+
node: item.value
|
|
3328
|
+
});
|
|
3329
|
+
else {
|
|
3330
|
+
const validator = member.name.value === "sets" ? validateSet : validateModifier;
|
|
3331
|
+
errors.push(...validator(item.value, false, {
|
|
3332
|
+
logger,
|
|
3333
|
+
src
|
|
3334
|
+
}));
|
|
3335
|
+
}
|
|
3336
|
+
break;
|
|
3337
|
+
case "resolutionOrder":
|
|
3338
|
+
hasResolutionOrder = true;
|
|
3339
|
+
if (member.value.type !== "Array") errors.push({
|
|
3340
|
+
...entry,
|
|
3341
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
3342
|
+
node: member.value
|
|
3343
|
+
});
|
|
3344
|
+
else if (member.value.elements.length === 0) errors.push({
|
|
3345
|
+
...entry,
|
|
3346
|
+
message: `"resolutionOrder" can’t be empty array.`,
|
|
3347
|
+
node: member.value
|
|
3348
|
+
});
|
|
3349
|
+
else for (const item of member.value.elements) if (item.value.type !== "Object") errors.push({
|
|
3350
|
+
...entry,
|
|
3351
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3352
|
+
node: item.value
|
|
3353
|
+
});
|
|
3354
|
+
else {
|
|
3355
|
+
const itemMembers = getObjMembers(item.value);
|
|
3356
|
+
if (itemMembers.$ref?.type === "String") continue;
|
|
3357
|
+
if (itemMembers.type?.type === "String") if (itemMembers.type.value === "set") validateSet(item.value, true, {
|
|
3358
|
+
logger,
|
|
3359
|
+
src
|
|
3360
|
+
});
|
|
3361
|
+
else if (itemMembers.type.value === "modifier") validateModifier(item.value, true, {
|
|
3362
|
+
logger,
|
|
3363
|
+
src
|
|
3364
|
+
});
|
|
3365
|
+
else errors.push({
|
|
3366
|
+
...entry,
|
|
3367
|
+
message: `Unknown type ${JSON.stringify(itemMembers.type.value)}`,
|
|
3368
|
+
node: itemMembers.type
|
|
3369
|
+
});
|
|
3370
|
+
if (itemMembers.sources?.type === "Array") validateSet(item.value, true, {
|
|
3371
|
+
logger,
|
|
3372
|
+
src
|
|
3373
|
+
});
|
|
3374
|
+
else if (itemMembers.contexts?.type === "Object") validateModifier(item.value, true, {
|
|
3375
|
+
logger,
|
|
3376
|
+
src
|
|
3377
|
+
});
|
|
3378
|
+
else if (itemMembers.name?.type === "String" || itemMembers.description?.type === "String") validateSet(item.value, true, {
|
|
3379
|
+
logger,
|
|
3380
|
+
src
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
break;
|
|
3384
|
+
case "$defs":
|
|
3385
|
+
case "$extensions":
|
|
3386
|
+
if (member.value.type !== "Object") errors.push({
|
|
3387
|
+
...entry,
|
|
3388
|
+
message: `Expected object`,
|
|
3389
|
+
node: member.value
|
|
3390
|
+
});
|
|
3391
|
+
break;
|
|
3392
|
+
case "$schema":
|
|
3393
|
+
case "$ref":
|
|
3394
|
+
if (member.value.type !== "String") errors.push({
|
|
3395
|
+
...entry,
|
|
3396
|
+
message: `Expected string`,
|
|
3397
|
+
node: member.value
|
|
3398
|
+
});
|
|
3399
|
+
break;
|
|
3400
|
+
default:
|
|
3401
|
+
errors.push({
|
|
3402
|
+
...entry,
|
|
3403
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
3404
|
+
node: member.name,
|
|
3405
|
+
src
|
|
3406
|
+
});
|
|
3407
|
+
break;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
if (!hasVersion) errors.push({
|
|
3411
|
+
...entry,
|
|
3412
|
+
message: `Missing "version".`,
|
|
3413
|
+
node,
|
|
3414
|
+
src
|
|
3415
|
+
});
|
|
3416
|
+
if (!hasResolutionOrder) errors.push({
|
|
3417
|
+
...entry,
|
|
3418
|
+
message: `Missing "resolutionOrder".`,
|
|
3419
|
+
node,
|
|
3420
|
+
src
|
|
3421
|
+
});
|
|
3422
|
+
if (errors.length) logger.error(...errors);
|
|
3423
|
+
}
|
|
3424
|
+
function validateSet(node, isInline = false, { src }) {
|
|
3425
|
+
const entry = {
|
|
3426
|
+
group: "parser",
|
|
3427
|
+
label: "resolver",
|
|
3428
|
+
src
|
|
3429
|
+
};
|
|
3430
|
+
const errors = [];
|
|
3431
|
+
let hasName = !isInline;
|
|
3432
|
+
let hasType = !isInline;
|
|
3433
|
+
let hasSources = false;
|
|
3434
|
+
for (const member of node.members) {
|
|
3435
|
+
if (member.name.type !== "String") continue;
|
|
3436
|
+
switch (member.name.value) {
|
|
3437
|
+
case "name":
|
|
3438
|
+
hasName = true;
|
|
3439
|
+
if (member.value.type !== "String") errors.push({
|
|
3440
|
+
...entry,
|
|
3441
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3442
|
+
node: member.value
|
|
3443
|
+
});
|
|
3444
|
+
break;
|
|
3445
|
+
case "description":
|
|
3446
|
+
if (member.value.type !== "String") errors.push({
|
|
3447
|
+
...entry,
|
|
3448
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3449
|
+
node: member.value
|
|
3450
|
+
});
|
|
3451
|
+
break;
|
|
3452
|
+
case "type":
|
|
3453
|
+
hasType = true;
|
|
3454
|
+
if (member.value.type !== "String") errors.push({
|
|
3455
|
+
...entry,
|
|
3456
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3457
|
+
node: member.value
|
|
3458
|
+
});
|
|
3459
|
+
else if (member.value.value !== "set") errors.push({
|
|
3460
|
+
...entry,
|
|
3461
|
+
message: "\"type\" must be \"set\"."
|
|
3462
|
+
});
|
|
3463
|
+
break;
|
|
3464
|
+
case "sources":
|
|
3465
|
+
hasSources = true;
|
|
3466
|
+
if (member.value.type !== "Array") errors.push({
|
|
3467
|
+
...entry,
|
|
3468
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
3469
|
+
node: member.value
|
|
3470
|
+
});
|
|
3471
|
+
else if (member.value.elements.length === 0) errors.push({
|
|
3472
|
+
...entry,
|
|
3473
|
+
message: `"sources" can’t be empty array.`,
|
|
3474
|
+
node: member.value
|
|
3475
|
+
});
|
|
3476
|
+
else for (const source of member.value.elements) if (source.value.type !== "Object") errors.push({
|
|
3477
|
+
...entry,
|
|
3478
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3479
|
+
node: source.value
|
|
3480
|
+
});
|
|
3481
|
+
break;
|
|
3482
|
+
case "$defs":
|
|
3483
|
+
case "$extensions":
|
|
3484
|
+
if (member.value.type !== "Object") errors.push({
|
|
3485
|
+
...entry,
|
|
3486
|
+
message: `Expected object`,
|
|
3487
|
+
node: member.value
|
|
3488
|
+
});
|
|
3489
|
+
break;
|
|
3490
|
+
case "$ref":
|
|
3491
|
+
if (member.value.type !== "String") errors.push({
|
|
3492
|
+
...entry,
|
|
3493
|
+
message: `Expected string`,
|
|
3494
|
+
node: member.value
|
|
3495
|
+
});
|
|
3496
|
+
break;
|
|
3497
|
+
default:
|
|
3498
|
+
errors.push({
|
|
3499
|
+
...entry,
|
|
3500
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
3501
|
+
node: member.name
|
|
3502
|
+
});
|
|
3503
|
+
break;
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
if (!hasName) errors.push({
|
|
3507
|
+
...entry,
|
|
3508
|
+
message: `Missing "name".`,
|
|
3509
|
+
node
|
|
3510
|
+
});
|
|
3511
|
+
if (!hasType) errors.push({
|
|
3512
|
+
...entry,
|
|
3513
|
+
message: `"type": "set" missing.`,
|
|
3514
|
+
node
|
|
3515
|
+
});
|
|
3516
|
+
if (!hasSources) errors.push({
|
|
3517
|
+
...entry,
|
|
3518
|
+
message: `Missing "sources".`,
|
|
3519
|
+
node
|
|
3520
|
+
});
|
|
3521
|
+
return errors;
|
|
3522
|
+
}
|
|
3523
|
+
function validateModifier(node, isInline = false, { src }) {
|
|
3524
|
+
const errors = [];
|
|
3525
|
+
const entry = {
|
|
3526
|
+
group: "parser",
|
|
3527
|
+
label: "resolver",
|
|
3528
|
+
src
|
|
3529
|
+
};
|
|
3530
|
+
let hasName = !isInline;
|
|
3531
|
+
let hasType = !isInline;
|
|
3532
|
+
let hasContexts = false;
|
|
3533
|
+
for (const member of node.members) {
|
|
3534
|
+
if (member.name.type !== "String") continue;
|
|
3535
|
+
switch (member.name.value) {
|
|
3536
|
+
case "name":
|
|
3537
|
+
hasName = true;
|
|
3538
|
+
if (member.value.type !== "String") errors.push({
|
|
3539
|
+
...entry,
|
|
3540
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3541
|
+
node: member.value
|
|
3542
|
+
});
|
|
3543
|
+
break;
|
|
3544
|
+
case "description":
|
|
3545
|
+
if (member.value.type !== "String") errors.push({
|
|
3546
|
+
...entry,
|
|
3547
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3548
|
+
node: member.value
|
|
3549
|
+
});
|
|
3550
|
+
break;
|
|
3551
|
+
case "type":
|
|
3552
|
+
hasType = true;
|
|
3553
|
+
if (member.value.type !== "String") errors.push({
|
|
3554
|
+
...entry,
|
|
3555
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3556
|
+
node: member.value
|
|
3557
|
+
});
|
|
3558
|
+
else if (member.value.value !== "modifier") errors.push({
|
|
3559
|
+
...entry,
|
|
3560
|
+
message: "\"type\" must be \"modifier\"."
|
|
3561
|
+
});
|
|
3562
|
+
break;
|
|
3563
|
+
case "contexts":
|
|
3564
|
+
hasContexts = true;
|
|
3565
|
+
if (member.value.type !== "Object") errors.push({
|
|
3566
|
+
...entry,
|
|
3567
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3568
|
+
node: member.value
|
|
3569
|
+
});
|
|
3570
|
+
else if (member.value.members.length === 0) errors.push({
|
|
3571
|
+
...entry,
|
|
3572
|
+
message: `"contexts" can’t be empty object.`,
|
|
3573
|
+
node: member.value
|
|
3574
|
+
});
|
|
3575
|
+
else for (const context of member.value.members) if (context.value.type !== "Array") errors.push({
|
|
3576
|
+
...entry,
|
|
3577
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
3578
|
+
node: context.value
|
|
3579
|
+
});
|
|
3580
|
+
else for (const source of context.value.elements) if (source.value.type !== "Object") errors.push({
|
|
3581
|
+
...entry,
|
|
3582
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3583
|
+
node: source.value
|
|
3584
|
+
});
|
|
3585
|
+
break;
|
|
3586
|
+
case "default":
|
|
3587
|
+
if (member.value.type !== "String") errors.push({
|
|
3588
|
+
...entry,
|
|
3589
|
+
message: `Expected string`,
|
|
3590
|
+
node: member.value
|
|
3591
|
+
});
|
|
3592
|
+
else {
|
|
3593
|
+
const contexts = getObjMember(node, "contexts");
|
|
3594
|
+
if (!contexts || !getObjMember(contexts, member.value.value)) errors.push({
|
|
3595
|
+
...entry,
|
|
3596
|
+
message: "Invalid default context",
|
|
3597
|
+
node: member.value
|
|
3598
|
+
});
|
|
3599
|
+
}
|
|
3600
|
+
break;
|
|
3601
|
+
case "$defs":
|
|
3602
|
+
case "$extensions":
|
|
3603
|
+
if (member.value.type !== "Object") errors.push({
|
|
3604
|
+
...entry,
|
|
3605
|
+
message: `Expected object`,
|
|
3606
|
+
node: member.value
|
|
3607
|
+
});
|
|
3608
|
+
break;
|
|
3609
|
+
case "$ref":
|
|
3610
|
+
if (member.value.type !== "String") errors.push({
|
|
3611
|
+
...entry,
|
|
3612
|
+
message: `Expected string`,
|
|
3613
|
+
node: member.value
|
|
3614
|
+
});
|
|
3615
|
+
break;
|
|
3616
|
+
default:
|
|
3617
|
+
errors.push({
|
|
3618
|
+
...entry,
|
|
3619
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
3620
|
+
node: member.name
|
|
3621
|
+
});
|
|
3622
|
+
break;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
if (!hasName) errors.push({
|
|
3626
|
+
...entry,
|
|
3627
|
+
message: `Missing "name".`,
|
|
3628
|
+
node
|
|
3629
|
+
});
|
|
3630
|
+
if (!hasType) errors.push({
|
|
3631
|
+
...entry,
|
|
3632
|
+
message: `"type": "modifier" missing.`,
|
|
3633
|
+
node
|
|
3634
|
+
});
|
|
3635
|
+
if (!hasContexts) errors.push({
|
|
3636
|
+
...entry,
|
|
3637
|
+
message: `Missing "contexts".`,
|
|
3638
|
+
node
|
|
3639
|
+
});
|
|
3640
|
+
return errors;
|
|
3641
|
+
}
|
|
3642
|
+
|
|
3643
|
+
//#endregion
|
|
3644
|
+
//#region src/resolver/load.ts
|
|
3645
|
+
/** Quick-parse input sources and find a resolver */
|
|
3646
|
+
async function loadResolver(inputs, { config, logger, req, yamlToMomoa }) {
|
|
3647
|
+
let resolverDoc;
|
|
3648
|
+
let tokens = {};
|
|
3649
|
+
const entry = {
|
|
3650
|
+
group: "parser",
|
|
3651
|
+
label: "init"
|
|
3652
|
+
};
|
|
3653
|
+
for (const input of inputs) {
|
|
3654
|
+
let document;
|
|
3655
|
+
if (typeof input.src === "string") if (maybeRawJSON(input.src)) document = toMomoa(input.src);
|
|
3656
|
+
else if (yamlToMomoa) document = yamlToMomoa(input.src);
|
|
3657
|
+
else logger.error({
|
|
3658
|
+
...entry,
|
|
3659
|
+
message: `Install yaml-to-momoa package to parse YAML, and pass in as option, e.g.:
|
|
3660
|
+
|
|
3661
|
+
import { bundle } from '@terrazzo/json-schema-tools';
|
|
3662
|
+
import yamlToMomoa from 'yaml-to-momoa';
|
|
3663
|
+
|
|
3664
|
+
bundle(yamlString, { yamlToMomoa });`
|
|
3665
|
+
});
|
|
3666
|
+
else if (input.src && typeof input.src === "object") document = toMomoa(JSON.stringify(input.src, void 0, 2));
|
|
3667
|
+
else logger.error({
|
|
3668
|
+
...entry,
|
|
3669
|
+
message: `Could not parse ${input.filename}. Is this valid JSON or YAML?`
|
|
3670
|
+
});
|
|
3671
|
+
if (!document || !isLikelyResolver(document)) continue;
|
|
3672
|
+
if (inputs.length > 1) logger.error({
|
|
3673
|
+
...entry,
|
|
3632
3674
|
message: `Resolver must be the only input, found ${inputs.length} sources.`
|
|
3633
3675
|
});
|
|
3634
3676
|
resolverDoc = document;
|
|
@@ -3687,8 +3729,8 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3687
3729
|
...inputDefaults,
|
|
3688
3730
|
...inputRaw
|
|
3689
3731
|
};
|
|
3690
|
-
const
|
|
3691
|
-
if (resolverCache[
|
|
3732
|
+
const permutationID = getPermutationID(input);
|
|
3733
|
+
if (resolverCache[permutationID]) return resolverCache[permutationID];
|
|
3692
3734
|
for (const item of resolverSource.resolutionOrder) switch (item.type) {
|
|
3693
3735
|
case "set":
|
|
3694
3736
|
for (const s of item.sources) tokensRaw = merge(tokensRaw, s);
|
|
@@ -3697,8 +3739,7 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3697
3739
|
const context = input[item.name];
|
|
3698
3740
|
const sources$1 = item.contexts[context];
|
|
3699
3741
|
if (!sources$1) logger.error({
|
|
3700
|
-
group: "
|
|
3701
|
-
label: "resolver",
|
|
3742
|
+
group: "resolver",
|
|
3702
3743
|
message: `Modifier ${item.name} has no context ${JSON.stringify(context)}.`
|
|
3703
3744
|
});
|
|
3704
3745
|
for (const s of sources$1 ?? []) tokensRaw = merge(tokensRaw, s);
|
|
@@ -3715,9 +3756,10 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3715
3756
|
config,
|
|
3716
3757
|
logger,
|
|
3717
3758
|
sourceByFilename: { [resolverSource._source.filename.href]: rootSource },
|
|
3759
|
+
isResolver: true,
|
|
3718
3760
|
sources
|
|
3719
3761
|
});
|
|
3720
|
-
resolverCache[
|
|
3762
|
+
resolverCache[permutationID] = tokens;
|
|
3721
3763
|
return tokens;
|
|
3722
3764
|
},
|
|
3723
3765
|
source: resolverSource,
|
|
@@ -3725,17 +3767,41 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3725
3767
|
if (!allPermutations.length) allPermutations.push(...calculatePermutations(Object.entries(validContexts)));
|
|
3726
3768
|
return allPermutations;
|
|
3727
3769
|
},
|
|
3728
|
-
isValidInput(input) {
|
|
3770
|
+
isValidInput(input, throwError = false) {
|
|
3729
3771
|
if (!input || typeof input !== "object") logger.error({
|
|
3730
|
-
group: "
|
|
3731
|
-
label: "resolver",
|
|
3772
|
+
group: "resolver",
|
|
3732
3773
|
message: `Invalid input: ${JSON.stringify(input)}.`
|
|
3733
3774
|
});
|
|
3734
|
-
|
|
3775
|
+
for (const k of Object.keys(input)) if (!(k in validContexts)) {
|
|
3776
|
+
if (throwError) logger.error({
|
|
3777
|
+
group: "resolver",
|
|
3778
|
+
message: `No such modifier ${JSON.stringify(k)}`
|
|
3779
|
+
});
|
|
3780
|
+
return false;
|
|
3781
|
+
}
|
|
3735
3782
|
for (const [name, contexts] of Object.entries(validContexts)) if (name in input) {
|
|
3736
|
-
if (!contexts.includes(input[name]))
|
|
3737
|
-
|
|
3783
|
+
if (!contexts.includes(input[name])) {
|
|
3784
|
+
if (throwError) logger.error({
|
|
3785
|
+
group: "resolver",
|
|
3786
|
+
message: `Modifier "${name}" has no context ${JSON.stringify(input[name])}.`
|
|
3787
|
+
});
|
|
3788
|
+
return false;
|
|
3789
|
+
}
|
|
3790
|
+
} else if (!(name in inputDefaults)) {
|
|
3791
|
+
if (throwError) logger.error({
|
|
3792
|
+
group: "resolver",
|
|
3793
|
+
message: `Modifier "${name}" missing value (no default set).`
|
|
3794
|
+
});
|
|
3795
|
+
return false;
|
|
3796
|
+
}
|
|
3738
3797
|
return true;
|
|
3798
|
+
},
|
|
3799
|
+
getPermutationID(input) {
|
|
3800
|
+
this.isValidInput(input, true);
|
|
3801
|
+
return getPermutationID({
|
|
3802
|
+
...inputDefaults,
|
|
3803
|
+
...input
|
|
3804
|
+
});
|
|
3739
3805
|
}
|
|
3740
3806
|
};
|
|
3741
3807
|
}
|