@terrazzo/parser 2.0.0-alpha.7 → 2.0.0-beta.1
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 +3 -1
- package/dist/index.d.ts +39 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1989 -1921
- 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
|
})));
|
|
@@ -897,262 +912,134 @@ const rule$16 = {
|
|
|
897
912
|
var required_typography_properties_default = rule$16;
|
|
898
913
|
|
|
899
914
|
//#endregion
|
|
900
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
901
|
-
const
|
|
915
|
+
//#region src/lint/plugin-core/rules/valid-font-family.ts
|
|
916
|
+
const VALID_FONT_FAMILY = "core/valid-font-family";
|
|
902
917
|
const ERROR$9 = "ERROR";
|
|
903
918
|
const rule$15 = {
|
|
904
919
|
meta: {
|
|
905
|
-
messages: { [ERROR$9]: "Must be a
|
|
920
|
+
messages: { [ERROR$9]: "Must be a string, or array of strings." },
|
|
906
921
|
docs: {
|
|
907
|
-
description: "Require
|
|
908
|
-
url: docsLink(
|
|
922
|
+
description: "Require fontFamily tokens to follow the format.",
|
|
923
|
+
url: docsLink(VALID_FONT_FAMILY)
|
|
909
924
|
}
|
|
910
925
|
},
|
|
911
926
|
defaultOptions: {},
|
|
912
927
|
create({ tokens, report }) {
|
|
913
928
|
for (const t of Object.values(tokens)) {
|
|
914
|
-
if (t.aliasOf || !t.originalValue
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
929
|
+
if (t.aliasOf || !t.originalValue) continue;
|
|
930
|
+
switch (t.$type) {
|
|
931
|
+
case "fontFamily":
|
|
932
|
+
validateFontFamily(t.originalValue.$value, {
|
|
933
|
+
node: getObjMember(t.source.node, "$value"),
|
|
934
|
+
filename: t.source.filename
|
|
935
|
+
});
|
|
936
|
+
break;
|
|
937
|
+
case "typography":
|
|
938
|
+
if (typeof t.originalValue.$value === "object" && t.originalValue.$value.fontFamily) {
|
|
939
|
+
if (t.partialAliasOf?.fontFamily) continue;
|
|
940
|
+
const properties = getObjMembers(getObjMember(t.source.node, "$value"));
|
|
941
|
+
validateFontFamily(t.originalValue.$value.fontFamily, {
|
|
942
|
+
node: properties.fontFamily,
|
|
943
|
+
filename: t.source.filename
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
function validateFontFamily(value, { node, filename }) {
|
|
949
|
+
if (typeof value === "string") {
|
|
950
|
+
if (!value) report({
|
|
951
|
+
messageId: ERROR$9,
|
|
952
|
+
node,
|
|
953
|
+
filename
|
|
954
|
+
});
|
|
955
|
+
} else if (Array.isArray(value)) {
|
|
956
|
+
if (!value.every((v) => v && typeof v === "string")) report({
|
|
957
|
+
messageId: ERROR$9,
|
|
958
|
+
node,
|
|
959
|
+
filename
|
|
960
|
+
});
|
|
961
|
+
} else report({
|
|
921
962
|
messageId: ERROR$9,
|
|
922
|
-
|
|
923
|
-
|
|
963
|
+
node,
|
|
964
|
+
filename
|
|
924
965
|
});
|
|
925
966
|
}
|
|
926
967
|
}
|
|
927
968
|
}
|
|
928
969
|
};
|
|
929
|
-
var
|
|
970
|
+
var valid_font_family_default = rule$15;
|
|
930
971
|
|
|
931
972
|
//#endregion
|
|
932
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
933
|
-
const
|
|
973
|
+
//#region src/lint/plugin-core/rules/valid-font-weight.ts
|
|
974
|
+
const VALID_FONT_WEIGHT = "core/valid-font-weight";
|
|
934
975
|
const ERROR$8 = "ERROR";
|
|
935
|
-
const
|
|
976
|
+
const ERROR_STYLE = "ERROR_STYLE";
|
|
936
977
|
const rule$14 = {
|
|
937
978
|
meta: {
|
|
938
979
|
messages: {
|
|
939
|
-
[ERROR$8]: `
|
|
940
|
-
[
|
|
941
|
-
},
|
|
942
|
-
docs: {
|
|
943
|
-
description: "Require border tokens to follow the format.",
|
|
944
|
-
url: docsLink(VALID_BORDER)
|
|
945
|
-
}
|
|
946
|
-
},
|
|
947
|
-
defaultOptions: {},
|
|
948
|
-
create({ tokens, report }) {
|
|
949
|
-
for (const t of Object.values(tokens)) {
|
|
950
|
-
if (t.aliasOf || !t.originalValue || t.$type !== "border") continue;
|
|
951
|
-
validateBorder(t.originalValue.$value, {
|
|
952
|
-
node: getObjMember(t.source.node, "$value"),
|
|
953
|
-
filename: t.source.filename
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
function validateBorder(value, { node, filename }) {
|
|
957
|
-
if (!value || typeof value !== "object" || !BORDER_REQUIRED_PROPERTIES.every((property) => property in value)) report({
|
|
958
|
-
messageId: ERROR$8,
|
|
959
|
-
filename,
|
|
960
|
-
node
|
|
961
|
-
});
|
|
962
|
-
else for (const key of Object.keys(value)) if (!BORDER_REQUIRED_PROPERTIES.includes(key)) report({
|
|
963
|
-
messageId: ERROR_INVALID_PROP$7,
|
|
964
|
-
data: { key: JSON.stringify(key) },
|
|
965
|
-
node: getObjMember(node, key) ?? node,
|
|
966
|
-
filename
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
};
|
|
971
|
-
var valid_border_default = rule$14;
|
|
972
|
-
|
|
973
|
-
//#endregion
|
|
974
|
-
//#region src/lint/plugin-core/rules/valid-color.ts
|
|
975
|
-
const VALID_COLOR = "core/valid-color";
|
|
976
|
-
const ERROR_ALPHA = "ERROR_ALPHA";
|
|
977
|
-
const ERROR_INVALID_COLOR = "ERROR_INVALID_COLOR";
|
|
978
|
-
const ERROR_INVALID_COLOR_SPACE = "ERROR_INVALID_COLOR_SPACE";
|
|
979
|
-
const ERROR_INVALID_COMPONENT_LENGTH = "ERROR_INVALID_COMPONENT_LENGTH";
|
|
980
|
-
const ERROR_INVALID_HEX8 = "ERROR_INVALID_HEX8";
|
|
981
|
-
const ERROR_INVALID_PROP$6 = "ERROR_INVALID_PROP";
|
|
982
|
-
const ERROR_MISSING_COMPONENTS = "ERROR_MISSING_COMPONENTS";
|
|
983
|
-
const ERROR_OBJ_FORMAT = "ERROR_OBJ_FORMAT";
|
|
984
|
-
const ERROR_OUT_OF_RANGE = "ERROR_OUT_OF_RANGE";
|
|
985
|
-
const rule$13 = {
|
|
986
|
-
meta: {
|
|
987
|
-
messages: {
|
|
988
|
-
[ERROR_ALPHA]: `Alpha {{ alpha }} not in range 0 – 1.`,
|
|
989
|
-
[ERROR_INVALID_COLOR_SPACE]: `Invalid color space: {{ colorSpace }}. Expected ${new Intl.ListFormat("en-us", { type: "disjunction" }).format(Object.keys(COLORSPACE))}`,
|
|
990
|
-
[ERROR_INVALID_COLOR]: `Could not parse color {{ color }}.`,
|
|
991
|
-
[ERROR_INVALID_COMPONENT_LENGTH]: "Expected {{ expected }} components, received {{ got }}.",
|
|
992
|
-
[ERROR_INVALID_HEX8]: `Hex value can’t be semi-transparent.`,
|
|
993
|
-
[ERROR_INVALID_PROP$6]: `Unknown property {{ key }}.`,
|
|
994
|
-
[ERROR_MISSING_COMPONENTS]: "Expected components to be array of numbers, received {{ got }}.",
|
|
995
|
-
[ERROR_OBJ_FORMAT]: "Migrate to the new object format, e.g. \"#ff0000\" → { \"colorSpace\": \"srgb\", \"components\": [1, 0, 0] } }",
|
|
996
|
-
[ERROR_OUT_OF_RANGE]: `Invalid range for color space {{ colorSpace }}. Expected {{ range }}.`
|
|
980
|
+
[ERROR$8]: `Must either be a valid number (0 - 999) or a valid font weight: ${new Intl.ListFormat("en-us", { type: "disjunction" }).format(Object.keys(FONT_WEIGHTS))}.`,
|
|
981
|
+
[ERROR_STYLE]: "Expected style {{ style }}, received {{ value }}."
|
|
997
982
|
},
|
|
998
983
|
docs: {
|
|
999
|
-
description: "Require
|
|
1000
|
-
url: docsLink(
|
|
984
|
+
description: "Require number tokens to follow the format.",
|
|
985
|
+
url: docsLink(VALID_FONT_WEIGHT)
|
|
1001
986
|
}
|
|
1002
987
|
},
|
|
1003
|
-
defaultOptions: {
|
|
1004
|
-
legacyFormat: false,
|
|
1005
|
-
ignoreRanges: false
|
|
1006
|
-
},
|
|
988
|
+
defaultOptions: { style: void 0 },
|
|
1007
989
|
create({ tokens, options, report }) {
|
|
1008
990
|
for (const t of Object.values(tokens)) {
|
|
1009
991
|
if (t.aliasOf || !t.originalValue) continue;
|
|
1010
992
|
switch (t.$type) {
|
|
1011
|
-
case "
|
|
1012
|
-
|
|
993
|
+
case "fontWeight":
|
|
994
|
+
validateFontWeight(t.originalValue.$value, {
|
|
1013
995
|
node: getObjMember(t.source.node, "$value"),
|
|
1014
996
|
filename: t.source.filename
|
|
1015
997
|
});
|
|
1016
998
|
break;
|
|
1017
|
-
case "
|
|
1018
|
-
if (t.originalValue.$value
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
case "gradient": {
|
|
1024
|
-
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1025
|
-
for (let i = 0; i < t.originalValue.$value.length; i++) {
|
|
1026
|
-
const stop = t.originalValue.$value[i];
|
|
1027
|
-
if (!stop.color || isAlias(stop.color)) continue;
|
|
1028
|
-
validateColor(stop.color, {
|
|
1029
|
-
node: getObjMember($valueNode.elements[i].value, "color"),
|
|
1030
|
-
filename: t.source.filename
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
break;
|
|
1034
|
-
}
|
|
1035
|
-
case "shadow": {
|
|
1036
|
-
const $value = Array.isArray(t.originalValue.$value) ? t.originalValue.$value : [t.originalValue.$value];
|
|
1037
|
-
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1038
|
-
for (let i = 0; i < $value.length; i++) {
|
|
1039
|
-
const layer = $value[i];
|
|
1040
|
-
if (!layer.color || isAlias(layer.color)) continue;
|
|
1041
|
-
validateColor(layer.color, {
|
|
1042
|
-
node: $valueNode.type === "Object" ? getObjMember($valueNode, "color") : getObjMember($valueNode.elements[i].value, "color"),
|
|
999
|
+
case "typography":
|
|
1000
|
+
if (typeof t.originalValue.$value === "object" && t.originalValue.$value.fontWeight) {
|
|
1001
|
+
if (t.partialAliasOf?.fontWeight) continue;
|
|
1002
|
+
const properties = getObjMembers(getObjMember(t.source.node, "$value"));
|
|
1003
|
+
validateFontWeight(t.originalValue.$value.fontWeight, {
|
|
1004
|
+
node: properties.fontWeight,
|
|
1043
1005
|
filename: t.source.filename
|
|
1044
1006
|
});
|
|
1045
1007
|
}
|
|
1046
1008
|
break;
|
|
1047
|
-
}
|
|
1048
1009
|
}
|
|
1049
|
-
function
|
|
1050
|
-
if (
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
node,
|
|
1054
|
-
filename
|
|
1055
|
-
});
|
|
1056
|
-
else if (typeof value === "object") {
|
|
1057
|
-
for (const key of Object.keys(value)) if (![
|
|
1058
|
-
"colorSpace",
|
|
1059
|
-
"components",
|
|
1060
|
-
"channels",
|
|
1061
|
-
"hex",
|
|
1062
|
-
"alpha"
|
|
1063
|
-
].includes(key)) report({
|
|
1064
|
-
messageId: ERROR_INVALID_PROP$6,
|
|
1065
|
-
data: { key: JSON.stringify(key) },
|
|
1066
|
-
node: getObjMember(node, key) ?? node,
|
|
1067
|
-
filename
|
|
1068
|
-
});
|
|
1069
|
-
const colorSpace = "colorSpace" in value && typeof value.colorSpace === "string" ? value.colorSpace : void 0;
|
|
1070
|
-
const csData = COLORSPACE[colorSpace] || void 0;
|
|
1071
|
-
if (!("colorSpace" in value) || !csData) {
|
|
1072
|
-
report({
|
|
1073
|
-
messageId: ERROR_INVALID_COLOR_SPACE,
|
|
1074
|
-
data: { colorSpace },
|
|
1075
|
-
node: getObjMember(node, "colorSpace") ?? node,
|
|
1076
|
-
filename
|
|
1077
|
-
});
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
const components = "components" in value ? value.components : void 0;
|
|
1081
|
-
if (Array.isArray(components)) if (csData?.ranges && components?.length === csData.ranges.length) {
|
|
1082
|
-
for (let i = 0; i < components.length; i++) if (!Number.isFinite(components[i]) || components[i] < csData.ranges[i][0] || components[i] > csData.ranges[i][1]) {
|
|
1083
|
-
if (!(colorSpace === "hsl" && components[0] === null) && !(colorSpace === "hwb" && components[0] === null) && !(colorSpace === "lch" && components[2] === null) && !(colorSpace === "oklch" && components[2] === null)) report({
|
|
1084
|
-
messageId: ERROR_OUT_OF_RANGE,
|
|
1085
|
-
data: {
|
|
1086
|
-
colorSpace,
|
|
1087
|
-
range: `[${csData.ranges.map((r) => `${r[0]}–${r[1]}`).join(", ")}]`
|
|
1088
|
-
},
|
|
1089
|
-
node: getObjMember(node, "components") ?? node,
|
|
1090
|
-
filename
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
} else report({
|
|
1094
|
-
messageId: ERROR_INVALID_COMPONENT_LENGTH,
|
|
1010
|
+
function validateFontWeight(value, { node, filename }) {
|
|
1011
|
+
if (typeof value === "string") {
|
|
1012
|
+
if (options.style === "numbers") report({
|
|
1013
|
+
messageId: ERROR_STYLE,
|
|
1095
1014
|
data: {
|
|
1096
|
-
|
|
1097
|
-
|
|
1015
|
+
style: "numbers",
|
|
1016
|
+
value
|
|
1098
1017
|
},
|
|
1099
|
-
node
|
|
1018
|
+
node,
|
|
1100
1019
|
filename
|
|
1101
1020
|
});
|
|
1102
|
-
else report({
|
|
1103
|
-
messageId:
|
|
1104
|
-
|
|
1105
|
-
node: getObjMember(node, "components") ?? node,
|
|
1021
|
+
else if (!(value in FONT_WEIGHTS)) report({
|
|
1022
|
+
messageId: ERROR$8,
|
|
1023
|
+
node,
|
|
1106
1024
|
filename
|
|
1107
1025
|
});
|
|
1108
|
-
|
|
1109
|
-
if (
|
|
1110
|
-
messageId:
|
|
1111
|
-
data: {
|
|
1112
|
-
|
|
1026
|
+
} else if (typeof value === "number") {
|
|
1027
|
+
if (options.style === "names") report({
|
|
1028
|
+
messageId: ERROR_STYLE,
|
|
1029
|
+
data: {
|
|
1030
|
+
style: "names",
|
|
1031
|
+
value
|
|
1032
|
+
},
|
|
1033
|
+
node,
|
|
1113
1034
|
filename
|
|
1114
1035
|
});
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
let color;
|
|
1118
|
-
try {
|
|
1119
|
-
color = parseColor(hex);
|
|
1120
|
-
} catch {
|
|
1121
|
-
report({
|
|
1122
|
-
messageId: ERROR_INVALID_COLOR,
|
|
1123
|
-
data: { color: hex },
|
|
1124
|
-
node: getObjMember(node, "hex") ?? node,
|
|
1125
|
-
filename
|
|
1126
|
-
});
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
if (color.alpha !== 1) report({
|
|
1130
|
-
messageId: ERROR_INVALID_HEX8,
|
|
1131
|
-
data: { color: hex },
|
|
1132
|
-
node: getObjMember(node, "hex") ?? node,
|
|
1133
|
-
filename
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
} else if (typeof value === "string") {
|
|
1137
|
-
if (isAlias(value)) return;
|
|
1138
|
-
if (!options.legacyFormat) report({
|
|
1139
|
-
messageId: ERROR_OBJ_FORMAT,
|
|
1140
|
-
data: { color: JSON.stringify(value) },
|
|
1036
|
+
else if (!(value >= 0 && value < 1e3)) report({
|
|
1037
|
+
messageId: ERROR$8,
|
|
1141
1038
|
node,
|
|
1142
1039
|
filename
|
|
1143
1040
|
});
|
|
1144
|
-
else try {
|
|
1145
|
-
parseColor(value);
|
|
1146
|
-
} catch {
|
|
1147
|
-
report({
|
|
1148
|
-
messageId: ERROR_INVALID_COLOR,
|
|
1149
|
-
data: { color: JSON.stringify(value) },
|
|
1150
|
-
node,
|
|
1151
|
-
filename
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
1041
|
} else report({
|
|
1155
|
-
messageId:
|
|
1042
|
+
messageId: ERROR$8,
|
|
1156
1043
|
node,
|
|
1157
1044
|
filename
|
|
1158
1045
|
});
|
|
@@ -1160,64 +1047,96 @@ const rule$13 = {
|
|
|
1160
1047
|
}
|
|
1161
1048
|
}
|
|
1162
1049
|
};
|
|
1163
|
-
var
|
|
1050
|
+
var valid_font_weight_default = rule$14;
|
|
1164
1051
|
|
|
1165
1052
|
//#endregion
|
|
1166
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1167
|
-
const
|
|
1168
|
-
const
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1171
|
-
const rule$
|
|
1053
|
+
//#region src/lint/plugin-core/rules/valid-gradient.ts
|
|
1054
|
+
const VALID_GRADIENT = "core/valid-gradient";
|
|
1055
|
+
const ERROR_MISSING$1 = "ERROR_MISSING";
|
|
1056
|
+
const ERROR_POSITION = "ERROR_POSITION";
|
|
1057
|
+
const ERROR_INVALID_PROP$7 = "ERROR_INVALID_PROP";
|
|
1058
|
+
const rule$13 = {
|
|
1172
1059
|
meta: {
|
|
1173
1060
|
messages: {
|
|
1174
|
-
[
|
|
1175
|
-
[
|
|
1176
|
-
[
|
|
1061
|
+
[ERROR_MISSING$1]: "Must be an array of { color, position } objects.",
|
|
1062
|
+
[ERROR_POSITION]: "Expected number 0-1, received {{ value }}.",
|
|
1063
|
+
[ERROR_INVALID_PROP$7]: "Unknown property {{ key }}."
|
|
1177
1064
|
},
|
|
1178
1065
|
docs: {
|
|
1179
|
-
description: "Require
|
|
1180
|
-
url: docsLink(
|
|
1066
|
+
description: "Require gradient tokens to follow the format.",
|
|
1067
|
+
url: docsLink(VALID_GRADIENT)
|
|
1181
1068
|
}
|
|
1182
1069
|
},
|
|
1183
1070
|
defaultOptions: {},
|
|
1184
1071
|
create({ tokens, report }) {
|
|
1185
1072
|
for (const t of Object.values(tokens)) {
|
|
1186
|
-
if (t.aliasOf || !t.originalValue) continue;
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1073
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "gradient") continue;
|
|
1074
|
+
validateGradient(t.originalValue.$value, {
|
|
1075
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1076
|
+
filename: t.source.filename
|
|
1077
|
+
});
|
|
1078
|
+
function validateGradient(value, { node, filename }) {
|
|
1079
|
+
if (Array.isArray(value)) for (let i = 0; i < value.length; i++) {
|
|
1080
|
+
const stop = value[i];
|
|
1081
|
+
if (!stop || typeof stop !== "object") {
|
|
1082
|
+
report({
|
|
1083
|
+
messageId: ERROR_MISSING$1,
|
|
1084
|
+
node,
|
|
1085
|
+
filename
|
|
1086
|
+
});
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
for (const property of GRADIENT_REQUIRED_STOP_PROPERTIES) if (!(property in stop)) report({
|
|
1090
|
+
messageId: ERROR_MISSING$1,
|
|
1091
|
+
node: node.elements[i],
|
|
1092
|
+
filename
|
|
1192
1093
|
});
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1094
|
+
for (const key of Object.keys(stop)) if (!GRADIENT_REQUIRED_STOP_PROPERTIES.includes(key)) report({
|
|
1095
|
+
messageId: ERROR_INVALID_PROP$7,
|
|
1096
|
+
data: { key: JSON.stringify(key) },
|
|
1097
|
+
node: node.elements[i],
|
|
1098
|
+
filename
|
|
1099
|
+
});
|
|
1100
|
+
if ("position" in stop && typeof stop.position !== "number" && !isAlias(stop.position)) report({
|
|
1101
|
+
messageId: ERROR_POSITION,
|
|
1102
|
+
data: { value: stop.position },
|
|
1103
|
+
node: getObjMember(node.elements[i].value, "position"),
|
|
1104
|
+
filename
|
|
1199
1105
|
});
|
|
1200
1106
|
}
|
|
1107
|
+
else report({
|
|
1108
|
+
messageId: ERROR_MISSING$1,
|
|
1109
|
+
node,
|
|
1110
|
+
filename
|
|
1111
|
+
});
|
|
1201
1112
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
var valid_gradient_default = rule$13;
|
|
1117
|
+
|
|
1118
|
+
//#endregion
|
|
1119
|
+
//#region src/lint/plugin-core/rules/valid-link.ts
|
|
1120
|
+
const VALID_LINK = "core/valid-link";
|
|
1121
|
+
const ERROR$7 = "ERROR";
|
|
1122
|
+
const rule$12 = {
|
|
1123
|
+
meta: {
|
|
1124
|
+
messages: { [ERROR$7]: "Must be a string." },
|
|
1125
|
+
docs: {
|
|
1126
|
+
description: "Require link tokens to follow the Terrazzo extension.",
|
|
1127
|
+
url: docsLink(VALID_LINK)
|
|
1128
|
+
}
|
|
1129
|
+
},
|
|
1130
|
+
defaultOptions: {},
|
|
1131
|
+
create({ tokens, report }) {
|
|
1132
|
+
for (const t of Object.values(tokens)) {
|
|
1133
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "link") continue;
|
|
1134
|
+
validateLink(t.originalValue.$value, {
|
|
1135
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1136
|
+
filename: t.source.filename
|
|
1137
|
+
});
|
|
1138
|
+
function validateLink(value, { node, filename }) {
|
|
1139
|
+
if (!value || typeof value !== "string") report({
|
|
1221
1140
|
messageId: ERROR$7,
|
|
1222
1141
|
node,
|
|
1223
1142
|
filename
|
|
@@ -1226,160 +1145,44 @@ const rule$12 = {
|
|
|
1226
1145
|
}
|
|
1227
1146
|
}
|
|
1228
1147
|
};
|
|
1229
|
-
var
|
|
1148
|
+
var valid_link_default = rule$12;
|
|
1230
1149
|
|
|
1231
1150
|
//#endregion
|
|
1232
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1233
|
-
const
|
|
1234
|
-
const
|
|
1235
|
-
const ERROR_INVALID_PROP$5 = "ERROR_INVALID_PROP";
|
|
1236
|
-
const ERROR_LEGACY$1 = "ERROR_LEGACY";
|
|
1237
|
-
const ERROR_UNIT$1 = "ERROR_UNIT";
|
|
1238
|
-
const ERROR_VALUE$1 = "ERROR_VALUE";
|
|
1151
|
+
//#region src/lint/plugin-core/rules/valid-number.ts
|
|
1152
|
+
const VALID_NUMBER = "core/valid-number";
|
|
1153
|
+
const ERROR_NAN = "ERROR_NAN";
|
|
1239
1154
|
const rule$11 = {
|
|
1240
1155
|
meta: {
|
|
1241
|
-
messages: {
|
|
1242
|
-
[ERROR_FORMAT$1]: "Invalid dimension: {{ value }}. Expected object with \"value\" and \"unit\".",
|
|
1243
|
-
[ERROR_LEGACY$1]: "Migrate to the new object format: { \"value\": 10, \"unit\": \"px\" }.",
|
|
1244
|
-
[ERROR_UNIT$1]: "Unit {{ unit }} not allowed. Expected {{ allowed }}.",
|
|
1245
|
-
[ERROR_INVALID_PROP$5]: "Unknown property {{ key }}.",
|
|
1246
|
-
[ERROR_VALUE$1]: "Expected number, received {{ value }}."
|
|
1247
|
-
},
|
|
1156
|
+
messages: { [ERROR_NAN]: "Must be a number." },
|
|
1248
1157
|
docs: {
|
|
1249
|
-
description: "Require
|
|
1250
|
-
url: docsLink(
|
|
1158
|
+
description: "Require number tokens to follow the format.",
|
|
1159
|
+
url: docsLink(VALID_NUMBER)
|
|
1251
1160
|
}
|
|
1252
1161
|
},
|
|
1253
|
-
defaultOptions: {
|
|
1254
|
-
|
|
1255
|
-
allowedUnits: [
|
|
1256
|
-
"px",
|
|
1257
|
-
"em",
|
|
1258
|
-
"rem"
|
|
1259
|
-
]
|
|
1260
|
-
},
|
|
1261
|
-
create({ tokens, options, report }) {
|
|
1162
|
+
defaultOptions: {},
|
|
1163
|
+
create({ tokens, report }) {
|
|
1262
1164
|
for (const t of Object.values(tokens)) {
|
|
1263
1165
|
if (t.aliasOf || !t.originalValue) continue;
|
|
1264
1166
|
switch (t.$type) {
|
|
1265
|
-
case "
|
|
1266
|
-
|
|
1167
|
+
case "number":
|
|
1168
|
+
validateNumber(t.originalValue.$value, {
|
|
1267
1169
|
node: getObjMember(t.source.node, "$value"),
|
|
1268
1170
|
filename: t.source.filename
|
|
1269
1171
|
});
|
|
1270
1172
|
break;
|
|
1271
|
-
case "
|
|
1272
|
-
if (typeof t.originalValue.$value === "object" && Array.isArray(t.originalValue.$value.dashArray)) {
|
|
1273
|
-
const dashArray = getObjMember(getObjMember(t.source.node, "$value"), "dashArray");
|
|
1274
|
-
for (let i = 0; i < t.originalValue.$value.dashArray.length; i++) {
|
|
1275
|
-
if (isAlias(t.originalValue.$value.dashArray[i])) continue;
|
|
1276
|
-
validateDimension(t.originalValue.$value.dashArray[i], {
|
|
1277
|
-
node: dashArray.elements[i].value,
|
|
1278
|
-
filename: t.source.filename
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
break;
|
|
1283
|
-
case "border": {
|
|
1173
|
+
case "typography": {
|
|
1284
1174
|
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1285
1175
|
if (typeof t.originalValue.$value === "object") {
|
|
1286
|
-
if (t.originalValue.$value.
|
|
1287
|
-
node: getObjMember($valueNode, "
|
|
1176
|
+
if (t.originalValue.$value.lineHeight && !isAlias(t.originalValue.$value.lineHeight) && typeof t.originalValue.$value.lineHeight !== "object") validateNumber(t.originalValue.$value.lineHeight, {
|
|
1177
|
+
node: getObjMember($valueNode, "lineHeight"),
|
|
1288
1178
|
filename: t.source.filename
|
|
1289
1179
|
});
|
|
1290
|
-
if (typeof t.originalValue.$value.style === "object" && Array.isArray(t.originalValue.$value.style.dashArray)) {
|
|
1291
|
-
const dashArray = getObjMember(getObjMember($valueNode, "style"), "dashArray");
|
|
1292
|
-
for (let i = 0; i < t.originalValue.$value.style.dashArray.length; i++) {
|
|
1293
|
-
if (isAlias(t.originalValue.$value.style.dashArray[i])) continue;
|
|
1294
|
-
validateDimension(t.originalValue.$value.style.dashArray[i], {
|
|
1295
|
-
node: dashArray.elements[i].value,
|
|
1296
|
-
filename: t.source.filename
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
break;
|
|
1302
|
-
}
|
|
1303
|
-
case "shadow":
|
|
1304
|
-
if (t.originalValue.$value && typeof t.originalValue.$value === "object") {
|
|
1305
|
-
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1306
|
-
const valueArray = Array.isArray(t.originalValue.$value) ? t.originalValue.$value : [t.originalValue.$value];
|
|
1307
|
-
for (let i = 0; i < valueArray.length; i++) {
|
|
1308
|
-
const node = $valueNode.type === "Array" ? $valueNode.elements[i].value : $valueNode;
|
|
1309
|
-
for (const property of [
|
|
1310
|
-
"offsetX",
|
|
1311
|
-
"offsetY",
|
|
1312
|
-
"blur",
|
|
1313
|
-
"spread"
|
|
1314
|
-
]) {
|
|
1315
|
-
if (isAlias(valueArray[i][property])) continue;
|
|
1316
|
-
validateDimension(valueArray[i][property], {
|
|
1317
|
-
node: getObjMember(node, property),
|
|
1318
|
-
filename: t.source.filename
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
break;
|
|
1324
|
-
case "typography": {
|
|
1325
|
-
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1326
|
-
if (typeof t.originalValue.$value === "object") {
|
|
1327
|
-
for (const property of [
|
|
1328
|
-
"fontSize",
|
|
1329
|
-
"lineHeight",
|
|
1330
|
-
"letterSpacing"
|
|
1331
|
-
]) if (property in t.originalValue.$value) {
|
|
1332
|
-
if (isAlias(t.originalValue.$value[property]) || property === "lineHeight" && typeof t.originalValue.$value[property] === "number") continue;
|
|
1333
|
-
validateDimension(t.originalValue.$value[property], {
|
|
1334
|
-
node: getObjMember($valueNode, property),
|
|
1335
|
-
filename: t.source.filename
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
1180
|
}
|
|
1339
|
-
break;
|
|
1340
1181
|
}
|
|
1341
1182
|
}
|
|
1342
|
-
function
|
|
1343
|
-
if (
|
|
1344
|
-
|
|
1345
|
-
messageId: ERROR_INVALID_PROP$5,
|
|
1346
|
-
data: { key: JSON.stringify(key) },
|
|
1347
|
-
node: getObjMember(node, key) ?? node,
|
|
1348
|
-
filename
|
|
1349
|
-
});
|
|
1350
|
-
const { unit, value: numValue } = value;
|
|
1351
|
-
if (!("value" in value || "unit" in value)) {
|
|
1352
|
-
report({
|
|
1353
|
-
messageId: ERROR_FORMAT$1,
|
|
1354
|
-
data: { value },
|
|
1355
|
-
node,
|
|
1356
|
-
filename
|
|
1357
|
-
});
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1360
|
-
if (!options.allowedUnits.includes(unit)) report({
|
|
1361
|
-
messageId: ERROR_UNIT$1,
|
|
1362
|
-
data: {
|
|
1363
|
-
unit,
|
|
1364
|
-
allowed: new Intl.ListFormat("en-us", { type: "disjunction" }).format(options.allowedUnits)
|
|
1365
|
-
},
|
|
1366
|
-
node: getObjMember(node, "unit") ?? node,
|
|
1367
|
-
filename
|
|
1368
|
-
});
|
|
1369
|
-
if (!Number.isFinite(numValue)) report({
|
|
1370
|
-
messageId: ERROR_VALUE$1,
|
|
1371
|
-
data: { value },
|
|
1372
|
-
node: getObjMember(node, "value") ?? node,
|
|
1373
|
-
filename
|
|
1374
|
-
});
|
|
1375
|
-
} else if (typeof value === "string" && !options.legacyFormat) report({
|
|
1376
|
-
messageId: ERROR_LEGACY$1,
|
|
1377
|
-
node,
|
|
1378
|
-
filename
|
|
1379
|
-
});
|
|
1380
|
-
else report({
|
|
1381
|
-
messageId: ERROR_FORMAT$1,
|
|
1382
|
-
data: { value },
|
|
1183
|
+
function validateNumber(value, { node, filename }) {
|
|
1184
|
+
if (typeof value !== "number" || Number.isNaN(value)) report({
|
|
1185
|
+
messageId: ERROR_NAN,
|
|
1383
1186
|
node,
|
|
1384
1187
|
filename
|
|
1385
1188
|
});
|
|
@@ -1387,150 +1190,74 @@ const rule$11 = {
|
|
|
1387
1190
|
}
|
|
1388
1191
|
}
|
|
1389
1192
|
};
|
|
1390
|
-
var
|
|
1193
|
+
var valid_number_default = rule$11;
|
|
1391
1194
|
|
|
1392
1195
|
//#endregion
|
|
1393
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1394
|
-
const
|
|
1395
|
-
const
|
|
1396
|
-
const ERROR_INVALID_PROP$
|
|
1397
|
-
const ERROR_LEGACY = "ERROR_LEGACY";
|
|
1398
|
-
const ERROR_UNIT = "ERROR_UNIT";
|
|
1399
|
-
const ERROR_VALUE = "ERROR_VALUE";
|
|
1196
|
+
//#region src/lint/plugin-core/rules/valid-shadow.ts
|
|
1197
|
+
const VALID_SHADOW = "core/valid-shadow";
|
|
1198
|
+
const ERROR$6 = "ERROR";
|
|
1199
|
+
const ERROR_INVALID_PROP$6 = "ERROR_INVALID_PROP";
|
|
1400
1200
|
const rule$10 = {
|
|
1401
1201
|
meta: {
|
|
1402
1202
|
messages: {
|
|
1403
|
-
[
|
|
1404
|
-
[
|
|
1405
|
-
[ERROR_INVALID_PROP$4]: "Unknown property: {{ key }}.",
|
|
1406
|
-
[ERROR_UNIT]: "Unknown unit {{ unit }}. Expected \"ms\" or \"s\".",
|
|
1407
|
-
[ERROR_VALUE]: "Expected number, received {{ value }}."
|
|
1203
|
+
[ERROR$6]: `Missing required properties: ${new Intl.ListFormat("en-us", { type: "conjunction" }).format(SHADOW_REQUIRED_PROPERTIES)}.`,
|
|
1204
|
+
[ERROR_INVALID_PROP$6]: "Unknown property {{ key }}."
|
|
1408
1205
|
},
|
|
1409
1206
|
docs: {
|
|
1410
|
-
description: "Require
|
|
1411
|
-
url: docsLink(
|
|
1207
|
+
description: "Require shadow tokens to follow the format.",
|
|
1208
|
+
url: docsLink(VALID_SHADOW)
|
|
1412
1209
|
}
|
|
1413
1210
|
},
|
|
1414
|
-
defaultOptions: {
|
|
1415
|
-
|
|
1416
|
-
unknownUnits: false
|
|
1417
|
-
},
|
|
1418
|
-
create({ tokens, options, report }) {
|
|
1211
|
+
defaultOptions: {},
|
|
1212
|
+
create({ tokens, report }) {
|
|
1419
1213
|
for (const t of Object.values(tokens)) {
|
|
1420
|
-
if (t.aliasOf || !t.originalValue) continue;
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
if (typeof t.originalValue.$value === "object") {
|
|
1430
|
-
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1431
|
-
for (const property of ["duration", "delay"]) if (t.originalValue.$value[property] && !isAlias(t.originalValue.$value[property])) validateDuration(t.originalValue.$value[property], {
|
|
1432
|
-
node: getObjMember($valueNode, property),
|
|
1433
|
-
filename: t.source.filename
|
|
1434
|
-
});
|
|
1435
|
-
}
|
|
1436
|
-
break;
|
|
1437
|
-
}
|
|
1438
|
-
function validateDuration(value, { node, filename }) {
|
|
1439
|
-
if (value && typeof value === "object") {
|
|
1440
|
-
for (const key of Object.keys(value)) if (!["value", "unit"].includes(key)) report({
|
|
1441
|
-
messageId: ERROR_INVALID_PROP$4,
|
|
1442
|
-
data: { key: JSON.stringify(key) },
|
|
1443
|
-
node: getObjMember(node, key) ?? node,
|
|
1444
|
-
filename
|
|
1445
|
-
});
|
|
1446
|
-
const { unit, value: numValue } = value;
|
|
1447
|
-
if (!("value" in value || "unit" in value)) {
|
|
1448
|
-
report({
|
|
1449
|
-
messageId: ERROR_FORMAT,
|
|
1450
|
-
data: { value },
|
|
1451
|
-
node,
|
|
1452
|
-
filename
|
|
1453
|
-
});
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
if (!options.unknownUnits && !["ms", "s"].includes(unit)) report({
|
|
1457
|
-
messageId: ERROR_UNIT,
|
|
1458
|
-
data: { unit },
|
|
1459
|
-
node: getObjMember(node, "unit") ?? node,
|
|
1460
|
-
filename
|
|
1461
|
-
});
|
|
1462
|
-
if (!Number.isFinite(numValue)) report({
|
|
1463
|
-
messageId: ERROR_VALUE,
|
|
1464
|
-
data: { value },
|
|
1465
|
-
node: getObjMember(node, "value") ?? node,
|
|
1466
|
-
filename
|
|
1467
|
-
});
|
|
1468
|
-
} else if (typeof value === "string" && !options.legacyFormat) report({
|
|
1469
|
-
messageId: ERROR_FORMAT,
|
|
1214
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "shadow") continue;
|
|
1215
|
+
validateShadow(t.originalValue.$value, {
|
|
1216
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1217
|
+
filename: t.source.filename
|
|
1218
|
+
});
|
|
1219
|
+
function validateShadow(value, { node, filename }) {
|
|
1220
|
+
const wrappedValue = Array.isArray(value) ? value : [value];
|
|
1221
|
+
for (let i = 0; i < wrappedValue.length; i++) if (!wrappedValue[i] || typeof wrappedValue[i] !== "object" || !SHADOW_REQUIRED_PROPERTIES.every((property) => property in wrappedValue[i])) report({
|
|
1222
|
+
messageId: ERROR$6,
|
|
1470
1223
|
node,
|
|
1471
1224
|
filename
|
|
1472
1225
|
});
|
|
1473
|
-
else report({
|
|
1474
|
-
messageId:
|
|
1475
|
-
data: {
|
|
1476
|
-
node,
|
|
1226
|
+
else for (const key of Object.keys(wrappedValue[i])) if (![...SHADOW_REQUIRED_PROPERTIES, "inset"].includes(key)) report({
|
|
1227
|
+
messageId: ERROR_INVALID_PROP$6,
|
|
1228
|
+
data: { key: JSON.stringify(key) },
|
|
1229
|
+
node: getObjMember(node.type === "Array" ? node.elements[i].value : node, key),
|
|
1477
1230
|
filename
|
|
1478
1231
|
});
|
|
1479
1232
|
}
|
|
1480
1233
|
}
|
|
1481
1234
|
}
|
|
1482
1235
|
};
|
|
1483
|
-
var
|
|
1236
|
+
var valid_shadow_default = rule$10;
|
|
1484
1237
|
|
|
1485
1238
|
//#endregion
|
|
1486
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1487
|
-
const
|
|
1488
|
-
const ERROR$
|
|
1239
|
+
//#region src/lint/plugin-core/rules/valid-string.ts
|
|
1240
|
+
const VALID_STRING = "core/valid-string";
|
|
1241
|
+
const ERROR$5 = "ERROR";
|
|
1489
1242
|
const rule$9 = {
|
|
1490
1243
|
meta: {
|
|
1491
|
-
messages: { [ERROR$
|
|
1244
|
+
messages: { [ERROR$5]: "Must be a string." },
|
|
1492
1245
|
docs: {
|
|
1493
|
-
description: "Require
|
|
1494
|
-
url: docsLink(
|
|
1246
|
+
description: "Require string tokens to follow the Terrazzo extension.",
|
|
1247
|
+
url: docsLink(VALID_STRING)
|
|
1495
1248
|
}
|
|
1496
1249
|
},
|
|
1497
1250
|
defaultOptions: {},
|
|
1498
1251
|
create({ tokens, report }) {
|
|
1499
1252
|
for (const t of Object.values(tokens)) {
|
|
1500
|
-
if (t.aliasOf || !t.originalValue) continue;
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
case "typography":
|
|
1509
|
-
if (typeof t.originalValue.$value === "object" && t.originalValue.$value.fontFamily) {
|
|
1510
|
-
if (t.partialAliasOf?.fontFamily) continue;
|
|
1511
|
-
const properties = getObjMembers(getObjMember(t.source.node, "$value"));
|
|
1512
|
-
validateFontFamily(t.originalValue.$value.fontFamily, {
|
|
1513
|
-
node: properties.fontFamily,
|
|
1514
|
-
filename: t.source.filename
|
|
1515
|
-
});
|
|
1516
|
-
}
|
|
1517
|
-
break;
|
|
1518
|
-
}
|
|
1519
|
-
function validateFontFamily(value, { node, filename }) {
|
|
1520
|
-
if (typeof value === "string") {
|
|
1521
|
-
if (!value) report({
|
|
1522
|
-
messageId: ERROR$6,
|
|
1523
|
-
node,
|
|
1524
|
-
filename
|
|
1525
|
-
});
|
|
1526
|
-
} else if (Array.isArray(value)) {
|
|
1527
|
-
if (!value.every((v) => v && typeof v === "string")) report({
|
|
1528
|
-
messageId: ERROR$6,
|
|
1529
|
-
node,
|
|
1530
|
-
filename
|
|
1531
|
-
});
|
|
1532
|
-
} else report({
|
|
1533
|
-
messageId: ERROR$6,
|
|
1253
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "string") continue;
|
|
1254
|
+
validateString(t.originalValue.$value, {
|
|
1255
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1256
|
+
filename: t.source.filename
|
|
1257
|
+
});
|
|
1258
|
+
function validateString(value, { node, filename }) {
|
|
1259
|
+
if (typeof value !== "string") report({
|
|
1260
|
+
messageId: ERROR$5,
|
|
1534
1261
|
node,
|
|
1535
1262
|
filename
|
|
1536
1263
|
});
|
|
@@ -1538,79 +1265,83 @@ const rule$9 = {
|
|
|
1538
1265
|
}
|
|
1539
1266
|
}
|
|
1540
1267
|
};
|
|
1541
|
-
var
|
|
1268
|
+
var valid_string_default = rule$9;
|
|
1542
1269
|
|
|
1543
1270
|
//#endregion
|
|
1544
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1545
|
-
const
|
|
1546
|
-
const
|
|
1547
|
-
const
|
|
1271
|
+
//#region src/lint/plugin-core/rules/valid-stroke-style.ts
|
|
1272
|
+
const VALID_STROKE_STYLE = "core/valid-stroke-style";
|
|
1273
|
+
const ERROR_STR = "ERROR_STR";
|
|
1274
|
+
const ERROR_OBJ = "ERROR_OBJ";
|
|
1275
|
+
const ERROR_LINE_CAP = "ERROR_LINE_CAP";
|
|
1276
|
+
const ERROR_INVALID_PROP$5 = "ERROR_INVALID_PROP";
|
|
1548
1277
|
const rule$8 = {
|
|
1549
1278
|
meta: {
|
|
1550
1279
|
messages: {
|
|
1551
|
-
[
|
|
1552
|
-
[
|
|
1280
|
+
[ERROR_STR]: `Value most be one of ${new Intl.ListFormat("en-us", { type: "disjunction" }).format(STROKE_STYLE_STRING_VALUES)}.`,
|
|
1281
|
+
[ERROR_OBJ]: `Missing required properties: ${new Intl.ListFormat("en-us", { type: "conjunction" }).format(TRANSITION_REQUIRED_PROPERTIES)}.`,
|
|
1282
|
+
[ERROR_LINE_CAP]: `lineCap must be one of ${new Intl.ListFormat("en-us", { type: "disjunction" }).format(STROKE_STYLE_LINE_CAP_VALUES)}.`,
|
|
1283
|
+
[ERROR_INVALID_PROP$5]: "Unknown property: {{ key }}."
|
|
1553
1284
|
},
|
|
1554
1285
|
docs: {
|
|
1555
|
-
description: "Require
|
|
1556
|
-
url: docsLink(
|
|
1286
|
+
description: "Require strokeStyle tokens to follow the format.",
|
|
1287
|
+
url: docsLink(VALID_STROKE_STYLE)
|
|
1557
1288
|
}
|
|
1558
1289
|
},
|
|
1559
|
-
defaultOptions: {
|
|
1560
|
-
create({ tokens,
|
|
1290
|
+
defaultOptions: {},
|
|
1291
|
+
create({ tokens, report }) {
|
|
1561
1292
|
for (const t of Object.values(tokens)) {
|
|
1562
1293
|
if (t.aliasOf || !t.originalValue) continue;
|
|
1563
1294
|
switch (t.$type) {
|
|
1564
|
-
case "
|
|
1565
|
-
|
|
1295
|
+
case "strokeStyle":
|
|
1296
|
+
validateStrokeStyle(t.originalValue.$value, {
|
|
1566
1297
|
node: getObjMember(t.source.node, "$value"),
|
|
1567
1298
|
filename: t.source.filename
|
|
1568
1299
|
});
|
|
1569
1300
|
break;
|
|
1570
|
-
case "
|
|
1571
|
-
if (typeof t.originalValue.$value === "object"
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
node: properties.fontWeight,
|
|
1301
|
+
case "border":
|
|
1302
|
+
if (t.originalValue.$value && typeof t.originalValue.$value === "object") {
|
|
1303
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1304
|
+
if (t.originalValue.$value.style) validateStrokeStyle(t.originalValue.$value.style, {
|
|
1305
|
+
node: getObjMember($valueNode, "style"),
|
|
1576
1306
|
filename: t.source.filename
|
|
1577
1307
|
});
|
|
1578
1308
|
}
|
|
1579
1309
|
break;
|
|
1580
1310
|
}
|
|
1581
|
-
function
|
|
1311
|
+
function validateStrokeStyle(value, { node, filename }) {
|
|
1582
1312
|
if (typeof value === "string") {
|
|
1583
|
-
if (
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
}
|
|
1313
|
+
if (!isAlias(value) && !STROKE_STYLE_STRING_VALUES.includes(value)) {
|
|
1314
|
+
report({
|
|
1315
|
+
messageId: ERROR_STR,
|
|
1316
|
+
node,
|
|
1317
|
+
filename
|
|
1318
|
+
});
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
} else if (value && typeof value === "object") {
|
|
1322
|
+
if (!STROKE_STYLE_OBJECT_REQUIRED_PROPERTIES.every((property) => property in value)) report({
|
|
1323
|
+
messageId: ERROR_OBJ,
|
|
1589
1324
|
node,
|
|
1590
1325
|
filename
|
|
1591
1326
|
});
|
|
1592
|
-
|
|
1593
|
-
messageId:
|
|
1594
|
-
node,
|
|
1327
|
+
if (!Array.isArray(value.dashArray)) report({
|
|
1328
|
+
messageId: ERROR_OBJ,
|
|
1329
|
+
node: getObjMember(node, "dashArray"),
|
|
1595
1330
|
filename
|
|
1596
1331
|
});
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
data: {
|
|
1601
|
-
style: "names",
|
|
1602
|
-
value
|
|
1603
|
-
},
|
|
1604
|
-
node,
|
|
1332
|
+
if (!STROKE_STYLE_LINE_CAP_VALUES.includes(value.lineCap)) report({
|
|
1333
|
+
messageId: ERROR_OBJ,
|
|
1334
|
+
node: getObjMember(node, "lineCap"),
|
|
1605
1335
|
filename
|
|
1606
1336
|
});
|
|
1607
|
-
|
|
1608
|
-
messageId:
|
|
1609
|
-
|
|
1337
|
+
for (const key of Object.keys(value)) if (!["dashArray", "lineCap"].includes(key)) report({
|
|
1338
|
+
messageId: ERROR_INVALID_PROP$5,
|
|
1339
|
+
data: { key: JSON.stringify(key) },
|
|
1340
|
+
node: getObjMember(node, key),
|
|
1610
1341
|
filename
|
|
1611
1342
|
});
|
|
1612
1343
|
} else report({
|
|
1613
|
-
messageId:
|
|
1344
|
+
messageId: ERROR_OBJ,
|
|
1614
1345
|
node,
|
|
1615
1346
|
filename
|
|
1616
1347
|
});
|
|
@@ -1618,97 +1349,92 @@ const rule$8 = {
|
|
|
1618
1349
|
}
|
|
1619
1350
|
}
|
|
1620
1351
|
};
|
|
1621
|
-
var
|
|
1352
|
+
var valid_stroke_style_default = rule$8;
|
|
1622
1353
|
|
|
1623
1354
|
//#endregion
|
|
1624
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1625
|
-
const
|
|
1626
|
-
const
|
|
1627
|
-
const
|
|
1628
|
-
const ERROR_INVALID_PROP$3 = "ERROR_INVALID_PROP";
|
|
1355
|
+
//#region src/lint/plugin-core/rules/valid-transition.ts
|
|
1356
|
+
const VALID_TRANSITION = "core/valid-transition";
|
|
1357
|
+
const ERROR$4 = "ERROR";
|
|
1358
|
+
const ERROR_INVALID_PROP$4 = "ERROR_INVALID_PROP";
|
|
1629
1359
|
const rule$7 = {
|
|
1630
1360
|
meta: {
|
|
1631
1361
|
messages: {
|
|
1632
|
-
[
|
|
1633
|
-
[
|
|
1634
|
-
[ERROR_INVALID_PROP$3]: "Unknown property {{ key }}."
|
|
1362
|
+
[ERROR$4]: `Missing required properties: ${new Intl.ListFormat("en-us", { type: "conjunction" }).format(TRANSITION_REQUIRED_PROPERTIES)}.`,
|
|
1363
|
+
[ERROR_INVALID_PROP$4]: "Unknown property: {{ key }}."
|
|
1635
1364
|
},
|
|
1636
1365
|
docs: {
|
|
1637
|
-
description: "Require
|
|
1638
|
-
url: docsLink(
|
|
1366
|
+
description: "Require transition tokens to follow the format.",
|
|
1367
|
+
url: docsLink(VALID_TRANSITION)
|
|
1639
1368
|
}
|
|
1640
1369
|
},
|
|
1641
1370
|
defaultOptions: {},
|
|
1642
1371
|
create({ tokens, report }) {
|
|
1643
1372
|
for (const t of Object.values(tokens)) {
|
|
1644
|
-
if (t.aliasOf || !t.originalValue || t.$type !== "
|
|
1645
|
-
|
|
1373
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "transition") continue;
|
|
1374
|
+
validateTransition(t.originalValue.$value, {
|
|
1646
1375
|
node: getObjMember(t.source.node, "$value"),
|
|
1647
1376
|
filename: t.source.filename
|
|
1648
1377
|
});
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
node: node.elements[i],
|
|
1663
|
-
filename
|
|
1664
|
-
});
|
|
1665
|
-
for (const key of Object.keys(stop)) if (!GRADIENT_REQUIRED_STOP_PROPERTIES.includes(key)) report({
|
|
1666
|
-
messageId: ERROR_INVALID_PROP$3,
|
|
1667
|
-
data: { key: JSON.stringify(key) },
|
|
1668
|
-
node: node.elements[i],
|
|
1669
|
-
filename
|
|
1670
|
-
});
|
|
1671
|
-
if ("position" in stop && typeof stop.position !== "number" && !isAlias(stop.position)) report({
|
|
1672
|
-
messageId: ERROR_POSITION,
|
|
1673
|
-
data: { value: stop.position },
|
|
1674
|
-
node: getObjMember(node.elements[i].value, "position"),
|
|
1675
|
-
filename
|
|
1676
|
-
});
|
|
1677
|
-
}
|
|
1678
|
-
else report({
|
|
1679
|
-
messageId: ERROR_MISSING$1,
|
|
1680
|
-
node,
|
|
1681
|
-
filename
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1378
|
+
}
|
|
1379
|
+
function validateTransition(value, { node, filename }) {
|
|
1380
|
+
if (!value || typeof value !== "object" || !TRANSITION_REQUIRED_PROPERTIES.every((property) => property in value)) report({
|
|
1381
|
+
messageId: ERROR$4,
|
|
1382
|
+
node,
|
|
1383
|
+
filename
|
|
1384
|
+
});
|
|
1385
|
+
else for (const key of Object.keys(value)) if (!TRANSITION_REQUIRED_PROPERTIES.includes(key)) report({
|
|
1386
|
+
messageId: ERROR_INVALID_PROP$4,
|
|
1387
|
+
data: { key: JSON.stringify(key) },
|
|
1388
|
+
node: getObjMember(node, key),
|
|
1389
|
+
filename
|
|
1390
|
+
});
|
|
1684
1391
|
}
|
|
1685
1392
|
}
|
|
1686
1393
|
};
|
|
1687
|
-
var
|
|
1394
|
+
var valid_transition_default = rule$7;
|
|
1688
1395
|
|
|
1689
1396
|
//#endregion
|
|
1690
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1691
|
-
const
|
|
1692
|
-
const ERROR$
|
|
1397
|
+
//#region src/lint/plugin-core/rules/valid-typography.ts
|
|
1398
|
+
const VALID_TYPOGRAPHY = "core/valid-typography";
|
|
1399
|
+
const ERROR$3 = "ERROR";
|
|
1400
|
+
const ERROR_MISSING = "ERROR_MISSING";
|
|
1693
1401
|
const rule$6 = {
|
|
1694
1402
|
meta: {
|
|
1695
|
-
messages: {
|
|
1403
|
+
messages: {
|
|
1404
|
+
[ERROR$3]: `Expected object, received {{ value }}.`,
|
|
1405
|
+
[ERROR_MISSING]: `Missing required property "{{ property }}".`
|
|
1406
|
+
},
|
|
1696
1407
|
docs: {
|
|
1697
|
-
description: "Require
|
|
1698
|
-
url: docsLink(
|
|
1408
|
+
description: "Require typography tokens to follow the format.",
|
|
1409
|
+
url: docsLink(VALID_TYPOGRAPHY)
|
|
1699
1410
|
}
|
|
1700
1411
|
},
|
|
1701
|
-
defaultOptions: {
|
|
1702
|
-
|
|
1412
|
+
defaultOptions: { requiredProperties: [
|
|
1413
|
+
"fontFamily",
|
|
1414
|
+
"fontSize",
|
|
1415
|
+
"fontWeight",
|
|
1416
|
+
"letterSpacing",
|
|
1417
|
+
"lineHeight"
|
|
1418
|
+
] },
|
|
1419
|
+
create({ tokens, options, report }) {
|
|
1420
|
+
const isIgnored = options.ignore ? wcmatch(options.ignore) : () => false;
|
|
1703
1421
|
for (const t of Object.values(tokens)) {
|
|
1704
|
-
if (t.aliasOf || !t.originalValue || t.$type !== "
|
|
1705
|
-
|
|
1422
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "typography" || isIgnored(t.id)) continue;
|
|
1423
|
+
validateTypography(t.originalValue.$value, {
|
|
1706
1424
|
node: getObjMember(t.source.node, "$value"),
|
|
1707
1425
|
filename: t.source.filename
|
|
1708
1426
|
});
|
|
1709
|
-
function
|
|
1710
|
-
if (
|
|
1711
|
-
|
|
1427
|
+
function validateTypography(value, { node, filename }) {
|
|
1428
|
+
if (value && typeof value === "object") {
|
|
1429
|
+
for (const property of options.requiredProperties) if (!(property in value)) report({
|
|
1430
|
+
messageId: ERROR_MISSING,
|
|
1431
|
+
data: { property },
|
|
1432
|
+
node,
|
|
1433
|
+
filename
|
|
1434
|
+
});
|
|
1435
|
+
} else report({
|
|
1436
|
+
messageId: ERROR$3,
|
|
1437
|
+
data: { value: JSON.stringify(value) },
|
|
1712
1438
|
node,
|
|
1713
1439
|
filename
|
|
1714
1440
|
});
|
|
@@ -1716,203 +1442,265 @@ const rule$6 = {
|
|
|
1716
1442
|
}
|
|
1717
1443
|
}
|
|
1718
1444
|
};
|
|
1719
|
-
var
|
|
1445
|
+
var valid_typography_default = rule$6;
|
|
1720
1446
|
|
|
1721
1447
|
//#endregion
|
|
1722
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1723
|
-
const
|
|
1724
|
-
const
|
|
1448
|
+
//#region src/lint/plugin-core/rules/valid-boolean.ts
|
|
1449
|
+
const VALID_BOOLEAN = "core/valid-boolean";
|
|
1450
|
+
const ERROR$2 = "ERROR";
|
|
1725
1451
|
const rule$5 = {
|
|
1726
1452
|
meta: {
|
|
1727
|
-
messages: { [
|
|
1453
|
+
messages: { [ERROR$2]: "Must be a boolean." },
|
|
1728
1454
|
docs: {
|
|
1729
|
-
description: "Require
|
|
1730
|
-
url: docsLink(
|
|
1455
|
+
description: "Require boolean tokens to follow the Terrazzo extension.",
|
|
1456
|
+
url: docsLink(VALID_BOOLEAN)
|
|
1731
1457
|
}
|
|
1732
1458
|
},
|
|
1733
1459
|
defaultOptions: {},
|
|
1734
1460
|
create({ tokens, report }) {
|
|
1735
1461
|
for (const t of Object.values(tokens)) {
|
|
1736
|
-
if (t.aliasOf || !t.originalValue) continue;
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
if (typeof t.originalValue.$value === "object") {
|
|
1747
|
-
if (t.originalValue.$value.lineHeight && !isAlias(t.originalValue.$value.lineHeight) && typeof t.originalValue.$value.lineHeight !== "object") validateNumber(t.originalValue.$value.lineHeight, {
|
|
1748
|
-
node: getObjMember($valueNode, "lineHeight"),
|
|
1749
|
-
filename: t.source.filename
|
|
1750
|
-
});
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
function validateNumber(value, { node, filename }) {
|
|
1755
|
-
if (typeof value !== "number" || Number.isNaN(value)) report({
|
|
1756
|
-
messageId: ERROR_NAN,
|
|
1757
|
-
node,
|
|
1758
|
-
filename
|
|
1462
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "boolean") continue;
|
|
1463
|
+
validateBoolean(t.originalValue.$value, {
|
|
1464
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1465
|
+
filename: t.source.filename
|
|
1466
|
+
});
|
|
1467
|
+
function validateBoolean(value, { node, filename }) {
|
|
1468
|
+
if (typeof value !== "boolean") report({
|
|
1469
|
+
messageId: ERROR$2,
|
|
1470
|
+
filename,
|
|
1471
|
+
node
|
|
1759
1472
|
});
|
|
1760
1473
|
}
|
|
1761
1474
|
}
|
|
1762
1475
|
}
|
|
1763
1476
|
};
|
|
1764
|
-
var
|
|
1477
|
+
var valid_boolean_default = rule$5;
|
|
1765
1478
|
|
|
1766
1479
|
//#endregion
|
|
1767
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1768
|
-
const
|
|
1769
|
-
const ERROR$
|
|
1770
|
-
const ERROR_INVALID_PROP$
|
|
1480
|
+
//#region src/lint/plugin-core/rules/valid-border.ts
|
|
1481
|
+
const VALID_BORDER = "core/valid-border";
|
|
1482
|
+
const ERROR$1 = "ERROR";
|
|
1483
|
+
const ERROR_INVALID_PROP$3 = "ERROR_INVALID_PROP";
|
|
1771
1484
|
const rule$4 = {
|
|
1772
1485
|
meta: {
|
|
1773
1486
|
messages: {
|
|
1774
|
-
[ERROR$
|
|
1775
|
-
[ERROR_INVALID_PROP$
|
|
1487
|
+
[ERROR$1]: `Border token missing required properties: ${new Intl.ListFormat("en-us", { type: "conjunction" }).format(BORDER_REQUIRED_PROPERTIES)}.`,
|
|
1488
|
+
[ERROR_INVALID_PROP$3]: "Unknown property: {{ key }}."
|
|
1776
1489
|
},
|
|
1777
1490
|
docs: {
|
|
1778
|
-
description: "Require
|
|
1779
|
-
url: docsLink(
|
|
1491
|
+
description: "Require border tokens to follow the format.",
|
|
1492
|
+
url: docsLink(VALID_BORDER)
|
|
1780
1493
|
}
|
|
1781
1494
|
},
|
|
1782
1495
|
defaultOptions: {},
|
|
1783
1496
|
create({ tokens, report }) {
|
|
1784
1497
|
for (const t of Object.values(tokens)) {
|
|
1785
|
-
if (t.aliasOf || !t.originalValue || t.$type !== "
|
|
1786
|
-
|
|
1498
|
+
if (t.aliasOf || !t.originalValue || t.$type !== "border") continue;
|
|
1499
|
+
validateBorder(t.originalValue.$value, {
|
|
1787
1500
|
node: getObjMember(t.source.node, "$value"),
|
|
1788
1501
|
filename: t.source.filename
|
|
1789
1502
|
});
|
|
1790
|
-
function validateShadow(value, { node, filename }) {
|
|
1791
|
-
const wrappedValue = Array.isArray(value) ? value : [value];
|
|
1792
|
-
for (let i = 0; i < wrappedValue.length; i++) if (!wrappedValue[i] || typeof wrappedValue[i] !== "object" || !SHADOW_REQUIRED_PROPERTIES.every((property) => property in wrappedValue[i])) report({
|
|
1793
|
-
messageId: ERROR$3,
|
|
1794
|
-
node,
|
|
1795
|
-
filename
|
|
1796
|
-
});
|
|
1797
|
-
else for (const key of Object.keys(wrappedValue[i])) if (![...SHADOW_REQUIRED_PROPERTIES, "inset"].includes(key)) report({
|
|
1798
|
-
messageId: ERROR_INVALID_PROP$2,
|
|
1799
|
-
data: { key: JSON.stringify(key) },
|
|
1800
|
-
node: getObjMember(node.type === "Array" ? node.elements[i].value : node, key),
|
|
1801
|
-
filename
|
|
1802
|
-
});
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
};
|
|
1807
|
-
var valid_shadow_default = rule$4;
|
|
1808
|
-
|
|
1809
|
-
//#endregion
|
|
1810
|
-
//#region src/lint/plugin-core/rules/valid-string.ts
|
|
1811
|
-
const VALID_STRING = "core/valid-string";
|
|
1812
|
-
const ERROR$2 = "ERROR";
|
|
1813
|
-
const rule$3 = {
|
|
1814
|
-
meta: {
|
|
1815
|
-
messages: { [ERROR$2]: "Must be a string." },
|
|
1816
|
-
docs: {
|
|
1817
|
-
description: "Require string tokens to follow the Terrazzo extension.",
|
|
1818
|
-
url: docsLink(VALID_STRING)
|
|
1819
1503
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1504
|
+
function validateBorder(value, { node, filename }) {
|
|
1505
|
+
if (!value || typeof value !== "object" || !BORDER_REQUIRED_PROPERTIES.every((property) => property in value)) report({
|
|
1506
|
+
messageId: ERROR$1,
|
|
1507
|
+
filename,
|
|
1508
|
+
node
|
|
1509
|
+
});
|
|
1510
|
+
else for (const key of Object.keys(value)) if (!BORDER_REQUIRED_PROPERTIES.includes(key)) report({
|
|
1511
|
+
messageId: ERROR_INVALID_PROP$3,
|
|
1512
|
+
data: { key: JSON.stringify(key) },
|
|
1513
|
+
node: getObjMember(node, key) ?? node,
|
|
1514
|
+
filename
|
|
1828
1515
|
});
|
|
1829
|
-
function validateString(value, { node, filename }) {
|
|
1830
|
-
if (typeof value !== "string") report({
|
|
1831
|
-
messageId: ERROR$2,
|
|
1832
|
-
node,
|
|
1833
|
-
filename
|
|
1834
|
-
});
|
|
1835
|
-
}
|
|
1836
1516
|
}
|
|
1837
1517
|
}
|
|
1838
1518
|
};
|
|
1839
|
-
var
|
|
1519
|
+
var valid_border_default = rule$4;
|
|
1840
1520
|
|
|
1841
1521
|
//#endregion
|
|
1842
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1843
|
-
const
|
|
1844
|
-
const
|
|
1845
|
-
const
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
1848
|
-
const
|
|
1522
|
+
//#region src/lint/plugin-core/rules/valid-color.ts
|
|
1523
|
+
const VALID_COLOR = "core/valid-color";
|
|
1524
|
+
const ERROR_ALPHA = "ERROR_ALPHA";
|
|
1525
|
+
const ERROR_INVALID_COLOR = "ERROR_INVALID_COLOR";
|
|
1526
|
+
const ERROR_INVALID_COLOR_SPACE = "ERROR_INVALID_COLOR_SPACE";
|
|
1527
|
+
const ERROR_INVALID_COMPONENT_LENGTH = "ERROR_INVALID_COMPONENT_LENGTH";
|
|
1528
|
+
const ERROR_INVALID_HEX8 = "ERROR_INVALID_HEX8";
|
|
1529
|
+
const ERROR_INVALID_PROP$2 = "ERROR_INVALID_PROP";
|
|
1530
|
+
const ERROR_MISSING_COMPONENTS = "ERROR_MISSING_COMPONENTS";
|
|
1531
|
+
const ERROR_OBJ_FORMAT = "ERROR_OBJ_FORMAT";
|
|
1532
|
+
const ERROR_OUT_OF_RANGE = "ERROR_OUT_OF_RANGE";
|
|
1533
|
+
const rule$3 = {
|
|
1849
1534
|
meta: {
|
|
1850
1535
|
messages: {
|
|
1851
|
-
[
|
|
1852
|
-
[
|
|
1853
|
-
[
|
|
1854
|
-
[
|
|
1536
|
+
[ERROR_ALPHA]: `Alpha {{ alpha }} not in range 0 – 1.`,
|
|
1537
|
+
[ERROR_INVALID_COLOR_SPACE]: `Invalid color space: {{ colorSpace }}. Expected ${new Intl.ListFormat("en-us", { type: "disjunction" }).format(Object.keys(COLORSPACE))}`,
|
|
1538
|
+
[ERROR_INVALID_COLOR]: `Could not parse color {{ color }}.`,
|
|
1539
|
+
[ERROR_INVALID_COMPONENT_LENGTH]: "Expected {{ expected }} components, received {{ got }}.",
|
|
1540
|
+
[ERROR_INVALID_HEX8]: `Hex value can’t be semi-transparent.`,
|
|
1541
|
+
[ERROR_INVALID_PROP$2]: `Unknown property {{ key }}.`,
|
|
1542
|
+
[ERROR_MISSING_COMPONENTS]: "Expected components to be array of numbers, received {{ got }}.",
|
|
1543
|
+
[ERROR_OBJ_FORMAT]: "Migrate to the new object format, e.g. \"#ff0000\" → { \"colorSpace\": \"srgb\", \"components\": [1, 0, 0] } }",
|
|
1544
|
+
[ERROR_OUT_OF_RANGE]: `Invalid range for color space {{ colorSpace }}. Expected {{ range }}.`
|
|
1855
1545
|
},
|
|
1856
1546
|
docs: {
|
|
1857
|
-
description: "Require
|
|
1858
|
-
url: docsLink(
|
|
1547
|
+
description: "Require color tokens to follow the format.",
|
|
1548
|
+
url: docsLink(VALID_COLOR)
|
|
1859
1549
|
}
|
|
1860
1550
|
},
|
|
1861
|
-
defaultOptions: {
|
|
1862
|
-
|
|
1551
|
+
defaultOptions: {
|
|
1552
|
+
legacyFormat: false,
|
|
1553
|
+
ignoreRanges: false
|
|
1554
|
+
},
|
|
1555
|
+
create({ tokens, options, report }) {
|
|
1863
1556
|
for (const t of Object.values(tokens)) {
|
|
1864
1557
|
if (t.aliasOf || !t.originalValue) continue;
|
|
1865
1558
|
switch (t.$type) {
|
|
1866
|
-
case "
|
|
1867
|
-
|
|
1559
|
+
case "color":
|
|
1560
|
+
validateColor(t.originalValue.$value, {
|
|
1868
1561
|
node: getObjMember(t.source.node, "$value"),
|
|
1869
1562
|
filename: t.source.filename
|
|
1870
1563
|
});
|
|
1871
1564
|
break;
|
|
1872
1565
|
case "border":
|
|
1873
|
-
if (t.originalValue.$value &&
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1566
|
+
if (t.originalValue.$value.color && !isAlias(t.originalValue.$value.color)) validateColor(t.originalValue.$value.color, {
|
|
1567
|
+
node: getObjMember(getObjMember(t.source.node, "$value"), "color"),
|
|
1568
|
+
filename: t.source.filename
|
|
1569
|
+
});
|
|
1570
|
+
break;
|
|
1571
|
+
case "gradient": {
|
|
1572
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1573
|
+
for (let i = 0; i < t.originalValue.$value.length; i++) {
|
|
1574
|
+
const stop = t.originalValue.$value[i];
|
|
1575
|
+
if (!stop.color || isAlias(stop.color)) continue;
|
|
1576
|
+
validateColor(stop.color, {
|
|
1577
|
+
node: getObjMember($valueNode.elements[i].value, "color"),
|
|
1578
|
+
filename: t.source.filename
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
case "shadow": {
|
|
1584
|
+
const $value = Array.isArray(t.originalValue.$value) ? t.originalValue.$value : [t.originalValue.$value];
|
|
1585
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1586
|
+
for (let i = 0; i < $value.length; i++) {
|
|
1587
|
+
const layer = $value[i];
|
|
1588
|
+
if (!layer.color || isAlias(layer.color)) continue;
|
|
1589
|
+
validateColor(layer.color, {
|
|
1590
|
+
node: $valueNode.type === "Object" ? getObjMember($valueNode, "color") : getObjMember($valueNode.elements[i].value, "color"),
|
|
1591
|
+
filename: t.source.filename
|
|
1592
|
+
});
|
|
1879
1593
|
}
|
|
1880
1594
|
break;
|
|
1595
|
+
}
|
|
1881
1596
|
}
|
|
1882
|
-
function
|
|
1883
|
-
if (
|
|
1884
|
-
|
|
1597
|
+
function validateColor(value, { node, filename }) {
|
|
1598
|
+
if (!value) report({
|
|
1599
|
+
messageId: ERROR_INVALID_COLOR,
|
|
1600
|
+
data: { color: JSON.stringify(value) },
|
|
1601
|
+
node,
|
|
1602
|
+
filename
|
|
1603
|
+
});
|
|
1604
|
+
else if (typeof value === "object") {
|
|
1605
|
+
for (const key of Object.keys(value)) if (![
|
|
1606
|
+
"colorSpace",
|
|
1607
|
+
"components",
|
|
1608
|
+
"channels",
|
|
1609
|
+
"hex",
|
|
1610
|
+
"alpha"
|
|
1611
|
+
].includes(key)) report({
|
|
1612
|
+
messageId: ERROR_INVALID_PROP$2,
|
|
1613
|
+
data: { key: JSON.stringify(key) },
|
|
1614
|
+
node: getObjMember(node, key) ?? node,
|
|
1615
|
+
filename
|
|
1616
|
+
});
|
|
1617
|
+
const colorSpace = "colorSpace" in value && typeof value.colorSpace === "string" ? value.colorSpace : void 0;
|
|
1618
|
+
const csData = COLORSPACE[colorSpace] || void 0;
|
|
1619
|
+
if (!("colorSpace" in value) || !csData) {
|
|
1885
1620
|
report({
|
|
1886
|
-
messageId:
|
|
1887
|
-
|
|
1621
|
+
messageId: ERROR_INVALID_COLOR_SPACE,
|
|
1622
|
+
data: { colorSpace },
|
|
1623
|
+
node: getObjMember(node, "colorSpace") ?? node,
|
|
1888
1624
|
filename
|
|
1889
1625
|
});
|
|
1890
1626
|
return;
|
|
1891
1627
|
}
|
|
1892
|
-
|
|
1893
|
-
if (
|
|
1894
|
-
|
|
1895
|
-
|
|
1628
|
+
const components = "components" in value ? value.components : void 0;
|
|
1629
|
+
if (Array.isArray(components)) if (csData?.ranges && components?.length === csData.ranges.length) {
|
|
1630
|
+
for (let i = 0; i < components.length; i++) if (!Number.isFinite(components[i]) || components[i] < csData.ranges[i][0] || components[i] > csData.ranges[i][1]) {
|
|
1631
|
+
if (!(colorSpace === "hsl" && components[0] === null) && !(colorSpace === "hwb" && components[0] === null) && !(colorSpace === "lch" && components[2] === null) && !(colorSpace === "oklch" && components[2] === null)) report({
|
|
1632
|
+
messageId: ERROR_OUT_OF_RANGE,
|
|
1633
|
+
data: {
|
|
1634
|
+
colorSpace,
|
|
1635
|
+
range: `[${csData.ranges.map((r) => `${r[0]}–${r[1]}`).join(", ")}]`
|
|
1636
|
+
},
|
|
1637
|
+
node: getObjMember(node, "components") ?? node,
|
|
1638
|
+
filename
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
} else report({
|
|
1642
|
+
messageId: ERROR_INVALID_COMPONENT_LENGTH,
|
|
1643
|
+
data: {
|
|
1644
|
+
expected: csData?.ranges.length,
|
|
1645
|
+
got: components?.length ?? 0
|
|
1646
|
+
},
|
|
1647
|
+
node: getObjMember(node, "components") ?? node,
|
|
1896
1648
|
filename
|
|
1897
1649
|
});
|
|
1898
|
-
|
|
1899
|
-
messageId:
|
|
1900
|
-
|
|
1650
|
+
else report({
|
|
1651
|
+
messageId: ERROR_MISSING_COMPONENTS,
|
|
1652
|
+
data: { got: JSON.stringify(components) },
|
|
1653
|
+
node: getObjMember(node, "components") ?? node,
|
|
1901
1654
|
filename
|
|
1902
1655
|
});
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1656
|
+
const alpha = "alpha" in value ? value.alpha : void 0;
|
|
1657
|
+
if (alpha !== void 0 && (typeof alpha !== "number" || alpha < 0 || alpha > 1)) report({
|
|
1658
|
+
messageId: ERROR_ALPHA,
|
|
1659
|
+
data: { alpha },
|
|
1660
|
+
node: getObjMember(node, "alpha") ?? node,
|
|
1906
1661
|
filename
|
|
1907
1662
|
});
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1663
|
+
const hex = "hex" in value ? value.hex : void 0;
|
|
1664
|
+
if (hex) {
|
|
1665
|
+
let color;
|
|
1666
|
+
try {
|
|
1667
|
+
color = parseColor(hex);
|
|
1668
|
+
} catch {
|
|
1669
|
+
report({
|
|
1670
|
+
messageId: ERROR_INVALID_COLOR,
|
|
1671
|
+
data: { color: hex },
|
|
1672
|
+
node: getObjMember(node, "hex") ?? node,
|
|
1673
|
+
filename
|
|
1674
|
+
});
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
if (color.alpha !== 1) report({
|
|
1678
|
+
messageId: ERROR_INVALID_HEX8,
|
|
1679
|
+
data: { color: hex },
|
|
1680
|
+
node: getObjMember(node, "hex") ?? node,
|
|
1681
|
+
filename
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
} else if (typeof value === "string") {
|
|
1685
|
+
if (isAlias(value)) return;
|
|
1686
|
+
if (!options.legacyFormat) report({
|
|
1687
|
+
messageId: ERROR_OBJ_FORMAT,
|
|
1688
|
+
data: { color: JSON.stringify(value) },
|
|
1689
|
+
node,
|
|
1912
1690
|
filename
|
|
1913
1691
|
});
|
|
1692
|
+
else try {
|
|
1693
|
+
parseColor(value);
|
|
1694
|
+
} catch {
|
|
1695
|
+
report({
|
|
1696
|
+
messageId: ERROR_INVALID_COLOR,
|
|
1697
|
+
data: { color: JSON.stringify(value) },
|
|
1698
|
+
node,
|
|
1699
|
+
filename
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1914
1702
|
} else report({
|
|
1915
|
-
messageId:
|
|
1703
|
+
messageId: ERROR_INVALID_COLOR,
|
|
1916
1704
|
node,
|
|
1917
1705
|
filename
|
|
1918
1706
|
});
|
|
@@ -1920,92 +1708,319 @@ const rule$2 = {
|
|
|
1920
1708
|
}
|
|
1921
1709
|
}
|
|
1922
1710
|
};
|
|
1923
|
-
var
|
|
1711
|
+
var valid_color_default = rule$3;
|
|
1924
1712
|
|
|
1925
1713
|
//#endregion
|
|
1926
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1927
|
-
const
|
|
1928
|
-
const ERROR
|
|
1929
|
-
const
|
|
1930
|
-
const
|
|
1714
|
+
//#region src/lint/plugin-core/rules/valid-cubic-bezier.ts
|
|
1715
|
+
const VALID_CUBIC_BEZIER = "core/valid-cubic-bezier";
|
|
1716
|
+
const ERROR = "ERROR";
|
|
1717
|
+
const ERROR_X = "ERROR_X";
|
|
1718
|
+
const ERROR_Y = "ERROR_Y";
|
|
1719
|
+
const rule$2 = {
|
|
1931
1720
|
meta: {
|
|
1932
1721
|
messages: {
|
|
1933
|
-
[ERROR
|
|
1934
|
-
[
|
|
1722
|
+
[ERROR]: "Expected [number, number, number, number].",
|
|
1723
|
+
[ERROR_X]: "x values must be between 0-1.",
|
|
1724
|
+
[ERROR_Y]: "y values must be a valid number."
|
|
1935
1725
|
},
|
|
1936
1726
|
docs: {
|
|
1937
|
-
description: "Require
|
|
1938
|
-
url: docsLink(
|
|
1727
|
+
description: "Require cubicBezier tokens to follow the format.",
|
|
1728
|
+
url: docsLink(VALID_CUBIC_BEZIER)
|
|
1939
1729
|
}
|
|
1940
1730
|
},
|
|
1941
1731
|
defaultOptions: {},
|
|
1942
1732
|
create({ tokens, report }) {
|
|
1943
1733
|
for (const t of Object.values(tokens)) {
|
|
1944
|
-
if (t.aliasOf || !t.originalValue
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1734
|
+
if (t.aliasOf || !t.originalValue) continue;
|
|
1735
|
+
switch (t.$type) {
|
|
1736
|
+
case "cubicBezier":
|
|
1737
|
+
validateCubicBezier(t.originalValue.$value, {
|
|
1738
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1739
|
+
filename: t.source.filename
|
|
1740
|
+
});
|
|
1741
|
+
break;
|
|
1742
|
+
case "transition": if (typeof t.originalValue.$value === "object" && t.originalValue.$value.timingFunction && !isAlias(t.originalValue.$value.timingFunction)) {
|
|
1743
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1744
|
+
validateCubicBezier(t.originalValue.$value.timingFunction, {
|
|
1745
|
+
node: getObjMember($valueNode, "timingFunction"),
|
|
1746
|
+
filename: t.source.filename
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
function validateCubicBezier(value, { node, filename }) {
|
|
1751
|
+
if (Array.isArray(value) && value.length === 4) {
|
|
1752
|
+
for (const pos of [0, 2]) {
|
|
1753
|
+
if (isAlias(value[pos]) || isPure$ref(value[pos])) continue;
|
|
1754
|
+
if (!(value[pos] >= 0 && value[pos] <= 1)) report({
|
|
1755
|
+
messageId: ERROR_X,
|
|
1756
|
+
node: node.elements[pos],
|
|
1757
|
+
filename
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
for (const pos of [1, 3]) {
|
|
1761
|
+
if (isAlias(value[pos]) || isPure$ref(value[pos])) continue;
|
|
1762
|
+
if (typeof value[pos] !== "number") report({
|
|
1763
|
+
messageId: ERROR_Y,
|
|
1764
|
+
node: node.elements[pos],
|
|
1765
|
+
filename
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
} else report({
|
|
1769
|
+
messageId: ERROR,
|
|
1770
|
+
node,
|
|
1771
|
+
filename
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1962
1774
|
}
|
|
1963
1775
|
}
|
|
1964
1776
|
};
|
|
1965
|
-
var
|
|
1777
|
+
var valid_cubic_bezier_default = rule$2;
|
|
1966
1778
|
|
|
1967
1779
|
//#endregion
|
|
1968
|
-
//#region src/lint/plugin-core/rules/valid-
|
|
1969
|
-
const
|
|
1970
|
-
const
|
|
1971
|
-
const
|
|
1972
|
-
const
|
|
1780
|
+
//#region src/lint/plugin-core/rules/valid-dimension.ts
|
|
1781
|
+
const VALID_DIMENSION = "core/valid-dimension";
|
|
1782
|
+
const ERROR_FORMAT$1 = "ERROR_FORMAT";
|
|
1783
|
+
const ERROR_INVALID_PROP$1 = "ERROR_INVALID_PROP";
|
|
1784
|
+
const ERROR_LEGACY$1 = "ERROR_LEGACY";
|
|
1785
|
+
const ERROR_UNIT$1 = "ERROR_UNIT";
|
|
1786
|
+
const ERROR_VALUE$1 = "ERROR_VALUE";
|
|
1787
|
+
const rule$1 = {
|
|
1973
1788
|
meta: {
|
|
1974
1789
|
messages: {
|
|
1975
|
-
[
|
|
1976
|
-
[
|
|
1790
|
+
[ERROR_FORMAT$1]: "Invalid dimension: {{ value }}. Expected object with \"value\" and \"unit\".",
|
|
1791
|
+
[ERROR_LEGACY$1]: "Migrate to the new object format: { \"value\": 10, \"unit\": \"px\" }.",
|
|
1792
|
+
[ERROR_UNIT$1]: "Unit {{ unit }} not allowed. Expected {{ allowed }}.",
|
|
1793
|
+
[ERROR_INVALID_PROP$1]: "Unknown property {{ key }}.",
|
|
1794
|
+
[ERROR_VALUE$1]: "Expected number, received {{ value }}."
|
|
1977
1795
|
},
|
|
1978
1796
|
docs: {
|
|
1979
|
-
description: "Require
|
|
1980
|
-
url: docsLink(
|
|
1797
|
+
description: "Require dimension tokens to follow the format",
|
|
1798
|
+
url: docsLink(VALID_DIMENSION)
|
|
1981
1799
|
}
|
|
1982
1800
|
},
|
|
1983
|
-
defaultOptions: {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1801
|
+
defaultOptions: {
|
|
1802
|
+
legacyFormat: false,
|
|
1803
|
+
allowedUnits: [
|
|
1804
|
+
"px",
|
|
1805
|
+
"em",
|
|
1806
|
+
"rem"
|
|
1807
|
+
]
|
|
1808
|
+
},
|
|
1990
1809
|
create({ tokens, options, report }) {
|
|
1991
|
-
const isIgnored = options.ignore ? wcmatch(options.ignore) : () => false;
|
|
1992
1810
|
for (const t of Object.values(tokens)) {
|
|
1993
|
-
if (t.aliasOf || !t.originalValue
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1811
|
+
if (t.aliasOf || !t.originalValue) continue;
|
|
1812
|
+
switch (t.$type) {
|
|
1813
|
+
case "dimension":
|
|
1814
|
+
validateDimension(t.originalValue.$value, {
|
|
1815
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1816
|
+
filename: t.source.filename
|
|
1817
|
+
});
|
|
1818
|
+
break;
|
|
1819
|
+
case "strokeStyle":
|
|
1820
|
+
if (typeof t.originalValue.$value === "object" && Array.isArray(t.originalValue.$value.dashArray)) {
|
|
1821
|
+
const dashArray = getObjMember(getObjMember(t.source.node, "$value"), "dashArray");
|
|
1822
|
+
for (let i = 0; i < t.originalValue.$value.dashArray.length; i++) {
|
|
1823
|
+
if (isAlias(t.originalValue.$value.dashArray[i])) continue;
|
|
1824
|
+
validateDimension(t.originalValue.$value.dashArray[i], {
|
|
1825
|
+
node: dashArray.elements[i].value,
|
|
1826
|
+
filename: t.source.filename
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
break;
|
|
1831
|
+
case "border": {
|
|
1832
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1833
|
+
if (typeof t.originalValue.$value === "object") {
|
|
1834
|
+
if (t.originalValue.$value.width && !isAlias(t.originalValue.$value.width)) validateDimension(t.originalValue.$value.width, {
|
|
1835
|
+
node: getObjMember($valueNode, "width"),
|
|
1836
|
+
filename: t.source.filename
|
|
1837
|
+
});
|
|
1838
|
+
if (typeof t.originalValue.$value.style === "object" && Array.isArray(t.originalValue.$value.style.dashArray)) {
|
|
1839
|
+
const dashArray = getObjMember(getObjMember($valueNode, "style"), "dashArray");
|
|
1840
|
+
for (let i = 0; i < t.originalValue.$value.style.dashArray.length; i++) {
|
|
1841
|
+
if (isAlias(t.originalValue.$value.style.dashArray[i])) continue;
|
|
1842
|
+
validateDimension(t.originalValue.$value.style.dashArray[i], {
|
|
1843
|
+
node: dashArray.elements[i].value,
|
|
1844
|
+
filename: t.source.filename
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
case "shadow":
|
|
1852
|
+
if (t.originalValue.$value && typeof t.originalValue.$value === "object") {
|
|
1853
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1854
|
+
const valueArray = Array.isArray(t.originalValue.$value) ? t.originalValue.$value : [t.originalValue.$value];
|
|
1855
|
+
for (let i = 0; i < valueArray.length; i++) {
|
|
1856
|
+
const node = $valueNode.type === "Array" ? $valueNode.elements[i].value : $valueNode;
|
|
1857
|
+
for (const property of [
|
|
1858
|
+
"offsetX",
|
|
1859
|
+
"offsetY",
|
|
1860
|
+
"blur",
|
|
1861
|
+
"spread"
|
|
1862
|
+
]) {
|
|
1863
|
+
if (isAlias(valueArray[i][property])) continue;
|
|
1864
|
+
validateDimension(valueArray[i][property], {
|
|
1865
|
+
node: getObjMember(node, property),
|
|
1866
|
+
filename: t.source.filename
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
break;
|
|
1872
|
+
case "typography": {
|
|
1873
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1874
|
+
if (typeof t.originalValue.$value === "object") {
|
|
1875
|
+
for (const property of [
|
|
1876
|
+
"fontSize",
|
|
1877
|
+
"lineHeight",
|
|
1878
|
+
"letterSpacing"
|
|
1879
|
+
]) if (property in t.originalValue.$value) {
|
|
1880
|
+
if (isAlias(t.originalValue.$value[property]) || property === "lineHeight" && typeof t.originalValue.$value[property] === "number") continue;
|
|
1881
|
+
validateDimension(t.originalValue.$value[property], {
|
|
1882
|
+
node: getObjMember($valueNode, property),
|
|
1883
|
+
filename: t.source.filename
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
break;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
function validateDimension(value, { node, filename }) {
|
|
1999
1891
|
if (value && typeof value === "object") {
|
|
2000
|
-
for (const
|
|
2001
|
-
messageId:
|
|
2002
|
-
data: {
|
|
2003
|
-
node,
|
|
1892
|
+
for (const key of Object.keys(value)) if (!["value", "unit"].includes(key)) report({
|
|
1893
|
+
messageId: ERROR_INVALID_PROP$1,
|
|
1894
|
+
data: { key: JSON.stringify(key) },
|
|
1895
|
+
node: getObjMember(node, key) ?? node,
|
|
2004
1896
|
filename
|
|
2005
1897
|
});
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
1898
|
+
const { unit, value: numValue } = value;
|
|
1899
|
+
if (!("value" in value || "unit" in value)) {
|
|
1900
|
+
report({
|
|
1901
|
+
messageId: ERROR_FORMAT$1,
|
|
1902
|
+
data: { value },
|
|
1903
|
+
node,
|
|
1904
|
+
filename
|
|
1905
|
+
});
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (!options.allowedUnits.includes(unit)) report({
|
|
1909
|
+
messageId: ERROR_UNIT$1,
|
|
1910
|
+
data: {
|
|
1911
|
+
unit,
|
|
1912
|
+
allowed: new Intl.ListFormat("en-us", { type: "disjunction" }).format(options.allowedUnits)
|
|
1913
|
+
},
|
|
1914
|
+
node: getObjMember(node, "unit") ?? node,
|
|
1915
|
+
filename
|
|
1916
|
+
});
|
|
1917
|
+
if (!Number.isFinite(numValue)) report({
|
|
1918
|
+
messageId: ERROR_VALUE$1,
|
|
1919
|
+
data: { value },
|
|
1920
|
+
node: getObjMember(node, "value") ?? node,
|
|
1921
|
+
filename
|
|
1922
|
+
});
|
|
1923
|
+
} else if (typeof value === "string" && !options.legacyFormat) report({
|
|
1924
|
+
messageId: ERROR_LEGACY$1,
|
|
1925
|
+
node,
|
|
1926
|
+
filename
|
|
1927
|
+
});
|
|
1928
|
+
else report({
|
|
1929
|
+
messageId: ERROR_FORMAT$1,
|
|
1930
|
+
data: { value },
|
|
1931
|
+
node,
|
|
1932
|
+
filename
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
var valid_dimension_default = rule$1;
|
|
1939
|
+
|
|
1940
|
+
//#endregion
|
|
1941
|
+
//#region src/lint/plugin-core/rules/valid-duration.ts
|
|
1942
|
+
const VALID_DURATION = "core/valid-duration";
|
|
1943
|
+
const ERROR_FORMAT = "ERROR_FORMAT";
|
|
1944
|
+
const ERROR_INVALID_PROP = "ERROR_INVALID_PROP";
|
|
1945
|
+
const ERROR_LEGACY = "ERROR_LEGACY";
|
|
1946
|
+
const ERROR_UNIT = "ERROR_UNIT";
|
|
1947
|
+
const ERROR_VALUE = "ERROR_VALUE";
|
|
1948
|
+
const rule = {
|
|
1949
|
+
meta: {
|
|
1950
|
+
messages: {
|
|
1951
|
+
[ERROR_FORMAT]: "Migrate to the new object format: { \"value\": 2, \"unit\": \"ms\" }.",
|
|
1952
|
+
[ERROR_LEGACY]: "Migrate to the new object format: { \"value\": 10, \"unit\": \"px\" }.",
|
|
1953
|
+
[ERROR_INVALID_PROP]: "Unknown property: {{ key }}.",
|
|
1954
|
+
[ERROR_UNIT]: "Unknown unit {{ unit }}. Expected \"ms\" or \"s\".",
|
|
1955
|
+
[ERROR_VALUE]: "Expected number, received {{ value }}."
|
|
1956
|
+
},
|
|
1957
|
+
docs: {
|
|
1958
|
+
description: "Require duration tokens to follow the format",
|
|
1959
|
+
url: docsLink(VALID_DURATION)
|
|
1960
|
+
}
|
|
1961
|
+
},
|
|
1962
|
+
defaultOptions: {
|
|
1963
|
+
legacyFormat: false,
|
|
1964
|
+
unknownUnits: false
|
|
1965
|
+
},
|
|
1966
|
+
create({ tokens, options, report }) {
|
|
1967
|
+
for (const t of Object.values(tokens)) {
|
|
1968
|
+
if (t.aliasOf || !t.originalValue) continue;
|
|
1969
|
+
switch (t.$type) {
|
|
1970
|
+
case "duration":
|
|
1971
|
+
validateDuration(t.originalValue.$value, {
|
|
1972
|
+
node: getObjMember(t.source.node, "$value"),
|
|
1973
|
+
filename: t.source.filename
|
|
1974
|
+
});
|
|
1975
|
+
break;
|
|
1976
|
+
case "transition":
|
|
1977
|
+
if (typeof t.originalValue.$value === "object") {
|
|
1978
|
+
const $valueNode = getObjMember(t.source.node, "$value");
|
|
1979
|
+
for (const property of ["duration", "delay"]) if (t.originalValue.$value[property] && !isAlias(t.originalValue.$value[property])) validateDuration(t.originalValue.$value[property], {
|
|
1980
|
+
node: getObjMember($valueNode, property),
|
|
1981
|
+
filename: t.source.filename
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
break;
|
|
1985
|
+
}
|
|
1986
|
+
function validateDuration(value, { node, filename }) {
|
|
1987
|
+
if (value && typeof value === "object") {
|
|
1988
|
+
for (const key of Object.keys(value)) if (!["value", "unit"].includes(key)) report({
|
|
1989
|
+
messageId: ERROR_INVALID_PROP,
|
|
1990
|
+
data: { key: JSON.stringify(key) },
|
|
1991
|
+
node: getObjMember(node, key) ?? node,
|
|
1992
|
+
filename
|
|
1993
|
+
});
|
|
1994
|
+
const { unit, value: numValue } = value;
|
|
1995
|
+
if (!("value" in value || "unit" in value)) {
|
|
1996
|
+
report({
|
|
1997
|
+
messageId: ERROR_FORMAT,
|
|
1998
|
+
data: { value },
|
|
1999
|
+
node,
|
|
2000
|
+
filename
|
|
2001
|
+
});
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
if (!options.unknownUnits && !["ms", "s"].includes(unit)) report({
|
|
2005
|
+
messageId: ERROR_UNIT,
|
|
2006
|
+
data: { unit },
|
|
2007
|
+
node: getObjMember(node, "unit") ?? node,
|
|
2008
|
+
filename
|
|
2009
|
+
});
|
|
2010
|
+
if (!Number.isFinite(numValue)) report({
|
|
2011
|
+
messageId: ERROR_VALUE,
|
|
2012
|
+
data: { value },
|
|
2013
|
+
node: getObjMember(node, "value") ?? node,
|
|
2014
|
+
filename
|
|
2015
|
+
});
|
|
2016
|
+
} else if (typeof value === "string" && !options.legacyFormat) report({
|
|
2017
|
+
messageId: ERROR_FORMAT,
|
|
2018
|
+
node,
|
|
2019
|
+
filename
|
|
2020
|
+
});
|
|
2021
|
+
else report({
|
|
2022
|
+
messageId: ERROR_FORMAT,
|
|
2023
|
+
data: { value },
|
|
2009
2024
|
node,
|
|
2010
2025
|
filename
|
|
2011
2026
|
});
|
|
@@ -2013,7 +2028,7 @@ const rule = {
|
|
|
2013
2028
|
}
|
|
2014
2029
|
}
|
|
2015
2030
|
};
|
|
2016
|
-
var
|
|
2031
|
+
var valid_duration_default = rule;
|
|
2017
2032
|
|
|
2018
2033
|
//#endregion
|
|
2019
2034
|
//#region src/lint/plugin-core/index.ts
|
|
@@ -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,1179 +2439,1207 @@ 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/
|
|
2449
|
+
//#region src/parse/assert.ts
|
|
2450
|
+
function assert(value, logger, entry) {
|
|
2451
|
+
if (!value) logger.error(entry);
|
|
2452
|
+
}
|
|
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
|
|
2435
2462
|
/**
|
|
2436
|
-
*
|
|
2437
|
-
*
|
|
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.
|
|
2463
|
+
* Normalize token value.
|
|
2464
|
+
* The reason for the “any” typing is this aligns various user-provided inputs to the type
|
|
2442
2465
|
*/
|
|
2443
|
-
function
|
|
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;
|
|
2469
|
-
}
|
|
2470
|
-
const MESSAGE_EXPECTED = {
|
|
2471
|
-
STRING: "Expected string.",
|
|
2472
|
-
OBJECT: "Expected object.",
|
|
2473
|
-
ARRAY: "Expected array."
|
|
2474
|
-
};
|
|
2475
|
-
/**
|
|
2476
|
-
* Validate a resolver document.
|
|
2477
|
-
* There’s a ton of boilerplate here, only to surface detailed code frames. Is there a better abstraction?
|
|
2478
|
-
*/
|
|
2479
|
-
function validateResolver(node, { logger, src }) {
|
|
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
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
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;
|
|
2500
|
+
}
|
|
2501
|
+
return value;
|
|
2502
|
+
}
|
|
2503
|
+
switch (token.$type) {
|
|
2504
|
+
case "color":
|
|
2505
|
+
for (const mode of Object.keys(token.mode)) token.mode[mode].$value = normalizeColor(token.mode[mode].$value, token.mode[mode].source.node);
|
|
2506
|
+
token.$value = token.mode["."].$value;
|
|
2507
|
+
break;
|
|
2508
|
+
case "fontFamily":
|
|
2509
|
+
for (const mode of Object.keys(token.mode)) token.mode[mode].$value = normalizeFontFamily(token.mode[mode].$value);
|
|
2510
|
+
token.$value = token.mode["."].$value;
|
|
2511
|
+
break;
|
|
2512
|
+
case "fontWeight":
|
|
2513
|
+
for (const mode of Object.keys(token.mode)) token.mode[mode].$value = normalizeFontWeight(token.mode[mode].$value);
|
|
2514
|
+
token.$value = token.mode["."].$value;
|
|
2515
|
+
break;
|
|
2516
|
+
case "border":
|
|
2517
|
+
for (const mode of Object.keys(token.mode)) {
|
|
2518
|
+
const border = token.mode[mode].$value;
|
|
2519
|
+
if (!border || typeof border !== "object") continue;
|
|
2520
|
+
if (border.color) border.color = normalizeColor(border.color, getObjMember(token.mode[mode].source.node, "color"));
|
|
2521
|
+
}
|
|
2522
|
+
token.$value = token.mode["."].$value;
|
|
2523
|
+
break;
|
|
2524
|
+
case "shadow":
|
|
2525
|
+
for (const mode of Object.keys(token.mode)) {
|
|
2526
|
+
if (!Array.isArray(token.mode[mode].$value)) token.mode[mode].$value = [token.mode[mode].$value];
|
|
2527
|
+
const $value = token.mode[mode].$value;
|
|
2528
|
+
for (let i = 0; i < $value.length; i++) {
|
|
2529
|
+
const shadow = $value[i];
|
|
2530
|
+
if (!shadow || typeof shadow !== "object") continue;
|
|
2531
|
+
const shadowNode = token.mode[mode].source.node.type === "Array" ? token.mode[mode].source.node.elements[i].value : token.mode[mode].source.node;
|
|
2532
|
+
if (shadow.color) shadow.color = normalizeColor(shadow.color, getObjMember(shadowNode, "color"));
|
|
2533
|
+
if (!("inset" in shadow)) shadow.inset = false;
|
|
2529
2534
|
}
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
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
|
-
});
|
|
2535
|
+
}
|
|
2536
|
+
token.$value = token.mode["."].$value;
|
|
2537
|
+
break;
|
|
2538
|
+
case "gradient":
|
|
2539
|
+
for (const mode of Object.keys(token.mode)) {
|
|
2540
|
+
if (!Array.isArray(token.mode[mode].$value)) continue;
|
|
2541
|
+
const $value = token.mode[mode].$value;
|
|
2542
|
+
for (let i = 0; i < $value.length; i++) {
|
|
2543
|
+
const stop = $value[i];
|
|
2544
|
+
if (!stop || typeof stop !== "object") continue;
|
|
2545
|
+
const stopNode = token.mode[mode].source.node?.elements?.[i]?.value;
|
|
2546
|
+
if (stop.color) stop.color = normalizeColor(stop.color, getObjMember(stopNode, "color"));
|
|
2576
2547
|
}
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
errors.push({
|
|
2596
|
-
...entry,
|
|
2597
|
-
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
2598
|
-
node: member.name,
|
|
2599
|
-
src
|
|
2600
|
-
});
|
|
2601
|
-
break;
|
|
2602
|
-
}
|
|
2548
|
+
}
|
|
2549
|
+
token.$value = token.mode["."].$value;
|
|
2550
|
+
break;
|
|
2551
|
+
case "typography":
|
|
2552
|
+
for (const mode of Object.keys(token.mode)) {
|
|
2553
|
+
const $value = token.mode[mode].$value;
|
|
2554
|
+
if (typeof $value !== "object") return;
|
|
2555
|
+
for (const [k, v] of Object.entries($value)) switch (k) {
|
|
2556
|
+
case "fontFamily":
|
|
2557
|
+
$value[k] = normalizeFontFamily(v);
|
|
2558
|
+
break;
|
|
2559
|
+
case "fontWeight":
|
|
2560
|
+
$value[k] = normalizeFontWeight(v);
|
|
2561
|
+
break;
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
token.$value = token.mode["."].$value;
|
|
2565
|
+
break;
|
|
2603
2566
|
}
|
|
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
2567
|
}
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2568
|
+
|
|
2569
|
+
//#endregion
|
|
2570
|
+
//#region src/parse/token.ts
|
|
2571
|
+
/** Convert valid DTCG alias to $ref */
|
|
2572
|
+
function aliasToGroupRef(alias) {
|
|
2573
|
+
const id = parseAlias(alias);
|
|
2574
|
+
if (id === alias) return;
|
|
2575
|
+
return { $ref: `#/${id.replace(/~/g, "~0").replace(/\//g, "~1").replace(/\./g, "/")}` };
|
|
2576
|
+
}
|
|
2577
|
+
/** Convert valid DTCG alias to $ref */
|
|
2578
|
+
function aliasToTokenRef(alias, mode) {
|
|
2579
|
+
const id = parseAlias(alias);
|
|
2580
|
+
if (id === alias) return;
|
|
2581
|
+
return { $ref: `#/${id.replace(/~/g, "~0").replace(/\//g, "~1").replace(/\./g, "/")}${mode && mode !== "." ? `/$extensions/mode/${mode}` : ""}/$value` };
|
|
2582
|
+
}
|
|
2583
|
+
/** Generate a TokenNormalized from a Momoa node */
|
|
2584
|
+
function tokenFromNode(node, { groups, path, source, ignore }) {
|
|
2585
|
+
if (!(node.type === "Object" && !!getObjMember(node, "$value") && !path.includes("$extensions"))) return;
|
|
2586
|
+
const jsonID = encodeFragment(path);
|
|
2587
|
+
const id = path.join(".").replace(/\.\$root$/, "");
|
|
2588
|
+
const originalToken = momoa.evaluate(node);
|
|
2589
|
+
const group = groups[encodeFragment(path.slice(0, -1))];
|
|
2590
|
+
if (group?.tokens && !group.tokens.includes(id)) group.tokens.push(id);
|
|
2591
|
+
const nodeSource = {
|
|
2592
|
+
filename: source.filename.href,
|
|
2593
|
+
node
|
|
2594
|
+
};
|
|
2595
|
+
const token = {
|
|
2596
|
+
id,
|
|
2597
|
+
$type: originalToken.$type || group.$type,
|
|
2598
|
+
$description: originalToken.$description || void 0,
|
|
2599
|
+
$deprecated: originalToken.$deprecated ?? group.$deprecated ?? void 0,
|
|
2600
|
+
$value: originalToken.$value,
|
|
2601
|
+
$extensions: originalToken.$extensions || void 0,
|
|
2602
|
+
$extends: originalToken.$extends || void 0,
|
|
2603
|
+
aliasChain: void 0,
|
|
2604
|
+
aliasedBy: void 0,
|
|
2605
|
+
aliasOf: void 0,
|
|
2606
|
+
partialAliasOf: void 0,
|
|
2607
|
+
dependencies: void 0,
|
|
2608
|
+
group,
|
|
2609
|
+
originalValue: void 0,
|
|
2610
|
+
source: nodeSource,
|
|
2611
|
+
jsonID,
|
|
2612
|
+
mode: { ".": {
|
|
2613
|
+
$value: originalToken.$value,
|
|
2614
|
+
aliasOf: void 0,
|
|
2615
|
+
aliasChain: void 0,
|
|
2616
|
+
partialAliasOf: void 0,
|
|
2617
|
+
aliasedBy: void 0,
|
|
2618
|
+
originalValue: void 0,
|
|
2619
|
+
dependencies: void 0,
|
|
2620
|
+
source: {
|
|
2621
|
+
...nodeSource,
|
|
2622
|
+
node: getObjMember(nodeSource.node, "$value") ?? nodeSource.node
|
|
2623
|
+
}
|
|
2624
|
+
} }
|
|
2625
|
+
};
|
|
2626
|
+
if (ignore?.deprecated && token.$deprecated || ignore?.tokens && wcmatch(ignore.tokens)(token.id)) return;
|
|
2627
|
+
const $extensions = getObjMember(node, "$extensions");
|
|
2628
|
+
if ($extensions) {
|
|
2629
|
+
const modeNode = getObjMember($extensions, "mode");
|
|
2630
|
+
for (const mode of Object.keys(token.$extensions.mode ?? {})) {
|
|
2631
|
+
const modeValue = token.$extensions.mode[mode];
|
|
2632
|
+
token.mode[mode] = {
|
|
2633
|
+
$value: modeValue,
|
|
2634
|
+
aliasOf: void 0,
|
|
2635
|
+
aliasChain: void 0,
|
|
2636
|
+
partialAliasOf: void 0,
|
|
2637
|
+
aliasedBy: void 0,
|
|
2638
|
+
originalValue: void 0,
|
|
2639
|
+
dependencies: void 0,
|
|
2640
|
+
source: {
|
|
2641
|
+
...nodeSource,
|
|
2642
|
+
node: getObjMember(modeNode, mode)
|
|
2643
|
+
}
|
|
2644
|
+
};
|
|
2693
2645
|
}
|
|
2694
2646
|
}
|
|
2695
|
-
|
|
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;
|
|
2647
|
+
return token;
|
|
2711
2648
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2649
|
+
/** Generate originalValue and source from node */
|
|
2650
|
+
function tokenRawValuesFromNode(node, { filename, path }) {
|
|
2651
|
+
if (!(node.type === "Object" && getObjMember(node, "$value") && !path.includes("$extensions"))) return;
|
|
2652
|
+
const rawValues = {
|
|
2653
|
+
jsonID: encodeFragment(path),
|
|
2654
|
+
originalValue: momoa.evaluate(node),
|
|
2655
|
+
source: {
|
|
2656
|
+
loc: filename,
|
|
2657
|
+
filename,
|
|
2658
|
+
node
|
|
2659
|
+
},
|
|
2660
|
+
mode: {}
|
|
2718
2661
|
};
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2662
|
+
rawValues.mode["."] = {
|
|
2663
|
+
originalValue: rawValues.originalValue.$value,
|
|
2664
|
+
source: {
|
|
2665
|
+
...rawValues.source,
|
|
2666
|
+
node: getObjMember(node, "$value")
|
|
2667
|
+
}
|
|
2668
|
+
};
|
|
2669
|
+
const $extensions = getObjMember(node, "$extensions");
|
|
2670
|
+
if ($extensions) {
|
|
2671
|
+
const modes = getObjMember($extensions, "mode");
|
|
2672
|
+
if (modes) for (const modeMember of modes.members) {
|
|
2673
|
+
const mode = modeMember.name.value;
|
|
2674
|
+
rawValues.mode[mode] = {
|
|
2675
|
+
originalValue: momoa.evaluate(modeMember.value),
|
|
2676
|
+
source: {
|
|
2677
|
+
loc: filename,
|
|
2678
|
+
filename,
|
|
2679
|
+
node: modeMember.value
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
return rawValues;
|
|
2685
|
+
}
|
|
2686
|
+
/** Arbitrary keys that should be associated with a token group */
|
|
2687
|
+
const GROUP_PROPERTIES = [
|
|
2688
|
+
"$deprecated",
|
|
2689
|
+
"$description",
|
|
2690
|
+
"$extensions",
|
|
2691
|
+
"$type"
|
|
2692
|
+
];
|
|
2693
|
+
/**
|
|
2694
|
+
* Generate a group from a node.
|
|
2695
|
+
* This method mutates the groups index as it goes because of group inheritance.
|
|
2696
|
+
* As it encounters new groups it may have to update other groups.
|
|
2697
|
+
*/
|
|
2698
|
+
function groupFromNode(node, { path, groups }) {
|
|
2699
|
+
const id = path.join(".");
|
|
2700
|
+
const jsonID = encodeFragment(path);
|
|
2701
|
+
if (!groups[jsonID]) groups[jsonID] = {
|
|
2702
|
+
id,
|
|
2703
|
+
$deprecated: void 0,
|
|
2704
|
+
$description: void 0,
|
|
2705
|
+
$extensions: void 0,
|
|
2706
|
+
$type: void 0,
|
|
2707
|
+
tokens: []
|
|
2708
|
+
};
|
|
2709
|
+
const groupIDs = Object.keys(groups);
|
|
2710
|
+
groupIDs.sort();
|
|
2711
|
+
for (const groupID of groupIDs) if (jsonID.startsWith(groupID) && groupID !== jsonID) {
|
|
2712
|
+
groups[jsonID].$deprecated = groups[groupID]?.$deprecated ?? groups[jsonID].$deprecated;
|
|
2713
|
+
groups[jsonID].$description = groups[groupID]?.$description ?? groups[jsonID].$description;
|
|
2714
|
+
groups[jsonID].$type = groups[groupID]?.$type ?? groups[jsonID].$type;
|
|
2715
|
+
}
|
|
2716
|
+
for (const m of node.members) {
|
|
2717
|
+
if (m.name.type !== "String" || !GROUP_PROPERTIES.includes(m.name.value)) continue;
|
|
2718
|
+
groups[jsonID][m.name.value] = momoa.evaluate(m.value);
|
|
2719
|
+
}
|
|
2720
|
+
return groups[jsonID];
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Link and reverse-link tokens in one pass.
|
|
2724
|
+
*/
|
|
2725
|
+
function graphAliases(refMap, { tokens, logger, sources }) {
|
|
2726
|
+
const getTokenRef = (ref) => ref.replace(/\/(\$value|\$extensions)\/?.*/, "");
|
|
2727
|
+
for (const [jsonID, { refChain }] of Object.entries(refMap)) {
|
|
2728
|
+
if (!refChain.length) continue;
|
|
2729
|
+
const mode = jsonID.match(/\/\$extensions\/mode\/([^/]+)/)?.[1] || ".";
|
|
2730
|
+
const rootRef = getTokenRef(jsonID);
|
|
2731
|
+
const modeValue = tokens[rootRef]?.mode[mode];
|
|
2732
|
+
if (!modeValue) continue;
|
|
2733
|
+
if (!modeValue.dependencies) modeValue.dependencies = [];
|
|
2734
|
+
modeValue.dependencies.push(...refChain.filter((r) => !modeValue.dependencies.includes(r)));
|
|
2735
|
+
modeValue.dependencies.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
2736
|
+
if (jsonID.endsWith("/$value") || tokens[jsonID]) {
|
|
2737
|
+
modeValue.aliasOf = refToTokenID(refChain.at(-1));
|
|
2738
|
+
modeValue.aliasChain = [...refChain.map(refToTokenID)];
|
|
2739
|
+
}
|
|
2740
|
+
const partial = jsonID.replace(/.*\/\$value\/?/, "").split("/").filter(Boolean);
|
|
2741
|
+
if (partial.length && modeValue.$value && typeof modeValue.$value === "object") {
|
|
2742
|
+
let node = modeValue.$value;
|
|
2743
|
+
let sourceNode = modeValue.source.node;
|
|
2744
|
+
if (!modeValue.partialAliasOf) modeValue.partialAliasOf = Array.isArray(modeValue.$value) || tokens[rootRef]?.$type === "shadow" ? [] : {};
|
|
2745
|
+
let partialAliasOf = modeValue.partialAliasOf;
|
|
2746
|
+
if (tokens[rootRef]?.$type === "shadow" && !Array.isArray(node)) {
|
|
2747
|
+
if (Array.isArray(modeValue.partialAliasOf) && !modeValue.partialAliasOf.length) modeValue.partialAliasOf.push({});
|
|
2748
|
+
partialAliasOf = modeValue.partialAliasOf[0];
|
|
2749
|
+
}
|
|
2750
|
+
for (let i = 0; i < partial.length; i++) {
|
|
2751
|
+
let key = partial[i];
|
|
2752
|
+
if (String(Number(key)) === key) key = Number(key);
|
|
2753
|
+
if (key in node && typeof node[key] !== "undefined") {
|
|
2754
|
+
node = node[key];
|
|
2755
|
+
if (sourceNode.type === "Object") sourceNode = getObjMember(sourceNode, key) ?? sourceNode;
|
|
2756
|
+
else if (sourceNode.type === "Array") sourceNode = sourceNode.elements[key]?.value ?? sourceNode;
|
|
2757
|
+
}
|
|
2758
|
+
if (i === partial.length - 1) {
|
|
2759
|
+
const aliasedID = getTokenRef(refChain[0]);
|
|
2760
|
+
if (!(aliasedID in tokens)) {
|
|
2761
|
+
logger.error({
|
|
2762
|
+
group: "parser",
|
|
2763
|
+
label: "init",
|
|
2764
|
+
message: `Invalid alias: ${aliasedID}`,
|
|
2765
|
+
node: sourceNode,
|
|
2766
|
+
src: sources[tokens[rootRef].source.filename]?.src
|
|
2767
|
+
});
|
|
2768
|
+
break;
|
|
2769
|
+
}
|
|
2770
|
+
partialAliasOf[key] = refToTokenID(aliasedID);
|
|
2771
|
+
}
|
|
2772
|
+
if (!(key in partialAliasOf)) partialAliasOf[key] = Array.isArray(node) ? [] : {};
|
|
2773
|
+
partialAliasOf = partialAliasOf[key];
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
const aliasedByRefs = [jsonID, ...refChain].reverse();
|
|
2777
|
+
for (let i = 0; i < aliasedByRefs.length; i++) {
|
|
2778
|
+
const baseRef = getTokenRef(aliasedByRefs[i]);
|
|
2779
|
+
const baseToken = tokens[baseRef]?.mode[mode] || tokens[baseRef];
|
|
2780
|
+
if (!baseToken) continue;
|
|
2781
|
+
const upstream = aliasedByRefs.slice(i + 1);
|
|
2782
|
+
if (!upstream.length) break;
|
|
2783
|
+
if (!baseToken.aliasedBy) baseToken.aliasedBy = [];
|
|
2784
|
+
for (let j = 0; j < upstream.length; j++) {
|
|
2785
|
+
const downstream = refToTokenID(upstream[j]);
|
|
2786
|
+
if (!baseToken.aliasedBy.includes(downstream)) {
|
|
2787
|
+
baseToken.aliasedBy.push(downstream);
|
|
2788
|
+
if (mode === ".") tokens[baseRef].aliasedBy = baseToken.aliasedBy;
|
|
2783
2789
|
}
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
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;
|
|
2790
|
+
}
|
|
2791
|
+
baseToken.aliasedBy.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
2792
|
+
}
|
|
2793
|
+
if (mode === ".") {
|
|
2794
|
+
tokens[rootRef].aliasChain = modeValue.aliasChain;
|
|
2795
|
+
tokens[rootRef].aliasedBy = modeValue.aliasedBy;
|
|
2796
|
+
tokens[rootRef].aliasOf = modeValue.aliasOf;
|
|
2797
|
+
tokens[rootRef].dependencies = modeValue.dependencies;
|
|
2798
|
+
tokens[rootRef].partialAliasOf = modeValue.partialAliasOf;
|
|
2807
2799
|
}
|
|
2808
2800
|
}
|
|
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
2801
|
}
|
|
2826
|
-
|
|
2827
|
-
//#endregion
|
|
2828
|
-
//#region src/parse/normalize.ts
|
|
2829
2802
|
/**
|
|
2830
|
-
*
|
|
2831
|
-
*
|
|
2803
|
+
* Convert Reference Object to token ID.
|
|
2804
|
+
* This can then be turned into an alias by surrounding with { … }
|
|
2805
|
+
* ⚠️ This is not mode-aware. This will flatten multiple modes into the same root token.
|
|
2832
2806
|
*/
|
|
2833
|
-
function
|
|
2834
|
-
const
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2807
|
+
function refToTokenID($ref) {
|
|
2808
|
+
const path = typeof $ref === "object" ? $ref.$ref : $ref;
|
|
2809
|
+
if (typeof path !== "string") return;
|
|
2810
|
+
const { subpath } = parseRef(path);
|
|
2811
|
+
if (subpath?.[0] === "$defs") subpath.splice(0, 2);
|
|
2812
|
+
return subpath?.length && subpath.join(".").replace(/\.(\$root|\$value|\$extensions).*$/, "") || void 0;
|
|
2813
|
+
}
|
|
2814
|
+
const EXPECTED_NESTED_ALIAS = {
|
|
2815
|
+
border: {
|
|
2816
|
+
color: ["color"],
|
|
2817
|
+
stroke: ["strokeStyle"],
|
|
2818
|
+
width: ["dimension"]
|
|
2819
|
+
},
|
|
2820
|
+
gradient: {
|
|
2821
|
+
color: ["color"],
|
|
2822
|
+
position: ["number"]
|
|
2823
|
+
},
|
|
2824
|
+
shadow: {
|
|
2825
|
+
color: ["color"],
|
|
2826
|
+
offsetX: ["dimension"],
|
|
2827
|
+
offsetY: ["dimension"],
|
|
2828
|
+
blur: ["dimension"],
|
|
2829
|
+
spread: ["dimension"],
|
|
2830
|
+
inset: ["boolean"]
|
|
2831
|
+
},
|
|
2832
|
+
strokeStyle: { dashArray: ["dimension"] },
|
|
2833
|
+
transition: {
|
|
2834
|
+
duration: ["duration"],
|
|
2835
|
+
delay: ["duration"],
|
|
2836
|
+
timingFunction: ["cubicBezier"]
|
|
2837
|
+
},
|
|
2838
|
+
typography: {
|
|
2839
|
+
fontFamily: ["fontFamily"],
|
|
2840
|
+
fontWeight: ["fontWeight"],
|
|
2841
|
+
fontSize: ["dimension"],
|
|
2842
|
+
lineHeight: ["dimension", "number"],
|
|
2843
|
+
letterSpacing: ["dimension"],
|
|
2844
|
+
paragraphSpacing: ["dimension", "string"],
|
|
2845
|
+
wordSpacing: ["dimension", "string"]
|
|
2869
2846
|
}
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
case "shadow":
|
|
2892
|
-
for (const mode of Object.keys(token.mode)) {
|
|
2893
|
-
if (!Array.isArray(token.mode[mode].$value)) token.mode[mode].$value = [token.mode[mode].$value];
|
|
2894
|
-
const $value = token.mode[mode].$value;
|
|
2895
|
-
for (let i = 0; i < $value.length; i++) {
|
|
2896
|
-
const shadow = $value[i];
|
|
2897
|
-
if (!shadow || typeof shadow !== "object") continue;
|
|
2898
|
-
const shadowNode = token.mode[mode].source.node.type === "Array" ? token.mode[mode].source.node.elements[i].value : token.mode[mode].source.node;
|
|
2899
|
-
if (shadow.color) shadow.color = normalizeColor(shadow.color, getObjMember(shadowNode, "color"));
|
|
2900
|
-
if (!("inset" in shadow)) shadow.inset = false;
|
|
2847
|
+
};
|
|
2848
|
+
/**
|
|
2849
|
+
* Resolve DTCG aliases, $extends, and $ref
|
|
2850
|
+
*/
|
|
2851
|
+
function resolveAliases(tokens, { logger, refMap, sources }) {
|
|
2852
|
+
for (const token of Object.values(tokens)) {
|
|
2853
|
+
const aliasEntry = {
|
|
2854
|
+
group: "parser",
|
|
2855
|
+
label: "init",
|
|
2856
|
+
src: sources[token.source.filename]?.src,
|
|
2857
|
+
node: getObjMember(token.source.node, "$value")
|
|
2858
|
+
};
|
|
2859
|
+
for (const mode of Object.keys(token.mode)) {
|
|
2860
|
+
function resolveInner(alias, refChain) {
|
|
2861
|
+
const nextRef = aliasToTokenRef(alias, mode)?.$ref;
|
|
2862
|
+
if (!nextRef) {
|
|
2863
|
+
logger.error({
|
|
2864
|
+
...aliasEntry,
|
|
2865
|
+
message: `Internal error resolving ${JSON.stringify(refChain)}`
|
|
2866
|
+
});
|
|
2867
|
+
throw new Error("Internal error");
|
|
2901
2868
|
}
|
|
2869
|
+
if (refChain.includes(nextRef)) logger.error({
|
|
2870
|
+
...aliasEntry,
|
|
2871
|
+
message: "Circular alias detected."
|
|
2872
|
+
});
|
|
2873
|
+
const nextJSONID = nextRef.replace(/\/(\$value|\$extensions).*/, "");
|
|
2874
|
+
const nextToken = tokens[nextJSONID]?.mode[mode] || tokens[nextJSONID]?.mode["."];
|
|
2875
|
+
if (!nextToken) logger.error({
|
|
2876
|
+
...aliasEntry,
|
|
2877
|
+
message: `Could not resolve alias ${alias}.`
|
|
2878
|
+
});
|
|
2879
|
+
refChain.push(nextRef);
|
|
2880
|
+
if (isAlias(nextToken.originalValue)) return resolveInner(nextToken.originalValue, refChain);
|
|
2881
|
+
return nextJSONID;
|
|
2902
2882
|
}
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
if (
|
|
2883
|
+
function traverseAndResolve(value, { node, expectedTypes, path }) {
|
|
2884
|
+
if (typeof value !== "string") {
|
|
2885
|
+
if (Array.isArray(value)) for (let i = 0; i < value.length; i++) {
|
|
2886
|
+
if (!value[i]) continue;
|
|
2887
|
+
value[i] = traverseAndResolve(value[i], {
|
|
2888
|
+
node: node.elements?.[i]?.value,
|
|
2889
|
+
expectedTypes: expectedTypes?.includes("cubicBezier") ? ["number"] : expectedTypes,
|
|
2890
|
+
path: [...path, i]
|
|
2891
|
+
}).$value;
|
|
2892
|
+
}
|
|
2893
|
+
else if (typeof value === "object") for (const key of Object.keys(value)) {
|
|
2894
|
+
if (!expectedTypes?.length || !EXPECTED_NESTED_ALIAS[expectedTypes[0]]) continue;
|
|
2895
|
+
value[key] = traverseAndResolve(value[key], {
|
|
2896
|
+
node: getObjMember(node, key),
|
|
2897
|
+
expectedTypes: EXPECTED_NESTED_ALIAS[expectedTypes[0]][key],
|
|
2898
|
+
path: [...path, key]
|
|
2899
|
+
}).$value;
|
|
2900
|
+
}
|
|
2901
|
+
return { $value: value };
|
|
2914
2902
|
}
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
for (const [k, v] of Object.entries($value)) switch (k) {
|
|
2923
|
-
case "fontFamily":
|
|
2924
|
-
$value[k] = normalizeFontFamily(v);
|
|
2925
|
-
break;
|
|
2926
|
-
case "fontWeight":
|
|
2927
|
-
$value[k] = normalizeFontWeight(v);
|
|
2928
|
-
break;
|
|
2903
|
+
if (!isAlias(value)) {
|
|
2904
|
+
if (!expectedTypes?.includes("string") && (value.includes("{") || value.includes("}"))) logger.error({
|
|
2905
|
+
...aliasEntry,
|
|
2906
|
+
message: "Invalid alias syntax.",
|
|
2907
|
+
node
|
|
2908
|
+
});
|
|
2909
|
+
return { $value: value };
|
|
2929
2910
|
}
|
|
2911
|
+
const refChain = [];
|
|
2912
|
+
const resolvedID = resolveInner(value, refChain);
|
|
2913
|
+
if (expectedTypes?.length && !expectedTypes.includes(tokens[resolvedID].$type)) logger.error({
|
|
2914
|
+
...aliasEntry,
|
|
2915
|
+
message: `Cannot alias to $type "${tokens[resolvedID].$type}" from $type "${expectedTypes.join(" / ")}".`,
|
|
2916
|
+
node
|
|
2917
|
+
});
|
|
2918
|
+
refMap[path.join("/")] = {
|
|
2919
|
+
filename: token.source.filename,
|
|
2920
|
+
refChain
|
|
2921
|
+
};
|
|
2922
|
+
return {
|
|
2923
|
+
$type: tokens[resolvedID].$type,
|
|
2924
|
+
$value: tokens[resolvedID].mode[mode]?.$value || tokens[resolvedID].$value
|
|
2925
|
+
};
|
|
2930
2926
|
}
|
|
2931
|
-
|
|
2932
|
-
|
|
2927
|
+
const pathBase = mode === "." ? token.jsonID : `${token.jsonID}/$extensions/mode/${mode}`;
|
|
2928
|
+
const { $type, $value } = traverseAndResolve(token.mode[mode].$value, {
|
|
2929
|
+
node: aliasEntry.node,
|
|
2930
|
+
expectedTypes: token.$type ? [token.$type] : void 0,
|
|
2931
|
+
path: [pathBase, "$value"]
|
|
2932
|
+
});
|
|
2933
|
+
if (!token.$type) token.$type = $type;
|
|
2934
|
+
if ($value) token.mode[mode].$value = $value;
|
|
2935
|
+
if (mode === ".") token.$value = token.mode[mode].$value;
|
|
2936
|
+
}
|
|
2933
2937
|
}
|
|
2934
2938
|
}
|
|
2935
2939
|
|
|
2936
2940
|
//#endregion
|
|
2937
|
-
//#region src/parse/
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
return { $ref: `#/${id.replace(/~/g, "~0").replace(/\//g, "~1").replace(/\./g, "/")}` };
|
|
2943
|
-
}
|
|
2944
|
-
/** Convert valid DTCG alias to $ref */
|
|
2945
|
-
function aliasToTokenRef(alias, mode) {
|
|
2946
|
-
const id = parseAlias(alias);
|
|
2947
|
-
if (id === alias) return;
|
|
2948
|
-
return { $ref: `#/${id.replace(/~/g, "~0").replace(/\//g, "~1").replace(/\./g, "/")}${mode && mode !== "." ? `/$extensions/mode/${mode}` : ""}/$value` };
|
|
2949
|
-
}
|
|
2950
|
-
/** Generate a TokenNormalized from a Momoa node */
|
|
2951
|
-
function tokenFromNode(node, { groups, path, source, ignore }) {
|
|
2952
|
-
if (!(node.type === "Object" && !!getObjMember(node, "$value") && !path.includes("$extensions"))) return;
|
|
2953
|
-
const jsonID = encodeFragment(path);
|
|
2954
|
-
const id = path.join(".");
|
|
2955
|
-
const originalToken = momoa.evaluate(node);
|
|
2956
|
-
const group = groups[encodeFragment(path.slice(0, -1))];
|
|
2957
|
-
if (group?.tokens && !group.tokens.includes(id)) group.tokens.push(id);
|
|
2958
|
-
const nodeSource = {
|
|
2959
|
-
filename: source.filename.href,
|
|
2960
|
-
node
|
|
2941
|
+
//#region src/parse/process.ts
|
|
2942
|
+
function processTokens(rootSource, { config, logger, sourceByFilename, isResolver }) {
|
|
2943
|
+
const entry = {
|
|
2944
|
+
group: "parser",
|
|
2945
|
+
label: "init"
|
|
2961
2946
|
};
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
$
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2947
|
+
const refMap = {};
|
|
2948
|
+
function resolveRef(node, chain) {
|
|
2949
|
+
const { subpath } = parseRef(node.value);
|
|
2950
|
+
assert(subpath, logger, {
|
|
2951
|
+
...entry,
|
|
2952
|
+
message: "Can’t resolve $ref",
|
|
2953
|
+
node,
|
|
2954
|
+
src: rootSource.src
|
|
2955
|
+
});
|
|
2956
|
+
const next = findNode(rootSource.document, subpath);
|
|
2957
|
+
assert(next, logger, {
|
|
2958
|
+
...entry,
|
|
2959
|
+
message: "Can't find $ref",
|
|
2960
|
+
node,
|
|
2961
|
+
src: rootSource.src
|
|
2962
|
+
});
|
|
2963
|
+
if (next?.type === "Object") {
|
|
2964
|
+
const next$ref = getObjMember(next, "$ref");
|
|
2965
|
+
if (next$ref && next$ref.type === "String") {
|
|
2966
|
+
if (chain.includes(next$ref.value)) logger.error({
|
|
2967
|
+
...entry,
|
|
2968
|
+
message: `Circular $ref detected: ${JSON.stringify(next$ref.value)}`,
|
|
2969
|
+
node: next$ref,
|
|
2970
|
+
src: rootSource.src
|
|
2971
|
+
});
|
|
2972
|
+
chain.push(next$ref.value);
|
|
2973
|
+
return resolveRef(next$ref, chain);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
return next;
|
|
2977
|
+
}
|
|
2978
|
+
const inlineStart = performance.now();
|
|
2979
|
+
traverse(rootSource.document, { enter(node, _parent, rawPath) {
|
|
2980
|
+
if (rawPath.includes("$extensions") || node.type !== "Object") return;
|
|
2981
|
+
const $ref = node.type === "Object" ? getObjMember(node, "$ref") : void 0;
|
|
2982
|
+
if (!$ref) return;
|
|
2983
|
+
assertStringNode($ref, logger, {
|
|
2984
|
+
...entry,
|
|
2985
|
+
message: "Invalid $ref. Expected string.",
|
|
2986
|
+
node: $ref,
|
|
2987
|
+
src: rootSource.src
|
|
2988
|
+
});
|
|
2989
|
+
const jsonID = encodeFragment(rawPath);
|
|
2990
|
+
refMap[jsonID] = {
|
|
2991
|
+
filename: rootSource.filename.href,
|
|
2992
|
+
refChain: [$ref.value]
|
|
2993
|
+
};
|
|
2994
|
+
const resolved = resolveRef($ref, refMap[jsonID].refChain);
|
|
2995
|
+
if (resolved.type === "Object") {
|
|
2996
|
+
node.members.splice(node.members.findIndex((m) => m.name.type === "String" && m.name.value === "$ref"), 1);
|
|
2997
|
+
replaceNode(node, mergeObjects(resolved, node));
|
|
2998
|
+
} else replaceNode(node, resolved);
|
|
2999
|
+
} });
|
|
3000
|
+
logger.debug({
|
|
3001
|
+
...entry,
|
|
3002
|
+
message: "Inline aliases",
|
|
3003
|
+
timing: performance.now() - inlineStart
|
|
3004
|
+
});
|
|
3005
|
+
function flatten$extends(node, chain) {
|
|
3006
|
+
const memberKeys = node.members.map((m) => m.name.type === "String" && m.name.value).filter(Boolean);
|
|
3007
|
+
if (memberKeys.includes("$extends")) {
|
|
3008
|
+
const $extends = getObjMember(node, "$extends");
|
|
3009
|
+
assertStringNode($extends, logger, {
|
|
3010
|
+
...entry,
|
|
3011
|
+
message: "$extends must be a string",
|
|
3012
|
+
node: $extends,
|
|
3013
|
+
src: rootSource.src
|
|
3014
|
+
});
|
|
3015
|
+
if (memberKeys.includes("$value")) logger.error({
|
|
3016
|
+
...entry,
|
|
3017
|
+
message: "$extends can’t exist within a token",
|
|
3018
|
+
node: $extends,
|
|
3019
|
+
src: rootSource.src
|
|
3020
|
+
});
|
|
3021
|
+
const next = isAlias($extends.value) ? aliasToGroupRef($extends.value) : void 0;
|
|
3022
|
+
assert(next, logger, {
|
|
3023
|
+
...entry,
|
|
3024
|
+
message: "$extends must be a valid alias",
|
|
3025
|
+
node: $extends,
|
|
3026
|
+
src: rootSource.src
|
|
3027
|
+
});
|
|
3028
|
+
if (chain.includes(next.$ref) || chain.some((value) => value.startsWith(next.$ref) || next.$ref.startsWith(value))) logger.error({
|
|
3029
|
+
...entry,
|
|
3030
|
+
message: "Circular $extends detected",
|
|
3031
|
+
node: $extends,
|
|
3032
|
+
src: rootSource.src
|
|
3033
|
+
});
|
|
3034
|
+
chain.push(next.$ref);
|
|
3035
|
+
const extended = findNode(rootSource.document, parseRef(next.$ref).subpath ?? []);
|
|
3036
|
+
assert(extended, logger, {
|
|
3037
|
+
...entry,
|
|
3038
|
+
message: "Could not resolve $extends",
|
|
3039
|
+
node: $extends,
|
|
3040
|
+
src: rootSource.src
|
|
3041
|
+
});
|
|
3042
|
+
assertObjectNode(extended, logger, {
|
|
3043
|
+
...entry,
|
|
3044
|
+
message: "$extends must resolve to a group of tokens",
|
|
3045
|
+
node
|
|
3046
|
+
});
|
|
3047
|
+
flatten$extends(extended, chain);
|
|
3048
|
+
replaceNode(node, mergeObjects(extended, node));
|
|
3049
|
+
}
|
|
3050
|
+
for (const member of node.members) if (member.value.type === "Object" && member.name.type === "String" && !["$value", "$extensions"].includes(member.name.value)) traverse(member.value, { enter(subnode, _parent) {
|
|
3051
|
+
if (subnode.type === "Object") flatten$extends(subnode, chain);
|
|
3052
|
+
} });
|
|
3053
|
+
}
|
|
3054
|
+
const extendsStart = performance.now();
|
|
3055
|
+
flatten$extends(rootSource.document.body, []);
|
|
3056
|
+
logger.debug({
|
|
3057
|
+
...entry,
|
|
3058
|
+
message: "Resolving $extends",
|
|
3059
|
+
timing: performance.now() - extendsStart
|
|
3060
|
+
});
|
|
3061
|
+
const firstPass = performance.now();
|
|
3062
|
+
const tokens = {};
|
|
3063
|
+
const tokenIDs = [];
|
|
3064
|
+
const groups = {};
|
|
3065
|
+
traverse(rootSource.document, { enter(node, _parent, rawPath) {
|
|
3066
|
+
if (node.type !== "Object") return;
|
|
3067
|
+
groupFromNode(node, {
|
|
3068
|
+
path: isResolver ? filterResolverPaths(rawPath) : rawPath,
|
|
3069
|
+
groups
|
|
3070
|
+
});
|
|
3071
|
+
const token = tokenFromNode(node, {
|
|
3072
|
+
groups,
|
|
3073
|
+
ignore: config.ignore,
|
|
3074
|
+
path: isResolver ? filterResolverPaths(rawPath) : rawPath,
|
|
3075
|
+
source: rootSource
|
|
3076
|
+
});
|
|
3077
|
+
if (token) {
|
|
3078
|
+
tokenIDs.push(token.jsonID);
|
|
3079
|
+
tokens[token.jsonID] = token;
|
|
3080
|
+
}
|
|
3081
|
+
} });
|
|
3082
|
+
logger.debug({
|
|
3083
|
+
...entry,
|
|
3084
|
+
message: "Parsing: 1st pass",
|
|
3085
|
+
timing: performance.now() - firstPass
|
|
3086
|
+
});
|
|
3087
|
+
const secondPass = performance.now();
|
|
3088
|
+
for (const source of Object.values(sourceByFilename)) traverse(source.document, { enter(node, _parent, path) {
|
|
3089
|
+
if (node.type !== "Object") return;
|
|
3090
|
+
const tokenRawValues = tokenRawValuesFromNode(node, {
|
|
3091
|
+
filename: source.filename.href,
|
|
3092
|
+
path
|
|
3093
|
+
});
|
|
3094
|
+
if (tokenRawValues && tokens[tokenRawValues?.jsonID]) {
|
|
3095
|
+
tokens[tokenRawValues.jsonID].originalValue = tokenRawValues.originalValue;
|
|
3096
|
+
tokens[tokenRawValues.jsonID].source = tokenRawValues.source;
|
|
3097
|
+
for (const mode of Object.keys(tokenRawValues.mode)) {
|
|
3098
|
+
tokens[tokenRawValues.jsonID].mode[mode].originalValue = tokenRawValues.mode[mode].originalValue;
|
|
3099
|
+
tokens[tokenRawValues.jsonID].mode[mode].source = tokenRawValues.mode[mode].source;
|
|
2990
3100
|
}
|
|
2991
|
-
} }
|
|
2992
|
-
};
|
|
2993
|
-
if (ignore?.deprecated && token.$deprecated || ignore?.tokens && wcmatch(ignore.tokens)(token.id)) return;
|
|
2994
|
-
const $extensions = getObjMember(node, "$extensions");
|
|
2995
|
-
if ($extensions) {
|
|
2996
|
-
const modeNode = getObjMember($extensions, "mode");
|
|
2997
|
-
for (const mode of Object.keys(token.$extensions.mode ?? {})) {
|
|
2998
|
-
const modeValue = token.$extensions.mode[mode];
|
|
2999
|
-
token.mode[mode] = {
|
|
3000
|
-
$value: modeValue,
|
|
3001
|
-
aliasOf: void 0,
|
|
3002
|
-
aliasChain: void 0,
|
|
3003
|
-
partialAliasOf: void 0,
|
|
3004
|
-
aliasedBy: void 0,
|
|
3005
|
-
originalValue: void 0,
|
|
3006
|
-
dependencies: void 0,
|
|
3007
|
-
source: {
|
|
3008
|
-
...nodeSource,
|
|
3009
|
-
node: getObjMember(modeNode, mode)
|
|
3010
|
-
}
|
|
3011
|
-
};
|
|
3012
3101
|
}
|
|
3102
|
+
} });
|
|
3103
|
+
resolveAliases(tokens, {
|
|
3104
|
+
logger,
|
|
3105
|
+
sources: sourceByFilename,
|
|
3106
|
+
refMap
|
|
3107
|
+
});
|
|
3108
|
+
logger.debug({
|
|
3109
|
+
...entry,
|
|
3110
|
+
message: "Parsing: 2nd pass",
|
|
3111
|
+
timing: performance.now() - secondPass
|
|
3112
|
+
});
|
|
3113
|
+
const aliasStart = performance.now();
|
|
3114
|
+
graphAliases(refMap, {
|
|
3115
|
+
tokens,
|
|
3116
|
+
logger,
|
|
3117
|
+
sources: sourceByFilename
|
|
3118
|
+
});
|
|
3119
|
+
logger.debug({
|
|
3120
|
+
...entry,
|
|
3121
|
+
message: "Alias graph built",
|
|
3122
|
+
timing: performance.now() - aliasStart
|
|
3123
|
+
});
|
|
3124
|
+
const normalizeStart = performance.now();
|
|
3125
|
+
for (const id of tokenIDs) {
|
|
3126
|
+
const token = tokens[id];
|
|
3127
|
+
normalize(token, {
|
|
3128
|
+
logger,
|
|
3129
|
+
src: sourceByFilename[token.source.filename]?.src
|
|
3130
|
+
});
|
|
3013
3131
|
}
|
|
3014
|
-
|
|
3132
|
+
logger.debug({
|
|
3133
|
+
...entry,
|
|
3134
|
+
message: "Normalized values",
|
|
3135
|
+
timing: performance.now() - normalizeStart
|
|
3136
|
+
});
|
|
3137
|
+
if (config.alphabetize === false) return tokens;
|
|
3138
|
+
const sortStart = performance.now();
|
|
3139
|
+
const tokensSorted = {};
|
|
3140
|
+
tokenIDs.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
3141
|
+
for (const path of tokenIDs) {
|
|
3142
|
+
const id = refToTokenID(path);
|
|
3143
|
+
tokensSorted[id] = tokens[path];
|
|
3144
|
+
}
|
|
3145
|
+
for (const group of Object.values(groups)) group.tokens.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
3146
|
+
logger.debug({
|
|
3147
|
+
...entry,
|
|
3148
|
+
message: "Sorted tokens",
|
|
3149
|
+
timing: performance.now() - sortStart
|
|
3150
|
+
});
|
|
3151
|
+
return tokensSorted;
|
|
3015
3152
|
}
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3153
|
+
|
|
3154
|
+
//#endregion
|
|
3155
|
+
//#region src/resolver/normalize.ts
|
|
3156
|
+
/** Normalize resolver (assuming it’s been validated) */
|
|
3157
|
+
async function normalizeResolver(document, { logger, filename, req, src, yamlToMomoa }) {
|
|
3158
|
+
const resolverBundle = await bundle([{
|
|
3159
|
+
filename,
|
|
3160
|
+
src
|
|
3161
|
+
}], {
|
|
3162
|
+
req,
|
|
3163
|
+
yamlToMomoa
|
|
3164
|
+
});
|
|
3165
|
+
const resolverSource = momoa.evaluate(resolverBundle.document);
|
|
3166
|
+
replaceNode(document, resolverBundle.document);
|
|
3167
|
+
for (const set of Object.values(resolverSource.sets ?? {})) for (const source of set.sources) resolvePartials(source, {
|
|
3168
|
+
resolver: resolverSource,
|
|
3169
|
+
logger
|
|
3170
|
+
});
|
|
3171
|
+
for (const modifier of Object.values(resolverSource.modifiers ?? {})) for (const context of Object.values(modifier.contexts)) for (const source of context) resolvePartials(source, {
|
|
3172
|
+
resolver: resolverSource,
|
|
3173
|
+
logger
|
|
3174
|
+
});
|
|
3175
|
+
for (const item of resolverSource.resolutionOrder ?? []) resolvePartials(item, {
|
|
3176
|
+
resolver: resolverSource,
|
|
3177
|
+
logger
|
|
3178
|
+
});
|
|
3179
|
+
return {
|
|
3180
|
+
name: resolverSource.name,
|
|
3181
|
+
version: resolverSource.version,
|
|
3182
|
+
description: resolverSource.description,
|
|
3183
|
+
sets: resolverSource.sets,
|
|
3184
|
+
modifiers: resolverSource.modifiers,
|
|
3185
|
+
resolutionOrder: resolverSource.resolutionOrder,
|
|
3186
|
+
_source: {
|
|
3024
3187
|
filename,
|
|
3025
|
-
|
|
3026
|
-
},
|
|
3027
|
-
mode: {}
|
|
3028
|
-
};
|
|
3029
|
-
rawValues.mode["."] = {
|
|
3030
|
-
originalValue: rawValues.originalValue.$value,
|
|
3031
|
-
source: {
|
|
3032
|
-
...rawValues.source,
|
|
3033
|
-
node: getObjMember(node, "$value")
|
|
3188
|
+
document
|
|
3034
3189
|
}
|
|
3035
3190
|
};
|
|
3036
|
-
const $extensions = getObjMember(node, "$extensions");
|
|
3037
|
-
if ($extensions) {
|
|
3038
|
-
const modes = getObjMember($extensions, "mode");
|
|
3039
|
-
if (modes) for (const modeMember of modes.members) {
|
|
3040
|
-
const mode = modeMember.name.value;
|
|
3041
|
-
rawValues.mode[mode] = {
|
|
3042
|
-
originalValue: momoa.evaluate(modeMember.value),
|
|
3043
|
-
source: {
|
|
3044
|
-
loc: filename,
|
|
3045
|
-
filename,
|
|
3046
|
-
node: modeMember.value
|
|
3047
|
-
}
|
|
3048
|
-
};
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
return rawValues;
|
|
3052
3191
|
}
|
|
3053
|
-
/**
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
];
|
|
3060
|
-
/**
|
|
3061
|
-
* Generate a group from a node.
|
|
3062
|
-
* This method mutates the groups index as it goes because of group inheritance.
|
|
3063
|
-
* As it encounters new groups it may have to update other groups.
|
|
3064
|
-
*/
|
|
3065
|
-
function groupFromNode(node, { path, groups }) {
|
|
3066
|
-
const id = path.join(".");
|
|
3067
|
-
const jsonID = encodeFragment(path);
|
|
3068
|
-
if (!groups[jsonID]) groups[jsonID] = {
|
|
3069
|
-
id,
|
|
3070
|
-
$deprecated: void 0,
|
|
3071
|
-
$description: void 0,
|
|
3072
|
-
$extensions: void 0,
|
|
3073
|
-
$type: void 0,
|
|
3074
|
-
tokens: []
|
|
3192
|
+
/** Resolve $refs for already-initialized JS */
|
|
3193
|
+
function resolvePartials(source, { resolver, logger }) {
|
|
3194
|
+
if (!source) return;
|
|
3195
|
+
const entry = {
|
|
3196
|
+
group: "parser",
|
|
3197
|
+
label: "resolver"
|
|
3075
3198
|
};
|
|
3076
|
-
const
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
*/
|
|
3092
|
-
function graphAliases(refMap, { tokens, logger, sources }) {
|
|
3093
|
-
const getTokenRef = (ref) => ref.replace(/\/(\$value|\$extensions)\/?.*/, "");
|
|
3094
|
-
for (const [jsonID, { refChain }] of Object.entries(refMap)) {
|
|
3095
|
-
if (!refChain.length) continue;
|
|
3096
|
-
const mode = jsonID.match(/\/\$extensions\/mode\/([^/]+)/)?.[1] || ".";
|
|
3097
|
-
const rootRef = getTokenRef(jsonID);
|
|
3098
|
-
const modeValue = tokens[rootRef]?.mode[mode];
|
|
3099
|
-
if (!modeValue) continue;
|
|
3100
|
-
if (!modeValue.dependencies) modeValue.dependencies = [];
|
|
3101
|
-
modeValue.dependencies.push(...refChain.filter((r) => !modeValue.dependencies.includes(r)));
|
|
3102
|
-
modeValue.dependencies.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
3103
|
-
if (jsonID.endsWith("/$value") || tokens[jsonID]) {
|
|
3104
|
-
modeValue.aliasOf = refToTokenID(refChain.at(-1));
|
|
3105
|
-
modeValue.aliasChain = [...refChain.map(refToTokenID)];
|
|
3106
|
-
}
|
|
3107
|
-
const partial = jsonID.replace(/.*\/\$value\/?/, "").split("/").filter(Boolean);
|
|
3108
|
-
if (partial.length && modeValue.$value && typeof modeValue.$value === "object") {
|
|
3109
|
-
let node = modeValue.$value;
|
|
3110
|
-
let sourceNode = modeValue.source.node;
|
|
3111
|
-
if (!modeValue.partialAliasOf) modeValue.partialAliasOf = Array.isArray(modeValue.$value) || tokens[rootRef]?.$type === "shadow" ? [] : {};
|
|
3112
|
-
let partialAliasOf = modeValue.partialAliasOf;
|
|
3113
|
-
if (tokens[rootRef]?.$type === "shadow" && !Array.isArray(node)) {
|
|
3114
|
-
if (Array.isArray(modeValue.partialAliasOf) && !modeValue.partialAliasOf.length) modeValue.partialAliasOf.push({});
|
|
3115
|
-
partialAliasOf = modeValue.partialAliasOf[0];
|
|
3116
|
-
}
|
|
3117
|
-
for (let i = 0; i < partial.length; i++) {
|
|
3118
|
-
let key = partial[i];
|
|
3119
|
-
if (String(Number(key)) === key) key = Number(key);
|
|
3120
|
-
if (key in node && typeof node[key] !== "undefined") {
|
|
3121
|
-
node = node[key];
|
|
3122
|
-
if (sourceNode.type === "Object") sourceNode = getObjMember(sourceNode, key) ?? sourceNode;
|
|
3123
|
-
else if (sourceNode.type === "Array") sourceNode = sourceNode.elements[key]?.value ?? sourceNode;
|
|
3124
|
-
}
|
|
3125
|
-
if (i === partial.length - 1) {
|
|
3126
|
-
const aliasedID = getTokenRef(refChain[0]);
|
|
3127
|
-
if (!(aliasedID in tokens)) {
|
|
3128
|
-
logger.error({
|
|
3129
|
-
group: "parser",
|
|
3130
|
-
label: "init",
|
|
3131
|
-
message: `Invalid alias: ${aliasedID}`,
|
|
3132
|
-
node: sourceNode,
|
|
3133
|
-
src: sources[tokens[rootRef].source.filename]?.src
|
|
3134
|
-
});
|
|
3135
|
-
break;
|
|
3136
|
-
}
|
|
3137
|
-
partialAliasOf[key] = refToTokenID(aliasedID);
|
|
3138
|
-
}
|
|
3139
|
-
if (!(key in partialAliasOf)) partialAliasOf[key] = Array.isArray(node) ? [] : {};
|
|
3140
|
-
partialAliasOf = partialAliasOf[key];
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
const aliasedByRefs = [jsonID, ...refChain].reverse();
|
|
3144
|
-
for (let i = 0; i < aliasedByRefs.length; i++) {
|
|
3145
|
-
const baseRef = getTokenRef(aliasedByRefs[i]);
|
|
3146
|
-
const baseToken = tokens[baseRef]?.mode[mode] || tokens[baseRef];
|
|
3147
|
-
if (!baseToken) continue;
|
|
3148
|
-
const upstream = aliasedByRefs.slice(i + 1);
|
|
3149
|
-
if (!upstream.length) break;
|
|
3150
|
-
if (!baseToken.aliasedBy) baseToken.aliasedBy = [];
|
|
3151
|
-
for (let j = 0; j < upstream.length; j++) {
|
|
3152
|
-
const downstream = refToTokenID(upstream[j]);
|
|
3153
|
-
if (!baseToken.aliasedBy.includes(downstream)) {
|
|
3154
|
-
baseToken.aliasedBy.push(downstream);
|
|
3155
|
-
if (mode === ".") tokens[baseRef].aliasedBy = baseToken.aliasedBy;
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
baseToken.aliasedBy.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
3159
|
-
}
|
|
3160
|
-
if (mode === ".") {
|
|
3161
|
-
tokens[rootRef].aliasChain = modeValue.aliasChain;
|
|
3162
|
-
tokens[rootRef].aliasedBy = modeValue.aliasedBy;
|
|
3163
|
-
tokens[rootRef].aliasOf = modeValue.aliasOf;
|
|
3164
|
-
tokens[rootRef].dependencies = modeValue.dependencies;
|
|
3165
|
-
tokens[rootRef].partialAliasOf = modeValue.partialAliasOf;
|
|
3199
|
+
if (Array.isArray(source)) for (const item of source) resolvePartials(item, {
|
|
3200
|
+
resolver,
|
|
3201
|
+
logger
|
|
3202
|
+
});
|
|
3203
|
+
else if (typeof source === "object") for (const k of Object.keys(source)) if (k === "$ref") {
|
|
3204
|
+
const $ref = source[k];
|
|
3205
|
+
const { url, subpath = [] } = parseRef($ref);
|
|
3206
|
+
if (url !== "." || !subpath.length) logger.error({
|
|
3207
|
+
...entry,
|
|
3208
|
+
message: `Could not load $ref ${JSON.stringify($ref)}`
|
|
3209
|
+
});
|
|
3210
|
+
const found = findObject(resolver, subpath ?? [], logger);
|
|
3211
|
+
if (subpath[0] === "sets" || subpath[0] === "modifiers") {
|
|
3212
|
+
found.type = subpath[0].replace(/s$/, "");
|
|
3213
|
+
found.name = subpath[1];
|
|
3166
3214
|
}
|
|
3215
|
+
if (found) {
|
|
3216
|
+
for (const k2 of Object.keys(found)) source[k2] = found[k2];
|
|
3217
|
+
delete source.$ref;
|
|
3218
|
+
} else logger.error({
|
|
3219
|
+
...entry,
|
|
3220
|
+
message: `Could not find ${JSON.stringify($ref)}`
|
|
3221
|
+
});
|
|
3222
|
+
} else resolvePartials(source[k], {
|
|
3223
|
+
resolver,
|
|
3224
|
+
logger
|
|
3225
|
+
});
|
|
3226
|
+
}
|
|
3227
|
+
function findObject(dict, path, logger) {
|
|
3228
|
+
let node = dict;
|
|
3229
|
+
for (const idRaw of path) {
|
|
3230
|
+
const id = idRaw.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
3231
|
+
if (!(id in node)) logger.error({
|
|
3232
|
+
group: "parser",
|
|
3233
|
+
label: "resolver",
|
|
3234
|
+
message: `Could not load $ref ${encodeFragment(path)}`
|
|
3235
|
+
});
|
|
3236
|
+
node = node[id];
|
|
3167
3237
|
}
|
|
3238
|
+
return node;
|
|
3168
3239
|
}
|
|
3240
|
+
|
|
3241
|
+
//#endregion
|
|
3242
|
+
//#region src/resolver/validate.ts
|
|
3169
3243
|
/**
|
|
3170
|
-
*
|
|
3171
|
-
*
|
|
3172
|
-
*
|
|
3244
|
+
* Determine whether this is likely a resolver
|
|
3245
|
+
* We use terms the word “likely” because this occurs before validation. Since
|
|
3246
|
+
* we may be dealing with a doc _intended_ to be a resolver, but may be lacking
|
|
3247
|
+
* some critical information, how can we determine intent? There’s a bit of
|
|
3248
|
+
* guesswork here, but we try and find a reasonable edge case where we sniff out
|
|
3249
|
+
* invalid DTCG syntax that a resolver doc would have.
|
|
3173
3250
|
*/
|
|
3174
|
-
function
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
},
|
|
3199
|
-
strokeStyle: { dashArray: ["dimension"] },
|
|
3200
|
-
transition: {
|
|
3201
|
-
duration: ["duration"],
|
|
3202
|
-
delay: ["duration"],
|
|
3203
|
-
timingFunction: ["cubicBezier"]
|
|
3204
|
-
},
|
|
3205
|
-
typography: {
|
|
3206
|
-
fontFamily: ["fontFamily"],
|
|
3207
|
-
fontWeight: ["fontWeight"],
|
|
3208
|
-
fontSize: ["dimension"],
|
|
3209
|
-
lineHeight: ["dimension", "number"],
|
|
3210
|
-
letterSpacing: ["dimension"]
|
|
3251
|
+
function isLikelyResolver(doc) {
|
|
3252
|
+
if (doc.body.type !== "Object") return false;
|
|
3253
|
+
for (const member of doc.body.members) {
|
|
3254
|
+
if (member.name.type !== "String") continue;
|
|
3255
|
+
switch (member.name.value) {
|
|
3256
|
+
case "name":
|
|
3257
|
+
case "description":
|
|
3258
|
+
case "version":
|
|
3259
|
+
if (member.value.type === "String") return true;
|
|
3260
|
+
break;
|
|
3261
|
+
case "sets":
|
|
3262
|
+
case "modifiers":
|
|
3263
|
+
if (member.value.type !== "Object") continue;
|
|
3264
|
+
if (getObjMember(member.value, "description")?.type === "String") return true;
|
|
3265
|
+
if (member.name.value === "sets" && getObjMember(member.value, "sources")?.type === "Array") return true;
|
|
3266
|
+
else if (member.name.value === "modifiers") {
|
|
3267
|
+
const contexts = getObjMember(member.value, "contexts");
|
|
3268
|
+
if (contexts?.type === "Object" && contexts.members.some((m) => m.value.type === "Array")) return true;
|
|
3269
|
+
}
|
|
3270
|
+
break;
|
|
3271
|
+
case "resolutionOrder":
|
|
3272
|
+
if (member.value.type === "Array") return true;
|
|
3273
|
+
break;
|
|
3274
|
+
}
|
|
3211
3275
|
}
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
const MESSAGE_EXPECTED = {
|
|
3279
|
+
STRING: "Expected string.",
|
|
3280
|
+
OBJECT: "Expected object.",
|
|
3281
|
+
ARRAY: "Expected array."
|
|
3212
3282
|
};
|
|
3213
3283
|
/**
|
|
3214
|
-
*
|
|
3284
|
+
* Validate a resolver document.
|
|
3285
|
+
* There’s a ton of boilerplate here, only to surface detailed code frames. Is there a better abstraction?
|
|
3215
3286
|
*/
|
|
3216
|
-
function
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3287
|
+
function validateResolver(node, { logger, src }) {
|
|
3288
|
+
const entry = {
|
|
3289
|
+
group: "parser",
|
|
3290
|
+
label: "resolver",
|
|
3291
|
+
src
|
|
3292
|
+
};
|
|
3293
|
+
if (node.body.type !== "Object") logger.error({
|
|
3294
|
+
...entry,
|
|
3295
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3296
|
+
node
|
|
3297
|
+
});
|
|
3298
|
+
const errors = [];
|
|
3299
|
+
let hasVersion = false;
|
|
3300
|
+
let hasResolutionOrder = false;
|
|
3301
|
+
for (const member of node.body.members) {
|
|
3302
|
+
if (member.name.type !== "String") continue;
|
|
3303
|
+
switch (member.name.value) {
|
|
3304
|
+
case "name":
|
|
3305
|
+
case "description":
|
|
3306
|
+
if (member.value.type !== "String") errors.push({
|
|
3307
|
+
...entry,
|
|
3308
|
+
message: MESSAGE_EXPECTED.STRING
|
|
3237
3309
|
});
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3310
|
+
break;
|
|
3311
|
+
case "version":
|
|
3312
|
+
hasVersion = true;
|
|
3313
|
+
if (member.value.type !== "String" || member.value.value !== "2025.10") errors.push({
|
|
3314
|
+
...entry,
|
|
3315
|
+
message: `Expected "version" to be "2025.10".`,
|
|
3316
|
+
node: member.value
|
|
3243
3317
|
});
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
path: [...path, key]
|
|
3264
|
-
}).$value;
|
|
3265
|
-
}
|
|
3266
|
-
return { $value: value };
|
|
3318
|
+
break;
|
|
3319
|
+
case "sets":
|
|
3320
|
+
case "modifiers":
|
|
3321
|
+
if (member.value.type !== "Object") errors.push({
|
|
3322
|
+
...entry,
|
|
3323
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3324
|
+
node: member.value
|
|
3325
|
+
});
|
|
3326
|
+
else for (const item of member.value.members) if (item.value.type !== "Object") errors.push({
|
|
3327
|
+
...entry,
|
|
3328
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3329
|
+
node: item.value
|
|
3330
|
+
});
|
|
3331
|
+
else {
|
|
3332
|
+
const validator = member.name.value === "sets" ? validateSet : validateModifier;
|
|
3333
|
+
errors.push(...validator(item.value, false, {
|
|
3334
|
+
logger,
|
|
3335
|
+
src
|
|
3336
|
+
}));
|
|
3267
3337
|
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3338
|
+
break;
|
|
3339
|
+
case "resolutionOrder":
|
|
3340
|
+
hasResolutionOrder = true;
|
|
3341
|
+
if (member.value.type !== "Array") errors.push({
|
|
3342
|
+
...entry,
|
|
3343
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
3344
|
+
node: member.value
|
|
3345
|
+
});
|
|
3346
|
+
else if (member.value.elements.length === 0) errors.push({
|
|
3347
|
+
...entry,
|
|
3348
|
+
message: `"resolutionOrder" can’t be empty array.`,
|
|
3349
|
+
node: member.value
|
|
3350
|
+
});
|
|
3351
|
+
else for (const item of member.value.elements) if (item.value.type !== "Object") errors.push({
|
|
3352
|
+
...entry,
|
|
3353
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3354
|
+
node: item.value
|
|
3355
|
+
});
|
|
3356
|
+
else {
|
|
3357
|
+
const itemMembers = getObjMembers(item.value);
|
|
3358
|
+
if (itemMembers.$ref?.type === "String") continue;
|
|
3359
|
+
if (itemMembers.type?.type === "String") if (itemMembers.type.value === "set") validateSet(item.value, true, {
|
|
3360
|
+
logger,
|
|
3361
|
+
src
|
|
3362
|
+
});
|
|
3363
|
+
else if (itemMembers.type.value === "modifier") validateModifier(item.value, true, {
|
|
3364
|
+
logger,
|
|
3365
|
+
src
|
|
3366
|
+
});
|
|
3367
|
+
else errors.push({
|
|
3368
|
+
...entry,
|
|
3369
|
+
message: `Unknown type ${JSON.stringify(itemMembers.type.value)}`,
|
|
3370
|
+
node: itemMembers.type
|
|
3371
|
+
});
|
|
3372
|
+
if (itemMembers.sources?.type === "Array") validateSet(item.value, true, {
|
|
3373
|
+
logger,
|
|
3374
|
+
src
|
|
3375
|
+
});
|
|
3376
|
+
else if (itemMembers.contexts?.type === "Object") validateModifier(item.value, true, {
|
|
3377
|
+
logger,
|
|
3378
|
+
src
|
|
3379
|
+
});
|
|
3380
|
+
else if (itemMembers.name?.type === "String" || itemMembers.description?.type === "String") validateSet(item.value, true, {
|
|
3381
|
+
logger,
|
|
3382
|
+
src
|
|
3273
3383
|
});
|
|
3274
|
-
return { $value: value };
|
|
3275
3384
|
}
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3385
|
+
break;
|
|
3386
|
+
case "$defs":
|
|
3387
|
+
case "$extensions":
|
|
3388
|
+
if (member.value.type !== "Object") errors.push({
|
|
3389
|
+
...entry,
|
|
3390
|
+
message: `Expected object`,
|
|
3391
|
+
node: member.value
|
|
3392
|
+
});
|
|
3393
|
+
break;
|
|
3394
|
+
case "$schema":
|
|
3395
|
+
case "$ref":
|
|
3396
|
+
if (member.value.type !== "String") errors.push({
|
|
3397
|
+
...entry,
|
|
3398
|
+
message: `Expected string`,
|
|
3399
|
+
node: member.value
|
|
3282
3400
|
});
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
};
|
|
3287
|
-
return {
|
|
3288
|
-
$type: tokens[resolvedID].$type,
|
|
3289
|
-
$value: tokens[resolvedID].mode[mode]?.$value || tokens[resolvedID].$value
|
|
3290
|
-
};
|
|
3291
|
-
}
|
|
3292
|
-
const pathBase = mode === "." ? token.jsonID : `${token.jsonID}/$extensions/mode/${mode}`;
|
|
3293
|
-
const { $type, $value } = traverseAndResolve(token.mode[mode].$value, {
|
|
3294
|
-
node: aliasEntry.node,
|
|
3295
|
-
expectedTypes: token.$type ? [token.$type] : void 0,
|
|
3296
|
-
path: [pathBase, "$value"]
|
|
3297
|
-
});
|
|
3298
|
-
if (!token.$type) token.$type = $type;
|
|
3299
|
-
if ($value) token.mode[mode].$value = $value;
|
|
3300
|
-
if (mode === ".") token.$value = token.mode[mode].$value;
|
|
3301
|
-
}
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
//#endregion
|
|
3306
|
-
//#region src/parse/process.ts
|
|
3307
|
-
function processTokens(rootSource, { config, logger, sourceByFilename }) {
|
|
3308
|
-
const entry = {
|
|
3309
|
-
group: "parser",
|
|
3310
|
-
label: "init"
|
|
3311
|
-
};
|
|
3312
|
-
const refMap = {};
|
|
3313
|
-
function resolveRef(node, chain) {
|
|
3314
|
-
const { subpath } = parseRef(node.value);
|
|
3315
|
-
if (!subpath) logger.error({
|
|
3316
|
-
...entry,
|
|
3317
|
-
message: "Can’t resolve $ref",
|
|
3318
|
-
node,
|
|
3319
|
-
src: rootSource.src
|
|
3320
|
-
});
|
|
3321
|
-
const next = findNode(rootSource.document, subpath);
|
|
3322
|
-
if (next?.type === "Object") {
|
|
3323
|
-
const next$ref = getObjMember(next, "$ref");
|
|
3324
|
-
if (next$ref && next$ref.type === "String") {
|
|
3325
|
-
if (chain.includes(next$ref.value)) logger.error({
|
|
3401
|
+
break;
|
|
3402
|
+
default:
|
|
3403
|
+
errors.push({
|
|
3326
3404
|
...entry,
|
|
3327
|
-
message: `
|
|
3328
|
-
node:
|
|
3329
|
-
src
|
|
3405
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
3406
|
+
node: member.name,
|
|
3407
|
+
src
|
|
3330
3408
|
});
|
|
3331
|
-
|
|
3332
|
-
return resolveRef(next$ref, chain);
|
|
3333
|
-
}
|
|
3334
|
-
}
|
|
3335
|
-
return next;
|
|
3336
|
-
}
|
|
3337
|
-
const inlineStart = performance.now();
|
|
3338
|
-
traverse(rootSource.document, { enter(node, _parent, rawPath) {
|
|
3339
|
-
if (rawPath.includes("$extensions") || node.type !== "Object") return;
|
|
3340
|
-
const $ref = node.type === "Object" ? getObjMember(node, "$ref") : void 0;
|
|
3341
|
-
if (!$ref) return;
|
|
3342
|
-
if ($ref.type !== "String") logger.error({
|
|
3343
|
-
...entry,
|
|
3344
|
-
message: "Invalid $ref. Expected string.",
|
|
3345
|
-
node: $ref,
|
|
3346
|
-
src: rootSource.src
|
|
3347
|
-
});
|
|
3348
|
-
const jsonID = encodeFragment(rawPath);
|
|
3349
|
-
refMap[jsonID] = {
|
|
3350
|
-
filename: rootSource.filename.href,
|
|
3351
|
-
refChain: [$ref.value]
|
|
3352
|
-
};
|
|
3353
|
-
const resolved = resolveRef($ref, refMap[jsonID].refChain);
|
|
3354
|
-
if (resolved.type === "Object") {
|
|
3355
|
-
node.members.splice(node.members.findIndex((m) => m.name.type === "String" && m.name.value === "$ref"), 1);
|
|
3356
|
-
replaceNode(node, mergeObjects(resolved, node));
|
|
3357
|
-
} else replaceNode(node, resolved);
|
|
3358
|
-
} });
|
|
3359
|
-
logger.debug({
|
|
3360
|
-
...entry,
|
|
3361
|
-
message: "Inline aliases",
|
|
3362
|
-
timing: performance.now() - inlineStart
|
|
3363
|
-
});
|
|
3364
|
-
function flatten$extends(node, chain) {
|
|
3365
|
-
const memberKeys = node.members.map((m) => m.name.type === "String" && m.name.value).filter(Boolean);
|
|
3366
|
-
let extended;
|
|
3367
|
-
if (memberKeys.includes("$extends")) {
|
|
3368
|
-
const $extends = getObjMember(node, "$extends");
|
|
3369
|
-
if ($extends.type !== "String") logger.error({
|
|
3370
|
-
...entry,
|
|
3371
|
-
message: "$extends must be a string",
|
|
3372
|
-
node: $extends,
|
|
3373
|
-
src: rootSource.src
|
|
3374
|
-
});
|
|
3375
|
-
if (memberKeys.includes("$value")) logger.error({
|
|
3376
|
-
...entry,
|
|
3377
|
-
message: "$extends can’t exist within a token",
|
|
3378
|
-
node: $extends,
|
|
3379
|
-
src: rootSource.src
|
|
3380
|
-
});
|
|
3381
|
-
const next = isAlias($extends.value) ? aliasToGroupRef($extends.value) : void 0;
|
|
3382
|
-
if (!next) logger.error({
|
|
3383
|
-
...entry,
|
|
3384
|
-
message: "$extends must be a valid alias",
|
|
3385
|
-
node: $extends,
|
|
3386
|
-
src: rootSource.src
|
|
3387
|
-
});
|
|
3388
|
-
if (chain.includes(next.$ref) || chain.some((value) => value.startsWith(next.$ref) || next.$ref.startsWith(value))) logger.error({
|
|
3389
|
-
...entry,
|
|
3390
|
-
message: "Circular $extends detected",
|
|
3391
|
-
node: $extends,
|
|
3392
|
-
src: rootSource.src
|
|
3393
|
-
});
|
|
3394
|
-
chain.push(next.$ref);
|
|
3395
|
-
extended = findNode(rootSource.document, parseRef(next.$ref).subpath ?? []);
|
|
3396
|
-
if (!extended) logger.error({
|
|
3397
|
-
...entry,
|
|
3398
|
-
message: "Could not resolve $extends",
|
|
3399
|
-
node: $extends,
|
|
3400
|
-
src: rootSource.src
|
|
3401
|
-
});
|
|
3402
|
-
if (extended.type !== "Object") logger.error({
|
|
3403
|
-
...entry,
|
|
3404
|
-
message: "$extends must resolve to a group of tokens",
|
|
3405
|
-
node
|
|
3406
|
-
});
|
|
3407
|
-
flatten$extends(extended, chain);
|
|
3408
|
-
replaceNode(node, mergeObjects(extended, node));
|
|
3409
|
+
break;
|
|
3409
3410
|
}
|
|
3410
|
-
for (const member of node.members) if (member.value.type === "Object" && member.name.type === "String" && !["$value", "$extensions"].includes(member.name.value)) traverse(member.value, { enter(subnode, _parent) {
|
|
3411
|
-
if (subnode.type === "Object") flatten$extends(subnode, chain);
|
|
3412
|
-
} });
|
|
3413
3411
|
}
|
|
3414
|
-
|
|
3415
|
-
flatten$extends(rootSource.document.body, []);
|
|
3416
|
-
logger.debug({
|
|
3412
|
+
if (!hasVersion) errors.push({
|
|
3417
3413
|
...entry,
|
|
3418
|
-
message:
|
|
3419
|
-
|
|
3414
|
+
message: `Missing "version".`,
|
|
3415
|
+
node,
|
|
3416
|
+
src
|
|
3420
3417
|
});
|
|
3421
|
-
|
|
3422
|
-
const tokens = {};
|
|
3423
|
-
const tokenIDs = [];
|
|
3424
|
-
const groups = {};
|
|
3425
|
-
const isResolver = isLikelyResolver(rootSource.document);
|
|
3426
|
-
traverse(rootSource.document, { enter(node, _parent, rawPath) {
|
|
3427
|
-
if (node.type !== "Object") return;
|
|
3428
|
-
groupFromNode(node, {
|
|
3429
|
-
path: isResolver ? filterResolverPaths(rawPath) : rawPath,
|
|
3430
|
-
groups
|
|
3431
|
-
});
|
|
3432
|
-
const token = tokenFromNode(node, {
|
|
3433
|
-
groups,
|
|
3434
|
-
ignore: config.ignore,
|
|
3435
|
-
path: isResolver ? filterResolverPaths(rawPath) : rawPath,
|
|
3436
|
-
source: rootSource
|
|
3437
|
-
});
|
|
3438
|
-
if (token) {
|
|
3439
|
-
tokenIDs.push(token.jsonID);
|
|
3440
|
-
tokens[token.jsonID] = token;
|
|
3441
|
-
}
|
|
3442
|
-
} });
|
|
3443
|
-
logger.debug({
|
|
3418
|
+
if (!hasResolutionOrder) errors.push({
|
|
3444
3419
|
...entry,
|
|
3445
|
-
message:
|
|
3446
|
-
|
|
3420
|
+
message: `Missing "resolutionOrder".`,
|
|
3421
|
+
node,
|
|
3422
|
+
src
|
|
3447
3423
|
});
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3424
|
+
if (errors.length) logger.error(...errors);
|
|
3425
|
+
}
|
|
3426
|
+
function validateSet(node, isInline = false, { src }) {
|
|
3427
|
+
const entry = {
|
|
3428
|
+
group: "parser",
|
|
3429
|
+
label: "resolver",
|
|
3430
|
+
src
|
|
3431
|
+
};
|
|
3432
|
+
const errors = [];
|
|
3433
|
+
let hasName = !isInline;
|
|
3434
|
+
let hasType = !isInline;
|
|
3435
|
+
let hasSources = false;
|
|
3436
|
+
for (const member of node.members) {
|
|
3437
|
+
if (member.name.type !== "String") continue;
|
|
3438
|
+
switch (member.name.value) {
|
|
3439
|
+
case "name":
|
|
3440
|
+
hasName = true;
|
|
3441
|
+
if (member.value.type !== "String") errors.push({
|
|
3442
|
+
...entry,
|
|
3443
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3444
|
+
node: member.value
|
|
3445
|
+
});
|
|
3446
|
+
break;
|
|
3447
|
+
case "description":
|
|
3448
|
+
if (member.value.type !== "String") errors.push({
|
|
3449
|
+
...entry,
|
|
3450
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3451
|
+
node: member.value
|
|
3452
|
+
});
|
|
3453
|
+
break;
|
|
3454
|
+
case "type":
|
|
3455
|
+
hasType = true;
|
|
3456
|
+
if (member.value.type !== "String") errors.push({
|
|
3457
|
+
...entry,
|
|
3458
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3459
|
+
node: member.value
|
|
3460
|
+
});
|
|
3461
|
+
else if (member.value.value !== "set") errors.push({
|
|
3462
|
+
...entry,
|
|
3463
|
+
message: "\"type\" must be \"set\"."
|
|
3464
|
+
});
|
|
3465
|
+
break;
|
|
3466
|
+
case "sources":
|
|
3467
|
+
hasSources = true;
|
|
3468
|
+
if (member.value.type !== "Array") errors.push({
|
|
3469
|
+
...entry,
|
|
3470
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
3471
|
+
node: member.value
|
|
3472
|
+
});
|
|
3473
|
+
else if (member.value.elements.length === 0) errors.push({
|
|
3474
|
+
...entry,
|
|
3475
|
+
message: `"sources" can’t be empty array.`,
|
|
3476
|
+
node: member.value
|
|
3477
|
+
});
|
|
3478
|
+
else for (const source of member.value.elements) if (source.value.type !== "Object") errors.push({
|
|
3479
|
+
...entry,
|
|
3480
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3481
|
+
node: source.value
|
|
3482
|
+
});
|
|
3483
|
+
break;
|
|
3484
|
+
case "$defs":
|
|
3485
|
+
case "$extensions":
|
|
3486
|
+
if (member.value.type !== "Object") errors.push({
|
|
3487
|
+
...entry,
|
|
3488
|
+
message: `Expected object`,
|
|
3489
|
+
node: member.value
|
|
3490
|
+
});
|
|
3491
|
+
break;
|
|
3492
|
+
case "$ref":
|
|
3493
|
+
if (member.value.type !== "String") errors.push({
|
|
3494
|
+
...entry,
|
|
3495
|
+
message: `Expected string`,
|
|
3496
|
+
node: member.value
|
|
3497
|
+
});
|
|
3498
|
+
break;
|
|
3499
|
+
default:
|
|
3500
|
+
errors.push({
|
|
3501
|
+
...entry,
|
|
3502
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
3503
|
+
node: member.name
|
|
3504
|
+
});
|
|
3505
|
+
break;
|
|
3462
3506
|
}
|
|
3463
|
-
} });
|
|
3464
|
-
resolveAliases(tokens, {
|
|
3465
|
-
logger,
|
|
3466
|
-
sources: sourceByFilename,
|
|
3467
|
-
refMap
|
|
3468
|
-
});
|
|
3469
|
-
logger.debug({
|
|
3470
|
-
...entry,
|
|
3471
|
-
message: "Parsing: 2nd pass",
|
|
3472
|
-
timing: performance.now() - secondPass
|
|
3473
|
-
});
|
|
3474
|
-
const aliasStart = performance.now();
|
|
3475
|
-
graphAliases(refMap, {
|
|
3476
|
-
tokens,
|
|
3477
|
-
logger,
|
|
3478
|
-
sources: sourceByFilename
|
|
3479
|
-
});
|
|
3480
|
-
logger.debug({
|
|
3481
|
-
...entry,
|
|
3482
|
-
message: "Alias graph built",
|
|
3483
|
-
timing: performance.now() - aliasStart
|
|
3484
|
-
});
|
|
3485
|
-
const normalizeStart = performance.now();
|
|
3486
|
-
for (const id of tokenIDs) {
|
|
3487
|
-
const token = tokens[id];
|
|
3488
|
-
normalize(token, {
|
|
3489
|
-
logger,
|
|
3490
|
-
src: sourceByFilename[token.source.filename]?.src
|
|
3491
|
-
});
|
|
3492
3507
|
}
|
|
3493
|
-
|
|
3508
|
+
if (!hasName) errors.push({
|
|
3494
3509
|
...entry,
|
|
3495
|
-
message:
|
|
3496
|
-
|
|
3510
|
+
message: `Missing "name".`,
|
|
3511
|
+
node
|
|
3497
3512
|
});
|
|
3498
|
-
|
|
3499
|
-
const tokensSorted = {};
|
|
3500
|
-
tokenIDs.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
3501
|
-
for (const path of tokenIDs) {
|
|
3502
|
-
const id = refToTokenID(path);
|
|
3503
|
-
tokensSorted[id] = tokens[path];
|
|
3504
|
-
}
|
|
3505
|
-
for (const group of Object.values(groups)) group.tokens.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
|
|
3506
|
-
logger.debug({
|
|
3513
|
+
if (!hasType) errors.push({
|
|
3507
3514
|
...entry,
|
|
3508
|
-
message: "
|
|
3509
|
-
|
|
3510
|
-
});
|
|
3511
|
-
return tokensSorted;
|
|
3512
|
-
}
|
|
3513
|
-
|
|
3514
|
-
//#endregion
|
|
3515
|
-
//#region src/resolver/normalize.ts
|
|
3516
|
-
/** Normalize resolver (assuming it’s been validated) */
|
|
3517
|
-
async function normalizeResolver(document, { logger, filename, req, src, yamlToMomoa }) {
|
|
3518
|
-
const resolverBundle = await bundle([{
|
|
3519
|
-
filename,
|
|
3520
|
-
src
|
|
3521
|
-
}], {
|
|
3522
|
-
req,
|
|
3523
|
-
yamlToMomoa
|
|
3524
|
-
});
|
|
3525
|
-
const resolverSource = momoa.evaluate(resolverBundle.document);
|
|
3526
|
-
replaceNode(document, resolverBundle.document);
|
|
3527
|
-
for (const set of Object.values(resolverSource.sets ?? {})) for (const source of set.sources) resolvePartials(source, {
|
|
3528
|
-
resolver: resolverSource,
|
|
3529
|
-
logger
|
|
3530
|
-
});
|
|
3531
|
-
for (const modifier of Object.values(resolverSource.modifiers ?? {})) for (const context of Object.values(modifier.contexts)) for (const source of context) resolvePartials(source, {
|
|
3532
|
-
resolver: resolverSource,
|
|
3533
|
-
logger
|
|
3515
|
+
message: `"type": "set" missing.`,
|
|
3516
|
+
node
|
|
3534
3517
|
});
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3518
|
+
if (!hasSources) errors.push({
|
|
3519
|
+
...entry,
|
|
3520
|
+
message: `Missing "sources".`,
|
|
3521
|
+
node
|
|
3538
3522
|
});
|
|
3539
|
-
return
|
|
3540
|
-
name: resolverSource.name,
|
|
3541
|
-
version: resolverSource.version,
|
|
3542
|
-
description: resolverSource.description,
|
|
3543
|
-
sets: resolverSource.sets,
|
|
3544
|
-
modifiers: resolverSource.modifiers,
|
|
3545
|
-
resolutionOrder: resolverSource.resolutionOrder,
|
|
3546
|
-
_source: {
|
|
3547
|
-
filename,
|
|
3548
|
-
document
|
|
3549
|
-
}
|
|
3550
|
-
};
|
|
3523
|
+
return errors;
|
|
3551
3524
|
}
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
if (!source) return;
|
|
3525
|
+
function validateModifier(node, isInline = false, { src }) {
|
|
3526
|
+
const errors = [];
|
|
3555
3527
|
const entry = {
|
|
3556
3528
|
group: "parser",
|
|
3557
|
-
label: "resolver"
|
|
3529
|
+
label: "resolver",
|
|
3530
|
+
src
|
|
3558
3531
|
};
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3532
|
+
let hasName = !isInline;
|
|
3533
|
+
let hasType = !isInline;
|
|
3534
|
+
let hasContexts = false;
|
|
3535
|
+
for (const member of node.members) {
|
|
3536
|
+
if (member.name.type !== "String") continue;
|
|
3537
|
+
switch (member.name.value) {
|
|
3538
|
+
case "name":
|
|
3539
|
+
hasName = true;
|
|
3540
|
+
if (member.value.type !== "String") errors.push({
|
|
3541
|
+
...entry,
|
|
3542
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3543
|
+
node: member.value
|
|
3544
|
+
});
|
|
3545
|
+
break;
|
|
3546
|
+
case "description":
|
|
3547
|
+
if (member.value.type !== "String") errors.push({
|
|
3548
|
+
...entry,
|
|
3549
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3550
|
+
node: member.value
|
|
3551
|
+
});
|
|
3552
|
+
break;
|
|
3553
|
+
case "type":
|
|
3554
|
+
hasType = true;
|
|
3555
|
+
if (member.value.type !== "String") errors.push({
|
|
3556
|
+
...entry,
|
|
3557
|
+
message: MESSAGE_EXPECTED.STRING,
|
|
3558
|
+
node: member.value
|
|
3559
|
+
});
|
|
3560
|
+
else if (member.value.value !== "modifier") errors.push({
|
|
3561
|
+
...entry,
|
|
3562
|
+
message: "\"type\" must be \"modifier\"."
|
|
3563
|
+
});
|
|
3564
|
+
break;
|
|
3565
|
+
case "contexts":
|
|
3566
|
+
hasContexts = true;
|
|
3567
|
+
if (member.value.type !== "Object") errors.push({
|
|
3568
|
+
...entry,
|
|
3569
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3570
|
+
node: member.value
|
|
3571
|
+
});
|
|
3572
|
+
else if (member.value.members.length === 0) errors.push({
|
|
3573
|
+
...entry,
|
|
3574
|
+
message: `"contexts" can’t be empty object.`,
|
|
3575
|
+
node: member.value
|
|
3576
|
+
});
|
|
3577
|
+
else for (const context of member.value.members) if (context.value.type !== "Array") errors.push({
|
|
3578
|
+
...entry,
|
|
3579
|
+
message: MESSAGE_EXPECTED.ARRAY,
|
|
3580
|
+
node: context.value
|
|
3581
|
+
});
|
|
3582
|
+
else for (const source of context.value.elements) if (source.value.type !== "Object") errors.push({
|
|
3583
|
+
...entry,
|
|
3584
|
+
message: MESSAGE_EXPECTED.OBJECT,
|
|
3585
|
+
node: source.value
|
|
3586
|
+
});
|
|
3587
|
+
break;
|
|
3588
|
+
case "default":
|
|
3589
|
+
if (member.value.type !== "String") errors.push({
|
|
3590
|
+
...entry,
|
|
3591
|
+
message: `Expected string`,
|
|
3592
|
+
node: member.value
|
|
3593
|
+
});
|
|
3594
|
+
else {
|
|
3595
|
+
const contexts = getObjMember(node, "contexts");
|
|
3596
|
+
if (!contexts || !getObjMember(contexts, member.value.value)) errors.push({
|
|
3597
|
+
...entry,
|
|
3598
|
+
message: "Invalid default context",
|
|
3599
|
+
node: member.value
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
break;
|
|
3603
|
+
case "$defs":
|
|
3604
|
+
case "$extensions":
|
|
3605
|
+
if (member.value.type !== "Object") errors.push({
|
|
3606
|
+
...entry,
|
|
3607
|
+
message: `Expected object`,
|
|
3608
|
+
node: member.value
|
|
3609
|
+
});
|
|
3610
|
+
break;
|
|
3611
|
+
case "$ref":
|
|
3612
|
+
if (member.value.type !== "String") errors.push({
|
|
3613
|
+
...entry,
|
|
3614
|
+
message: `Expected string`,
|
|
3615
|
+
node: member.value
|
|
3616
|
+
});
|
|
3617
|
+
break;
|
|
3618
|
+
default:
|
|
3619
|
+
errors.push({
|
|
3620
|
+
...entry,
|
|
3621
|
+
message: `Unknown key ${JSON.stringify(member.name.value)}`,
|
|
3622
|
+
node: member.name
|
|
3623
|
+
});
|
|
3624
|
+
break;
|
|
3574
3625
|
}
|
|
3575
|
-
if (found) {
|
|
3576
|
-
for (const k2 of Object.keys(found)) source[k2] = found[k2];
|
|
3577
|
-
delete source.$ref;
|
|
3578
|
-
} else logger.error({
|
|
3579
|
-
...entry,
|
|
3580
|
-
message: `Could not find ${JSON.stringify($ref)}`
|
|
3581
|
-
});
|
|
3582
|
-
} else resolvePartials(source[k], {
|
|
3583
|
-
resolver,
|
|
3584
|
-
logger
|
|
3585
|
-
});
|
|
3586
|
-
}
|
|
3587
|
-
function findObject(dict, path, logger) {
|
|
3588
|
-
let node = dict;
|
|
3589
|
-
for (const idRaw of path) {
|
|
3590
|
-
const id = idRaw.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
3591
|
-
if (!(id in node)) logger.error({
|
|
3592
|
-
group: "parser",
|
|
3593
|
-
label: "resolver",
|
|
3594
|
-
message: `Could not load $ref ${encodeFragment(path)}`
|
|
3595
|
-
});
|
|
3596
|
-
node = node[id];
|
|
3597
3626
|
}
|
|
3598
|
-
|
|
3627
|
+
if (!hasName) errors.push({
|
|
3628
|
+
...entry,
|
|
3629
|
+
message: `Missing "name".`,
|
|
3630
|
+
node
|
|
3631
|
+
});
|
|
3632
|
+
if (!hasType) errors.push({
|
|
3633
|
+
...entry,
|
|
3634
|
+
message: `"type": "modifier" missing.`,
|
|
3635
|
+
node
|
|
3636
|
+
});
|
|
3637
|
+
if (!hasContexts) errors.push({
|
|
3638
|
+
...entry,
|
|
3639
|
+
message: `Missing "contexts".`,
|
|
3640
|
+
node
|
|
3641
|
+
});
|
|
3642
|
+
return errors;
|
|
3599
3643
|
}
|
|
3600
3644
|
|
|
3601
3645
|
//#endregion
|
|
@@ -3687,8 +3731,8 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3687
3731
|
...inputDefaults,
|
|
3688
3732
|
...inputRaw
|
|
3689
3733
|
};
|
|
3690
|
-
const
|
|
3691
|
-
if (resolverCache[
|
|
3734
|
+
const permutationID = getPermutationID(input);
|
|
3735
|
+
if (resolverCache[permutationID]) return resolverCache[permutationID];
|
|
3692
3736
|
for (const item of resolverSource.resolutionOrder) switch (item.type) {
|
|
3693
3737
|
case "set":
|
|
3694
3738
|
for (const s of item.sources) tokensRaw = merge(tokensRaw, s);
|
|
@@ -3697,8 +3741,7 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3697
3741
|
const context = input[item.name];
|
|
3698
3742
|
const sources$1 = item.contexts[context];
|
|
3699
3743
|
if (!sources$1) logger.error({
|
|
3700
|
-
group: "
|
|
3701
|
-
label: "resolver",
|
|
3744
|
+
group: "resolver",
|
|
3702
3745
|
message: `Modifier ${item.name} has no context ${JSON.stringify(context)}.`
|
|
3703
3746
|
});
|
|
3704
3747
|
for (const s of sources$1 ?? []) tokensRaw = merge(tokensRaw, s);
|
|
@@ -3715,9 +3758,10 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3715
3758
|
config,
|
|
3716
3759
|
logger,
|
|
3717
3760
|
sourceByFilename: { [resolverSource._source.filename.href]: rootSource },
|
|
3761
|
+
isResolver: true,
|
|
3718
3762
|
sources
|
|
3719
3763
|
});
|
|
3720
|
-
resolverCache[
|
|
3764
|
+
resolverCache[permutationID] = tokens;
|
|
3721
3765
|
return tokens;
|
|
3722
3766
|
},
|
|
3723
3767
|
source: resolverSource,
|
|
@@ -3725,17 +3769,41 @@ function createResolver(resolverSource, { config, logger, sources }) {
|
|
|
3725
3769
|
if (!allPermutations.length) allPermutations.push(...calculatePermutations(Object.entries(validContexts)));
|
|
3726
3770
|
return allPermutations;
|
|
3727
3771
|
},
|
|
3728
|
-
isValidInput(input) {
|
|
3772
|
+
isValidInput(input, throwError = false) {
|
|
3729
3773
|
if (!input || typeof input !== "object") logger.error({
|
|
3730
|
-
group: "
|
|
3731
|
-
label: "resolver",
|
|
3774
|
+
group: "resolver",
|
|
3732
3775
|
message: `Invalid input: ${JSON.stringify(input)}.`
|
|
3733
3776
|
});
|
|
3734
|
-
|
|
3777
|
+
for (const k of Object.keys(input)) if (!(k in validContexts)) {
|
|
3778
|
+
if (throwError) logger.error({
|
|
3779
|
+
group: "resolver",
|
|
3780
|
+
message: `No such modifier ${JSON.stringify(k)}`
|
|
3781
|
+
});
|
|
3782
|
+
return false;
|
|
3783
|
+
}
|
|
3735
3784
|
for (const [name, contexts] of Object.entries(validContexts)) if (name in input) {
|
|
3736
|
-
if (!contexts.includes(input[name]))
|
|
3737
|
-
|
|
3785
|
+
if (!contexts.includes(input[name])) {
|
|
3786
|
+
if (throwError) logger.error({
|
|
3787
|
+
group: "resolver",
|
|
3788
|
+
message: `Modifier "${name}" has no context ${JSON.stringify(input[name])}.`
|
|
3789
|
+
});
|
|
3790
|
+
return false;
|
|
3791
|
+
}
|
|
3792
|
+
} else if (!(name in inputDefaults)) {
|
|
3793
|
+
if (throwError) logger.error({
|
|
3794
|
+
group: "resolver",
|
|
3795
|
+
message: `Modifier "${name}" missing value (no default set).`
|
|
3796
|
+
});
|
|
3797
|
+
return false;
|
|
3798
|
+
}
|
|
3738
3799
|
return true;
|
|
3800
|
+
},
|
|
3801
|
+
getPermutationID(input) {
|
|
3802
|
+
this.isValidInput(input, true);
|
|
3803
|
+
return getPermutationID({
|
|
3804
|
+
...inputDefaults,
|
|
3805
|
+
...input
|
|
3806
|
+
});
|
|
3739
3807
|
}
|
|
3740
3808
|
};
|
|
3741
3809
|
}
|