@terrazzo/parser 0.7.3 → 0.8.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/index.d.ts +705 -12
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4603 -12
  5. package/dist/index.js.map +1 -1
  6. package/package.json +11 -7
  7. package/src/build/index.ts +8 -6
  8. package/src/index.ts +76 -1
  9. package/dist/build/index.d.ts +0 -20
  10. package/dist/build/index.d.ts.map +0 -1
  11. package/dist/build/index.js +0 -166
  12. package/dist/build/index.js.map +0 -1
  13. package/dist/config.d.ts +0 -8
  14. package/dist/config.d.ts.map +0 -1
  15. package/dist/config.js +0 -290
  16. package/dist/config.js.map +0 -1
  17. package/dist/lib/code-frame.d.ts +0 -31
  18. package/dist/lib/code-frame.d.ts.map +0 -1
  19. package/dist/lib/code-frame.js +0 -108
  20. package/dist/lib/code-frame.js.map +0 -1
  21. package/dist/lint/index.d.ts +0 -12
  22. package/dist/lint/index.d.ts.map +0 -1
  23. package/dist/lint/index.js +0 -105
  24. package/dist/lint/index.js.map +0 -1
  25. package/dist/lint/plugin-core/index.d.ts +0 -13
  26. package/dist/lint/plugin-core/index.d.ts.map +0 -1
  27. package/dist/lint/plugin-core/index.js +0 -40
  28. package/dist/lint/plugin-core/index.js.map +0 -1
  29. package/dist/lint/plugin-core/lib/docs.d.ts +0 -2
  30. package/dist/lint/plugin-core/lib/docs.d.ts.map +0 -1
  31. package/dist/lint/plugin-core/lib/docs.js +0 -4
  32. package/dist/lint/plugin-core/lib/docs.js.map +0 -1
  33. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +0 -40
  34. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts.map +0 -1
  35. package/dist/lint/plugin-core/rules/a11y-min-contrast.js +0 -58
  36. package/dist/lint/plugin-core/rules/a11y-min-contrast.js.map +0 -1
  37. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +0 -14
  38. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts.map +0 -1
  39. package/dist/lint/plugin-core/rules/a11y-min-font-size.js +0 -45
  40. package/dist/lint/plugin-core/rules/a11y-min-font-size.js.map +0 -1
  41. package/dist/lint/plugin-core/rules/colorspace.d.ts +0 -15
  42. package/dist/lint/plugin-core/rules/colorspace.d.ts.map +0 -1
  43. package/dist/lint/plugin-core/rules/colorspace.js +0 -85
  44. package/dist/lint/plugin-core/rules/colorspace.js.map +0 -1
  45. package/dist/lint/plugin-core/rules/consistent-naming.d.ts +0 -12
  46. package/dist/lint/plugin-core/rules/consistent-naming.d.ts.map +0 -1
  47. package/dist/lint/plugin-core/rules/consistent-naming.js +0 -49
  48. package/dist/lint/plugin-core/rules/consistent-naming.js.map +0 -1
  49. package/dist/lint/plugin-core/rules/descriptions.d.ts +0 -10
  50. package/dist/lint/plugin-core/rules/descriptions.d.ts.map +0 -1
  51. package/dist/lint/plugin-core/rules/descriptions.js +0 -32
  52. package/dist/lint/plugin-core/rules/descriptions.js.map +0 -1
  53. package/dist/lint/plugin-core/rules/duplicate-values.d.ts +0 -10
  54. package/dist/lint/plugin-core/rules/duplicate-values.d.ts.map +0 -1
  55. package/dist/lint/plugin-core/rules/duplicate-values.js +0 -65
  56. package/dist/lint/plugin-core/rules/duplicate-values.js.map +0 -1
  57. package/dist/lint/plugin-core/rules/max-gamut.d.ts +0 -15
  58. package/dist/lint/plugin-core/rules/max-gamut.d.ts.map +0 -1
  59. package/dist/lint/plugin-core/rules/max-gamut.js +0 -101
  60. package/dist/lint/plugin-core/rules/max-gamut.js.map +0 -1
  61. package/dist/lint/plugin-core/rules/required-children.d.ts +0 -19
  62. package/dist/lint/plugin-core/rules/required-children.d.ts.map +0 -1
  63. package/dist/lint/plugin-core/rules/required-children.js +0 -78
  64. package/dist/lint/plugin-core/rules/required-children.js.map +0 -1
  65. package/dist/lint/plugin-core/rules/required-modes.d.ts +0 -14
  66. package/dist/lint/plugin-core/rules/required-modes.d.ts.map +0 -1
  67. package/dist/lint/plugin-core/rules/required-modes.js +0 -52
  68. package/dist/lint/plugin-core/rules/required-modes.js.map +0 -1
  69. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +0 -11
  70. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts.map +0 -1
  71. package/dist/lint/plugin-core/rules/required-typography-properties.js +0 -38
  72. package/dist/lint/plugin-core/rules/required-typography-properties.js.map +0 -1
  73. package/dist/logger.d.ts +0 -77
  74. package/dist/logger.d.ts.map +0 -1
  75. package/dist/logger.js +0 -136
  76. package/dist/logger.js.map +0 -1
  77. package/dist/parse/alias.d.ts +0 -34
  78. package/dist/parse/alias.d.ts.map +0 -1
  79. package/dist/parse/alias.js +0 -302
  80. package/dist/parse/alias.js.map +0 -1
  81. package/dist/parse/index.d.ts +0 -41
  82. package/dist/parse/index.d.ts.map +0 -1
  83. package/dist/parse/index.js +0 -273
  84. package/dist/parse/index.js.map +0 -1
  85. package/dist/parse/json.d.ts +0 -52
  86. package/dist/parse/json.d.ts.map +0 -1
  87. package/dist/parse/json.js +0 -168
  88. package/dist/parse/json.js.map +0 -1
  89. package/dist/parse/normalize.d.ts +0 -24
  90. package/dist/parse/normalize.d.ts.map +0 -1
  91. package/dist/parse/normalize.js +0 -176
  92. package/dist/parse/normalize.js.map +0 -1
  93. package/dist/parse/validate.d.ts +0 -90
  94. package/dist/parse/validate.d.ts.map +0 -1
  95. package/dist/parse/validate.js +0 -787
  96. package/dist/parse/validate.js.map +0 -1
  97. package/dist/types.d.ts +0 -265
  98. package/dist/types.d.ts.map +0 -1
  99. package/dist/types.js +0 -2
  100. package/dist/types.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,13 +1,4604 @@
1
- export { default as build } from './build/index.js';
2
- export * from './build/index.js';
3
- export { default as defineConfig } from './config.js';
4
- export * from './config.js';
5
- export { default as lintRunner } from './lint/index.js';
6
- export * from './lint/index.js';
7
- export { default as Logger } from './logger.js';
8
- export * from './logger.js';
9
- export { default as parse } from './parse/index.js';
10
- export * from './parse/index.js';
11
- export * from './types.js';
12
- export * from '@terrazzo/token-tools/dist/types.js';
1
+ import { isAlias, isTokenMatch, parseAlias, parseColor, pluralize, splitID, tokenToCulori } from "@terrazzo/token-tools";
2
+ import wcmatch from "wildcard-match";
3
+ import pc from "picocolors";
4
+ import { merge } from "merge-anything";
5
+ import { clampChroma, wcagContrast } from "culori";
6
+
7
+ //#region src/lib/code-frame.ts
8
+ /**
9
+ * Extract what lines should be marked and highlighted.
10
+ */
11
+ function getMarkerLines(loc, source, opts = {}) {
12
+ const startLoc = {
13
+ column: 0,
14
+ line: -1,
15
+ ...loc.start
16
+ };
17
+ const endLoc = {
18
+ ...startLoc,
19
+ ...loc.end
20
+ };
21
+ const { linesAbove = 2, linesBelow = 3 } = opts || {};
22
+ const startLine = startLoc.line;
23
+ const startColumn = startLoc.column;
24
+ const endLine = endLoc.line;
25
+ const endColumn = endLoc.column;
26
+ let start = Math.max(startLine - (linesAbove + 1), 0);
27
+ let end = Math.min(source.length, endLine + linesBelow);
28
+ if (startLine === -1) start = 0;
29
+ if (endLine === -1) end = source.length;
30
+ const lineDiff = endLine - startLine;
31
+ const markerLines = {};
32
+ if (lineDiff) for (let i = 0; i <= lineDiff; i++) {
33
+ const lineNumber = i + startLine;
34
+ if (!startColumn) markerLines[lineNumber] = true;
35
+ else if (i === 0) {
36
+ const sourceLength = source[lineNumber - 1].length;
37
+ markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
38
+ } else if (i === lineDiff) markerLines[lineNumber] = [0, endColumn];
39
+ else {
40
+ const sourceLength = source[lineNumber - i].length;
41
+ markerLines[lineNumber] = [0, sourceLength];
42
+ }
43
+ }
44
+ else if (startColumn === endColumn) if (startColumn) markerLines[startLine] = [startColumn, 0];
45
+ else markerLines[startLine] = true;
46
+ else markerLines[startLine] = [startColumn, endColumn - startColumn];
47
+ return {
48
+ start,
49
+ end,
50
+ markerLines
51
+ };
52
+ }
53
+ /**
54
+ * RegExp to test for newlines in terminal.
55
+ */
56
+ const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
57
+ function codeFrameColumns(rawLines, loc, opts = {}) {
58
+ const lines = rawLines.split(NEWLINE);
59
+ const { start, end, markerLines } = getMarkerLines(loc, lines, opts);
60
+ const hasColumns = loc.start && typeof loc.start.column === "number";
61
+ const numberMaxWidth = String(end).length;
62
+ let frame = rawLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
63
+ const number = start + 1 + index;
64
+ const paddedNumber = ` ${number}`.slice(-numberMaxWidth);
65
+ const gutter = ` ${paddedNumber} |`;
66
+ const hasMarker = markerLines[number];
67
+ const lastMarkerLine = !markerLines[number + 1];
68
+ if (hasMarker) {
69
+ let markerLine = "";
70
+ if (Array.isArray(hasMarker)) {
71
+ const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
72
+ const numberOfMarkers = hasMarker[1] || 1;
73
+ markerLine = [
74
+ "\n ",
75
+ gutter.replace(/\d/g, " "),
76
+ " ",
77
+ markerSpacing,
78
+ "^".repeat(numberOfMarkers)
79
+ ].join("");
80
+ if (lastMarkerLine && opts.message) markerLine += ` ${opts.message}`;
81
+ }
82
+ return [
83
+ ">",
84
+ gutter,
85
+ line.length > 0 ? ` ${line}` : "",
86
+ markerLine
87
+ ].join("");
88
+ } else return ` ${gutter}${line.length > 0 ? ` ${line}` : ""}`;
89
+ }).join("\n");
90
+ if (opts.message && !hasColumns) frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
91
+ return frame;
92
+ }
93
+
94
+ //#endregion
95
+ //#region src/logger.ts
96
+ const LOG_ORDER = [
97
+ "error",
98
+ "warn",
99
+ "info",
100
+ "debug"
101
+ ];
102
+ const MESSAGE_COLOR = {
103
+ error: pc.red,
104
+ warn: pc.yellow
105
+ };
106
+ const timeFormatter = new Intl.DateTimeFormat("en-us", {
107
+ hour: "numeric",
108
+ hour12: false,
109
+ minute: "numeric",
110
+ second: "numeric",
111
+ fractionalSecondDigits: 3
112
+ });
113
+ /**
114
+ * @param {Entry} entry
115
+ * @param {Severity} severity
116
+ * @return {string}
117
+ */
118
+ function formatMessage(entry, severity) {
119
+ let message = entry.message;
120
+ message = `[${entry.group}${entry.label ? `:${entry.label}` : ""}] ${message}`;
121
+ if (severity in MESSAGE_COLOR) message = MESSAGE_COLOR[severity](message);
122
+ if (entry.src) {
123
+ const start = entry.node?.loc?.start ?? {
124
+ line: 0,
125
+ column: 0
126
+ };
127
+ const loc = entry.filename ? `${entry.filename?.href.replace(/^file:\/\//, "")}:${start?.line ?? 0}:${start?.column ?? 0}\n\n` : "";
128
+ const codeFrame = codeFrameColumns(entry.src, { start }, { highlightCode: false });
129
+ message = `${message}\n\n${loc}${codeFrame}`;
130
+ }
131
+ return message;
132
+ }
133
+ var Logger = class {
134
+ level = "info";
135
+ debugScope = "*";
136
+ errorCount = 0;
137
+ warnCount = 0;
138
+ infoCount = 0;
139
+ debugCount = 0;
140
+ constructor(options) {
141
+ if (options?.level) this.level = options.level;
142
+ if (options?.debugScope) this.debugScope = options.debugScope;
143
+ }
144
+ setLevel(level) {
145
+ this.level = level;
146
+ }
147
+ /** Log an error message (always; can’t be silenced) */
148
+ error(entry) {
149
+ this.errorCount++;
150
+ const message = formatMessage(entry, "error");
151
+ if (entry.continueOnError) {
152
+ console.error(message);
153
+ return;
154
+ }
155
+ if (entry.node) throw new TokensJSONError(message);
156
+ else throw new Error(message);
157
+ }
158
+ /** Log an info message (if logging level permits) */
159
+ info(entry) {
160
+ this.infoCount++;
161
+ if (this.level === "silent" || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf("info")) return;
162
+ const message = formatMessage(entry, "info");
163
+ console.log(message);
164
+ }
165
+ /** Log a warning message (if logging level permits) */
166
+ warn(entry) {
167
+ this.warnCount++;
168
+ if (this.level === "silent" || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf("warn")) return;
169
+ const message = formatMessage(entry, "warn");
170
+ console.warn(message);
171
+ }
172
+ /** Log a diagnostics message (if logging level permits) */
173
+ debug(entry) {
174
+ if (this.level === "silent" || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf("debug")) return;
175
+ this.debugCount++;
176
+ let message = formatMessage(entry, "debug");
177
+ const debugPrefix = entry.label ? `${entry.group}:${entry.label}` : entry.group;
178
+ if (this.debugScope !== "*" && !wcmatch(this.debugScope)(debugPrefix)) return;
179
+ message.replace(/\[config[^\]]+\]/, (match) => pc.green(match)).replace(/\[parser[^\]]+\]/, (match) => pc.magenta(match)).replace(/\[lint[^\]]+\]/, (match) => pc.yellow(match)).replace(/\[plugin[^\]]+\]/, (match) => pc.cyan(match));
180
+ message = `${pc.dim(timeFormatter.format(performance.now()))} ${message}`;
181
+ if (typeof entry.timing === "number") {
182
+ let timing = "";
183
+ if (entry.timing < 1e3) timing = `${Math.round(entry.timing * 100) / 100}ms`;
184
+ else if (entry.timing < 6e4) timing = `${Math.round(entry.timing * 100) / 1e5}s`;
185
+ message = `${message} ${pc.dim(`[${timing}]`)}`;
186
+ }
187
+ console.log(message);
188
+ }
189
+ /** Get stats for current logger instance */
190
+ stats() {
191
+ return {
192
+ errorCount: this.errorCount,
193
+ warnCount: this.warnCount,
194
+ infoCount: this.infoCount,
195
+ debugCount: this.debugCount
196
+ };
197
+ }
198
+ };
199
+ var TokensJSONError = class extends Error {
200
+ constructor(message) {
201
+ super(message);
202
+ this.name = "TokensJSONError";
203
+ }
204
+ };
205
+
206
+ //#endregion
207
+ //#region src/build/index.ts
208
+ const SINGLE_VALUE = "SINGLE_VALUE";
209
+ const MULTI_VALUE = "MULTI_VALUE";
210
+ /** Validate plugin setTransform() calls for immediate feedback */
211
+ function validateTransformParams({ params, logger, pluginName }) {
212
+ const baseMessage = {
213
+ group: "plugin",
214
+ label: pluginName,
215
+ message: ""
216
+ };
217
+ if (!params.value || typeof params.value !== "string" && typeof params.value !== "object" || Array.isArray(params.value)) logger.error({
218
+ ...baseMessage,
219
+ message: `setTransform() value expected string or object of strings, received ${Array.isArray(params.value) ? "Array" : typeof params.value}`
220
+ });
221
+ if (typeof params.value === "object" && Object.values(params.value).some((v) => typeof v !== "string")) logger.error({
222
+ ...baseMessage,
223
+ message: "setTransform() value expected object of strings, received some non-string values"
224
+ });
225
+ }
226
+ /** Run build stage */
227
+ async function build(tokens, { sources, logger = new Logger(), config }) {
228
+ const formats = {};
229
+ const result = { outputFiles: [] };
230
+ function getTransforms(params) {
231
+ if (!params?.format) {
232
+ logger.warn({
233
+ group: "plugin",
234
+ message: "\"format\" missing from getTransforms(), no tokens returned."
235
+ });
236
+ return [];
237
+ }
238
+ return (formats[params.format] ?? []).filter((token) => {
239
+ if (params.$type) {
240
+ if (typeof params.$type === "string" && token.token.$type !== params.$type) return false;
241
+ else if (Array.isArray(params.$type) && !params.$type.some(($type) => token.token.$type === $type)) return false;
242
+ }
243
+ if (params.id && params.id !== "*" && !isTokenMatch(token.token.id, Array.isArray(params.id) ? params.id : [params.id])) return false;
244
+ if (params.mode && !wcmatch(params.mode)(token.mode)) return false;
245
+ return true;
246
+ });
247
+ }
248
+ let transformsLocked = false;
249
+ const startTransform = performance.now();
250
+ for (const plugin of config.plugins) if (typeof plugin.transform === "function") await plugin.transform({
251
+ tokens,
252
+ sources,
253
+ getTransforms,
254
+ setTransform(id, params) {
255
+ if (transformsLocked) {
256
+ logger.warn({
257
+ message: "Attempted to call setTransform() after transform step has completed.",
258
+ group: "plugin",
259
+ label: plugin.name
260
+ });
261
+ return;
262
+ }
263
+ const token = tokens[id];
264
+ const cleanValue = typeof params.value === "string" ? params.value : { ...params.value };
265
+ if (typeof cleanValue === "object") {
266
+ for (const k of Object.keys(cleanValue)) if (cleanValue[k] === void 0) delete cleanValue[k];
267
+ }
268
+ validateTransformParams({
269
+ logger,
270
+ params: {
271
+ ...params,
272
+ value: cleanValue
273
+ },
274
+ pluginName: plugin.name
275
+ });
276
+ if (!formats[params.format]) formats[params.format] = [];
277
+ const foundTokenI = formats[params.format].findIndex((t) => id === t.id && (!params.localID || params.localID === t.localID) && (!params.mode || params.mode === t.mode));
278
+ if (foundTokenI === -1) formats[params.format].push({
279
+ ...params,
280
+ id,
281
+ value: cleanValue,
282
+ type: typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE,
283
+ mode: params.mode || ".",
284
+ token: structuredClone(token)
285
+ });
286
+ else {
287
+ formats[params.format][foundTokenI].value = cleanValue;
288
+ formats[params.format][foundTokenI].type = typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE;
289
+ }
290
+ }
291
+ });
292
+ transformsLocked = true;
293
+ logger.debug({
294
+ group: "parser",
295
+ label: "transform",
296
+ message: "transform() step",
297
+ timing: performance.now() - startTransform
298
+ });
299
+ const startBuild = performance.now();
300
+ for (const plugin of config.plugins) if (typeof plugin.build === "function") {
301
+ const pluginBuildStart = performance.now();
302
+ await plugin.build({
303
+ tokens,
304
+ sources,
305
+ getTransforms,
306
+ outputFile(filename, contents) {
307
+ const resolved = new URL(filename, config.outDir);
308
+ if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) logger.error({
309
+ group: "plugin",
310
+ message: `Can’t overwrite file "${filename}"`,
311
+ label: plugin.name
312
+ });
313
+ result.outputFiles.push({
314
+ filename,
315
+ contents,
316
+ plugin: plugin.name,
317
+ time: performance.now() - pluginBuildStart
318
+ });
319
+ }
320
+ });
321
+ }
322
+ logger.debug({
323
+ group: "parser",
324
+ label: "build",
325
+ message: "build() step",
326
+ timing: performance.now() - startBuild
327
+ });
328
+ const startBuildEnd = performance.now();
329
+ for (const plugin of config.plugins) if (typeof plugin.buildEnd === "function") await plugin.buildEnd({ outputFiles: structuredClone(result.outputFiles) });
330
+ logger.debug({
331
+ group: "parser",
332
+ label: "build",
333
+ message: "buildEnd() step",
334
+ timing: performance.now() - startBuildEnd
335
+ });
336
+ return result;
337
+ }
338
+
339
+ //#endregion
340
+ //#region src/lint/plugin-core/lib/docs.ts
341
+ function docsLink(ruleName) {
342
+ return `https://terrazzo.app/docs/cli/lint#${ruleName.replaceAll("/", "")}`;
343
+ }
344
+
345
+ //#endregion
346
+ //#region src/lint/plugin-core/rules/a11y-min-contrast.ts
347
+ const A11Y_MIN_CONTRAST = "a11y/min-contrast";
348
+ const WCAG2_MIN_CONTRAST = {
349
+ AA: {
350
+ default: 4.5,
351
+ large: 3
352
+ },
353
+ AAA: {
354
+ default: 7,
355
+ large: 4.5
356
+ }
357
+ };
358
+ const ERROR_INSUFFICIENT_CONTRAST = "INSUFFICIENT_CONTRAST";
359
+ const rule$9 = {
360
+ meta: {
361
+ messages: { [ERROR_INSUFFICIENT_CONTRAST]: "Pair {{ index }} failed; expected {{ expected }}, got {{ actual }} ({{ level }})" },
362
+ docs: {
363
+ description: "Enforce colors meet minimum contrast checks for WCAG 2.",
364
+ url: docsLink(A11Y_MIN_CONTRAST)
365
+ }
366
+ },
367
+ defaultOptions: {
368
+ level: "AA",
369
+ pairs: []
370
+ },
371
+ create({ tokens, options, report }) {
372
+ for (let i = 0; i < options.pairs.length; i++) {
373
+ const { foreground, background, largeText } = options.pairs[i];
374
+ if (!tokens[foreground]) throw new Error(`Token ${foreground} does not exist`);
375
+ if (tokens[foreground].$type !== "color") throw new Error(`Token ${foreground} isn’t a color`);
376
+ if (!tokens[background]) throw new Error(`Token ${background} does not exist`);
377
+ if (tokens[background].$type !== "color") throw new Error(`Token ${background} isn’t a color`);
378
+ const a = tokenToCulori(tokens[foreground].$value);
379
+ const b = tokenToCulori(tokens[background].$value);
380
+ const contrast = wcagContrast(a, b);
381
+ const min = WCAG2_MIN_CONTRAST[options.level ?? "AA"][largeText ? "large" : "default"];
382
+ if (contrast < min) report({
383
+ messageId: ERROR_INSUFFICIENT_CONTRAST,
384
+ data: {
385
+ index: i + 1,
386
+ expected: min,
387
+ actual: Math.round(contrast * 100) / 100,
388
+ level: options.level
389
+ }
390
+ });
391
+ }
392
+ }
393
+ };
394
+ var a11y_min_contrast_default = rule$9;
395
+
396
+ //#endregion
397
+ //#region src/lint/plugin-core/rules/a11y-min-font-size.ts
398
+ const A11Y_MIN_FONT_SIZE = "a11y/min-font-size";
399
+ const ERROR_TOO_SMALL = "TOO_SMALL";
400
+ const rule$8 = {
401
+ meta: {
402
+ messages: { [ERROR_TOO_SMALL]: "{{ id }} font size too small. Expected minimum of {{ min }}" },
403
+ docs: {
404
+ description: "Enforce font sizes are no smaller than the given value.",
405
+ url: docsLink(A11Y_MIN_FONT_SIZE)
406
+ }
407
+ },
408
+ defaultOptions: {},
409
+ create({ tokens, options, report }) {
410
+ if (!options.minSizePx && !options.minSizeRem) throw new Error("Must specify at least one of minSizePx or minSizeRem");
411
+ for (const t of Object.values(tokens)) {
412
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
413
+ if (t.aliasOf) continue;
414
+ if (t.$type === "typography" && "fontSize" in t.$value) {
415
+ const fontSize = t.$value.fontSize;
416
+ if (fontSize.unit === "px" && options.minSizePx && fontSize.value < options.minSizePx || fontSize.unit === "rem" && options.minSizeRem && fontSize.value < options.minSizeRem) report({
417
+ messageId: ERROR_TOO_SMALL,
418
+ data: {
419
+ id: t.id,
420
+ min: options.minSizePx ? `${options.minSizePx}px` : `${options.minSizeRem}rem`
421
+ }
422
+ });
423
+ }
424
+ }
425
+ }
426
+ };
427
+ var a11y_min_font_size_default = rule$8;
428
+
429
+ //#endregion
430
+ //#region src/lint/plugin-core/rules/colorspace.ts
431
+ const COLORSPACE = "core/colorspace";
432
+ const ERROR_COLOR$1 = "COLOR";
433
+ const ERROR_BORDER$1 = "BORDER";
434
+ const ERROR_GRADIENT$1 = "GRADIENT";
435
+ const ERROR_SHADOW$1 = "SHADOW";
436
+ const rule$7 = {
437
+ meta: {
438
+ messages: {
439
+ [ERROR_COLOR$1]: "Color {{ id }} not in colorspace {{ colorSpace }}",
440
+ [ERROR_BORDER$1]: "Border {{ id }} not in colorspace {{ colorSpace }}",
441
+ [ERROR_GRADIENT$1]: "Gradient {{ id }} not in colorspace {{ colorSpace }}",
442
+ [ERROR_SHADOW$1]: "Shadow {{ id }} not in colorspace {{ colorSpace }}"
443
+ },
444
+ docs: {
445
+ description: "Enforce that all colors are in a specific colorspace.",
446
+ url: docsLink(COLORSPACE)
447
+ }
448
+ },
449
+ defaultOptions: { colorSpace: "srgb" },
450
+ create({ tokens, options, report }) {
451
+ if (!options.colorSpace) return;
452
+ for (const t of Object.values(tokens)) {
453
+ if (options?.ignore && isTokenMatch(t.id, options.ignore)) continue;
454
+ if (t.aliasOf) continue;
455
+ switch (t.$type) {
456
+ case "color": {
457
+ if (t.$value.colorSpace !== options.colorSpace) report({
458
+ messageId: ERROR_COLOR$1,
459
+ data: {
460
+ id: t.id,
461
+ colorSpace: options.colorSpace
462
+ },
463
+ node: t.source.node
464
+ });
465
+ break;
466
+ }
467
+ case "border": {
468
+ if (!t.partialAliasOf?.color && t.$value.color.colorSpace !== options.colorSpace) report({
469
+ messageId: ERROR_BORDER$1,
470
+ data: {
471
+ id: t.id,
472
+ colorSpace: options.colorSpace
473
+ },
474
+ node: t.source.node
475
+ });
476
+ break;
477
+ }
478
+ case "gradient": {
479
+ for (let stopI = 0; stopI < t.$value.length; stopI++) if (!t.partialAliasOf?.[stopI]?.color && t.$value[stopI].color.colorSpace !== options.colorSpace) report({
480
+ messageId: ERROR_GRADIENT$1,
481
+ data: {
482
+ id: t.id,
483
+ colorSpace: options.colorSpace
484
+ },
485
+ node: t.source.node
486
+ });
487
+ break;
488
+ }
489
+ case "shadow": {
490
+ for (let shadowI = 0; shadowI < t.$value.length; shadowI++) if (!t.partialAliasOf?.[shadowI]?.color && t.$value[shadowI].color.colorSpace !== options.colorSpace) report({
491
+ messageId: ERROR_SHADOW$1,
492
+ data: {
493
+ id: t.id,
494
+ colorSpace: options.colorSpace
495
+ },
496
+ node: t.source.node
497
+ });
498
+ break;
499
+ }
500
+ }
501
+ }
502
+ }
503
+ };
504
+ var colorspace_default = rule$7;
505
+
506
+ //#endregion
507
+ //#region ../../node_modules/.pnpm/scule@1.3.0/node_modules/scule/dist/index.mjs
508
+ const NUMBER_CHAR_RE = /\d/;
509
+ const STR_SPLITTERS = [
510
+ "-",
511
+ "_",
512
+ "/",
513
+ "."
514
+ ];
515
+ function isUppercase(char = "") {
516
+ if (NUMBER_CHAR_RE.test(char)) return void 0;
517
+ return char !== char.toLowerCase();
518
+ }
519
+ function splitByCase(str, separators) {
520
+ const splitters = separators ?? STR_SPLITTERS;
521
+ const parts = [];
522
+ if (!str || typeof str !== "string") return parts;
523
+ let buff = "";
524
+ let previousUpper;
525
+ let previousSplitter;
526
+ for (const char of str) {
527
+ const isSplitter = splitters.includes(char);
528
+ if (isSplitter === true) {
529
+ parts.push(buff);
530
+ buff = "";
531
+ previousUpper = void 0;
532
+ continue;
533
+ }
534
+ const isUpper = isUppercase(char);
535
+ if (previousSplitter === false) {
536
+ if (previousUpper === false && isUpper === true) {
537
+ parts.push(buff);
538
+ buff = char;
539
+ previousUpper = isUpper;
540
+ continue;
541
+ }
542
+ if (previousUpper === true && isUpper === false && buff.length > 1) {
543
+ const lastChar = buff.at(-1);
544
+ parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
545
+ buff = lastChar + char;
546
+ previousUpper = isUpper;
547
+ continue;
548
+ }
549
+ }
550
+ buff += char;
551
+ previousUpper = isUpper;
552
+ previousSplitter = isSplitter;
553
+ }
554
+ parts.push(buff);
555
+ return parts;
556
+ }
557
+ function upperFirst(str) {
558
+ return str ? str[0].toUpperCase() + str.slice(1) : "";
559
+ }
560
+ function lowerFirst(str) {
561
+ return str ? str[0].toLowerCase() + str.slice(1) : "";
562
+ }
563
+ function pascalCase(str, opts) {
564
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
565
+ }
566
+ function camelCase(str, opts) {
567
+ return lowerFirst(pascalCase(str || "", opts));
568
+ }
569
+ function kebabCase(str, joiner) {
570
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
571
+ }
572
+ function snakeCase(str) {
573
+ return kebabCase(str || "", "_");
574
+ }
575
+
576
+ //#endregion
577
+ //#region src/lint/plugin-core/rules/consistent-naming.ts
578
+ const CONSISTENT_NAMING = "core/consistent-naming";
579
+ const ERROR_WRONG_FORMAT = "ERROR_WRONG_FORMAT";
580
+ const rule$6 = {
581
+ meta: {
582
+ messages: { [ERROR_WRONG_FORMAT]: "{{ id }} doesn’t match format {{ format }}" },
583
+ docs: {
584
+ description: "Enforce consistent naming for tokens.",
585
+ url: docsLink(CONSISTENT_NAMING)
586
+ }
587
+ },
588
+ defaultOptions: { format: "kebab-case" },
589
+ create({ tokens, options, report }) {
590
+ const basicFormatter = {
591
+ "kebab-case": kebabCase,
592
+ camelCase,
593
+ PascalCase: pascalCase,
594
+ snake_case: snakeCase,
595
+ SCREAMING_SNAKE_CASE: (name) => snakeCase(name).toLocaleUpperCase()
596
+ }[String(options.format)];
597
+ for (const t of Object.values(tokens)) if (basicFormatter) {
598
+ const parts = t.id.split(".");
599
+ if (!parts.every((part) => basicFormatter(part) === part)) report({
600
+ messageId: ERROR_WRONG_FORMAT,
601
+ data: {
602
+ id: t.id,
603
+ format: options.format
604
+ },
605
+ node: t.source.node
606
+ });
607
+ } else if (typeof options.format === "function") {
608
+ const result = options.format(t.id);
609
+ if (result) report({
610
+ messageId: ERROR_WRONG_FORMAT,
611
+ data: {
612
+ id: t.id,
613
+ format: "(custom)"
614
+ },
615
+ node: t.source.node
616
+ });
617
+ }
618
+ }
619
+ };
620
+ var consistent_naming_default = rule$6;
621
+
622
+ //#endregion
623
+ //#region src/lint/plugin-core/rules/descriptions.ts
624
+ const DESCRIPTIONS = "core/descriptions";
625
+ const ERROR_MISSING_DESCRIPTION = "MISSING_DESCRIPTION";
626
+ const rule$5 = {
627
+ meta: {
628
+ messages: { [ERROR_MISSING_DESCRIPTION]: "{{ id }} missing description" },
629
+ docs: {
630
+ description: "Enforce tokens have descriptions.",
631
+ url: docsLink(DESCRIPTIONS)
632
+ }
633
+ },
634
+ defaultOptions: {},
635
+ create({ tokens, options, report }) {
636
+ for (const t of Object.values(tokens)) {
637
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
638
+ if (!t.$description) report({
639
+ messageId: ERROR_MISSING_DESCRIPTION,
640
+ data: { id: t.id },
641
+ node: t.source.node
642
+ });
643
+ }
644
+ }
645
+ };
646
+ var descriptions_default = rule$5;
647
+
648
+ //#endregion
649
+ //#region src/lint/plugin-core/rules/duplicate-values.ts
650
+ const DUPLICATE_VALUES = "core/duplicate-values";
651
+ const ERROR_DUPLICATE_VALUE = "ERROR_DUPLICATE_VALUE";
652
+ const rule$4 = {
653
+ meta: {
654
+ messages: { [ERROR_DUPLICATE_VALUE]: "{{ id }} declared a duplicate value" },
655
+ docs: {
656
+ description: "Enforce tokens can’t redeclare the same value (excludes aliases).",
657
+ url: docsLink(DUPLICATE_VALUES)
658
+ }
659
+ },
660
+ defaultOptions: {},
661
+ create({ report, tokens, options }) {
662
+ const values = {};
663
+ for (const t of Object.values(tokens)) {
664
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
665
+ if (!values[t.$type]) values[t.$type] = /* @__PURE__ */ new Set();
666
+ if (t.$type === "boolean" || t.$type === "duration" || t.$type === "fontWeight" || t.$type === "link" || t.$type === "number" || t.$type === "string") {
667
+ if (typeof t.aliasOf === "string" && isAlias(t.aliasOf)) continue;
668
+ if (values[t.$type]?.has(t.$value)) report({
669
+ messageId: ERROR_DUPLICATE_VALUE,
670
+ data: { id: t.id },
671
+ node: t.source.node
672
+ });
673
+ values[t.$type]?.add(t.$value);
674
+ } else {
675
+ for (const v of values[t.$type].values() ?? []) if (JSON.stringify(t.$value) === JSON.stringify(v)) {
676
+ report({
677
+ messageId: ERROR_DUPLICATE_VALUE,
678
+ data: { id: t.id },
679
+ node: t.source.node
680
+ });
681
+ break;
682
+ }
683
+ values[t.$type].add(t.$value);
684
+ }
685
+ }
686
+ }
687
+ };
688
+ var duplicate_values_default = rule$4;
689
+
690
+ //#endregion
691
+ //#region src/lint/plugin-core/rules/max-gamut.ts
692
+ const MAX_GAMUT = "core/max-gamut";
693
+ const TOLERANCE = 1e-6;
694
+ /** is a Culori-parseable color within the specified gamut? */
695
+ function isWithinGamut(color, gamut) {
696
+ const parsed = tokenToCulori(color);
697
+ if (!parsed) return false;
698
+ if ([
699
+ "rgb",
700
+ "hsl",
701
+ "hsv",
702
+ "hwb"
703
+ ].includes(parsed.mode)) return true;
704
+ const clamped = clampChroma(parsed, parsed.mode, gamut === "srgb" ? "rgb" : gamut);
705
+ return isWithinThreshold(parsed, clamped);
706
+ }
707
+ /** is Color A close enough to Color B? */
708
+ function isWithinThreshold(a, b, tolerance = TOLERANCE) {
709
+ for (const k in a) {
710
+ if (k === "mode" || k === "alpha") continue;
711
+ if (!(k in b)) throw new Error(`Can’t compare ${a.mode} to ${b.mode}`);
712
+ if (Math.abs(a[k] - b[k]) > tolerance) return false;
713
+ }
714
+ return true;
715
+ }
716
+ const ERROR_COLOR = "COLOR";
717
+ const ERROR_BORDER = "BORDER";
718
+ const ERROR_GRADIENT = "GRADIENT";
719
+ const ERROR_SHADOW = "SHADOW";
720
+ const rule$3 = {
721
+ meta: {
722
+ messages: {
723
+ [ERROR_COLOR]: "Color {{ id }} is outside {{ gamut }} gamut",
724
+ [ERROR_BORDER]: "Border {{ id }} is outside {{ gamut }} gamut",
725
+ [ERROR_GRADIENT]: "Gradient {{ id }} is outside {{ gamut }} gamut",
726
+ [ERROR_SHADOW]: "Shadow {{ id }} is outside {{ gamut }} gamut"
727
+ },
728
+ docs: {
729
+ description: "Enforce colors are within the specified gamut.",
730
+ url: docsLink(MAX_GAMUT)
731
+ }
732
+ },
733
+ defaultOptions: { gamut: "rec2020" },
734
+ create({ tokens, options, report }) {
735
+ if (!options?.gamut) return;
736
+ if (options.gamut !== "srgb" && options.gamut !== "p3" && options.gamut !== "rec2020") throw new Error(`Unknown gamut "${options.gamut}". Options are "srgb", "p3", or "rec2020"`);
737
+ for (const t of Object.values(tokens)) {
738
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
739
+ if (t.aliasOf) continue;
740
+ switch (t.$type) {
741
+ case "color": {
742
+ if (!isWithinGamut(t.$value, options.gamut)) report({
743
+ messageId: ERROR_COLOR,
744
+ data: {
745
+ id: t.id,
746
+ gamut: options.gamut
747
+ },
748
+ node: t.source.node
749
+ });
750
+ break;
751
+ }
752
+ case "border": {
753
+ if (!t.partialAliasOf?.color && !isWithinGamut(t.$value.color, options.gamut)) report({
754
+ messageId: ERROR_BORDER,
755
+ data: {
756
+ id: t.id,
757
+ gamut: options.gamut
758
+ },
759
+ node: t.source.node
760
+ });
761
+ break;
762
+ }
763
+ case "gradient": {
764
+ for (let stopI = 0; stopI < t.$value.length; stopI++) if (!t.partialAliasOf?.[stopI]?.color && !isWithinGamut(t.$value[stopI].color, options.gamut)) report({
765
+ messageId: ERROR_GRADIENT,
766
+ data: {
767
+ id: t.id,
768
+ gamut: options.gamut
769
+ },
770
+ node: t.source.node
771
+ });
772
+ break;
773
+ }
774
+ case "shadow": {
775
+ for (let shadowI = 0; shadowI < t.$value.length; shadowI++) if (!t.partialAliasOf?.[shadowI]?.color && !isWithinGamut(t.$value[shadowI].color, options.gamut)) report({
776
+ messageId: ERROR_SHADOW,
777
+ data: {
778
+ id: t.id,
779
+ gamut: options.gamut
780
+ },
781
+ node: t.source.node
782
+ });
783
+ break;
784
+ }
785
+ }
786
+ }
787
+ }
788
+ };
789
+ var max_gamut_default = rule$3;
790
+
791
+ //#endregion
792
+ //#region src/lint/plugin-core/rules/required-children.ts
793
+ const REQUIRED_CHILDREN = "core/required-children";
794
+ const ERROR_EMPTY_MATCH = "EMPTY_MATCH";
795
+ const ERROR_MISSING_REQUIRED_TOKENS = "MISSING_REQUIRED_TOKENS";
796
+ const ERROR_MISSING_REQUIRED_GROUP = "MISSING_REQUIRED_GROUP";
797
+ const rule$2 = {
798
+ meta: {
799
+ messages: {
800
+ [ERROR_EMPTY_MATCH]: "No tokens matched {{ matcher }}",
801
+ [ERROR_MISSING_REQUIRED_TOKENS]: "Match {{ index }}: some groups missing required token \"{{ token }}\"",
802
+ [ERROR_MISSING_REQUIRED_GROUP]: "Match {{ index }}: some tokens missing required group \"{{ group }}\""
803
+ },
804
+ docs: {
805
+ description: "Enforce token groups have specific children, whether tokens and/or groups.",
806
+ url: docsLink(REQUIRED_CHILDREN)
807
+ }
808
+ },
809
+ defaultOptions: { matches: [] },
810
+ create({ tokens, options, report }) {
811
+ if (!options.matches?.length) throw new Error("Invalid config. Missing `matches: […]`");
812
+ for (let matchI = 0; matchI < options.matches.length; matchI++) {
813
+ const { match, requiredTokens, requiredGroups } = options.matches[matchI];
814
+ if (!match.length) throw new Error(`Match ${matchI}: must declare \`match: […]\``);
815
+ if (!requiredTokens?.length && !requiredGroups?.length) throw new Error(`Match ${matchI}: must declare either \`requiredTokens: […]\` or \`requiredGroups: […]\``);
816
+ const matchGroups = [];
817
+ const matchTokens = [];
818
+ let tokensMatched = false;
819
+ for (const t of Object.values(tokens)) {
820
+ if (!isTokenMatch(t.id, match)) continue;
821
+ tokensMatched = true;
822
+ const groups = t.id.split(".");
823
+ matchTokens.push(groups.pop());
824
+ matchGroups.push(...groups);
825
+ }
826
+ if (!tokensMatched) {
827
+ report({
828
+ messageId: ERROR_EMPTY_MATCH,
829
+ data: { matcher: JSON.stringify(match) }
830
+ });
831
+ continue;
832
+ }
833
+ if (requiredTokens) {
834
+ for (const id of requiredTokens) if (!matchTokens.includes(id)) report({
835
+ messageId: ERROR_MISSING_REQUIRED_TOKENS,
836
+ data: {
837
+ index: matchI,
838
+ token: id
839
+ }
840
+ });
841
+ }
842
+ if (requiredGroups) {
843
+ for (const groupName of requiredGroups) if (!matchGroups.includes(groupName)) report({
844
+ messageId: ERROR_MISSING_REQUIRED_GROUP,
845
+ data: {
846
+ index: matchI,
847
+ group: groupName
848
+ }
849
+ });
850
+ }
851
+ }
852
+ }
853
+ };
854
+ var required_children_default = rule$2;
855
+
856
+ //#endregion
857
+ //#region src/lint/plugin-core/rules/required-modes.ts
858
+ const REQUIRED_MODES = "core/required-modes";
859
+ const rule$1 = {
860
+ meta: { docs: {
861
+ description: "Enforce certain tokens have specific modes.",
862
+ url: docsLink(REQUIRED_MODES)
863
+ } },
864
+ defaultOptions: { matches: [] },
865
+ create({ tokens, options, report }) {
866
+ if (!options?.matches?.length) throw new Error("Invalid config. Missing `matches: […]`");
867
+ for (let matchI = 0; matchI < options.matches.length; matchI++) {
868
+ const { match, modes } = options.matches[matchI];
869
+ if (!match.length) throw new Error(`Match ${matchI}: must declare \`match: […]\``);
870
+ if (!modes?.length) throw new Error(`Match ${matchI}: must declare \`modes: […]\``);
871
+ let tokensMatched = false;
872
+ for (const t of Object.values(tokens)) {
873
+ if (!isTokenMatch(t.id, match)) continue;
874
+ tokensMatched = true;
875
+ for (const mode of modes) if (!t.mode?.[mode]) report({
876
+ message: `Token ${t.id}: missing required mode "${mode}"`,
877
+ node: t.source.node
878
+ });
879
+ if (!tokensMatched) report({
880
+ message: `Match "${matchI}": no tokens matched ${JSON.stringify(match)}`,
881
+ node: t.source.node
882
+ });
883
+ }
884
+ }
885
+ }
886
+ };
887
+ var required_modes_default = rule$1;
888
+
889
+ //#endregion
890
+ //#region src/lint/plugin-core/rules/required-typography-properties.ts
891
+ const REQUIRED_TYPOGRAPHY_PROPERTIES = "core/required-typography-properties";
892
+ const rule = {
893
+ meta: { docs: {
894
+ description: "Enforce typography tokens have required properties.",
895
+ url: docsLink(REQUIRED_TYPOGRAPHY_PROPERTIES)
896
+ } },
897
+ defaultOptions: { properties: [] },
898
+ create({ tokens, options, report }) {
899
+ if (!options) return;
900
+ if (!options.properties.length) throw new Error(`"properties" can’t be empty`);
901
+ for (const t of Object.values(tokens)) {
902
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
903
+ if (t.$type !== "typography") continue;
904
+ if (t.aliasOf) continue;
905
+ for (const p of options.properties) if (!t.partialAliasOf?.[p] && !(p in t.$value)) report({
906
+ message: `${t.id} missing required typographic property "${p}"`,
907
+ node: t.source.node
908
+ });
909
+ }
910
+ }
911
+ };
912
+ var required_typography_properties_default = rule;
913
+
914
+ //#endregion
915
+ //#region src/lint/plugin-core/index.ts
916
+ function coreLintPlugin() {
917
+ return {
918
+ name: "@terrazzo/plugin-lint-core",
919
+ lint() {
920
+ return {
921
+ [COLORSPACE]: colorspace_default,
922
+ [CONSISTENT_NAMING]: consistent_naming_default,
923
+ [DESCRIPTIONS]: descriptions_default,
924
+ [DUPLICATE_VALUES]: duplicate_values_default,
925
+ [MAX_GAMUT]: max_gamut_default,
926
+ [REQUIRED_CHILDREN]: required_children_default,
927
+ [REQUIRED_MODES]: required_modes_default,
928
+ [REQUIRED_TYPOGRAPHY_PROPERTIES]: required_typography_properties_default,
929
+ [A11Y_MIN_CONTRAST]: a11y_min_contrast_default,
930
+ [A11Y_MIN_FONT_SIZE]: a11y_min_font_size_default
931
+ };
932
+ }
933
+ };
934
+ }
935
+
936
+ //#endregion
937
+ //#region src/config.ts
938
+ const TRAILING_SLASH_RE = /\/*$/;
939
+ /**
940
+ * Validate and normalize a config
941
+ */
942
+ function defineConfig(rawConfig, { logger = new Logger(), cwd } = {}) {
943
+ const configStart = performance.now();
944
+ if (!cwd) logger.error({
945
+ group: "config",
946
+ label: "core",
947
+ message: "defineConfig() missing `cwd` for JS API"
948
+ });
949
+ const config = merge({}, rawConfig);
950
+ normalizeTokens({
951
+ rawConfig,
952
+ config,
953
+ logger,
954
+ cwd
955
+ });
956
+ normalizeOutDir({
957
+ config,
958
+ cwd,
959
+ logger
960
+ });
961
+ normalizePlugins({
962
+ config,
963
+ logger
964
+ });
965
+ normalizeLint({
966
+ config,
967
+ logger
968
+ });
969
+ normalizeIgnore({
970
+ config,
971
+ logger
972
+ });
973
+ for (const plugin of config.plugins) plugin.config?.({ ...config });
974
+ logger.debug({
975
+ group: "parser",
976
+ label: "config",
977
+ message: "Finish config validation",
978
+ timing: performance.now() - configStart
979
+ });
980
+ return config;
981
+ }
982
+ /** Normalize config.tokens */
983
+ function normalizeTokens({ rawConfig, config, logger, cwd }) {
984
+ if (rawConfig.tokens === void 0) config.tokens = ["./tokens.json"];
985
+ else if (typeof rawConfig.tokens === "string") config.tokens = [rawConfig.tokens];
986
+ else if (Array.isArray(rawConfig.tokens)) {
987
+ config.tokens = [];
988
+ for (const file of rawConfig.tokens) if (typeof file === "string" || file instanceof URL) config.tokens.push(file);
989
+ else logger.error({
990
+ group: "config",
991
+ label: "tokens",
992
+ message: `Expected array of strings, encountered ${JSON.stringify(file)}`
993
+ });
994
+ } else logger.error({
995
+ group: "config",
996
+ label: "tokens",
997
+ message: `Expected string or array of strings, received ${typeof rawConfig.tokens}`
998
+ });
999
+ for (let i = 0; i < config.tokens.length; i++) {
1000
+ const filepath = config.tokens[i];
1001
+ if (filepath instanceof URL) continue;
1002
+ try {
1003
+ config.tokens[i] = new URL(filepath, cwd);
1004
+ } catch (err) {
1005
+ logger.error({
1006
+ group: "config",
1007
+ label: "tokens",
1008
+ message: `Invalid URL ${filepath}`
1009
+ });
1010
+ }
1011
+ }
1012
+ }
1013
+ /** Normalize config.outDir */
1014
+ function normalizeOutDir({ config, cwd, logger }) {
1015
+ if (config.outDir instanceof URL) {} else if (typeof config.outDir === "undefined") config.outDir = new URL("./tokens/", cwd);
1016
+ else if (typeof config.outDir !== "string") logger.error({
1017
+ group: "config",
1018
+ label: "outDir",
1019
+ message: `Expected string, received ${JSON.stringify(config.outDir)}`
1020
+ });
1021
+ else {
1022
+ config.outDir = new URL(config.outDir, cwd);
1023
+ config.outDir = new URL(config.outDir.href.replace(TRAILING_SLASH_RE, "/"));
1024
+ }
1025
+ }
1026
+ /** Normalize config.plugins */
1027
+ function normalizePlugins({ config, logger }) {
1028
+ if (typeof config.plugins === "undefined") config.plugins = [];
1029
+ if (!Array.isArray(config.plugins)) logger.error({
1030
+ group: "config",
1031
+ label: "plugins",
1032
+ message: `Expected array of plugins, received ${JSON.stringify(config.plugins)}`
1033
+ });
1034
+ config.plugins.push(coreLintPlugin());
1035
+ for (let n = 0; n < config.plugins.length; n++) {
1036
+ const plugin = config.plugins[n];
1037
+ if (typeof plugin !== "object") logger.error({
1038
+ group: "config",
1039
+ label: `plugin[${n}]`,
1040
+ message: `Expected output plugin, received ${JSON.stringify(plugin)}`
1041
+ });
1042
+ else if (!plugin.name) logger.error({
1043
+ group: "config",
1044
+ label: `plugin[${n}]`,
1045
+ message: `Missing "name"`
1046
+ });
1047
+ }
1048
+ config.plugins.sort((a, b) => {
1049
+ if (a.enforce === "pre" && b.enforce !== "pre") return -1;
1050
+ else if (a.enforce === "post" && b.enforce !== "post") return 1;
1051
+ return 0;
1052
+ });
1053
+ }
1054
+ function normalizeLint({ config, logger }) {
1055
+ if (config.lint !== void 0) {
1056
+ if (config.lint === null || typeof config.lint !== "object" || Array.isArray(config.lint)) logger.error({
1057
+ group: "config",
1058
+ label: "lint",
1059
+ message: "Must be an object"
1060
+ });
1061
+ if (!config.lint.build) config.lint.build = { enabled: true };
1062
+ if (config.lint.build.enabled !== void 0) {
1063
+ if (typeof config.lint.build.enabled !== "boolean") logger.error({
1064
+ group: "config",
1065
+ label: "lint › build › enabled",
1066
+ message: `Expected boolean, received ${JSON.stringify(config.lint.build)}`
1067
+ });
1068
+ } else config.lint.build.enabled = true;
1069
+ if (config.lint.rules === void 0) config.lint.rules = {};
1070
+ else {
1071
+ if (config.lint.rules === null || typeof config.lint.rules !== "object" || Array.isArray(config.lint.rules)) {
1072
+ logger.error({
1073
+ group: "config",
1074
+ label: "lint › rules",
1075
+ message: `Expected object, received ${JSON.stringify(config.lint.rules)}`
1076
+ });
1077
+ return;
1078
+ }
1079
+ const allRules = /* @__PURE__ */ new Map();
1080
+ for (const plugin of config.plugins) {
1081
+ if (typeof plugin.lint !== "function") continue;
1082
+ const pluginRules = plugin.lint();
1083
+ if (!pluginRules || Array.isArray(pluginRules) || typeof pluginRules !== "object") {
1084
+ logger.error({
1085
+ group: "config",
1086
+ label: `plugin › ${plugin.name}`,
1087
+ message: `Expected object for lint() received ${JSON.stringify(pluginRules)}`
1088
+ });
1089
+ continue;
1090
+ }
1091
+ for (const rule$10 of Object.keys(pluginRules)) {
1092
+ if (allRules.get(rule$10) && allRules.get(rule$10) !== plugin.name) logger.error({
1093
+ group: "config",
1094
+ label: `plugin › ${plugin.name}`,
1095
+ message: `Duplicate rule ${rule$10} already registered by plugin ${allRules.get(rule$10)}`
1096
+ });
1097
+ allRules.set(rule$10, plugin.name);
1098
+ }
1099
+ }
1100
+ for (const id of Object.keys(config.lint.rules)) {
1101
+ if (!allRules.has(id)) logger.error({
1102
+ group: "config",
1103
+ label: `lint › rule › ${id}`,
1104
+ message: "Unknown rule. Is the plugin installed?"
1105
+ });
1106
+ const value = config.lint.rules[id];
1107
+ let severity = "off";
1108
+ let options;
1109
+ if (typeof value === "number" || typeof value === "string") severity = value;
1110
+ else if (Array.isArray(value)) {
1111
+ severity = value[0];
1112
+ options = value[1];
1113
+ } else if (value !== void 0) logger.error({
1114
+ group: "config",
1115
+ label: `lint › rule › ${id}`,
1116
+ message: `Invalid eyntax. Expected \`string | number | Array\`, received ${JSON.stringify(value)}}`
1117
+ });
1118
+ config.lint.rules[id] = [severity, options];
1119
+ if (typeof severity === "number") {
1120
+ if (severity !== 0 && severity !== 1 && severity !== 2) logger.error({
1121
+ group: "config",
1122
+ label: `lint › rule › ${id}`,
1123
+ message: `Invalid number ${severity}. Specify 0 (off), 1 (warn), or 2 (error).`
1124
+ });
1125
+ config.lint.rules[id][0] = [
1126
+ "off",
1127
+ "warn",
1128
+ "error"
1129
+ ][severity];
1130
+ } else if (typeof severity === "string") {
1131
+ if (severity !== "off" && severity !== "warn" && severity !== "error") logger.error({
1132
+ group: "config",
1133
+ label: `lint › rule › ${id}`,
1134
+ message: `Invalid string ${JSON.stringify(severity)}. Specify "off", "warn", or "error".`
1135
+ });
1136
+ } else if (value !== null) logger.error({
1137
+ group: "config",
1138
+ label: `lint › rule › ${id}`,
1139
+ message: `Expected string or number, received ${JSON.stringify(value)}`
1140
+ });
1141
+ }
1142
+ }
1143
+ } else config.lint = {
1144
+ build: { enabled: true },
1145
+ rules: {}
1146
+ };
1147
+ }
1148
+ function normalizeIgnore({ config, logger }) {
1149
+ if (!config.ignore) config.ignore = {};
1150
+ config.ignore.tokens ??= [];
1151
+ config.ignore.deprecated ??= false;
1152
+ if (!Array.isArray(config.ignore.tokens) || config.ignore.tokens.some((x) => typeof x !== "string")) logger.error({
1153
+ group: "config",
1154
+ label: "ignore › tokens",
1155
+ message: `Expected array of strings, received ${JSON.stringify(config.ignore.tokens)}`
1156
+ });
1157
+ if (typeof config.ignore.deprecated !== "boolean") logger.error({
1158
+ group: "config",
1159
+ label: "ignore › deprecated",
1160
+ message: `Expected boolean, received ${JSON.stringify(config.ignore.deprecated)}`
1161
+ });
1162
+ }
1163
+ /** Merge configs */
1164
+ function mergeConfigs(a, b) {
1165
+ return merge(a, b);
1166
+ }
1167
+
1168
+ //#endregion
1169
+ //#region src/lint/index.ts
1170
+ const listFormat$1 = new Intl.ListFormat("en-us");
1171
+ async function lintRunner({ tokens, filename, config = {}, src, logger }) {
1172
+ const { plugins = [], lint } = config;
1173
+ const unusedLintRules = Object.keys(lint?.rules ?? {});
1174
+ for (const plugin of plugins) if (typeof plugin.lint === "function") {
1175
+ const s = performance.now();
1176
+ const linter = plugin.lint();
1177
+ const errors = [];
1178
+ const warnings = [];
1179
+ await Promise.all(Object.entries(linter).map(async ([id, rule$10]) => {
1180
+ if (!(id in lint.rules) || lint.rules[id] === null) return;
1181
+ const [severity, options] = lint.rules[id];
1182
+ if (severity === "off") return;
1183
+ await rule$10.create({
1184
+ id,
1185
+ report(descriptor) {
1186
+ let message = "";
1187
+ if (!descriptor.message && !descriptor.messageId) logger.error({
1188
+ group: "lint",
1189
+ label: `${plugin.name} › lint › ${id}`,
1190
+ message: "Unable to report error: missing message or messageId"
1191
+ });
1192
+ if (descriptor.message) message = descriptor.message;
1193
+ else {
1194
+ if (!(descriptor.messageId in (rule$10.meta?.messages ?? {}))) logger.error({
1195
+ group: "lint",
1196
+ label: `${plugin.name} › lint › ${id}`,
1197
+ message: `messageId "${descriptor.messageId}" does not exist`
1198
+ });
1199
+ message = rule$10.meta?.messages?.[descriptor.messageId] ?? "";
1200
+ }
1201
+ if (descriptor.data && typeof descriptor.data === "object") for (const [k, v] of Object.entries(descriptor.data)) {
1202
+ const formatted = [
1203
+ "string",
1204
+ "number",
1205
+ "boolean"
1206
+ ].includes(typeof v) ? String(v) : JSON.stringify(v);
1207
+ message = message.replace(/{{[^}]+}}/g, (inner) => {
1208
+ const key = inner.substring(2, inner.length - 2).trim();
1209
+ return key === k ? formatted : inner;
1210
+ });
1211
+ }
1212
+ (severity === "error" ? errors : warnings).push({
1213
+ group: "lint",
1214
+ label: id,
1215
+ message,
1216
+ filename,
1217
+ node: descriptor.node,
1218
+ src: descriptor.source?.src
1219
+ });
1220
+ },
1221
+ tokens,
1222
+ filename,
1223
+ src,
1224
+ options: merge(rule$10.meta?.defaultOptions ?? [], rule$10.defaultOptions ?? [], options)
1225
+ });
1226
+ const unusedLintRuleI = unusedLintRules.indexOf(id);
1227
+ if (unusedLintRuleI !== -1) unusedLintRules.splice(unusedLintRuleI, 1);
1228
+ }));
1229
+ for (const error of errors) logger.error({
1230
+ ...error,
1231
+ continueOnError: true
1232
+ });
1233
+ for (const warning of warnings) logger.warn(warning);
1234
+ logger.debug({
1235
+ group: "lint",
1236
+ label: plugin.name,
1237
+ message: "Finished",
1238
+ timing: performance.now() - s
1239
+ });
1240
+ if (errors.length) {
1241
+ const counts = [pluralize(errors.length, "error", "errors")];
1242
+ if (warnings.length) counts.push(pluralize(warnings.length, "warning", "warnings"));
1243
+ logger.error({
1244
+ group: "lint",
1245
+ message: `Lint failed with ${listFormat$1.format(counts)}`,
1246
+ label: plugin.name,
1247
+ continueOnError: false
1248
+ });
1249
+ }
1250
+ }
1251
+ for (const unusedRule of unusedLintRules) logger.warn({
1252
+ group: "lint",
1253
+ label: "lint",
1254
+ message: `Unknown lint rule "${unusedRule}"`
1255
+ });
1256
+ }
1257
+
1258
+ //#endregion
1259
+ //#region ../../node_modules/.pnpm/@humanwhocodes+momoa@3.3.8/node_modules/@humanwhocodes/momoa/dist/momoa.js
1260
+ /**
1261
+ * @fileoverview Character codes.
1262
+ * @author Nicholas C. Zakas
1263
+ */
1264
+ const CHAR_0 = 48;
1265
+ const CHAR_1 = 49;
1266
+ const CHAR_9 = 57;
1267
+ const CHAR_BACKSLASH = 92;
1268
+ const CHAR_DOLLAR = 36;
1269
+ const CHAR_DOT = 46;
1270
+ const CHAR_DOUBLE_QUOTE = 34;
1271
+ const CHAR_LOWER_A = 97;
1272
+ const CHAR_LOWER_E = 101;
1273
+ const CHAR_LOWER_F = 102;
1274
+ const CHAR_LOWER_N = 110;
1275
+ const CHAR_LOWER_T = 116;
1276
+ const CHAR_LOWER_U = 117;
1277
+ const CHAR_LOWER_X = 120;
1278
+ const CHAR_LOWER_Z = 122;
1279
+ const CHAR_MINUS = 45;
1280
+ const CHAR_NEWLINE = 10;
1281
+ const CHAR_PLUS = 43;
1282
+ const CHAR_RETURN = 13;
1283
+ const CHAR_SINGLE_QUOTE = 39;
1284
+ const CHAR_SLASH = 47;
1285
+ const CHAR_SPACE = 32;
1286
+ const CHAR_TAB = 9;
1287
+ const CHAR_UNDERSCORE = 95;
1288
+ const CHAR_UPPER_A = 65;
1289
+ const CHAR_UPPER_E = 69;
1290
+ const CHAR_UPPER_F = 70;
1291
+ const CHAR_UPPER_N = 78;
1292
+ const CHAR_UPPER_X = 88;
1293
+ const CHAR_UPPER_Z = 90;
1294
+ const CHAR_LOWER_B = 98;
1295
+ const CHAR_LOWER_R = 114;
1296
+ const CHAR_LOWER_V = 118;
1297
+ const CHAR_LINE_SEPARATOR = 8232;
1298
+ const CHAR_PARAGRAPH_SEPARATOR = 8233;
1299
+ const CHAR_UPPER_I = 73;
1300
+ const CHAR_STAR = 42;
1301
+ const CHAR_VTAB = 11;
1302
+ const CHAR_FORM_FEED = 12;
1303
+ const CHAR_NBSP = 160;
1304
+ const CHAR_BOM = 65279;
1305
+ const CHAR_NON_BREAKING_SPACE = 160;
1306
+ const CHAR_EN_QUAD = 8192;
1307
+ const CHAR_EM_QUAD = 8193;
1308
+ const CHAR_EN_SPACE = 8194;
1309
+ const CHAR_EM_SPACE = 8195;
1310
+ const CHAR_THREE_PER_EM_SPACE = 8196;
1311
+ const CHAR_FOUR_PER_EM_SPACE = 8197;
1312
+ const CHAR_SIX_PER_EM_SPACE = 8198;
1313
+ const CHAR_FIGURE_SPACE = 8199;
1314
+ const CHAR_PUNCTUATION_SPACE = 8200;
1315
+ const CHAR_THIN_SPACE = 8201;
1316
+ const CHAR_HAIR_SPACE = 8202;
1317
+ const CHAR_NARROW_NO_BREAK_SPACE = 8239;
1318
+ const CHAR_MEDIUM_MATHEMATICAL_SPACE = 8287;
1319
+ const CHAR_IDEOGRAPHIC_SPACE = 12288;
1320
+ /**
1321
+ * @fileoverview JSON syntax helpers
1322
+ * @author Nicholas C. Zakas
1323
+ */
1324
+ /** @typedef {import("./typedefs.js").TokenType} TokenType */
1325
+ const LBRACKET = "[";
1326
+ const RBRACKET = "]";
1327
+ const LBRACE = "{";
1328
+ const RBRACE = "}";
1329
+ const COLON = ":";
1330
+ const COMMA = ",";
1331
+ const TRUE = "true";
1332
+ const FALSE = "false";
1333
+ const NULL = "null";
1334
+ const NAN$1 = "NaN";
1335
+ const INFINITY$1 = "Infinity";
1336
+ const QUOTE = "\"";
1337
+ const escapeToChar = new Map([
1338
+ [CHAR_DOUBLE_QUOTE, QUOTE],
1339
+ [CHAR_BACKSLASH, "\\"],
1340
+ [CHAR_SLASH, "/"],
1341
+ [CHAR_LOWER_B, "\b"],
1342
+ [CHAR_LOWER_N, "\n"],
1343
+ [CHAR_LOWER_F, "\f"],
1344
+ [CHAR_LOWER_R, "\r"],
1345
+ [CHAR_LOWER_T, " "]
1346
+ ]);
1347
+ const json5EscapeToChar = new Map([
1348
+ ...escapeToChar,
1349
+ [CHAR_LOWER_V, "\v"],
1350
+ [CHAR_0, "\0"]
1351
+ ]);
1352
+ const charToEscape = new Map([
1353
+ [QUOTE, QUOTE],
1354
+ ["\\", "\\"],
1355
+ ["/", "/"],
1356
+ ["\b", "b"],
1357
+ ["\n", "n"],
1358
+ ["\f", "f"],
1359
+ ["\r", "r"],
1360
+ [" ", "t"]
1361
+ ]);
1362
+ const json5CharToEscape = new Map([
1363
+ ...charToEscape,
1364
+ ["\v", "v"],
1365
+ ["\0", "0"],
1366
+ ["\u2028", "u2028"],
1367
+ ["\u2029", "u2029"]
1368
+ ]);
1369
+ /** @type {Map<string,TokenType>} */
1370
+ const knownTokenTypes = new Map([
1371
+ [LBRACKET, "LBracket"],
1372
+ [RBRACKET, "RBracket"],
1373
+ [LBRACE, "LBrace"],
1374
+ [RBRACE, "RBrace"],
1375
+ [COLON, "Colon"],
1376
+ [COMMA, "Comma"],
1377
+ [TRUE, "Boolean"],
1378
+ [FALSE, "Boolean"],
1379
+ [NULL, "Null"]
1380
+ ]);
1381
+ /** @type {Map<string,TokenType>} */
1382
+ const knownJSON5TokenTypes = new Map([
1383
+ ...knownTokenTypes,
1384
+ [NAN$1, "Number"],
1385
+ [INFINITY$1, "Number"]
1386
+ ]);
1387
+ const json5LineTerminators = new Set([
1388
+ CHAR_NEWLINE,
1389
+ CHAR_RETURN,
1390
+ CHAR_LINE_SEPARATOR,
1391
+ CHAR_PARAGRAPH_SEPARATOR
1392
+ ]);
1393
+ /**
1394
+ * @fileoverview JSON tokenization/parsing errors
1395
+ * @author Nicholas C. Zakas
1396
+ */
1397
+ /** @typedef {import("./typedefs.js").Location} Location */
1398
+ /** @typedef {import("./typedefs.js").Token} Token */
1399
+ /**
1400
+ * Base class that attaches location to an error.
1401
+ */
1402
+ var ErrorWithLocation = class extends Error {
1403
+ /**
1404
+ * Creates a new instance.
1405
+ * @param {string} message The error message to report.
1406
+ * @param {Location} loc The location information for the error.
1407
+ */
1408
+ constructor(message, { line, column, offset }) {
1409
+ super(`${message} (${line}:${column})`);
1410
+ /**
1411
+ * The line on which the error occurred.
1412
+ * @type {number}
1413
+ */
1414
+ this.line = line;
1415
+ /**
1416
+ * The column on which the error occurred.
1417
+ * @type {number}
1418
+ */
1419
+ this.column = column;
1420
+ /**
1421
+ * The index into the string where the error occurred.
1422
+ * @type {number}
1423
+ */
1424
+ this.offset = offset;
1425
+ }
1426
+ };
1427
+ /**
1428
+ * Error thrown when an unexpected character is found during tokenizing.
1429
+ */
1430
+ var UnexpectedChar = class extends ErrorWithLocation {
1431
+ /**
1432
+ * Creates a new instance.
1433
+ * @param {number} unexpected The character that was found.
1434
+ * @param {Location} loc The location information for the found character.
1435
+ */
1436
+ constructor(unexpected, loc) {
1437
+ super(`Unexpected character '${String.fromCharCode(unexpected)}' found.`, loc);
1438
+ }
1439
+ };
1440
+ /**
1441
+ * Error thrown when an unexpected identifier is found during tokenizing.
1442
+ */
1443
+ var UnexpectedIdentifier = class extends ErrorWithLocation {
1444
+ /**
1445
+ * Creates a new instance.
1446
+ * @param {string} unexpected The character that was found.
1447
+ * @param {Location} loc The location information for the found character.
1448
+ */
1449
+ constructor(unexpected, loc) {
1450
+ super(`Unexpected identifier '${unexpected}' found.`, loc);
1451
+ }
1452
+ };
1453
+ /**
1454
+ * Error thrown when an unexpected token is found during parsing.
1455
+ */
1456
+ var UnexpectedToken = class extends ErrorWithLocation {
1457
+ /**
1458
+ * Creates a new instance.
1459
+ * @param {Token} token The token that was found.
1460
+ */
1461
+ constructor(token) {
1462
+ super(`Unexpected token ${token.type} found.`, token.loc.start);
1463
+ }
1464
+ };
1465
+ /**
1466
+ * Error thrown when the end of input is found where it isn't expected.
1467
+ */
1468
+ var UnexpectedEOF = class extends ErrorWithLocation {
1469
+ /**
1470
+ * Creates a new instance.
1471
+ * @param {Location} loc The location information for the found character.
1472
+ */
1473
+ constructor(loc) {
1474
+ super("Unexpected end of input found.", loc);
1475
+ }
1476
+ };
1477
+ const ID_Start = /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE83\uDE86-\uDE89\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]/;
1478
+ const ID_Continue = /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D00-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1CD0-\u1CD2\u1CD4-\u1CF9\u1D00-\u1DF9\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDE00-\uDE3E\uDE47\uDE50-\uDE83\uDE86-\uDE99\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4A\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/;
1479
+ /**
1480
+ * @fileoverview A charactor code reader.
1481
+ * @author Nicholas C. Zakas
1482
+ */
1483
+ const CHAR_CR = 13;
1484
+ const CHAR_LF = 10;
1485
+ /**
1486
+ * A reader that reads character codes from a string.
1487
+ */
1488
+ var CharCodeReader = class {
1489
+ /**
1490
+ * The text to read from.
1491
+ * @type {string}
1492
+ */
1493
+ #text = "";
1494
+ /**
1495
+ * The current line number.
1496
+ * @type {number}
1497
+ */
1498
+ #line = 1;
1499
+ /**
1500
+ * The current column number.
1501
+ * @type {number}
1502
+ */
1503
+ #column = 0;
1504
+ /**
1505
+ * The current offset in the text.
1506
+ * @type {number}
1507
+ */
1508
+ #offset = -1;
1509
+ /**
1510
+ * Whether the last character read was a new line.
1511
+ * @type {boolean}
1512
+ */
1513
+ #newLine = false;
1514
+ /**
1515
+ * The last character code read.
1516
+ * @type {number}
1517
+ */
1518
+ #last = -1;
1519
+ /**
1520
+ * Whether the reader has ended.
1521
+ * @type {boolean}
1522
+ */
1523
+ #ended = false;
1524
+ /**
1525
+ * Creates a new instance.
1526
+ * @param {string} text The text to read from
1527
+ */
1528
+ constructor(text) {
1529
+ this.#text = text;
1530
+ }
1531
+ /**
1532
+ * Ends the reader.
1533
+ * @returns {void}
1534
+ */
1535
+ #end() {
1536
+ if (this.#ended) return;
1537
+ this.#column++;
1538
+ this.#offset++;
1539
+ this.#last = -1;
1540
+ this.#ended = true;
1541
+ }
1542
+ /**
1543
+ * Returns the current position of the reader.
1544
+ * @returns {Location} An object with line, column, and offset properties.
1545
+ */
1546
+ locate() {
1547
+ return {
1548
+ line: this.#line,
1549
+ column: this.#column,
1550
+ offset: this.#offset
1551
+ };
1552
+ }
1553
+ /**
1554
+ * Reads the next character code in the text.
1555
+ * @returns {number} The next character code, or -1 if there are no more characters.
1556
+ */
1557
+ next() {
1558
+ if (this.#offset >= this.#text.length - 1) {
1559
+ this.#end();
1560
+ return -1;
1561
+ }
1562
+ this.#offset++;
1563
+ const charCode = this.#text.charCodeAt(this.#offset);
1564
+ if (this.#newLine) {
1565
+ this.#line++;
1566
+ this.#column = 1;
1567
+ this.#newLine = false;
1568
+ } else this.#column++;
1569
+ if (charCode === CHAR_CR) {
1570
+ this.#newLine = true;
1571
+ if (this.peek() === CHAR_LF) this.#offset++;
1572
+ } else if (charCode === CHAR_LF) this.#newLine = true;
1573
+ this.#last = charCode;
1574
+ return charCode;
1575
+ }
1576
+ /**
1577
+ * Peeks at the next character code in the text.
1578
+ * @returns {number} The next character code, or -1 if there are no more characters.
1579
+ */
1580
+ peek() {
1581
+ if (this.#offset === this.#text.length - 1) return -1;
1582
+ return this.#text.charCodeAt(this.#offset + 1);
1583
+ }
1584
+ /**
1585
+ * Determines if the next character code in the text matches a specific character code.
1586
+ * @param {(number) => boolean} fn A function to call on the next character.
1587
+ * @returns {boolean} True if the next character code matches, false if not.
1588
+ */
1589
+ match(fn) {
1590
+ if (fn(this.peek())) {
1591
+ this.next();
1592
+ return true;
1593
+ }
1594
+ return false;
1595
+ }
1596
+ /**
1597
+ * Returns the last character code read.
1598
+ * @returns {number} The last character code read.
1599
+ */
1600
+ current() {
1601
+ return this.#last;
1602
+ }
1603
+ };
1604
+ /**
1605
+ * @fileoverview JSON tokenizer
1606
+ * @author Nicholas C. Zakas
1607
+ */
1608
+ /** @typedef {import("./typedefs.js").Range} Range */
1609
+ /** @typedef {import("./typedefs.js").TokenizeOptions} TokenizeOptions */
1610
+ const INFINITY = "Infinity";
1611
+ const NAN = "NaN";
1612
+ const keywordStarts = new Set([
1613
+ CHAR_LOWER_T,
1614
+ CHAR_LOWER_F,
1615
+ CHAR_LOWER_N
1616
+ ]);
1617
+ const whitespace = new Set([
1618
+ CHAR_SPACE,
1619
+ CHAR_TAB,
1620
+ CHAR_NEWLINE,
1621
+ CHAR_RETURN
1622
+ ]);
1623
+ const json5Whitespace = new Set([
1624
+ ...whitespace,
1625
+ CHAR_VTAB,
1626
+ CHAR_FORM_FEED,
1627
+ CHAR_NBSP,
1628
+ CHAR_LINE_SEPARATOR,
1629
+ CHAR_PARAGRAPH_SEPARATOR,
1630
+ CHAR_BOM,
1631
+ CHAR_NON_BREAKING_SPACE,
1632
+ CHAR_EN_QUAD,
1633
+ CHAR_EM_QUAD,
1634
+ CHAR_EN_SPACE,
1635
+ CHAR_EM_SPACE,
1636
+ CHAR_THREE_PER_EM_SPACE,
1637
+ CHAR_FOUR_PER_EM_SPACE,
1638
+ CHAR_SIX_PER_EM_SPACE,
1639
+ CHAR_FIGURE_SPACE,
1640
+ CHAR_PUNCTUATION_SPACE,
1641
+ CHAR_THIN_SPACE,
1642
+ CHAR_HAIR_SPACE,
1643
+ CHAR_NARROW_NO_BREAK_SPACE,
1644
+ CHAR_MEDIUM_MATHEMATICAL_SPACE,
1645
+ CHAR_IDEOGRAPHIC_SPACE
1646
+ ]);
1647
+ /** @type {TokenizeOptions} */
1648
+ const DEFAULT_OPTIONS$1 = {
1649
+ mode: "json",
1650
+ ranges: false
1651
+ };
1652
+ const jsonKeywords = new Set([
1653
+ "true",
1654
+ "false",
1655
+ "null"
1656
+ ]);
1657
+ const tt = {
1658
+ EOF: 0,
1659
+ Number: 1,
1660
+ String: 2,
1661
+ Boolean: 3,
1662
+ Null: 4,
1663
+ NaN: 5,
1664
+ Infinity: 6,
1665
+ Identifier: 7,
1666
+ Colon: 20,
1667
+ LBrace: 21,
1668
+ RBrace: 22,
1669
+ LBracket: 23,
1670
+ RBracket: 24,
1671
+ Comma: 25,
1672
+ LineComment: 40,
1673
+ BlockComment: 41
1674
+ };
1675
+ /**
1676
+ * Determines if a given character is a decimal digit.
1677
+ * @param {number} c The character to check.
1678
+ * @returns {boolean} `true` if the character is a digit.
1679
+ */
1680
+ function isDigit(c) {
1681
+ return c >= CHAR_0 && c <= CHAR_9;
1682
+ }
1683
+ /**
1684
+ * Determines if a given character is a hexadecimal digit.
1685
+ * @param {number} c The character to check.
1686
+ * @returns {boolean} `true` if the character is a hexadecimal digit.
1687
+ */
1688
+ function isHexDigit(c) {
1689
+ return isDigit(c) || c >= CHAR_UPPER_A && c <= CHAR_UPPER_F || c >= CHAR_LOWER_A && c <= CHAR_LOWER_F;
1690
+ }
1691
+ /**
1692
+ * Determines if a given character is a positive digit (1-9).
1693
+ * @param {number} c The character to check.
1694
+ * @returns {boolean} `true` if the character is a positive digit.
1695
+ */
1696
+ function isPositiveDigit(c) {
1697
+ return c >= CHAR_1 && c <= CHAR_9;
1698
+ }
1699
+ /**
1700
+ * Determines if a given character is the start of a keyword.
1701
+ * @param {number} c The character to check.
1702
+ * @returns {boolean} `true` if the character is the start of a keyword.
1703
+ */
1704
+ function isKeywordStart(c) {
1705
+ return keywordStarts.has(c);
1706
+ }
1707
+ /**
1708
+ * Determines if a given character is the start of a number.
1709
+ * @param {number} c The character to check.
1710
+ * @returns {boolean} `true` if the character is the start of a number.
1711
+ */
1712
+ function isNumberStart(c) {
1713
+ return isDigit(c) || c === CHAR_DOT || c === CHAR_MINUS;
1714
+ }
1715
+ /**
1716
+ * Determines if a given character is the start of a JSON5 number.
1717
+ * @param {number} c The character to check.
1718
+ * @returns {boolean} `true` if the character is the start of a JSON5 number.
1719
+ */
1720
+ function isJSON5NumberStart(c) {
1721
+ return isNumberStart(c) || c === CHAR_PLUS;
1722
+ }
1723
+ /**
1724
+ * Determines if a given character is the start of a string.
1725
+ * @param {number} c The character to check.
1726
+ * @param {boolean} json5 `true` if JSON5 mode is enabled.
1727
+ * @returns {boolean} `true` if the character is the start of a string.
1728
+ */
1729
+ function isStringStart(c, json5) {
1730
+ return c === CHAR_DOUBLE_QUOTE || json5 && c === CHAR_SINGLE_QUOTE;
1731
+ }
1732
+ /**
1733
+ * Tests that a given character is a valid first character of a
1734
+ * JSON5 identifier
1735
+ * @param {number} c The character to check.
1736
+ * @returns {boolean} `true` if the character is a valid first character.
1737
+ */
1738
+ function isJSON5IdentifierStart(c) {
1739
+ if (c === CHAR_DOLLAR || c === CHAR_UNDERSCORE || c === CHAR_BACKSLASH) return true;
1740
+ if (c >= CHAR_LOWER_A && c <= CHAR_LOWER_Z || c >= CHAR_UPPER_A && c <= CHAR_UPPER_Z) return true;
1741
+ if (c === 8204 || c === 8205) return true;
1742
+ const ct = String.fromCharCode(c);
1743
+ return ID_Start.test(ct);
1744
+ }
1745
+ /**
1746
+ * Tests that a given character is a valid part of a JSON5 identifier.
1747
+ * @param {number} c The character to check.
1748
+ * @returns {boolean} `true` if the character is a valid part of an identifier.
1749
+ */
1750
+ function isJSON5IdentifierPart(c) {
1751
+ if (isJSON5IdentifierStart(c) || isDigit(c)) return true;
1752
+ const ct = String.fromCharCode(c);
1753
+ return ID_Continue.test(ct);
1754
+ }
1755
+ var Tokenizer = class {
1756
+ /**
1757
+ * Options for the tokenizer.
1758
+ * @type {TokenizeOptions}
1759
+ */
1760
+ #options;
1761
+ /**
1762
+ * The source text to tokenize.
1763
+ * @type {string}
1764
+ */
1765
+ #text;
1766
+ /**
1767
+ * The reader for the source text.
1768
+ * @type {CharCodeReader}
1769
+ */
1770
+ #reader;
1771
+ /**
1772
+ * Indicates if the tokenizer is in JSON5 mode.
1773
+ * @type {boolean}
1774
+ */
1775
+ #json5;
1776
+ /**
1777
+ * Indicates if comments are allowed.
1778
+ * @type {boolean}
1779
+ */
1780
+ #allowComments;
1781
+ /**
1782
+ * Indicates if ranges should be included in the tokens.
1783
+ * @type {boolean}
1784
+ */
1785
+ #ranges;
1786
+ /**
1787
+ * The last token type read.
1788
+ * @type {Token}
1789
+ */
1790
+ #token;
1791
+ /**
1792
+ * Determines if a character is an escaped character.
1793
+ * @type {(c:number) => boolean}
1794
+ */
1795
+ #isEscapedCharacter;
1796
+ /**
1797
+ * Determines if a character is a JSON5 line terminator.
1798
+ * @type {(c:number) => boolean}
1799
+ */
1800
+ #isJSON5LineTerminator;
1801
+ /**
1802
+ * Determines if a character is a JSON5 hex escape.
1803
+ * @type {(c:number) => boolean}
1804
+ */
1805
+ #isJSON5HexEscape;
1806
+ /**
1807
+ * Determines if a character is whitespace.
1808
+ * @type {(c:number) => boolean}
1809
+ */
1810
+ #isWhitespace;
1811
+ /**
1812
+ * Creates a new instance of the tokenizer.
1813
+ * @param {string} text The source text
1814
+ * @param {TokenizeOptions} [options] Options for the tokenizer.
1815
+ */
1816
+ constructor(text, options) {
1817
+ this.#text = text;
1818
+ this.#options = {
1819
+ ...DEFAULT_OPTIONS$1,
1820
+ ...options
1821
+ };
1822
+ this.#reader = new CharCodeReader(text);
1823
+ this.#json5 = this.#options.mode === "json5";
1824
+ this.#allowComments = this.#options.mode !== "json";
1825
+ this.#ranges = this.#options.ranges;
1826
+ this.#isEscapedCharacter = this.#json5 ? json5EscapeToChar.has.bind(json5EscapeToChar) : escapeToChar.has.bind(escapeToChar);
1827
+ this.#isJSON5LineTerminator = this.#json5 ? json5LineTerminators.has.bind(json5LineTerminators) : () => false;
1828
+ this.#isJSON5HexEscape = this.#json5 ? (c) => c === CHAR_LOWER_X : () => false;
1829
+ this.#isWhitespace = this.#json5 ? json5Whitespace.has.bind(json5Whitespace) : whitespace.has.bind(whitespace);
1830
+ }
1831
+ /**
1832
+ * Convenience function for throwing unexpected character errors.
1833
+ * @param {number} c The unexpected character.
1834
+ * @param {Location} [loc] The location of the unexpected character.
1835
+ * @returns {never}
1836
+ * @throws {UnexpectedChar} always.
1837
+ */
1838
+ #unexpected(c, loc = this.#reader.locate()) {
1839
+ throw new UnexpectedChar(c, loc);
1840
+ }
1841
+ /**
1842
+ * Convenience function for throwing unexpected identifier errors.
1843
+ * @param {string} identifier The unexpected identifier.
1844
+ * @param {Location} [loc] The location of the unexpected identifier.
1845
+ * @returns {never}
1846
+ * @throws {UnexpectedIdentifier} always.
1847
+ */
1848
+ #unexpectedIdentifier(identifier, loc = this.#reader.locate()) {
1849
+ throw new UnexpectedIdentifier(identifier, loc);
1850
+ }
1851
+ /**
1852
+ * Convenience function for throwing unexpected EOF errors.
1853
+ * @returns {never}
1854
+ * @throws {UnexpectedEOF} always.
1855
+ */
1856
+ #unexpectedEOF() {
1857
+ throw new UnexpectedEOF(this.#reader.locate());
1858
+ }
1859
+ /**
1860
+ * Creates a new token.
1861
+ * @param {TokenType} tokenType The type of token to create.
1862
+ * @param {number} length The length of the token.
1863
+ * @param {Location} startLoc The start location for the token.
1864
+ * @param {Location} [endLoc] The end location for the token.
1865
+ * @returns {Token} The token.
1866
+ */
1867
+ #createToken(tokenType, length, startLoc, endLoc) {
1868
+ const endOffset = startLoc.offset + length;
1869
+ let range = this.#options.ranges ? { range: [startLoc.offset, endOffset] } : void 0;
1870
+ return {
1871
+ type: tokenType,
1872
+ loc: {
1873
+ start: startLoc,
1874
+ end: endLoc || {
1875
+ line: startLoc.line,
1876
+ column: startLoc.column + length,
1877
+ offset: endOffset
1878
+ }
1879
+ },
1880
+ ...range
1881
+ };
1882
+ }
1883
+ /**
1884
+ * Reads in a specific number of hex digits.
1885
+ * @param {number} count The number of hex digits to read.
1886
+ * @returns {string} The hex digits read.
1887
+ */
1888
+ #readHexDigits(count) {
1889
+ let value = "";
1890
+ let c;
1891
+ for (let i = 0; i < count; i++) {
1892
+ c = this.#reader.peek();
1893
+ if (isHexDigit(c)) {
1894
+ this.#reader.next();
1895
+ value += String.fromCharCode(c);
1896
+ continue;
1897
+ }
1898
+ this.#unexpected(c);
1899
+ }
1900
+ return value;
1901
+ }
1902
+ /**
1903
+ * Reads in a JSON5 identifier. Also used for JSON but we validate
1904
+ * the identifier later.
1905
+ * @param {number} c The first character of the identifier.
1906
+ * @returns {string} The identifier read.
1907
+ * @throws {UnexpectedChar} when the identifier cannot be read.
1908
+ */
1909
+ #readIdentifier(c) {
1910
+ let value = "";
1911
+ do {
1912
+ value += String.fromCharCode(c);
1913
+ if (c === CHAR_BACKSLASH) {
1914
+ c = this.#reader.next();
1915
+ if (c !== CHAR_LOWER_U) this.#unexpected(c);
1916
+ value += String.fromCharCode(c);
1917
+ const hexDigits = this.#readHexDigits(4);
1918
+ const charCode = parseInt(hexDigits, 16);
1919
+ if (value.length === 2 && !isJSON5IdentifierStart(charCode)) {
1920
+ const loc = this.#reader.locate();
1921
+ this.#unexpected(CHAR_BACKSLASH, {
1922
+ line: loc.line,
1923
+ column: loc.column - 5,
1924
+ offset: loc.offset - 5
1925
+ });
1926
+ } else if (!isJSON5IdentifierPart(charCode)) {
1927
+ const loc = this.#reader.locate();
1928
+ this.#unexpected(charCode, {
1929
+ line: loc.line,
1930
+ column: loc.column - 5,
1931
+ offset: loc.offset - 5
1932
+ });
1933
+ }
1934
+ value += hexDigits;
1935
+ }
1936
+ c = this.#reader.peek();
1937
+ if (!isJSON5IdentifierPart(c)) break;
1938
+ this.#reader.next();
1939
+ } while (true);
1940
+ return value;
1941
+ }
1942
+ /**
1943
+ * Reads in a string. Works for both JSON and JSON5.
1944
+ * @param {number} c The first character of the string (either " or ').
1945
+ * @returns {number} The length of the string.
1946
+ * @throws {UnexpectedChar} when the string cannot be read.
1947
+ * @throws {UnexpectedEOF} when EOF is reached before the string is finalized.
1948
+ */
1949
+ #readString(c) {
1950
+ const delimiter = c;
1951
+ let length = 1;
1952
+ c = this.#reader.peek();
1953
+ while (c !== -1 && c !== delimiter) {
1954
+ this.#reader.next();
1955
+ length++;
1956
+ if (c === CHAR_BACKSLASH) {
1957
+ c = this.#reader.peek();
1958
+ if (this.#isEscapedCharacter(c) || this.#isJSON5LineTerminator(c)) {
1959
+ this.#reader.next();
1960
+ length++;
1961
+ } else if (c === CHAR_LOWER_U) {
1962
+ this.#reader.next();
1963
+ length++;
1964
+ const result = this.#readHexDigits(4);
1965
+ length += result.length;
1966
+ } else if (this.#isJSON5HexEscape(c)) {
1967
+ this.#reader.next();
1968
+ length++;
1969
+ const result = this.#readHexDigits(2);
1970
+ length += result.length;
1971
+ } else if (this.#json5) {
1972
+ this.#reader.next();
1973
+ length++;
1974
+ } else this.#unexpected(c);
1975
+ }
1976
+ c = this.#reader.peek();
1977
+ }
1978
+ if (c === -1) {
1979
+ this.#reader.next();
1980
+ this.#unexpectedEOF();
1981
+ }
1982
+ this.#reader.next();
1983
+ length++;
1984
+ return length;
1985
+ }
1986
+ /**
1987
+ * Reads a number. Works for both JSON and JSON5.
1988
+ * @param {number} c The first character of the number.
1989
+ * @returns {number} The length of the number.
1990
+ * @throws {UnexpectedChar} when the number cannot be read.
1991
+ * @throws {UnexpectedEOF} when EOF is reached before the number is finalized.
1992
+ */
1993
+ #readNumber(c) {
1994
+ let length = 1;
1995
+ if (c === CHAR_MINUS || this.#json5 && c === CHAR_PLUS) {
1996
+ c = this.#reader.peek();
1997
+ if (this.#json5) {
1998
+ if (c === CHAR_UPPER_I || c === CHAR_UPPER_N) {
1999
+ this.#reader.next();
2000
+ const identifier = this.#readIdentifier(c);
2001
+ if (identifier !== INFINITY && identifier !== NAN) this.#unexpected(c);
2002
+ return length + identifier.length;
2003
+ }
2004
+ }
2005
+ if (!isDigit(c)) this.#unexpected(c);
2006
+ this.#reader.next();
2007
+ length++;
2008
+ }
2009
+ if (c === CHAR_0) {
2010
+ c = this.#reader.peek();
2011
+ if (this.#json5 && (c === CHAR_LOWER_X || c === CHAR_UPPER_X)) {
2012
+ this.#reader.next();
2013
+ length++;
2014
+ c = this.#reader.peek();
2015
+ if (!isHexDigit(c)) {
2016
+ this.#reader.next();
2017
+ this.#unexpected(c);
2018
+ }
2019
+ do {
2020
+ this.#reader.next();
2021
+ length++;
2022
+ c = this.#reader.peek();
2023
+ } while (isHexDigit(c));
2024
+ } else if (isDigit(c)) this.#unexpected(c);
2025
+ } else if (!this.#json5 || c !== CHAR_DOT) {
2026
+ if (!isPositiveDigit(c)) this.#unexpected(c);
2027
+ c = this.#reader.peek();
2028
+ while (isDigit(c)) {
2029
+ this.#reader.next();
2030
+ length++;
2031
+ c = this.#reader.peek();
2032
+ }
2033
+ }
2034
+ if (c === CHAR_DOT) {
2035
+ let digitCount = -1;
2036
+ this.#reader.next();
2037
+ length++;
2038
+ digitCount++;
2039
+ c = this.#reader.peek();
2040
+ while (isDigit(c)) {
2041
+ this.#reader.next();
2042
+ length++;
2043
+ digitCount++;
2044
+ c = this.#reader.peek();
2045
+ }
2046
+ if (!this.#json5 && digitCount === 0) {
2047
+ this.#reader.next();
2048
+ if (c) this.#unexpected(c);
2049
+ else this.#unexpectedEOF();
2050
+ }
2051
+ }
2052
+ if (c === CHAR_LOWER_E || c === CHAR_UPPER_E) {
2053
+ this.#reader.next();
2054
+ length++;
2055
+ c = this.#reader.peek();
2056
+ if (c === CHAR_PLUS || c === CHAR_MINUS) {
2057
+ this.#reader.next();
2058
+ length++;
2059
+ c = this.#reader.peek();
2060
+ }
2061
+ if (c === -1) {
2062
+ this.#reader.next();
2063
+ this.#unexpectedEOF();
2064
+ }
2065
+ if (!isDigit(c)) {
2066
+ this.#reader.next();
2067
+ this.#unexpected(c);
2068
+ }
2069
+ while (isDigit(c)) {
2070
+ this.#reader.next();
2071
+ length++;
2072
+ c = this.#reader.peek();
2073
+ }
2074
+ }
2075
+ return length;
2076
+ }
2077
+ /**
2078
+ * Reads a comment. Works for both JSON and JSON5.
2079
+ * @param {number} c The first character of the comment.
2080
+ * @returns {{length: number, multiline: boolean}} The length of the comment, and whether the comment is multi-line.
2081
+ * @throws {UnexpectedChar} when the comment cannot be read.
2082
+ * @throws {UnexpectedEOF} when EOF is reached before the comment is finalized.
2083
+ */
2084
+ #readComment(c) {
2085
+ let length = 1;
2086
+ c = this.#reader.peek();
2087
+ if (c === CHAR_SLASH) {
2088
+ do {
2089
+ this.#reader.next();
2090
+ length += 1;
2091
+ c = this.#reader.peek();
2092
+ } while (c > -1 && c !== CHAR_RETURN && c !== CHAR_NEWLINE);
2093
+ return {
2094
+ length,
2095
+ multiline: false
2096
+ };
2097
+ }
2098
+ if (c === CHAR_STAR) {
2099
+ this.#reader.next();
2100
+ length += 1;
2101
+ while (c > -1) {
2102
+ c = this.#reader.peek();
2103
+ if (c === CHAR_STAR) {
2104
+ this.#reader.next();
2105
+ length += 1;
2106
+ c = this.#reader.peek();
2107
+ if (c === CHAR_SLASH) {
2108
+ this.#reader.next();
2109
+ length += 1;
2110
+ return {
2111
+ length,
2112
+ multiline: true
2113
+ };
2114
+ }
2115
+ } else {
2116
+ this.#reader.next();
2117
+ length += 1;
2118
+ }
2119
+ }
2120
+ this.#reader.next();
2121
+ this.#unexpectedEOF();
2122
+ }
2123
+ this.#reader.next();
2124
+ this.#unexpected(c);
2125
+ }
2126
+ /**
2127
+ * Returns the next token in the source text.
2128
+ * @returns {number} The code for the next token.
2129
+ */
2130
+ next() {
2131
+ let c = this.#reader.next();
2132
+ while (this.#isWhitespace(c)) c = this.#reader.next();
2133
+ if (c === -1) return tt.EOF;
2134
+ const start = this.#reader.locate();
2135
+ const ct = String.fromCharCode(c);
2136
+ if (this.#json5) if (knownJSON5TokenTypes.has(ct)) this.#token = this.#createToken(knownJSON5TokenTypes.get(ct), 1, start);
2137
+ else if (isJSON5IdentifierStart(c)) {
2138
+ const value = this.#readIdentifier(c);
2139
+ if (knownJSON5TokenTypes.has(value)) this.#token = this.#createToken(knownJSON5TokenTypes.get(value), value.length, start);
2140
+ else this.#token = this.#createToken("Identifier", value.length, start);
2141
+ } else if (isJSON5NumberStart(c)) {
2142
+ const result = this.#readNumber(c);
2143
+ this.#token = this.#createToken("Number", result, start);
2144
+ } else if (isStringStart(c, this.#json5)) {
2145
+ const result = this.#readString(c);
2146
+ const lastCharLoc = this.#reader.locate();
2147
+ this.#token = this.#createToken("String", result, start, {
2148
+ line: lastCharLoc.line,
2149
+ column: lastCharLoc.column + 1,
2150
+ offset: lastCharLoc.offset + 1
2151
+ });
2152
+ } else if (c === CHAR_SLASH && this.#allowComments) {
2153
+ const result = this.#readComment(c);
2154
+ const lastCharLoc = this.#reader.locate();
2155
+ this.#token = this.#createToken(!result.multiline ? "LineComment" : "BlockComment", result.length, start, {
2156
+ line: lastCharLoc.line,
2157
+ column: lastCharLoc.column + 1,
2158
+ offset: lastCharLoc.offset + 1
2159
+ });
2160
+ } else this.#unexpected(c);
2161
+ else if (knownTokenTypes.has(ct)) this.#token = this.#createToken(knownTokenTypes.get(ct), 1, start);
2162
+ else if (isKeywordStart(c)) {
2163
+ const value = this.#readIdentifier(c);
2164
+ if (!jsonKeywords.has(value)) this.#unexpectedIdentifier(value, start);
2165
+ this.#token = this.#createToken(knownTokenTypes.get(value), value.length, start);
2166
+ } else if (isNumberStart(c)) {
2167
+ const result = this.#readNumber(c);
2168
+ this.#token = this.#createToken("Number", result, start);
2169
+ } else if (isStringStart(c, this.#json5)) {
2170
+ const result = this.#readString(c);
2171
+ this.#token = this.#createToken("String", result, start);
2172
+ } else if (c === CHAR_SLASH && this.#allowComments) {
2173
+ const result = this.#readComment(c);
2174
+ const lastCharLoc = this.#reader.locate();
2175
+ this.#token = this.#createToken(!result.multiline ? "LineComment" : "BlockComment", result.length, start, {
2176
+ line: lastCharLoc.line,
2177
+ column: lastCharLoc.column + 1,
2178
+ offset: lastCharLoc.offset + 1
2179
+ });
2180
+ } else this.#unexpected(c);
2181
+ return tt[this.#token.type];
2182
+ }
2183
+ /**
2184
+ * Returns the current token in the source text.
2185
+ * @returns {Token} The current token.
2186
+ */
2187
+ get token() {
2188
+ return this.#token;
2189
+ }
2190
+ };
2191
+ /**
2192
+ * @fileoverview JSON AST types
2193
+ * @author Nicholas C. Zakas
2194
+ */
2195
+ /** @typedef {import("./typedefs.js").NodeParts} NodeParts */
2196
+ /** @typedef {import("./typedefs.js").DocumentNode} DocumentNode */
2197
+ /** @typedef {import("./typedefs.js").StringNode} StringNode */
2198
+ /** @typedef {import("./typedefs.js").NumberNode} NumberNode */
2199
+ /** @typedef {import("./typedefs.js").BooleanNode} BooleanNode */
2200
+ /** @typedef {import("./typedefs.js").MemberNode} MemberNode */
2201
+ /** @typedef {import("./typedefs.js").ObjectNode} ObjectNode */
2202
+ /** @typedef {import("./typedefs.js").ElementNode} ElementNode */
2203
+ /** @typedef {import("./typedefs.js").ArrayNode} ArrayNode */
2204
+ /** @typedef {import("./typedefs.js").NullNode} NullNode */
2205
+ /** @typedef {import("./typedefs.js").ValueNode} ValueNode */
2206
+ /** @typedef {import("./typedefs.js").IdentifierNode} IdentifierNode */
2207
+ /** @typedef {import("./typedefs.js").NaNNode} NaNNode */
2208
+ /** @typedef {import("./typedefs.js").InfinityNode} InfinityNode */
2209
+ /** @typedef {import("./typedefs.js").Sign} Sign */
2210
+ const types = {
2211
+ document(body, parts = {}) {
2212
+ return {
2213
+ type: "Document",
2214
+ body,
2215
+ loc: parts.loc,
2216
+ ...parts
2217
+ };
2218
+ },
2219
+ string(value, parts = {}) {
2220
+ return {
2221
+ type: "String",
2222
+ value,
2223
+ loc: parts.loc,
2224
+ ...parts
2225
+ };
2226
+ },
2227
+ number(value, parts = {}) {
2228
+ return {
2229
+ type: "Number",
2230
+ value,
2231
+ loc: parts.loc,
2232
+ ...parts
2233
+ };
2234
+ },
2235
+ boolean(value, parts = {}) {
2236
+ return {
2237
+ type: "Boolean",
2238
+ value,
2239
+ loc: parts.loc,
2240
+ ...parts
2241
+ };
2242
+ },
2243
+ null(parts = {}) {
2244
+ return {
2245
+ type: "Null",
2246
+ loc: parts.loc,
2247
+ ...parts
2248
+ };
2249
+ },
2250
+ array(elements, parts = {}) {
2251
+ return {
2252
+ type: "Array",
2253
+ elements,
2254
+ loc: parts.loc,
2255
+ ...parts
2256
+ };
2257
+ },
2258
+ element(value, parts = {}) {
2259
+ return {
2260
+ type: "Element",
2261
+ value,
2262
+ loc: parts.loc,
2263
+ ...parts
2264
+ };
2265
+ },
2266
+ object(members, parts = {}) {
2267
+ return {
2268
+ type: "Object",
2269
+ members,
2270
+ loc: parts.loc,
2271
+ ...parts
2272
+ };
2273
+ },
2274
+ member(name, value, parts = {}) {
2275
+ return {
2276
+ type: "Member",
2277
+ name,
2278
+ value,
2279
+ loc: parts.loc,
2280
+ ...parts
2281
+ };
2282
+ },
2283
+ identifier(name, parts = {}) {
2284
+ return {
2285
+ type: "Identifier",
2286
+ name,
2287
+ loc: parts.loc,
2288
+ ...parts
2289
+ };
2290
+ },
2291
+ nan(sign = "", parts = {}) {
2292
+ return {
2293
+ type: "NaN",
2294
+ sign,
2295
+ loc: parts.loc,
2296
+ ...parts
2297
+ };
2298
+ },
2299
+ infinity(sign = "", parts = {}) {
2300
+ return {
2301
+ type: "Infinity",
2302
+ sign,
2303
+ loc: parts.loc,
2304
+ ...parts
2305
+ };
2306
+ }
2307
+ };
2308
+ /**
2309
+ * @fileoverview JSON parser
2310
+ * @author Nicholas C. Zakas
2311
+ */
2312
+ /** @typedef {import("./typedefs.js").Node} Node */
2313
+ /** @typedef {import("./typedefs.js").Mode} Mode */
2314
+ /** @typedef {import("./typedefs.js").ParseOptions} ParseOptions */
2315
+ /** @type {ParseOptions} */
2316
+ const DEFAULT_OPTIONS = {
2317
+ mode: "json",
2318
+ ranges: false,
2319
+ tokens: false,
2320
+ allowTrailingCommas: false
2321
+ };
2322
+ const UNICODE_SEQUENCE = /\\u[\da-fA-F]{4}/gu;
2323
+ /**
2324
+ * Normalizes a JSON5 identifier by converting Unicode escape sequences into
2325
+ * their corresponding characters.
2326
+ * @param {string} identifier The identifier to normalize.
2327
+ * @returns {string} The normalized identifier.
2328
+ */
2329
+ function normalizeIdentifier(identifier) {
2330
+ return identifier.replace(UNICODE_SEQUENCE, (unicodeEscape) => {
2331
+ return String.fromCharCode(parseInt(unicodeEscape.slice(2), 16));
2332
+ });
2333
+ }
2334
+ /**
2335
+ * Converts a JSON-encoded string into a JavaScript string, interpreting each
2336
+ * escape sequence.
2337
+ * @param {string} value The text for the token.
2338
+ * @param {Token} token The string token to convert into a JavaScript string.
2339
+ * @param {boolean} json5 `true` if parsing JSON5, `false` otherwise.
2340
+ * @returns {string} A JavaScript string.
2341
+ */
2342
+ function getStringValue(value, token, json5 = false) {
2343
+ let result = "";
2344
+ let escapeIndex = value.indexOf("\\");
2345
+ let lastIndex = 0;
2346
+ while (escapeIndex >= 0) {
2347
+ result += value.slice(lastIndex, escapeIndex);
2348
+ const escapeChar = value.charAt(escapeIndex + 1);
2349
+ const escapeCharCode = escapeChar.charCodeAt(0);
2350
+ if (json5 && json5EscapeToChar.has(escapeCharCode)) {
2351
+ result += json5EscapeToChar.get(escapeCharCode);
2352
+ lastIndex = escapeIndex + 2;
2353
+ } else if (escapeToChar.has(escapeCharCode)) {
2354
+ result += escapeToChar.get(escapeCharCode);
2355
+ lastIndex = escapeIndex + 2;
2356
+ } else if (escapeChar === "u") {
2357
+ const hexCode = value.slice(escapeIndex + 2, escapeIndex + 6);
2358
+ if (hexCode.length < 4 || /[^0-9a-f]/i.test(hexCode)) throw new ErrorWithLocation(`Invalid unicode escape \\u${hexCode}.`, {
2359
+ line: token.loc.start.line,
2360
+ column: token.loc.start.column + escapeIndex,
2361
+ offset: token.loc.start.offset + escapeIndex
2362
+ });
2363
+ result += String.fromCharCode(parseInt(hexCode, 16));
2364
+ lastIndex = escapeIndex + 6;
2365
+ } else if (json5 && escapeChar === "x") {
2366
+ const hexCode = value.slice(escapeIndex + 2, escapeIndex + 4);
2367
+ if (hexCode.length < 2 || /[^0-9a-f]/i.test(hexCode)) throw new ErrorWithLocation(`Invalid hex escape \\x${hexCode}.`, {
2368
+ line: token.loc.start.line,
2369
+ column: token.loc.start.column + escapeIndex,
2370
+ offset: token.loc.start.offset + escapeIndex
2371
+ });
2372
+ result += String.fromCharCode(parseInt(hexCode, 16));
2373
+ lastIndex = escapeIndex + 4;
2374
+ } else if (json5 && json5LineTerminators.has(escapeCharCode)) {
2375
+ lastIndex = escapeIndex + 2;
2376
+ if (escapeChar === "\r" && value.charAt(lastIndex) === "\n") lastIndex++;
2377
+ } else if (json5) {
2378
+ result += escapeChar;
2379
+ lastIndex = escapeIndex + 2;
2380
+ } else throw new ErrorWithLocation(`Invalid escape \\${escapeChar}.`, {
2381
+ line: token.loc.start.line,
2382
+ column: token.loc.start.column + escapeIndex,
2383
+ offset: token.loc.start.offset + escapeIndex
2384
+ });
2385
+ escapeIndex = value.indexOf("\\", lastIndex);
2386
+ }
2387
+ result += value.slice(lastIndex);
2388
+ return result;
2389
+ }
2390
+ /**
2391
+ * Gets the JavaScript value represented by a JSON token.
2392
+ * @param {string} value The text value of the token.
2393
+ * @param {Token} token The JSON token to get a value for.
2394
+ * @param {boolean} json5 `true` if parsing JSON5, `false` otherwise.
2395
+ * @returns {string|boolean|number} A number, string, or boolean.
2396
+ * @throws {TypeError} If an unknown token type is found.
2397
+ */
2398
+ function getLiteralValue(value, token, json5 = false) {
2399
+ switch (token.type) {
2400
+ case "Boolean": return value === "true";
2401
+ case "Number":
2402
+ if (json5) {
2403
+ if (value.charCodeAt(0) === 45) return -Number(value.slice(1));
2404
+ if (value.charCodeAt(0) === 43) return Number(value.slice(1));
2405
+ }
2406
+ return Number(value);
2407
+ case "String": return getStringValue(value.slice(1, -1), token, json5);
2408
+ default: throw new TypeError(`Unknown token type "${token.type}.`);
2409
+ }
2410
+ }
2411
+ /**
2412
+ *
2413
+ * @param {string} text The text to parse.
2414
+ * @param {ParseOptions} [options] The options object.
2415
+ * @returns {DocumentNode} The AST representing the parsed JSON.
2416
+ * @throws {Error} When there is a parsing error.
2417
+ */
2418
+ function parse$1(text, options) {
2419
+ options = Object.freeze({
2420
+ ...DEFAULT_OPTIONS,
2421
+ ...options
2422
+ });
2423
+ const tokens = [];
2424
+ const tokenizer = new Tokenizer(text, {
2425
+ mode: options.mode,
2426
+ ranges: options.ranges
2427
+ });
2428
+ const json5 = options.mode === "json5";
2429
+ const allowTrailingCommas = options.allowTrailingCommas || json5;
2430
+ /**
2431
+ * Returns the next token knowing there are no comments.
2432
+ * @returns {number} The next token type or 0 if no next token.
2433
+ */
2434
+ function nextNoComments() {
2435
+ const nextType = tokenizer.next();
2436
+ if (nextType && options.tokens) tokens.push(tokenizer.token);
2437
+ return nextType;
2438
+ }
2439
+ /**
2440
+ * Returns the next token knowing there are comments to skip.
2441
+ * @returns {number} The next token type or 0 if no next token.
2442
+ */
2443
+ function nextSkipComments() {
2444
+ const nextType = tokenizer.next();
2445
+ if (nextType && options.tokens) tokens.push(tokenizer.token);
2446
+ if (nextType >= tt.LineComment) return nextSkipComments();
2447
+ return nextType;
2448
+ }
2449
+ const next = options.mode === "json" ? nextNoComments : nextSkipComments;
2450
+ /**
2451
+ * Asserts a token has the given type.
2452
+ * @param {number} token The token to check.
2453
+ * @param {number} type The token type.
2454
+ * @throws {UnexpectedToken} If the token type isn't expected.
2455
+ * @returns {void}
2456
+ */
2457
+ function assertTokenType(token, type) {
2458
+ if (token !== type) throw new UnexpectedToken(tokenizer.token);
2459
+ }
2460
+ /**
2461
+ * Asserts a token has one of the given types.
2462
+ * @param {number} token The token to check.
2463
+ * @param {number[]} types The token types.
2464
+ * @returns {void}
2465
+ * @throws {UnexpectedToken} If the token type isn't expected.
2466
+ */
2467
+ function assertTokenTypes(token, types$1) {
2468
+ if (!types$1.includes(token)) throw new UnexpectedToken(tokenizer.token);
2469
+ }
2470
+ /**
2471
+ * Creates a range only if ranges are specified.
2472
+ * @param {Location} start The start offset for the range.
2473
+ * @param {Location} end The end offset for the range.
2474
+ * @returns {{range:[number,number]}|undefined} An object with a
2475
+ */
2476
+ function createRange(start, end) {
2477
+ return options.ranges ? { range: [start.offset, end.offset] } : void 0;
2478
+ }
2479
+ /**
2480
+ * Creates a node for a string, boolean, or number.
2481
+ * @param {number} tokenType The token representing the literal.
2482
+ * @returns {StringNode|NumberNode|BooleanNode} The node representing
2483
+ * the value.
2484
+ */
2485
+ function createLiteralNode(tokenType) {
2486
+ const token = tokenizer.token;
2487
+ const range = createRange(token.loc.start, token.loc.end);
2488
+ const value = getLiteralValue(text.slice(token.loc.start.offset, token.loc.end.offset), token, json5);
2489
+ const loc = {
2490
+ start: { ...token.loc.start },
2491
+ end: { ...token.loc.end }
2492
+ };
2493
+ const parts = {
2494
+ loc,
2495
+ ...range
2496
+ };
2497
+ switch (tokenType) {
2498
+ case tt.String: return types.string(value, parts);
2499
+ case tt.Number: return types.number(value, parts);
2500
+ case tt.Boolean: return types.boolean(value, parts);
2501
+ default: throw new TypeError(`Unknown token type ${token.type}.`);
2502
+ }
2503
+ }
2504
+ /**
2505
+ * Creates a node for a JSON5 identifier.
2506
+ * @param {Token} token The token representing the identifer.
2507
+ * @returns {NaNNode|InfinityNode|IdentifierNode} The node representing
2508
+ * the value.
2509
+ */
2510
+ function createJSON5IdentifierNode(token) {
2511
+ const range = createRange(token.loc.start, token.loc.end);
2512
+ const identifier = text.slice(token.loc.start.offset, token.loc.end.offset);
2513
+ const loc = {
2514
+ start: { ...token.loc.start },
2515
+ end: { ...token.loc.end }
2516
+ };
2517
+ const parts = {
2518
+ loc,
2519
+ ...range
2520
+ };
2521
+ if (token.type !== "Identifier") {
2522
+ let sign = "";
2523
+ if (identifier[0] === "+" || identifier[0] === "-") sign = identifier[0];
2524
+ return types[identifier.includes("NaN") ? "nan" : "infinity"](sign, parts);
2525
+ }
2526
+ return types.identifier(normalizeIdentifier(identifier), parts);
2527
+ }
2528
+ /**
2529
+ * Creates a node for a null.
2530
+ * @param {Token} token The token representing null.
2531
+ * @returns {NullNode} The node representing null.
2532
+ */
2533
+ function createNullNode(token) {
2534
+ const range = createRange(token.loc.start, token.loc.end);
2535
+ return types.null({
2536
+ loc: {
2537
+ start: { ...token.loc.start },
2538
+ end: { ...token.loc.end }
2539
+ },
2540
+ ...range
2541
+ });
2542
+ }
2543
+ /**
2544
+ * Parses a property in an object.
2545
+ * @param {number} tokenType The token representing the property.
2546
+ * @returns {MemberNode} The node representing the property.
2547
+ * @throws {UnexpectedToken} When an unexpected token is found.
2548
+ * @throws {UnexpectedEOF} When the end of the file is reached.
2549
+ */
2550
+ function parseProperty(tokenType) {
2551
+ if (json5) assertTokenTypes(tokenType, [
2552
+ tt.String,
2553
+ tt.Identifier,
2554
+ tt.Number
2555
+ ]);
2556
+ else assertTokenType(tokenType, tt.String);
2557
+ const token = tokenizer.token;
2558
+ if (json5 && tokenType === tt.Number && /[+\-0-9]/.test(text[token.loc.start.offset])) throw new UnexpectedToken(token);
2559
+ let key = tokenType === tt.String ? createLiteralNode(tokenType) : createJSON5IdentifierNode(token);
2560
+ if (json5 && (key.type === "NaN" || key.type === "Infinity")) {
2561
+ if (key.sign !== "") throw new UnexpectedToken(tokenizer.token);
2562
+ key = types.identifier(key.type, {
2563
+ loc: key.loc,
2564
+ ...createRange(key.loc.start, key.loc.end)
2565
+ });
2566
+ }
2567
+ tokenType = next();
2568
+ assertTokenType(tokenType, tt.Colon);
2569
+ const value = parseValue();
2570
+ const range = createRange(key.loc.start, value.loc.end);
2571
+ return types.member(key, value, {
2572
+ loc: {
2573
+ start: { ...key.loc.start },
2574
+ end: { ...value.loc.end }
2575
+ },
2576
+ ...range
2577
+ });
2578
+ }
2579
+ /**
2580
+ * Parses an object literal.
2581
+ * @param {number} firstTokenType The first token type in the object.
2582
+ * @returns {ObjectNode} The object node.
2583
+ * @throws {UnexpectedEOF} When the end of the file is reached.
2584
+ * @throws {UnexpectedToken} When an unexpected token is found.
2585
+ */
2586
+ function parseObject(firstTokenType) {
2587
+ assertTokenType(firstTokenType, tt.LBrace);
2588
+ const firstToken = tokenizer.token;
2589
+ const members = [];
2590
+ let tokenType = next();
2591
+ if (tokenType !== tt.RBrace) do {
2592
+ members.push(parseProperty(tokenType));
2593
+ tokenType = next();
2594
+ if (!tokenType) throw new UnexpectedEOF(members[members.length - 1].loc.end);
2595
+ if (tokenType === tt.Comma) {
2596
+ tokenType = next();
2597
+ if (allowTrailingCommas && tokenType === tt.RBrace) break;
2598
+ } else break;
2599
+ } while (tokenType);
2600
+ assertTokenType(tokenType, tt.RBrace);
2601
+ const lastToken = tokenizer.token;
2602
+ const range = createRange(firstToken.loc.start, lastToken.loc.end);
2603
+ return types.object(members, {
2604
+ loc: {
2605
+ start: { ...firstToken.loc.start },
2606
+ end: { ...lastToken.loc.end }
2607
+ },
2608
+ ...range
2609
+ });
2610
+ }
2611
+ /**
2612
+ * Parses an array literal.
2613
+ * @param {number} firstTokenType The first token in the array.
2614
+ * @returns {ArrayNode} The array node.
2615
+ * @throws {UnexpectedToken} When an unexpected token is found.
2616
+ * @throws {UnexpectedEOF} When the end of the file is reached.
2617
+ */
2618
+ function parseArray(firstTokenType) {
2619
+ assertTokenType(firstTokenType, tt.LBracket);
2620
+ const firstToken = tokenizer.token;
2621
+ const elements = [];
2622
+ let tokenType = next();
2623
+ if (tokenType !== tt.RBracket) do {
2624
+ const value = parseValue(tokenType);
2625
+ elements.push(types.element(value, { loc: value.loc }));
2626
+ tokenType = next();
2627
+ if (tokenType === tt.Comma) {
2628
+ tokenType = next();
2629
+ if (allowTrailingCommas && tokenType === tt.RBracket) break;
2630
+ } else break;
2631
+ } while (tokenType);
2632
+ assertTokenType(tokenType, tt.RBracket);
2633
+ const lastToken = tokenizer.token;
2634
+ const range = createRange(firstToken.loc.start, lastToken.loc.end);
2635
+ return types.array(elements, {
2636
+ loc: {
2637
+ start: { ...firstToken.loc.start },
2638
+ end: { ...lastToken.loc.end }
2639
+ },
2640
+ ...range
2641
+ });
2642
+ }
2643
+ /**
2644
+ * Parses a JSON value.
2645
+ * @param {number} [tokenType] The token type to parse.
2646
+ * @returns {ValueNode|IdentifierNode} The node representing the value.
2647
+ */
2648
+ function parseValue(tokenType) {
2649
+ tokenType = tokenType ?? next();
2650
+ const token = tokenizer.token;
2651
+ switch (tokenType) {
2652
+ case tt.String:
2653
+ case tt.Boolean: return createLiteralNode(tokenType);
2654
+ case tt.Number:
2655
+ if (json5) {
2656
+ let tokenText = text.slice(token.loc.start.offset, token.loc.end.offset);
2657
+ if (tokenText[0] === "+" || tokenText[0] === "-") tokenText = tokenText.slice(1);
2658
+ if (tokenText === "NaN" || tokenText === "Infinity") return createJSON5IdentifierNode(token);
2659
+ }
2660
+ return createLiteralNode(tokenType);
2661
+ case tt.Null: return createNullNode(token);
2662
+ case tt.LBrace: return parseObject(tokenType);
2663
+ case tt.LBracket: return parseArray(tokenType);
2664
+ default: throw new UnexpectedToken(token);
2665
+ }
2666
+ }
2667
+ const docBody = parseValue();
2668
+ const unexpectedToken = next();
2669
+ if (unexpectedToken) throw new UnexpectedToken(tokenizer.token);
2670
+ const docParts = { loc: {
2671
+ start: {
2672
+ line: 1,
2673
+ column: 1,
2674
+ offset: 0
2675
+ },
2676
+ end: { ...docBody.loc.end }
2677
+ } };
2678
+ if (options.tokens) docParts.tokens = tokens;
2679
+ if (options.ranges) docParts.range = [docParts.loc.start.offset, docParts.loc.end.offset];
2680
+ return types.document(docBody, docParts);
2681
+ }
2682
+ /**
2683
+ * @fileoverview Evaluator for Momoa AST.
2684
+ * @author Nicholas C. Zakas
2685
+ */
2686
+ /** @typedef {import("./typedefs.js").AnyNode} AnyNode */
2687
+ /** @typedef {import("./typedefs.js").JSONValue} JSONValue */
2688
+ /**
2689
+ * Evaluates a Momoa AST node into a JavaScript value.
2690
+ * @param {AnyNode} node The node to interpet.
2691
+ * @returns {JSONValue} The JavaScript value for the node.
2692
+ */
2693
+ function evaluate(node) {
2694
+ switch (node.type) {
2695
+ case "String": return node.value;
2696
+ case "Number": return node.value;
2697
+ case "Boolean": return node.value;
2698
+ case "Null": return null;
2699
+ case "NaN": return NaN;
2700
+ case "Infinity": return node.sign === "-" ? -Infinity : Infinity;
2701
+ case "Identifier": return node.name;
2702
+ case "Array": return node.elements.map((element) => evaluate(element.value));
2703
+ case "Object": {
2704
+ /** @type {{[property: string]: JSONValue}} */
2705
+ const object = {};
2706
+ node.members.forEach((member) => {
2707
+ object[evaluate(member.name)] = evaluate(member.value);
2708
+ });
2709
+ return object;
2710
+ }
2711
+ case "Document": return evaluate(node.body);
2712
+ case "Element": throw new Error("Cannot evaluate array element outside of an array.");
2713
+ case "Member": throw new Error("Cannot evaluate object member outside of an object.");
2714
+ default: throw new Error(`Unknown node type ${node.type}.`);
2715
+ }
2716
+ }
2717
+ /**
2718
+ * @fileoverview Printer for Momoa AST.
2719
+ * @author Nicholas C. Zakas
2720
+ */
2721
+ /**
2722
+ * Prints the string representation of a Boolean node.
2723
+ * @param {BooleanNode} node The node to print.
2724
+ * @returns {string} The boolean value.
2725
+ */
2726
+ function printBoolean(node) {
2727
+ return node.value ? "true" : "false";
2728
+ }
2729
+ /**
2730
+ * Prints the string representation of a null node.
2731
+ * @returns {string} The string "null".
2732
+ */
2733
+ function printNull() {
2734
+ return "null";
2735
+ }
2736
+ /**
2737
+ * Prints the string representation of a number node.
2738
+ * @param {NumberNode} node The node to print.
2739
+ * @returns {string} The number value.
2740
+ */
2741
+ function printNumber(node) {
2742
+ return node.value.toString();
2743
+ }
2744
+ /**
2745
+ * Prints the string representation of a NaN node.
2746
+ * @returns {string} The string "NaN".
2747
+ */
2748
+ function printNaN() {
2749
+ return "NaN";
2750
+ }
2751
+ /**
2752
+ * Prints the string representation of an Infinity node.
2753
+ * @param {InfinityNode} node The node to print.
2754
+ * @returns {string} The string "Infinity" or "-Infinity".
2755
+ */
2756
+ function printInfinity(node) {
2757
+ return node.sign + "Infinity";
2758
+ }
2759
+ /**
2760
+ * Prints the string representation of a string node.
2761
+ * @param {StringNode} node The node to print.
2762
+ * @returns {string} The string value.
2763
+ */
2764
+ function printString(node) {
2765
+ let result = "\"";
2766
+ for (const c of node.value) {
2767
+ const newChar = json5CharToEscape.get(c);
2768
+ if (newChar) {
2769
+ result += "\\" + newChar;
2770
+ continue;
2771
+ }
2772
+ if (c === "\"") {
2773
+ result += "\\\"";
2774
+ continue;
2775
+ }
2776
+ if (c < " " || c === "") {
2777
+ const hex = c.codePointAt(0).toString(16).toUpperCase();
2778
+ result += `\\u${"0000".substring(hex.length)}${hex}`;
2779
+ continue;
2780
+ }
2781
+ result += c;
2782
+ }
2783
+ return result + "\"";
2784
+ }
2785
+ /**
2786
+ * Prints the string representation of an identifier node.
2787
+ * @param {IdentifierNode} node The node to print.
2788
+ * @returns {string} The identifier name.
2789
+ */
2790
+ function printIdentifier(node) {
2791
+ return node.name;
2792
+ }
2793
+ /**
2794
+ * Prints the string representation of an array node.
2795
+ * @param {ArrayNode} node The node to print.
2796
+ * @param {string} indent The string to use for indentation.
2797
+ * @param {number} indentLevel The current level of indentation.
2798
+ * @returns {string} The array value.
2799
+ */
2800
+ function printArray(node, indent, indentLevel) {
2801
+ const newLine = indent ? "\n" : "";
2802
+ const indentString = indent.repeat(indentLevel);
2803
+ const elementIndentString = indent.repeat(indentLevel + 1);
2804
+ return `[${newLine}${node.elements.map((element) => `${elementIndentString}${printValue(element.value, indent, indentLevel + 1)}`).join(`,${newLine}`)}${newLine}${indentString}]`;
2805
+ }
2806
+ /**
2807
+ * Prints the string representation of a member node.
2808
+ * @param {MemberNode} node The node to print.
2809
+ * @param {string} indent The string to use for indentation.
2810
+ * @param {number} indentLevel The current level of indentation.
2811
+ * @returns {string} The member value.
2812
+ */
2813
+ function printMember(node, indent, indentLevel) {
2814
+ const space = indent ? " " : "";
2815
+ return `${printValue(node.name, indent, indentLevel)}:${space}${printValue(node.value, indent, indentLevel + 1)}`;
2816
+ }
2817
+ /**
2818
+ * Prints the string representation of an object node.
2819
+ * @param {ObjectNode} node The node to print.
2820
+ * @param {string} indent The string to use for indentation.
2821
+ * @param {number} indentLevel The current level of indentation.
2822
+ * @returns {string} The object value.
2823
+ */
2824
+ function printObject(node, indent, indentLevel) {
2825
+ const newLine = indent ? "\n" : "";
2826
+ const indentString = indent.repeat(indentLevel);
2827
+ const memberIndentString = indent.repeat(indentLevel + 1);
2828
+ return `{${newLine}${node.members.map((member) => `${memberIndentString}${printMember(member, indent, indentLevel)}`).join(`,${newLine}`)}${newLine}${indentString}}`;
2829
+ }
2830
+ /**
2831
+ * Prints the string representation of a node.
2832
+ * @param {AnyNode} node The node to print.
2833
+ * @param {string} indentString The string to use for indentation.
2834
+ * @param {number} indentLevel The current level of indentation.
2835
+ * @returns {string} The string representation of the node.
2836
+ * @throws {TypeError} If the node type is unknown.
2837
+
2838
+ */
2839
+ function printValue(node, indentString, indentLevel) {
2840
+ switch (node.type) {
2841
+ case "String": return printString(node);
2842
+ case "Number": return printNumber(node);
2843
+ case "Boolean": return printBoolean(node);
2844
+ case "Null": return printNull();
2845
+ case "NaN": return printNaN();
2846
+ case "Infinity": return printInfinity(node);
2847
+ case "Identifier": return printIdentifier(node);
2848
+ case "Array": return printArray(node, indentString, indentLevel);
2849
+ case "Object": return printObject(node, indentString, indentLevel);
2850
+ case "Document": return printValue(node.body, indentString, indentLevel);
2851
+ default: throw new TypeError(`Unknown node type: ${node.type}`);
2852
+ }
2853
+ }
2854
+ /**
2855
+ * Converts a Momoa AST back into a JSON string.
2856
+ * @param {AnyNode} node The node to print.
2857
+ * @param {Object} options Options for the print.
2858
+ * @param {number} [options.indent=0] The number of spaces to indent each line. If
2859
+ * greater than 0, then newlines and indents will be added to output.
2860
+ * @returns {string} The JSON representation of the AST.
2861
+ */
2862
+ function print(node, { indent = 0 } = {}) {
2863
+ const indentLevel = 0;
2864
+ const indentString = " ".repeat(indent);
2865
+ return printValue(node, indentString, indentLevel);
2866
+ }
2867
+
2868
+ //#endregion
2869
+ //#region src/parse/json.ts
2870
+ const CHILD_KEYS = {
2871
+ Document: ["body"],
2872
+ Object: ["members"],
2873
+ Member: ["name", "value"],
2874
+ Element: ["value"],
2875
+ Array: ["elements"],
2876
+ String: [],
2877
+ Number: [],
2878
+ Boolean: [],
2879
+ Null: [],
2880
+ Identifier: [],
2881
+ NaN: [],
2882
+ Infinity: []
2883
+ };
2884
+ /** Determines if a given value is an AST node. */
2885
+ function isNode(value) {
2886
+ return !!value && typeof value === "object" && "type" in value && typeof value.type === "string";
2887
+ }
2888
+ /** Get ObjectNode members as object */
2889
+ function getObjMembers(node) {
2890
+ const members = {};
2891
+ if (node.type !== "Object") return members;
2892
+ for (let i = 0; i < node.members.length; i++) {
2893
+ const m = node.members[i];
2894
+ if (m.name.type !== "String") continue;
2895
+ members[m.name.value] = {
2896
+ ...m.value,
2897
+ index: i
2898
+ };
2899
+ }
2900
+ return members;
2901
+ }
2902
+ /** Inject members to ObjectNode */
2903
+ function injectObjMembers(node, members = []) {
2904
+ if (node.type !== "Object") return;
2905
+ node.members.push(...members);
2906
+ }
2907
+ /** Replace an ObjectNode’s contents outright with another */
2908
+ function replaceObjMembers(a, b) {
2909
+ a.members = b.type === "Document" && b.body?.members || b.members;
2910
+ }
2911
+ /**
2912
+ * Variation of Momoa’s traverse(), which keeps track of global path.
2913
+ * Allows mutation of AST (along with any consequences)
2914
+ */
2915
+ function traverse(root, visitor) {
2916
+ /**
2917
+ * Recursively visits a node.
2918
+ * @param {AnyNode} node The node to visit.
2919
+ * @param {AnyNode} [parent] The parent of the node to visit.
2920
+ * @return {void}
2921
+ */
2922
+ function visitNode(node, parent, path = []) {
2923
+ const nextPath = [...path];
2924
+ if (node.type === "Member") {
2925
+ const { name } = node;
2926
+ nextPath.push("value" in name ? name.value : String(name));
2927
+ }
2928
+ visitor.enter?.(node, parent, nextPath);
2929
+ const childNode = CHILD_KEYS[node.type];
2930
+ for (const key of childNode ?? []) {
2931
+ const value = node[key];
2932
+ if (!value) continue;
2933
+ if (Array.isArray(value)) for (let i = 0; i < value.length; i++) visitNode(value[i], node, key === "elements" ? [...nextPath, String(i)] : nextPath);
2934
+ else if (isNode(value)) visitNode(value, node, nextPath);
2935
+ }
2936
+ visitor.exit?.(node, parent, nextPath);
2937
+ }
2938
+ visitNode(root, void 0, []);
2939
+ }
2940
+ /** Determine if an input is likely a JSON string */
2941
+ function maybeRawJSON(input) {
2942
+ return typeof input === "string" && input.trim().startsWith("{");
2943
+ }
2944
+ /** Find Momoa node by traversing paths */
2945
+ function findNode(node, path) {
2946
+ if (!path.length) return;
2947
+ let nextNode;
2948
+ switch (node.type) {
2949
+ case "Document": return findNode(node.body, path);
2950
+ case "Object": {
2951
+ const [member, ...rest] = path;
2952
+ nextNode = node.members.find((m) => m.name.type === "String" && m.name.value === member)?.value;
2953
+ if (nextNode && rest.length) return findNode(nextNode, path.slice(1));
2954
+ break;
2955
+ }
2956
+ case "Array": {
2957
+ const [_index, ...rest] = path;
2958
+ const index = Number.parseInt(_index, 10);
2959
+ nextNode = node.elements[index]?.value;
2960
+ if (nextNode && rest.length) return findNode(nextNode, path.slice(1));
2961
+ break;
2962
+ }
2963
+ }
2964
+ return nextNode;
2965
+ }
2966
+ function toMomoa(input, { continueOnError, filename, logger, yamlToMomoa }) {
2967
+ let src = "";
2968
+ if (typeof input === "string") src = input;
2969
+ let document = {};
2970
+ if (typeof input === "string" && !maybeRawJSON(input)) if (yamlToMomoa) try {
2971
+ document = yamlToMomoa(input);
2972
+ } catch (err) {
2973
+ logger.error({
2974
+ group: "parser",
2975
+ label: "json",
2976
+ message: String(err),
2977
+ filename,
2978
+ src: input,
2979
+ continueOnError
2980
+ });
2981
+ }
2982
+ else logger.error({
2983
+ group: "parser",
2984
+ label: "yaml",
2985
+ message: `Install \`yaml-to-momoa\` package to parse YAML, and pass in as option, e.g.:
2986
+
2987
+ import { parse } from '@terrazzo/parser';
2988
+ import yamlToMomoa from 'yaml-to-momoa';
2989
+
2990
+ parse(yamlString, { yamlToMomoa });`,
2991
+ continueOnError: false
2992
+ });
2993
+ else document = parseJSON(input);
2994
+ if (!src) src = print(document, { indent: 2 });
2995
+ return {
2996
+ src,
2997
+ document
2998
+ };
2999
+ }
3000
+ /** Momoa, just with default options pre-set */
3001
+ function parseJSON(input, options) {
3002
+ return parse$1(typeof input === "string" ? input : JSON.stringify(input, void 0, 2), {
3003
+ mode: "jsonc",
3004
+ ranges: true,
3005
+ tokens: true,
3006
+ ...options
3007
+ });
3008
+ }
3009
+
3010
+ //#endregion
3011
+ //#region src/parse/alias.ts
3012
+ /**
3013
+ * Resolve aliases and update the token nodes.
3014
+ *
3015
+ * Data structures are in an awkward in-between phase, where they have
3016
+ * placeholders for data but we still need to resolve everything. As such,
3017
+ * TypeScript will raise errors expecting the final shape.
3018
+ *
3019
+ * This is also a bit tricky because different token types alias slightly
3020
+ * differently. For example, color tokens and other “primitive” tokens behave
3021
+ * as-expected. But composite tokens like Typography, Gradient, Border, etc. can
3022
+ * either fully- or partially-alias their values. Then we add modes to the mix,
3023
+ * and we have to do the work all over again for each mode declared.
3024
+ *
3025
+ * All that to say, there are a generous amount of TypeScript overrides here rather
3026
+ * than try to codify indeterminate shapes.
3027
+ */
3028
+ function applyAliases(token, options) {
3029
+ token.mode["."] ??= {};
3030
+ token.mode["."].$value = token.$value;
3031
+ token.mode["."].originalValue ??= token.originalValue.$value;
3032
+ token.mode["."].source ??= token.source;
3033
+ if (typeof token.$value === "string" && isAlias(token.$value)) {
3034
+ const { aliasChain, resolvedToken } = resolveAlias(token.$value, {
3035
+ ...options,
3036
+ token
3037
+ });
3038
+ token.aliasOf = resolvedToken.id;
3039
+ token.aliasChain = aliasChain;
3040
+ token.$value = resolvedToken.$value;
3041
+ }
3042
+ for (const mode of Object.keys(token.mode)) {
3043
+ const modeValue = token.mode[mode].$value;
3044
+ if (typeof modeValue === "string" && isAlias(modeValue)) {
3045
+ const expectedType = [token.$type];
3046
+ const { aliasChain, resolvedToken } = resolveAlias(modeValue, {
3047
+ ...options,
3048
+ token,
3049
+ expectedType,
3050
+ node: token.mode[mode].source?.node || options.node
3051
+ });
3052
+ token.mode[mode].aliasOf = resolvedToken.id;
3053
+ token.mode[mode].aliasChain = aliasChain;
3054
+ token.mode[mode].$value = resolvedToken.$value;
3055
+ continue;
3056
+ }
3057
+ if (typeof token.$value === "object" && typeof token.mode[mode].$value === "object" && !Array.isArray(token.$value)) {
3058
+ for (const [k, v] of Object.entries(token.$value)) if (!(k in token.mode[mode].$value)) token.mode[mode].$value[k] = v;
3059
+ }
3060
+ const node = getObjMembers(options.node).$value || options.node;
3061
+ switch (token.$type) {
3062
+ case "border": {
3063
+ applyBorderPartialAlias(token, mode, {
3064
+ ...options,
3065
+ node
3066
+ });
3067
+ break;
3068
+ }
3069
+ case "gradient": {
3070
+ applyGradientPartialAlias(token, mode, {
3071
+ ...options,
3072
+ node
3073
+ });
3074
+ break;
3075
+ }
3076
+ case "shadow": {
3077
+ applyShadowPartialAlias(token, mode, {
3078
+ ...options,
3079
+ node
3080
+ });
3081
+ break;
3082
+ }
3083
+ case "strokeStyle": {
3084
+ applyStrokeStylePartialAlias(token, mode, {
3085
+ ...options,
3086
+ node
3087
+ });
3088
+ break;
3089
+ }
3090
+ case "transition": {
3091
+ applyTransitionPartialAlias(token, mode, {
3092
+ ...options,
3093
+ node
3094
+ });
3095
+ break;
3096
+ }
3097
+ case "typography": {
3098
+ applyTypographyPartialAlias(token, mode, {
3099
+ ...options,
3100
+ node
3101
+ });
3102
+ break;
3103
+ }
3104
+ }
3105
+ }
3106
+ }
3107
+ const LIST_FORMAT = new Intl.ListFormat("en-us", { type: "disjunction" });
3108
+ /**
3109
+ * Resolve alias. Also add info on root node if it’s the root token (has .id)
3110
+ */
3111
+ function resolveAlias(alias, options) {
3112
+ const baseMessage = {
3113
+ group: "parser",
3114
+ label: "alias",
3115
+ node: options?.node,
3116
+ filename: options.filename,
3117
+ src: options.src
3118
+ };
3119
+ const { logger, token, tokensSet } = options;
3120
+ const shallowAliasID = parseAlias(alias);
3121
+ const { token: resolvedToken, chain } = _resolveAliasInner(shallowAliasID, options);
3122
+ if (!tokensSet[token.id].$type) tokensSet[token.id].$type = resolvedToken.$type;
3123
+ const expectedType = [...options.expectedType ?? []];
3124
+ if (token.$type && !expectedType?.length) expectedType.push(token.$type);
3125
+ if (expectedType?.length && !expectedType.includes(resolvedToken.$type)) logger.error({
3126
+ ...baseMessage,
3127
+ message: `Invalid alias: expected $type: ${LIST_FORMAT.format(expectedType)}, received $type: ${resolvedToken.$type}.`,
3128
+ node: options.node?.type === "Object" && getObjMembers(options.node).$value || baseMessage.node
3129
+ });
3130
+ if (chain?.length && resolvedToken) {
3131
+ let needsSort = false;
3132
+ for (const id of chain) {
3133
+ if (id !== resolvedToken.id && !resolvedToken.aliasedBy?.includes(id)) {
3134
+ resolvedToken.aliasedBy ??= [];
3135
+ resolvedToken.aliasedBy.push(id);
3136
+ needsSort = true;
3137
+ }
3138
+ if (token && !resolvedToken.aliasedBy?.includes(token.id)) {
3139
+ resolvedToken.aliasedBy ??= [];
3140
+ resolvedToken.aliasedBy.push(token.id);
3141
+ needsSort = true;
3142
+ }
3143
+ }
3144
+ if (needsSort) resolvedToken.aliasedBy.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
3145
+ }
3146
+ return {
3147
+ resolvedToken,
3148
+ aliasChain: chain
3149
+ };
3150
+ }
3151
+ function _resolveAliasInner(alias, { scanned = [],...options }) {
3152
+ const { logger, filename, src, node, tokensSet } = options;
3153
+ const baseMessage = {
3154
+ group: "parser",
3155
+ label: "alias",
3156
+ filename,
3157
+ src,
3158
+ node
3159
+ };
3160
+ const id = parseAlias(alias);
3161
+ if (!tokensSet[id]) logger.error({
3162
+ ...baseMessage,
3163
+ message: `Alias {${alias}} not found.`
3164
+ });
3165
+ if (scanned.includes(id)) logger.error({
3166
+ ...baseMessage,
3167
+ message: `Circular alias detected from ${alias}.`
3168
+ });
3169
+ const token = tokensSet[id];
3170
+ scanned.push(id);
3171
+ if (typeof token.originalValue.$value !== "string" || !isAlias(token.originalValue.$value)) return {
3172
+ token,
3173
+ chain: scanned
3174
+ };
3175
+ return _resolveAliasInner(token.originalValue.$value, {
3176
+ ...options,
3177
+ scanned
3178
+ });
3179
+ }
3180
+ function applyBorderPartialAlias(token, mode, options) {
3181
+ for (const [k, v] of Object.entries(token.mode[mode].$value)) if (typeof v === "string" && isAlias(v)) {
3182
+ token.mode[mode].partialAliasOf ??= {};
3183
+ const node = getObjMembers(options.node)[k] || options.node;
3184
+ const { resolvedToken } = resolveAlias(v, {
3185
+ ...options,
3186
+ token,
3187
+ expectedType: {
3188
+ color: ["color"],
3189
+ width: ["dimension"],
3190
+ style: ["strokeStyle"]
3191
+ }[k],
3192
+ node
3193
+ });
3194
+ token.mode[mode].partialAliasOf[k] = parseAlias(v);
3195
+ if (mode === ".") {
3196
+ token.partialAliasOf ??= {};
3197
+ token.partialAliasOf[k] = parseAlias(v);
3198
+ }
3199
+ token.mode[mode].$value[k] = resolvedToken.$value;
3200
+ }
3201
+ }
3202
+ function applyGradientPartialAlias(token, mode, options) {
3203
+ for (let i = 0; i < token.mode[mode].$value.length; i++) {
3204
+ const step = token.mode[mode].$value[i];
3205
+ for (const [k, v] of Object.entries(step)) if (typeof v === "string" && isAlias(v)) {
3206
+ token.mode[mode].partialAliasOf ??= [];
3207
+ token.mode[mode].partialAliasOf[i] ??= {};
3208
+ const expectedType = {
3209
+ color: ["color"],
3210
+ position: ["number"]
3211
+ }[k];
3212
+ let node = options.node?.elements?.[i]?.value || options.node;
3213
+ if (node.type === "Object") node = getObjMembers(node)[k] || node;
3214
+ const { resolvedToken } = resolveAlias(v, {
3215
+ ...options,
3216
+ token,
3217
+ expectedType,
3218
+ node
3219
+ });
3220
+ token.mode[mode].partialAliasOf[i][k] = parseAlias(v);
3221
+ if (mode === ".") {
3222
+ token.partialAliasOf ??= [];
3223
+ token.partialAliasOf[i] ??= {};
3224
+ token.partialAliasOf[i][k] = parseAlias(v);
3225
+ }
3226
+ step[k] = resolvedToken.$value;
3227
+ }
3228
+ }
3229
+ }
3230
+ function applyShadowPartialAlias(token, mode, options) {
3231
+ if (!Array.isArray(token.mode[mode].$value)) token.mode[mode].$value = [token.mode[mode].$value];
3232
+ for (let i = 0; i < token.mode[mode].$value.length; i++) {
3233
+ const layer = token.mode[mode].$value[i];
3234
+ for (const [k, v] of Object.entries(layer)) if (typeof v === "string" && isAlias(v)) {
3235
+ token.mode[mode].partialAliasOf ??= [];
3236
+ token.mode[mode].partialAliasOf[i] ??= {};
3237
+ const expectedType = {
3238
+ offsetX: ["dimension"],
3239
+ offsetY: ["dimension"],
3240
+ blur: ["dimension"],
3241
+ spread: ["dimension"],
3242
+ color: ["color"],
3243
+ inset: ["boolean"]
3244
+ }[k];
3245
+ let node = options.node?.elements?.[i] || options.node;
3246
+ if (node.type === "Object") node = getObjMembers(node)[k] || node;
3247
+ const { resolvedToken } = resolveAlias(v, {
3248
+ ...options,
3249
+ token,
3250
+ expectedType,
3251
+ node
3252
+ });
3253
+ token.mode[mode].partialAliasOf[i][k] = parseAlias(v);
3254
+ if (mode === ".") {
3255
+ token.partialAliasOf ??= [];
3256
+ token.partialAliasOf[i] ??= {};
3257
+ token.partialAliasOf[i][k] = parseAlias(v);
3258
+ }
3259
+ layer[k] = resolvedToken.$value;
3260
+ }
3261
+ }
3262
+ }
3263
+ function applyStrokeStylePartialAlias(token, mode, options) {
3264
+ if (typeof token.mode[mode].$value !== "object" || !("dashArray" in token.mode[mode].$value)) return;
3265
+ for (let i = 0; i < token.mode[mode].$value.dashArray.length; i++) {
3266
+ const dash = token.mode[mode].$value.dashArray[i];
3267
+ if (typeof dash === "string" && isAlias(dash)) {
3268
+ let node = getObjMembers(options.node).dashArray || options.node;
3269
+ if (node.type === "Array") node = node?.elements?.[i]?.value || node;
3270
+ const { resolvedToken } = resolveAlias(dash, {
3271
+ ...options,
3272
+ token,
3273
+ expectedType: ["dimension"],
3274
+ node
3275
+ });
3276
+ token.mode[mode].$value.dashArray[i] = resolvedToken.$value;
3277
+ }
3278
+ }
3279
+ }
3280
+ function applyTransitionPartialAlias(token, mode, options) {
3281
+ for (const [k, v] of Object.entries(token.mode[mode].$value)) if (typeof v === "string" && isAlias(v)) {
3282
+ token.mode[mode].partialAliasOf ??= {};
3283
+ const expectedType = {
3284
+ duration: ["duration"],
3285
+ delay: ["duration"],
3286
+ timingFunction: ["cubicBezier"]
3287
+ }[k];
3288
+ const node = getObjMembers(options.node)[k] || options.node;
3289
+ const { resolvedToken } = resolveAlias(v, {
3290
+ ...options,
3291
+ token,
3292
+ expectedType,
3293
+ node
3294
+ });
3295
+ token.mode[mode].partialAliasOf[k] = parseAlias(v);
3296
+ if (mode === ".") {
3297
+ token.partialAliasOf ??= {};
3298
+ token.partialAliasOf[k] = parseAlias(v);
3299
+ }
3300
+ token.mode[mode].$value[k] = resolvedToken.$value;
3301
+ }
3302
+ }
3303
+ function applyTypographyPartialAlias(token, mode, options) {
3304
+ for (const [k, v] of Object.entries(token.mode[mode].$value)) if (typeof v === "string" && isAlias(v)) {
3305
+ token.partialAliasOf ??= {};
3306
+ token.mode[mode].partialAliasOf ??= {};
3307
+ const expectedType = {
3308
+ fontFamily: ["fontFamily"],
3309
+ fontSize: ["dimension"],
3310
+ fontWeight: ["fontWeight"],
3311
+ letterSpacing: ["dimension"],
3312
+ lineHeight: ["dimension", "number"]
3313
+ }[k] || ["string"];
3314
+ const node = getObjMembers(options.node)[k] || options.node;
3315
+ const { resolvedToken } = resolveAlias(v, {
3316
+ ...options,
3317
+ token,
3318
+ expectedType,
3319
+ node
3320
+ });
3321
+ token.mode[mode].partialAliasOf[k] = parseAlias(v);
3322
+ if (mode === ".") token.partialAliasOf[k] = parseAlias(v);
3323
+ token.mode[mode].$value[k] = resolvedToken.$value;
3324
+ }
3325
+ }
3326
+
3327
+ //#endregion
3328
+ //#region src/parse/normalize.ts
3329
+ const FONT_WEIGHT_MAP = {
3330
+ thin: 100,
3331
+ hairline: 100,
3332
+ "extra-light": 200,
3333
+ "ultra-light": 200,
3334
+ light: 300,
3335
+ normal: 400,
3336
+ regular: 400,
3337
+ book: 400,
3338
+ medium: 500,
3339
+ "semi-bold": 600,
3340
+ "demi-bold": 600,
3341
+ bold: 700,
3342
+ "extra-bold": 800,
3343
+ "ultra-bold": 800,
3344
+ black: 900,
3345
+ heavy: 900,
3346
+ "extra-black": 950,
3347
+ "ultra-black": 950
3348
+ };
3349
+ const NUMBER_WITH_UNIT_RE = /(-?\d*\.?\d+)(.*)/;
3350
+ /** Fill in defaults, and return predictable shapes for tokens */
3351
+ function normalizeValue(token) {
3352
+ if (typeof token.$value === "string" && isAlias(token.$value)) return token.$value;
3353
+ switch (token.$type) {
3354
+ case "boolean": return !!token.$value;
3355
+ case "border": {
3356
+ if (typeof token.$value === "string") return token.$value;
3357
+ return {
3358
+ color: normalizeValue({
3359
+ $type: "color",
3360
+ $value: token.$value.color ?? "#000000"
3361
+ }),
3362
+ style: normalizeValue({
3363
+ $type: "strokeStyle",
3364
+ $value: token.$value.style ?? "solid"
3365
+ }),
3366
+ width: normalizeValue({
3367
+ $type: "dimension",
3368
+ $value: token.$value.width
3369
+ })
3370
+ };
3371
+ }
3372
+ case "color": {
3373
+ if (typeof token.$value === "string") return parseColor(token.$value);
3374
+ const newValue = {
3375
+ colorSpace: token.$value.colorSpace,
3376
+ components: token.$value.components ?? token.$value.channels,
3377
+ alpha: token.$value.alpha ?? 1
3378
+ };
3379
+ if ("hex" in token.$value) newValue.hex = token.$value.hex;
3380
+ return newValue;
3381
+ }
3382
+ case "cubicBezier": {
3383
+ if (typeof token.$value === "string") return token.$value;
3384
+ return token.$value.map((value) => typeof value === "number" ? normalizeValue({
3385
+ $type: "number",
3386
+ $value: value
3387
+ }) : value);
3388
+ }
3389
+ case "dimension": {
3390
+ if (token.$value === 0) return {
3391
+ value: 0,
3392
+ unit: "px"
3393
+ };
3394
+ if (typeof token.$value === "string") {
3395
+ const match = token.$value.match(NUMBER_WITH_UNIT_RE);
3396
+ return {
3397
+ value: Number.parseFloat(match?.[1] || token.$value),
3398
+ unit: match?.[2] || "px"
3399
+ };
3400
+ }
3401
+ return token.$value;
3402
+ }
3403
+ case "duration": {
3404
+ if (token.$value === 0) return {
3405
+ value: 0,
3406
+ unit: "ms"
3407
+ };
3408
+ if (typeof token.$value === "string") {
3409
+ const match = token.$value.match(NUMBER_WITH_UNIT_RE);
3410
+ return {
3411
+ value: Number.parseFloat(match?.[1] || token.$value),
3412
+ unit: match?.[2] || "ms"
3413
+ };
3414
+ }
3415
+ return token.$value;
3416
+ }
3417
+ case "fontFamily": return Array.isArray(token.$value) ? token.$value : [token.$value];
3418
+ case "fontWeight": {
3419
+ if (typeof token.$value === "string" && FONT_WEIGHT_MAP[token.$value]) return FONT_WEIGHT_MAP[token.$value];
3420
+ return Math.min(999, Math.max(1, typeof token.$value === "string" ? Number.parseInt(token.$value) : token.$value));
3421
+ }
3422
+ case "gradient": {
3423
+ if (typeof token.$value === "string") return token.$value;
3424
+ const output = [];
3425
+ for (let i = 0; i < token.$value.length; i++) {
3426
+ const stop = structuredClone(token.$value[i]);
3427
+ stop.color = normalizeValue({
3428
+ $type: "color",
3429
+ $value: stop.color
3430
+ });
3431
+ if (stop.position === void 0) stop.position = i / (token.$value.length - 1);
3432
+ output.push(stop);
3433
+ }
3434
+ return output;
3435
+ }
3436
+ case "number": return typeof token.$value === "number" ? token.$value : Number.parseFloat(token.$value);
3437
+ case "shadow": {
3438
+ if (typeof token.$value === "string") return token.$value;
3439
+ return (Array.isArray(token.$value) ? token.$value : [token.$value]).map((layer) => ({
3440
+ color: normalizeValue({
3441
+ $type: "color",
3442
+ $value: layer.color
3443
+ }),
3444
+ offsetX: normalizeValue({
3445
+ $type: "dimension",
3446
+ $value: layer.offsetX ?? {
3447
+ value: 0,
3448
+ unit: "px"
3449
+ }
3450
+ }),
3451
+ offsetY: normalizeValue({
3452
+ $type: "dimension",
3453
+ $value: layer.offsetY ?? {
3454
+ value: 0,
3455
+ unit: "px"
3456
+ }
3457
+ }),
3458
+ blur: normalizeValue({
3459
+ $type: "dimension",
3460
+ $value: layer.blur ?? {
3461
+ value: 0,
3462
+ unit: "px"
3463
+ }
3464
+ }),
3465
+ spread: normalizeValue({
3466
+ $type: "dimension",
3467
+ $value: layer.spread ?? {
3468
+ value: 0,
3469
+ unit: "px"
3470
+ }
3471
+ }),
3472
+ inset: layer.inset === true
3473
+ }));
3474
+ }
3475
+ case "strokeStyle": return token.$value;
3476
+ case "string": return String(token.$value);
3477
+ case "transition": {
3478
+ if (typeof token.$value === "string") return token.$value;
3479
+ return {
3480
+ duration: normalizeValue({
3481
+ $type: "duration",
3482
+ $value: token.$value.duration ?? 0
3483
+ }),
3484
+ delay: normalizeValue({
3485
+ $type: "duration",
3486
+ $value: token.$value.delay ?? 0
3487
+ }),
3488
+ timingFunction: normalizeValue({
3489
+ $type: "cubicBezier",
3490
+ $value: token.$value.timingFunction
3491
+ })
3492
+ };
3493
+ }
3494
+ case "typography": {
3495
+ if (typeof token.$value === "string") return token.$value;
3496
+ const output = {};
3497
+ for (const [k, $value] of Object.entries(token.$value)) switch (k) {
3498
+ case "fontFamily": {
3499
+ output[k] = normalizeValue({
3500
+ $type: "fontFamily",
3501
+ $value
3502
+ });
3503
+ break;
3504
+ }
3505
+ case "fontSize":
3506
+ case "letterSpacing": {
3507
+ output[k] = normalizeValue({
3508
+ $type: "dimension",
3509
+ $value
3510
+ });
3511
+ break;
3512
+ }
3513
+ case "lineHeight": {
3514
+ output[k] = normalizeValue({
3515
+ $type: typeof token.$value === "number" ? "number" : "dimension",
3516
+ $value
3517
+ });
3518
+ break;
3519
+ }
3520
+ default: {
3521
+ output[k] = $value;
3522
+ break;
3523
+ }
3524
+ }
3525
+ return output;
3526
+ }
3527
+ default: return token.$value;
3528
+ }
3529
+ }
3530
+
3531
+ //#endregion
3532
+ //#region src/parse/validate.ts
3533
+ const listFormat = new Intl.ListFormat("en-us", { type: "disjunction" });
3534
+ const VALID_COLORSPACES = new Set([
3535
+ "adobe-rgb",
3536
+ "display-p3",
3537
+ "hsl",
3538
+ "hwb",
3539
+ "lab",
3540
+ "lch",
3541
+ "oklab",
3542
+ "oklch",
3543
+ "prophoto",
3544
+ "rec2020",
3545
+ "srgb",
3546
+ "srgb-linear",
3547
+ "xyz",
3548
+ "xyz-d50",
3549
+ "xyz-d65"
3550
+ ]);
3551
+ const FONT_WEIGHT_VALUES = new Set([
3552
+ "thin",
3553
+ "hairline",
3554
+ "extra-light",
3555
+ "ultra-light",
3556
+ "light",
3557
+ "normal",
3558
+ "regular",
3559
+ "book",
3560
+ "medium",
3561
+ "semi-bold",
3562
+ "demi-bold",
3563
+ "bold",
3564
+ "extra-bold",
3565
+ "ultra-bold",
3566
+ "black",
3567
+ "heavy",
3568
+ "extra-black",
3569
+ "ultra-black"
3570
+ ]);
3571
+ const STROKE_STYLE_VALUES = new Set([
3572
+ "solid",
3573
+ "dashed",
3574
+ "dotted",
3575
+ "double",
3576
+ "groove",
3577
+ "ridge",
3578
+ "outset",
3579
+ "inset"
3580
+ ]);
3581
+ const STROKE_STYLE_LINE_CAP_VALUES = new Set([
3582
+ "round",
3583
+ "butt",
3584
+ "square"
3585
+ ]);
3586
+ /** Distinct from isAlias() in that this accepts malformed aliases */
3587
+ function isMaybeAlias(node) {
3588
+ if (node?.type === "String") return node.value.startsWith("{");
3589
+ return false;
3590
+ }
3591
+ /** Assert object members match given types */
3592
+ function validateMembersAs($value, properties, node, { filename, src, logger }) {
3593
+ const members = getObjMembers($value);
3594
+ for (const [name, value] of Object.entries(properties)) {
3595
+ const { validator, required } = value;
3596
+ if (!members[name]) {
3597
+ if (required) logger.error({
3598
+ group: "parser",
3599
+ label: "validate",
3600
+ message: `Missing required property "${name}"`,
3601
+ filename,
3602
+ node: $value,
3603
+ src
3604
+ });
3605
+ continue;
3606
+ }
3607
+ const memberValue = members[name];
3608
+ if (isMaybeAlias(memberValue)) validateAliasSyntax(memberValue, node, {
3609
+ filename,
3610
+ src,
3611
+ logger
3612
+ });
3613
+ else validator(memberValue, node, {
3614
+ filename,
3615
+ src,
3616
+ logger
3617
+ });
3618
+ }
3619
+ }
3620
+ /** Verify an Alias $value is formatted correctly */
3621
+ function validateAliasSyntax($value, _node, { filename, src, logger }) {
3622
+ if ($value.type !== "String" || !isAlias($value.value)) logger.error({
3623
+ group: "parser",
3624
+ label: "validate",
3625
+ message: `Invalid alias: ${print($value)}`,
3626
+ filename,
3627
+ node: $value,
3628
+ src
3629
+ });
3630
+ }
3631
+ /** Verify a Border token is valid */
3632
+ function validateBorder($value, node, { filename, src, logger }) {
3633
+ if ($value.type !== "Object") logger.error({
3634
+ group: "parser",
3635
+ label: "validate",
3636
+ message: `Expected object, received ${$value.type}`,
3637
+ filename,
3638
+ node: $value,
3639
+ src
3640
+ });
3641
+ else validateMembersAs($value, {
3642
+ color: {
3643
+ validator: validateColor,
3644
+ required: true
3645
+ },
3646
+ style: {
3647
+ validator: validateStrokeStyle,
3648
+ required: true
3649
+ },
3650
+ width: {
3651
+ validator: validateDimension,
3652
+ required: true
3653
+ }
3654
+ }, node, {
3655
+ filename,
3656
+ src,
3657
+ logger
3658
+ });
3659
+ }
3660
+ /** Verify a Color token is valid */
3661
+ function validateColor($value, node, { filename, src, logger }) {
3662
+ const baseMessage = {
3663
+ group: "parser",
3664
+ label: "validate",
3665
+ filename,
3666
+ node: $value,
3667
+ src
3668
+ };
3669
+ if ($value.type === "String") {
3670
+ if ($value.value === "") logger.error({
3671
+ ...baseMessage,
3672
+ message: "Expected color, received empty string"
3673
+ });
3674
+ } else if ($value.type === "Object") {
3675
+ const channelMemberI = $value.members.findIndex((m) => m.name.type === "String" && m.name.value === "channels");
3676
+ if (channelMemberI !== -1) {
3677
+ logger.warn({
3678
+ ...baseMessage,
3679
+ message: "\"channels\" is deprecated; rename \"channels\" to \"components\""
3680
+ });
3681
+ $value.members[channelMemberI].name.value = "components";
3682
+ }
3683
+ validateMembersAs($value, {
3684
+ colorSpace: {
3685
+ validator: (v) => {
3686
+ if (v.type !== "String") logger.error({
3687
+ ...baseMessage,
3688
+ message: `Expected string, received ${print(v)}`,
3689
+ node: v
3690
+ });
3691
+ if (!VALID_COLORSPACES.has(v.value)) logger.error({
3692
+ ...baseMessage,
3693
+ message: `Unsupported colorspace ${print(v)}`,
3694
+ node: v
3695
+ });
3696
+ },
3697
+ required: true
3698
+ },
3699
+ components: {
3700
+ validator: (v) => {
3701
+ if (v.type !== "Array") logger.error({
3702
+ ...baseMessage,
3703
+ message: `Expected array, received ${print(v)}`,
3704
+ node: v
3705
+ });
3706
+ else {
3707
+ if (v.elements?.length !== 3) logger.error({
3708
+ ...baseMessage,
3709
+ message: `Expected 3 components, received ${v.elements?.length ?? 0}`,
3710
+ node: v
3711
+ });
3712
+ for (const element of v.elements) if (element.value.type !== "Number") logger.error({
3713
+ ...baseMessage,
3714
+ message: `Expected number, received ${print(element.value)}`,
3715
+ node: element
3716
+ });
3717
+ }
3718
+ },
3719
+ required: true
3720
+ },
3721
+ hex: { validator: (v) => {
3722
+ if (v.type !== "String" || v.value.length === 6 || v.value.length === 8 || !/^#[a-f0-9]{3,8}$/i.test(v.value)) logger.error({
3723
+ ...baseMessage,
3724
+ message: `Invalid hex color ${print(v)}`,
3725
+ node: v
3726
+ });
3727
+ } },
3728
+ alpha: { validator: validateNumber }
3729
+ }, node, {
3730
+ filename,
3731
+ src,
3732
+ logger
3733
+ });
3734
+ } else logger.error({
3735
+ ...baseMessage,
3736
+ message: `Expected object, received ${$value.type}`,
3737
+ node: $value
3738
+ });
3739
+ }
3740
+ /** Verify a Cubic Bézier token is valid */
3741
+ function validateCubicBezier($value, _node, { filename, src, logger }) {
3742
+ const baseMessage = {
3743
+ group: "parser",
3744
+ label: "validate",
3745
+ filename,
3746
+ node: $value,
3747
+ src
3748
+ };
3749
+ if ($value.type !== "Array") logger.error({
3750
+ ...baseMessage,
3751
+ message: `Expected array of numbers, received ${print($value)}`
3752
+ });
3753
+ else if (!$value.elements.every((e) => e.value.type === "Number")) logger.error({
3754
+ ...baseMessage,
3755
+ message: "Expected an array of 4 numbers, received some non-numbers"
3756
+ });
3757
+ else if ($value.elements.length !== 4) logger.error({
3758
+ ...baseMessage,
3759
+ message: `Expected an array of 4 numbers, received ${$value.elements.length}`
3760
+ });
3761
+ }
3762
+ /** Verify a Dimension token is valid */
3763
+ function validateDimension($value, _node, { filename, src, logger }) {
3764
+ if ($value.type === "Number" && $value.value === 0) return;
3765
+ const baseMessage = {
3766
+ group: "parser",
3767
+ label: "validate",
3768
+ filename,
3769
+ node: $value,
3770
+ src
3771
+ };
3772
+ if ($value.type === "Object") {
3773
+ const { value: value$1, unit: unit$1 } = getObjMembers($value);
3774
+ if (!value$1) logger.error({
3775
+ ...baseMessage,
3776
+ message: "Missing required property \"value\"."
3777
+ });
3778
+ if (!unit$1) logger.error({
3779
+ ...baseMessage,
3780
+ message: "Missing required property \"unit\"."
3781
+ });
3782
+ if (value$1.type !== "Number") logger.error({
3783
+ ...baseMessage,
3784
+ message: `Expected number, received ${value$1.type}`,
3785
+ node: value$1
3786
+ });
3787
+ if (![
3788
+ "px",
3789
+ "em",
3790
+ "rem"
3791
+ ].includes(unit$1.value)) logger.error({
3792
+ ...baseMessage,
3793
+ message: `Expected unit "px", "em", or "rem", received ${print(unit$1)}`,
3794
+ node: unit$1
3795
+ });
3796
+ return;
3797
+ }
3798
+ if ($value.type !== "String") logger.error({
3799
+ ...baseMessage,
3800
+ message: `Expected string, received ${$value.type}`
3801
+ });
3802
+ const value = $value.value.match(/^-?[0-9.]+/)?.[0];
3803
+ const unit = $value.value.replace(value, "");
3804
+ if ($value.value === "") logger.error({
3805
+ ...baseMessage,
3806
+ message: "Expected dimension, received empty string"
3807
+ });
3808
+ else if (![
3809
+ "px",
3810
+ "em",
3811
+ "rem"
3812
+ ].includes(unit)) logger.error({
3813
+ ...baseMessage,
3814
+ message: `Expected unit "px", "em", or "rem", received ${JSON.stringify(unit || $value.value)}`
3815
+ });
3816
+ else if (!Number.isFinite(Number.parseFloat(value))) logger.error({
3817
+ ...baseMessage,
3818
+ message: `Expected dimension with units, received ${print($value)}`
3819
+ });
3820
+ }
3821
+ /** Verify a Duration token is valid */
3822
+ function validateDuration($value, _node, { filename, src, logger }) {
3823
+ if ($value.type === "Number" && $value.value === 0) return;
3824
+ const baseMessage = {
3825
+ group: "parser",
3826
+ label: "validate",
3827
+ filename,
3828
+ node: $value,
3829
+ src
3830
+ };
3831
+ if ($value.type === "Object") {
3832
+ const { value: value$1, unit: unit$1 } = getObjMembers($value);
3833
+ if (!value$1) logger.error({
3834
+ ...baseMessage,
3835
+ message: "Missing required property \"value\"."
3836
+ });
3837
+ if (!unit$1) logger.error({
3838
+ ...baseMessage,
3839
+ message: "Missing required property \"unit\"."
3840
+ });
3841
+ if (value$1?.type !== "Number") logger.error({
3842
+ ...baseMessage,
3843
+ message: `Expected number, received ${value$1?.type}`,
3844
+ node: value$1
3845
+ });
3846
+ if (!["ms", "s"].includes(unit$1.value)) logger.error({
3847
+ ...baseMessage,
3848
+ message: `Expected unit "ms" or "s", received ${print(unit$1)}`,
3849
+ node: unit$1
3850
+ });
3851
+ return;
3852
+ }
3853
+ if ($value.type !== "String") logger.error({
3854
+ ...baseMessage,
3855
+ message: `Expected string, received ${$value.type}`
3856
+ });
3857
+ const value = $value.value.match(/^-?[0-9.]+/)?.[0];
3858
+ const unit = $value.value.replace(value, "");
3859
+ if ($value.value === "") logger.error({
3860
+ ...baseMessage,
3861
+ message: "Expected duration, received empty string"
3862
+ });
3863
+ else if (!["ms", "s"].includes(unit)) logger.error({
3864
+ ...baseMessage,
3865
+ message: `Expected unit "ms" or "s", received ${JSON.stringify(unit || $value.value)}`
3866
+ });
3867
+ else if (!Number.isFinite(Number.parseFloat(value))) logger.error({
3868
+ ...baseMessage,
3869
+ message: `Expected duration with units, received ${print($value)}`
3870
+ });
3871
+ }
3872
+ /** Verify a Font Family token is valid */
3873
+ function validateFontFamily($value, _node, { filename, src, logger }) {
3874
+ const baseMessage = {
3875
+ group: "parser",
3876
+ label: "validate",
3877
+ filename,
3878
+ node: $value,
3879
+ src
3880
+ };
3881
+ if ($value.type !== "String" && $value.type !== "Array") logger.error({
3882
+ ...baseMessage,
3883
+ message: `Expected string or array of strings, received ${$value.type}`
3884
+ });
3885
+ if ($value.type === "String" && $value.value === "") logger.error({
3886
+ ...baseMessage,
3887
+ message: "Expected font family name, received empty string"
3888
+ });
3889
+ if ($value.type === "Array" && !$value.elements.every((e) => e.value.type === "String" && e.value.value !== "")) logger.error({
3890
+ ...baseMessage,
3891
+ message: "Expected an array of strings, received some non-strings or empty strings"
3892
+ });
3893
+ }
3894
+ /** Verify a Font Weight token is valid */
3895
+ function validateFontWeight($value, _node, { filename, src, logger }) {
3896
+ const baseMessage = {
3897
+ group: "parser",
3898
+ label: "validate",
3899
+ filename,
3900
+ node: $value,
3901
+ src
3902
+ };
3903
+ if ($value.type !== "String" && $value.type !== "Number") logger.error({
3904
+ ...baseMessage,
3905
+ message: `Expected a font weight name or number 0–1000, received ${$value.type}`
3906
+ });
3907
+ if ($value.type === "String" && !FONT_WEIGHT_VALUES.has($value.value)) logger.error({
3908
+ ...baseMessage,
3909
+ message: `Unknown font weight ${print($value)}. Expected one of: ${listFormat.format([...FONT_WEIGHT_VALUES])}.`
3910
+ });
3911
+ if ($value.type === "Number" && ($value.value < 0 || $value.value > 1e3)) logger.error({
3912
+ ...baseMessage,
3913
+ message: `Expected number 0–1000, received ${print($value)}`
3914
+ });
3915
+ }
3916
+ /** Verify a Gradient token is valid */
3917
+ function validateGradient($value, _node, { filename, src, logger }) {
3918
+ const baseMessage = {
3919
+ group: "parser",
3920
+ label: "validate",
3921
+ filename,
3922
+ node: $value,
3923
+ src
3924
+ };
3925
+ if ($value.type !== "Array") logger.error({
3926
+ ...baseMessage,
3927
+ message: `Expected array of gradient stops, received ${$value.type}`
3928
+ });
3929
+ else for (let i = 0; i < $value.elements.length; i++) {
3930
+ const element = $value.elements[i];
3931
+ if (element.value.type !== "Object") {
3932
+ logger.error({
3933
+ ...baseMessage,
3934
+ message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
3935
+ node: element
3936
+ });
3937
+ break;
3938
+ }
3939
+ validateMembersAs(element.value, {
3940
+ color: {
3941
+ validator: validateColor,
3942
+ required: true
3943
+ },
3944
+ position: {
3945
+ validator: validateNumber,
3946
+ required: true
3947
+ }
3948
+ }, element, {
3949
+ filename,
3950
+ src,
3951
+ logger
3952
+ });
3953
+ }
3954
+ }
3955
+ /** Verify a Number token is valid */
3956
+ function validateNumber($value, _node, { filename, src, logger }) {
3957
+ if ($value.type !== "Number") logger.error({
3958
+ group: "parser",
3959
+ label: "validate",
3960
+ message: `Expected number, received ${$value.type}`,
3961
+ filename,
3962
+ node: $value,
3963
+ src
3964
+ });
3965
+ }
3966
+ /** Verify a Boolean token is valid */
3967
+ function validateBoolean($value, _node, { filename, src, logger }) {
3968
+ if ($value.type !== "Boolean") logger.error({
3969
+ group: "parser",
3970
+ label: "validate",
3971
+ message: `Expected boolean, received ${$value.type}`,
3972
+ filename,
3973
+ node: $value,
3974
+ src
3975
+ });
3976
+ }
3977
+ /** Verify a Shadow token’s value is valid */
3978
+ function validateShadowLayer($value, node, { filename, src, logger }) {
3979
+ if ($value.type !== "Object") logger.error({
3980
+ group: "parser",
3981
+ label: "validate",
3982
+ message: `Expected Object, received ${$value.type}`,
3983
+ filename,
3984
+ node: $value,
3985
+ src
3986
+ });
3987
+ else validateMembersAs($value, {
3988
+ color: {
3989
+ validator: validateColor,
3990
+ required: true
3991
+ },
3992
+ offsetX: {
3993
+ validator: validateDimension,
3994
+ required: true
3995
+ },
3996
+ offsetY: {
3997
+ validator: validateDimension,
3998
+ required: true
3999
+ },
4000
+ blur: { validator: validateDimension },
4001
+ spread: { validator: validateDimension },
4002
+ inset: { validator: validateBoolean }
4003
+ }, node, {
4004
+ filename,
4005
+ src,
4006
+ logger
4007
+ });
4008
+ }
4009
+ /** Verify a Stroke Style token is valid. */
4010
+ function validateStrokeStyle($value, node, { filename, src, logger }) {
4011
+ const baseMessage = {
4012
+ group: "parser",
4013
+ label: "validate",
4014
+ filename,
4015
+ node: $value,
4016
+ src
4017
+ };
4018
+ if ($value.type === "String") {
4019
+ if (!STROKE_STYLE_VALUES.has($value.value)) logger.error({
4020
+ ...baseMessage,
4021
+ message: `Unknown stroke style ${print($value)}. Expected one of: ${listFormat.format([...STROKE_STYLE_VALUES])}.`
4022
+ });
4023
+ } else if ($value.type === "Object") {
4024
+ const strokeMembers = getObjMembers($value);
4025
+ for (const property of ["dashArray", "lineCap"]) if (!strokeMembers[property]) logger.error({
4026
+ ...baseMessage,
4027
+ message: `Missing required property "${property}"`
4028
+ });
4029
+ const { lineCap, dashArray } = strokeMembers;
4030
+ if (lineCap?.type !== "String" || !STROKE_STYLE_LINE_CAP_VALUES.has(lineCap.value)) logger.error({
4031
+ ...baseMessage,
4032
+ message: `Unknown lineCap value ${print(lineCap)}. Expected one of: ${listFormat.format([...STROKE_STYLE_LINE_CAP_VALUES])}.`,
4033
+ node
4034
+ });
4035
+ if (dashArray?.type === "Array") for (const element of dashArray.elements) if (element.value.type === "String" && element.value.value !== "") if (isMaybeAlias(element.value)) validateAliasSyntax(element.value, node, {
4036
+ logger,
4037
+ src
4038
+ });
4039
+ else validateDimension(element.value, node, {
4040
+ logger,
4041
+ src
4042
+ });
4043
+ else logger.error({
4044
+ ...baseMessage,
4045
+ message: "Expected array of strings, recieved some non-strings or empty strings.",
4046
+ node: element
4047
+ });
4048
+ else logger.error({
4049
+ ...baseMessage,
4050
+ message: `Expected array of strings, received ${dashArray.type}`
4051
+ });
4052
+ } else logger.error({
4053
+ ...baseMessage,
4054
+ message: `Expected string or object, received ${$value.type}`
4055
+ });
4056
+ }
4057
+ /** Verify a Transition token is valid */
4058
+ function validateTransition($value, node, { filename, src, logger }) {
4059
+ if ($value.type !== "Object") logger.error({
4060
+ group: "parser",
4061
+ label: "validate",
4062
+ message: `Expected object, received ${$value.type}`,
4063
+ filename,
4064
+ node: $value,
4065
+ src
4066
+ });
4067
+ else validateMembersAs($value, {
4068
+ duration: {
4069
+ validator: validateDuration,
4070
+ required: true
4071
+ },
4072
+ delay: {
4073
+ validator: validateDuration,
4074
+ required: false
4075
+ },
4076
+ timingFunction: {
4077
+ validator: validateCubicBezier,
4078
+ required: true
4079
+ }
4080
+ }, node, {
4081
+ filename,
4082
+ src,
4083
+ logger
4084
+ });
4085
+ }
4086
+ /**
4087
+ * Validate a MemberNode (the entire token object, plus its key in the parent
4088
+ * object) to see if it’s a valid DTCG token or not. Keeping the parent key
4089
+ * really helps in debug messages.
4090
+ */
4091
+ function validateTokenMemberNode(node, { filename, src, logger }) {
4092
+ const baseMessage = {
4093
+ group: "parser",
4094
+ label: "validate",
4095
+ filename,
4096
+ node,
4097
+ src
4098
+ };
4099
+ if (node.type !== "Member" && node.type !== "Object") logger.error({
4100
+ ...baseMessage,
4101
+ message: `Expected Object, received ${JSON.stringify(node.type)}`
4102
+ });
4103
+ const rootMembers = node.value.type === "Object" ? getObjMembers(node.value) : {};
4104
+ const $value = rootMembers.$value;
4105
+ const $type = rootMembers.$type;
4106
+ if (!$value) logger.error({
4107
+ ...baseMessage,
4108
+ message: "Token missing $value"
4109
+ });
4110
+ if (isMaybeAlias($value)) {
4111
+ validateAliasSyntax($value, node, {
4112
+ logger,
4113
+ src
4114
+ });
4115
+ return;
4116
+ }
4117
+ if (!$type) logger.error({
4118
+ ...baseMessage,
4119
+ message: "Token missing $type"
4120
+ });
4121
+ switch ($type.value) {
4122
+ case "color": {
4123
+ validateColor($value, node, {
4124
+ logger,
4125
+ src
4126
+ });
4127
+ break;
4128
+ }
4129
+ case "cubicBezier": {
4130
+ validateCubicBezier($value, node, {
4131
+ logger,
4132
+ src
4133
+ });
4134
+ break;
4135
+ }
4136
+ case "dimension": {
4137
+ validateDimension($value, node, {
4138
+ logger,
4139
+ src
4140
+ });
4141
+ break;
4142
+ }
4143
+ case "duration": {
4144
+ validateDuration($value, node, {
4145
+ logger,
4146
+ src
4147
+ });
4148
+ break;
4149
+ }
4150
+ case "fontFamily": {
4151
+ validateFontFamily($value, node, {
4152
+ logger,
4153
+ src
4154
+ });
4155
+ break;
4156
+ }
4157
+ case "fontWeight": {
4158
+ validateFontWeight($value, node, {
4159
+ logger,
4160
+ src
4161
+ });
4162
+ break;
4163
+ }
4164
+ case "number": {
4165
+ validateNumber($value, node, {
4166
+ logger,
4167
+ src
4168
+ });
4169
+ break;
4170
+ }
4171
+ case "shadow": {
4172
+ if ($value.type === "Object") validateShadowLayer($value, node, {
4173
+ logger,
4174
+ src
4175
+ });
4176
+ else if ($value.type === "Array") for (const element of $value.elements) validateShadowLayer(element.value, $value, {
4177
+ logger,
4178
+ src
4179
+ });
4180
+ else logger.error({
4181
+ ...baseMessage,
4182
+ message: `Expected shadow object or array of shadow objects, received ${$value.type}`,
4183
+ node: $value
4184
+ });
4185
+ break;
4186
+ }
4187
+ case "boolean": {
4188
+ if ($value.type !== "Boolean") logger.error({
4189
+ ...baseMessage,
4190
+ message: `Expected boolean, received ${$value.type}`,
4191
+ node: $value
4192
+ });
4193
+ break;
4194
+ }
4195
+ case "link": {
4196
+ if ($value.type !== "String") logger.error({
4197
+ ...baseMessage,
4198
+ message: `Expected string, received ${$value.type}`,
4199
+ node: $value
4200
+ });
4201
+ else if ($value.value === "") logger.error({
4202
+ ...baseMessage,
4203
+ message: "Expected URL, received empty string",
4204
+ node: $value
4205
+ });
4206
+ break;
4207
+ }
4208
+ case "string": {
4209
+ if ($value.type !== "String") logger.error({
4210
+ ...baseMessage,
4211
+ message: `Expected string, received ${$value.type}`,
4212
+ node: $value
4213
+ });
4214
+ break;
4215
+ }
4216
+ case "border": {
4217
+ validateBorder($value, node, {
4218
+ filename,
4219
+ src,
4220
+ logger
4221
+ });
4222
+ break;
4223
+ }
4224
+ case "gradient": {
4225
+ validateGradient($value, node, {
4226
+ filename,
4227
+ src,
4228
+ logger
4229
+ });
4230
+ break;
4231
+ }
4232
+ case "strokeStyle": {
4233
+ validateStrokeStyle($value, node, {
4234
+ filename,
4235
+ src,
4236
+ logger
4237
+ });
4238
+ break;
4239
+ }
4240
+ case "transition": {
4241
+ validateTransition($value, node, {
4242
+ filename,
4243
+ src,
4244
+ logger
4245
+ });
4246
+ break;
4247
+ }
4248
+ case "typography": {
4249
+ if ($value.type !== "Object") {
4250
+ logger.error({
4251
+ ...baseMessage,
4252
+ message: `Expected object, received ${$value.type}`,
4253
+ node: $value
4254
+ });
4255
+ break;
4256
+ }
4257
+ if ($value.members.length === 0) logger.error({
4258
+ ...baseMessage,
4259
+ message: "Empty typography token. Must contain at least 1 property.",
4260
+ node: $value
4261
+ });
4262
+ validateMembersAs($value, {
4263
+ fontFamily: { validator: validateFontFamily },
4264
+ fontWeight: { validator: validateFontWeight }
4265
+ }, node, {
4266
+ filename,
4267
+ src,
4268
+ logger
4269
+ });
4270
+ break;
4271
+ }
4272
+ default: break;
4273
+ }
4274
+ }
4275
+ /** Return any token node with its inherited $type */
4276
+ function getInheritedType(node, { subpath, $typeInheritance }) {
4277
+ if (node.value.type !== "Object") return;
4278
+ const $type = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$type");
4279
+ const $value = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$value");
4280
+ if ($typeInheritance && $type && !$value) $typeInheritance[subpath.join(".") || "."] = $type;
4281
+ const id = subpath.join(".");
4282
+ let parent$type;
4283
+ let longestPath = "";
4284
+ for (const [k, v] of Object.entries($typeInheritance ?? {})) if (k === "." || id.startsWith(k)) {
4285
+ if (k.length > longestPath.length) {
4286
+ parent$type = v;
4287
+ longestPath = k;
4288
+ }
4289
+ }
4290
+ return parent$type;
4291
+ }
4292
+ /**
4293
+ * Validate does a little more than validate; it also converts to TokenNormalized
4294
+ * and sets up the basic data structure. But aliases are unresolved, and we need
4295
+ * a 2nd normalization pass afterward.
4296
+ */
4297
+ function validateTokenNode(node, { config, filename, logger, parent, inheritedTypeNode, src, subpath }) {
4298
+ if (subpath.includes("$value") || node.value.type !== "Object") return;
4299
+ const members = getObjMembers(node.value);
4300
+ if (!members.$value || subpath.includes("$extensions") || subpath.includes("$deps")) return;
4301
+ const id = subpath.join(".");
4302
+ if (!subpath.includes(".$value") && members.value) logger.warn({
4303
+ group: "parser",
4304
+ label: "validate",
4305
+ message: `Group ${id} has "value". Did you mean "$value"?`,
4306
+ filename,
4307
+ node,
4308
+ src
4309
+ });
4310
+ const nodeWithType = structuredClone(node);
4311
+ let $type = members.$type?.type === "String" && members.$type.value || void 0;
4312
+ if (inheritedTypeNode && !members.$type) {
4313
+ injectObjMembers(nodeWithType.value, [inheritedTypeNode]);
4314
+ $type = inheritedTypeNode.value.value;
4315
+ }
4316
+ validateTokenMemberNode(nodeWithType, {
4317
+ filename,
4318
+ src,
4319
+ logger
4320
+ });
4321
+ const $deprecated = members.$deprecated && evaluate(members.$deprecated);
4322
+ if (config.ignore.deprecated && $deprecated || config.ignore.tokens && isTokenMatch(id, config.ignore.tokens)) return;
4323
+ const group = {
4324
+ id: splitID(id).group,
4325
+ tokens: []
4326
+ };
4327
+ if (inheritedTypeNode && inheritedTypeNode.value.type === "String") group.$type = inheritedTypeNode.value.value;
4328
+ const groupMembers = getObjMembers(parent);
4329
+ if (groupMembers.$description) group.$description = evaluate(groupMembers.$description);
4330
+ if (groupMembers.$extensions) group.$extensions = evaluate(groupMembers.$extensions);
4331
+ const $value = evaluate(members.$value);
4332
+ const token = {
4333
+ $type,
4334
+ $value,
4335
+ id,
4336
+ mode: {},
4337
+ originalValue: evaluate(node.value),
4338
+ group,
4339
+ source: {
4340
+ loc: filename?.href,
4341
+ node: nodeWithType.value
4342
+ }
4343
+ };
4344
+ if (members.$description?.type === "String" && members.$description.value) token.$description = members.$description.value;
4345
+ const extensions = members.$extensions ? getObjMembers(members.$extensions) : void 0;
4346
+ const modeValues = extensions?.mode ? getObjMembers(extensions.mode) : {};
4347
+ for (const mode of [".", ...Object.keys(modeValues)]) {
4348
+ const modeValue = mode === "." ? token.$value : evaluate(modeValues[mode]);
4349
+ token.mode[mode] = {
4350
+ $value: modeValue,
4351
+ originalValue: modeValue,
4352
+ source: {
4353
+ loc: filename?.href,
4354
+ node: modeValues[mode]
4355
+ }
4356
+ };
4357
+ }
4358
+ return token;
4359
+ }
4360
+
4361
+ //#endregion
4362
+ //#region src/parse/index.ts
4363
+ /** Parse */
4364
+ async function parse(_input, { logger = new Logger(), skipLint = false, config = {}, continueOnError = false, yamlToMomoa, transform, _sources = {} } = {}) {
4365
+ const input = Array.isArray(_input) ? _input : [_input];
4366
+ let tokensSet = {};
4367
+ if (!Array.isArray(input)) logger.error({
4368
+ group: "parser",
4369
+ label: "init",
4370
+ message: "Input must be an array of input objects."
4371
+ });
4372
+ await Promise.all(input.map(async (src, i) => {
4373
+ if (!src || typeof src !== "object") logger.error({
4374
+ group: "parser",
4375
+ label: "init",
4376
+ message: `Input (${i}) must be an object.`
4377
+ });
4378
+ if (!src.src || typeof src.src !== "string" && typeof src.src !== "object") logger.error({
4379
+ message: `Input (${i}) missing "src" with a JSON/YAML string, or JSON object.`,
4380
+ group: "parser",
4381
+ label: "init"
4382
+ });
4383
+ if (src.filename) {
4384
+ if (!(src.filename instanceof URL)) logger.error({
4385
+ message: `Input (${i}) "filename" must be a URL (remote or file URL).`,
4386
+ group: "parser",
4387
+ label: "init"
4388
+ });
4389
+ if (_sources[src.filename.href]) return;
4390
+ }
4391
+ const result = await parseSingle(src.src, {
4392
+ filename: src.filename,
4393
+ logger,
4394
+ config,
4395
+ skipLint,
4396
+ continueOnError,
4397
+ yamlToMomoa,
4398
+ transform
4399
+ });
4400
+ tokensSet = Object.assign(tokensSet, result.tokens);
4401
+ if (src.filename) _sources[src.filename.href] = {
4402
+ filename: src.filename,
4403
+ src: result.src,
4404
+ document: result.document
4405
+ };
4406
+ }));
4407
+ const totalStart = performance.now();
4408
+ const aliasesStart = performance.now();
4409
+ let aliasCount = 0;
4410
+ for (const [id, token] of Object.entries(tokensSet)) {
4411
+ applyAliases(token, {
4412
+ tokensSet,
4413
+ filename: _sources[token.source.loc]?.filename,
4414
+ src: _sources[token.source.loc]?.src,
4415
+ node: getObjMembers(token.source.node).$value || token.source.node,
4416
+ logger
4417
+ });
4418
+ aliasCount++;
4419
+ const { group: parentGroup } = splitID(id);
4420
+ if (parentGroup) for (const siblingID of Object.keys(tokensSet)) {
4421
+ const { group: siblingGroup } = splitID(siblingID);
4422
+ if (siblingGroup?.startsWith(parentGroup)) token.group.tokens.push(siblingID);
4423
+ }
4424
+ }
4425
+ logger.debug({
4426
+ message: `Resolved ${aliasCount} aliases`,
4427
+ group: "parser",
4428
+ label: "alias",
4429
+ timing: performance.now() - aliasesStart
4430
+ });
4431
+ logger.debug({
4432
+ message: "Finish all parser tasks",
4433
+ group: "parser",
4434
+ label: "core",
4435
+ timing: performance.now() - totalStart
4436
+ });
4437
+ if (continueOnError) {
4438
+ const { errorCount } = logger.stats();
4439
+ if (errorCount > 0) logger.error({
4440
+ group: "parser",
4441
+ message: `Parser encountered ${errorCount} ${pluralize(errorCount, "error", "errors")}. Exiting.`
4442
+ });
4443
+ }
4444
+ return {
4445
+ tokens: tokensSet,
4446
+ sources: Object.values(_sources)
4447
+ };
4448
+ }
4449
+ /** Parse a single input */
4450
+ async function parseSingle(input, { filename, logger, config, skipLint, continueOnError = false, transform, yamlToMomoa }) {
4451
+ const startParsing = performance.now();
4452
+ let { src, document } = toMomoa(input, {
4453
+ filename,
4454
+ logger,
4455
+ continueOnError,
4456
+ yamlToMomoa
4457
+ });
4458
+ logger.debug({
4459
+ group: "parser",
4460
+ label: "json",
4461
+ message: "Finish JSON parsing",
4462
+ timing: performance.now() - startParsing
4463
+ });
4464
+ const tokensSet = {};
4465
+ if (transform?.root) {
4466
+ const json = typeof input === "string" ? JSON.parse(input) : input;
4467
+ const result = transform?.root(json, ".", document);
4468
+ if (result) {
4469
+ const reRunResult = toMomoa(result, {
4470
+ filename,
4471
+ logger,
4472
+ continueOnError
4473
+ });
4474
+ src = reRunResult.src;
4475
+ document = reRunResult.document;
4476
+ }
4477
+ }
4478
+ let tokenCount = 0;
4479
+ const startValidate = performance.now();
4480
+ const $typeInheritance = {};
4481
+ traverse(document, { enter(node, parent, subpath) {
4482
+ if (node.type === "Document" && node.body.type === "Object" && node.body.members) {
4483
+ const { members: rootMembers } = node.body;
4484
+ const root$type = rootMembers.find((m) => m.name.type === "String" && m.name.value === "$type");
4485
+ const root$value = rootMembers.find((m) => m.name.type === "String" && m.name.value === "$value");
4486
+ if (root$type && !root$value) $typeInheritance["."] = root$type;
4487
+ }
4488
+ if (node.type === "Object" && subpath.length && !node.members.some((m) => m.name.type === "String" && m.name.value === "$value") && !subpath.includes("$value") && !subpath.includes("$extensions")) {
4489
+ if (transform?.group) {
4490
+ const newJSON = transform?.group(evaluate(node), subpath.join("."), node);
4491
+ if (newJSON) replaceObjMembers(node, parseJSON(newJSON));
4492
+ }
4493
+ }
4494
+ if (node.type === "Member") {
4495
+ const inheritedTypeNode = getInheritedType(node, {
4496
+ subpath,
4497
+ $typeInheritance
4498
+ });
4499
+ if (node.value.type === "Object") {
4500
+ const $type = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$type") || inheritedTypeNode;
4501
+ const $value = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$value");
4502
+ if ($value && $type?.value.type === "String" && transform?.[$type.value.value]) {
4503
+ const result = transform[$type.value.value]?.(evaluate(node.value), subpath.join("."), node);
4504
+ if (result) node.value = parseJSON(result).body;
4505
+ }
4506
+ const token = validateTokenNode(node, {
4507
+ filename,
4508
+ src,
4509
+ config,
4510
+ logger,
4511
+ parent,
4512
+ subpath,
4513
+ transform,
4514
+ inheritedTypeNode
4515
+ });
4516
+ if (token) {
4517
+ tokensSet[token.id] = token;
4518
+ tokenCount++;
4519
+ }
4520
+ }
4521
+ }
4522
+ } });
4523
+ logger.debug({
4524
+ message: `Validated ${tokenCount} tokens`,
4525
+ group: "parser",
4526
+ label: "validate",
4527
+ timing: performance.now() - startValidate
4528
+ });
4529
+ const normalizeStart = performance.now();
4530
+ for (const [id, token] of Object.entries(tokensSet)) {
4531
+ try {
4532
+ tokensSet[id].$value = normalizeValue(token);
4533
+ } catch (err) {
4534
+ let { node } = token.source;
4535
+ const members = getObjMembers(node);
4536
+ if (members.$value) node = members.$value;
4537
+ logger.error({
4538
+ group: "parser",
4539
+ label: "normalize",
4540
+ message: err.message,
4541
+ filename,
4542
+ src,
4543
+ node,
4544
+ continueOnError
4545
+ });
4546
+ }
4547
+ for (const [mode, modeValue] of Object.entries(token.mode)) {
4548
+ if (mode === ".") continue;
4549
+ try {
4550
+ tokensSet[id].mode[mode].$value = normalizeValue({
4551
+ $type: token.$type,
4552
+ ...modeValue
4553
+ });
4554
+ } catch (err) {
4555
+ let { node } = token.source;
4556
+ const members = getObjMembers(node);
4557
+ if (members.$value) node = members.$value;
4558
+ logger.error({
4559
+ group: "parser",
4560
+ label: "normalize",
4561
+ message: err.message,
4562
+ filename,
4563
+ src,
4564
+ node: modeValue.source.node,
4565
+ continueOnError
4566
+ });
4567
+ }
4568
+ }
4569
+ }
4570
+ logger.debug({
4571
+ message: `Normalized ${tokenCount} tokens`,
4572
+ group: "parser",
4573
+ label: "normalize",
4574
+ timing: performance.now() - normalizeStart
4575
+ });
4576
+ if (!skipLint && config?.plugins?.length) {
4577
+ const lintStart = performance.now();
4578
+ await lintRunner({
4579
+ tokens: tokensSet,
4580
+ src,
4581
+ config,
4582
+ logger
4583
+ });
4584
+ logger.debug({
4585
+ message: `Linted ${tokenCount} tokens`,
4586
+ group: "parser",
4587
+ label: "lint",
4588
+ timing: performance.now() - lintStart
4589
+ });
4590
+ } else logger.debug({
4591
+ message: "Linting skipped",
4592
+ group: "parser",
4593
+ label: "lint"
4594
+ });
4595
+ return {
4596
+ tokens: tokensSet,
4597
+ document,
4598
+ src
4599
+ };
4600
+ }
4601
+
4602
+ //#endregion
4603
+ export { CHILD_KEYS, FONT_WEIGHT_MAP, FONT_WEIGHT_VALUES, LOG_ORDER, Logger, MULTI_VALUE, SINGLE_VALUE, STROKE_STYLE_LINE_CAP_VALUES, STROKE_STYLE_VALUES, TokensJSONError, VALID_COLORSPACES, build, defineConfig, findNode, formatMessage, getInheritedType, getObjMembers, injectObjMembers, isNode, lintRunner, maybeRawJSON, mergeConfigs, normalizeValue as normalize, parse, parseJSON, replaceObjMembers, toMomoa, traverse, validateAliasSyntax, validateBoolean, validateBorder, validateColor, validateCubicBezier, validateDimension, validateDuration, validateFontFamily, validateFontWeight, validateGradient, validateNumber, validateShadowLayer, validateStrokeStyle, validateTokenMemberNode, validateTokenNode, validateTransition };
13
4604
  //# sourceMappingURL=index.js.map