@terrazzo/parser 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/index.d.ts +707 -12
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +4598 -12
  5. package/dist/index.js.map +1 -1
  6. package/package.json +11 -7
  7. package/rolldown.config.ts +24 -0
  8. package/src/build/index.ts +2 -2
  9. package/src/index.ts +76 -1
  10. package/dist/build/index.d.ts +0 -20
  11. package/dist/build/index.d.ts.map +0 -1
  12. package/dist/build/index.js +0 -166
  13. package/dist/build/index.js.map +0 -1
  14. package/dist/config.d.ts +0 -8
  15. package/dist/config.d.ts.map +0 -1
  16. package/dist/config.js +0 -290
  17. package/dist/config.js.map +0 -1
  18. package/dist/lib/code-frame.d.ts +0 -31
  19. package/dist/lib/code-frame.d.ts.map +0 -1
  20. package/dist/lib/code-frame.js +0 -108
  21. package/dist/lib/code-frame.js.map +0 -1
  22. package/dist/lint/index.d.ts +0 -12
  23. package/dist/lint/index.d.ts.map +0 -1
  24. package/dist/lint/index.js +0 -105
  25. package/dist/lint/index.js.map +0 -1
  26. package/dist/lint/plugin-core/index.d.ts +0 -13
  27. package/dist/lint/plugin-core/index.d.ts.map +0 -1
  28. package/dist/lint/plugin-core/index.js +0 -40
  29. package/dist/lint/plugin-core/index.js.map +0 -1
  30. package/dist/lint/plugin-core/lib/docs.d.ts +0 -2
  31. package/dist/lint/plugin-core/lib/docs.d.ts.map +0 -1
  32. package/dist/lint/plugin-core/lib/docs.js +0 -4
  33. package/dist/lint/plugin-core/lib/docs.js.map +0 -1
  34. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +0 -40
  35. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts.map +0 -1
  36. package/dist/lint/plugin-core/rules/a11y-min-contrast.js +0 -58
  37. package/dist/lint/plugin-core/rules/a11y-min-contrast.js.map +0 -1
  38. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +0 -14
  39. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts.map +0 -1
  40. package/dist/lint/plugin-core/rules/a11y-min-font-size.js +0 -45
  41. package/dist/lint/plugin-core/rules/a11y-min-font-size.js.map +0 -1
  42. package/dist/lint/plugin-core/rules/colorspace.d.ts +0 -15
  43. package/dist/lint/plugin-core/rules/colorspace.d.ts.map +0 -1
  44. package/dist/lint/plugin-core/rules/colorspace.js +0 -85
  45. package/dist/lint/plugin-core/rules/colorspace.js.map +0 -1
  46. package/dist/lint/plugin-core/rules/consistent-naming.d.ts +0 -12
  47. package/dist/lint/plugin-core/rules/consistent-naming.d.ts.map +0 -1
  48. package/dist/lint/plugin-core/rules/consistent-naming.js +0 -49
  49. package/dist/lint/plugin-core/rules/consistent-naming.js.map +0 -1
  50. package/dist/lint/plugin-core/rules/descriptions.d.ts +0 -10
  51. package/dist/lint/plugin-core/rules/descriptions.d.ts.map +0 -1
  52. package/dist/lint/plugin-core/rules/descriptions.js +0 -32
  53. package/dist/lint/plugin-core/rules/descriptions.js.map +0 -1
  54. package/dist/lint/plugin-core/rules/duplicate-values.d.ts +0 -10
  55. package/dist/lint/plugin-core/rules/duplicate-values.d.ts.map +0 -1
  56. package/dist/lint/plugin-core/rules/duplicate-values.js +0 -65
  57. package/dist/lint/plugin-core/rules/duplicate-values.js.map +0 -1
  58. package/dist/lint/plugin-core/rules/max-gamut.d.ts +0 -15
  59. package/dist/lint/plugin-core/rules/max-gamut.d.ts.map +0 -1
  60. package/dist/lint/plugin-core/rules/max-gamut.js +0 -101
  61. package/dist/lint/plugin-core/rules/max-gamut.js.map +0 -1
  62. package/dist/lint/plugin-core/rules/required-children.d.ts +0 -19
  63. package/dist/lint/plugin-core/rules/required-children.d.ts.map +0 -1
  64. package/dist/lint/plugin-core/rules/required-children.js +0 -78
  65. package/dist/lint/plugin-core/rules/required-children.js.map +0 -1
  66. package/dist/lint/plugin-core/rules/required-modes.d.ts +0 -14
  67. package/dist/lint/plugin-core/rules/required-modes.d.ts.map +0 -1
  68. package/dist/lint/plugin-core/rules/required-modes.js +0 -52
  69. package/dist/lint/plugin-core/rules/required-modes.js.map +0 -1
  70. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +0 -11
  71. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts.map +0 -1
  72. package/dist/lint/plugin-core/rules/required-typography-properties.js +0 -38
  73. package/dist/lint/plugin-core/rules/required-typography-properties.js.map +0 -1
  74. package/dist/logger.d.ts +0 -77
  75. package/dist/logger.d.ts.map +0 -1
  76. package/dist/logger.js +0 -136
  77. package/dist/logger.js.map +0 -1
  78. package/dist/parse/alias.d.ts +0 -34
  79. package/dist/parse/alias.d.ts.map +0 -1
  80. package/dist/parse/alias.js +0 -302
  81. package/dist/parse/alias.js.map +0 -1
  82. package/dist/parse/index.d.ts +0 -41
  83. package/dist/parse/index.d.ts.map +0 -1
  84. package/dist/parse/index.js +0 -273
  85. package/dist/parse/index.js.map +0 -1
  86. package/dist/parse/json.d.ts +0 -52
  87. package/dist/parse/json.d.ts.map +0 -1
  88. package/dist/parse/json.js +0 -168
  89. package/dist/parse/json.js.map +0 -1
  90. package/dist/parse/normalize.d.ts +0 -24
  91. package/dist/parse/normalize.d.ts.map +0 -1
  92. package/dist/parse/normalize.js +0 -176
  93. package/dist/parse/normalize.js.map +0 -1
  94. package/dist/parse/validate.d.ts +0 -90
  95. package/dist/parse/validate.d.ts.map +0 -1
  96. package/dist/parse/validate.js +0 -787
  97. package/dist/parse/validate.js.map +0 -1
  98. package/dist/types.d.ts +0 -265
  99. package/dist/types.d.ts.map +0 -1
  100. package/dist/types.js +0 -2
  101. package/dist/types.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,13 +1,4599 @@
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
+ /**
227
+ * Run build stage
228
+ */
229
+ async function build(tokens, { sources, logger = new Logger(), config }) {
230
+ const formats = {};
231
+ const result = { outputFiles: [] };
232
+ function getTransforms(params) {
233
+ return (formats[params.format] ?? []).filter((token) => {
234
+ if (params.$type) {
235
+ if (typeof params.$type === "string" && token.token.$type !== params.$type) return false;
236
+ else if (Array.isArray(params.$type) && !params.$type.some(($type) => token.token.$type === $type)) return false;
237
+ }
238
+ if (params.id && params.id !== "*" && !isTokenMatch(token.token.id, Array.isArray(params.id) ? params.id : [params.id])) return false;
239
+ if (params.mode && !wcmatch(params.mode)(token.mode)) return false;
240
+ return true;
241
+ });
242
+ }
243
+ let transformsLocked = false;
244
+ const startTransform = performance.now();
245
+ for (const plugin of config.plugins) if (typeof plugin.transform === "function") await plugin.transform({
246
+ tokens,
247
+ sources,
248
+ getTransforms,
249
+ setTransform(id, params) {
250
+ if (transformsLocked) {
251
+ logger.warn({
252
+ message: "Attempted to call setTransform() after transform step has completed.",
253
+ group: "plugin",
254
+ label: plugin.name
255
+ });
256
+ return;
257
+ }
258
+ const token = tokens[id];
259
+ const cleanValue = typeof params.value === "string" ? params.value : { ...params.value };
260
+ if (typeof cleanValue === "object") {
261
+ for (const k of Object.keys(cleanValue)) if (cleanValue[k] === void 0) delete cleanValue[k];
262
+ }
263
+ validateTransformParams({
264
+ logger,
265
+ params: {
266
+ ...params,
267
+ value: cleanValue
268
+ },
269
+ pluginName: plugin.name
270
+ });
271
+ if (!formats[params.format]) formats[params.format] = [];
272
+ const foundTokenI = formats[params.format].findIndex((t) => id === t.id && (!params.localID || params.localID === t.localID) && (!params.mode || params.mode === t.mode));
273
+ if (foundTokenI === -1) formats[params.format].push({
274
+ ...params,
275
+ id,
276
+ value: cleanValue,
277
+ type: typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE,
278
+ mode: params.mode || ".",
279
+ token: structuredClone(token)
280
+ });
281
+ else {
282
+ formats[params.format][foundTokenI].value = cleanValue;
283
+ formats[params.format][foundTokenI].type = typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE;
284
+ }
285
+ }
286
+ });
287
+ transformsLocked = true;
288
+ logger.debug({
289
+ group: "parser",
290
+ label: "transform",
291
+ message: "transform() step",
292
+ timing: performance.now() - startTransform
293
+ });
294
+ const startBuild = performance.now();
295
+ for (const plugin of config.plugins) if (typeof plugin.build === "function") {
296
+ const pluginBuildStart = performance.now();
297
+ await plugin.build({
298
+ tokens,
299
+ sources,
300
+ getTransforms,
301
+ outputFile(filename, contents) {
302
+ const resolved = new URL(filename, config.outDir);
303
+ if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) logger.error({
304
+ group: "plugin",
305
+ message: `Can’t overwrite file "${filename}"`,
306
+ label: plugin.name
307
+ });
308
+ result.outputFiles.push({
309
+ filename,
310
+ contents,
311
+ plugin: plugin.name,
312
+ time: performance.now() - pluginBuildStart
313
+ });
314
+ }
315
+ });
316
+ }
317
+ logger.debug({
318
+ group: "parser",
319
+ label: "build",
320
+ message: "build() step",
321
+ timing: performance.now() - startBuild
322
+ });
323
+ const startBuildEnd = performance.now();
324
+ for (const plugin of config.plugins) if (typeof plugin.buildEnd === "function") await plugin.buildEnd({ outputFiles: structuredClone(result.outputFiles) });
325
+ logger.debug({
326
+ group: "parser",
327
+ label: "build",
328
+ message: "buildEnd() step",
329
+ timing: performance.now() - startBuildEnd
330
+ });
331
+ return result;
332
+ }
333
+
334
+ //#endregion
335
+ //#region src/lint/plugin-core/lib/docs.ts
336
+ function docsLink(ruleName) {
337
+ return `https://terrazzo.app/docs/cli/lint#${ruleName.replaceAll("/", "")}`;
338
+ }
339
+
340
+ //#endregion
341
+ //#region src/lint/plugin-core/rules/a11y-min-contrast.ts
342
+ const A11Y_MIN_CONTRAST = "a11y/min-contrast";
343
+ const WCAG2_MIN_CONTRAST = {
344
+ AA: {
345
+ default: 4.5,
346
+ large: 3
347
+ },
348
+ AAA: {
349
+ default: 7,
350
+ large: 4.5
351
+ }
352
+ };
353
+ const ERROR_INSUFFICIENT_CONTRAST = "INSUFFICIENT_CONTRAST";
354
+ const rule$9 = {
355
+ meta: {
356
+ messages: { [ERROR_INSUFFICIENT_CONTRAST]: "Pair {{ index }} failed; expected {{ expected }}, got {{ actual }} ({{ level }})" },
357
+ docs: {
358
+ description: "Enforce colors meet minimum contrast checks for WCAG 2.",
359
+ url: docsLink(A11Y_MIN_CONTRAST)
360
+ }
361
+ },
362
+ defaultOptions: {
363
+ level: "AA",
364
+ pairs: []
365
+ },
366
+ create({ tokens, options, report }) {
367
+ for (let i = 0; i < options.pairs.length; i++) {
368
+ const { foreground, background, largeText } = options.pairs[i];
369
+ if (!tokens[foreground]) throw new Error(`Token ${foreground} does not exist`);
370
+ if (tokens[foreground].$type !== "color") throw new Error(`Token ${foreground} isn’t a color`);
371
+ if (!tokens[background]) throw new Error(`Token ${background} does not exist`);
372
+ if (tokens[background].$type !== "color") throw new Error(`Token ${background} isn’t a color`);
373
+ const a = tokenToCulori(tokens[foreground].$value);
374
+ const b = tokenToCulori(tokens[background].$value);
375
+ const contrast = wcagContrast(a, b);
376
+ const min = WCAG2_MIN_CONTRAST[options.level ?? "AA"][largeText ? "large" : "default"];
377
+ if (contrast < min) report({
378
+ messageId: ERROR_INSUFFICIENT_CONTRAST,
379
+ data: {
380
+ index: i + 1,
381
+ expected: min,
382
+ actual: Math.round(contrast * 100) / 100,
383
+ level: options.level
384
+ }
385
+ });
386
+ }
387
+ }
388
+ };
389
+ var a11y_min_contrast_default = rule$9;
390
+
391
+ //#endregion
392
+ //#region src/lint/plugin-core/rules/a11y-min-font-size.ts
393
+ const A11Y_MIN_FONT_SIZE = "a11y/min-font-size";
394
+ const ERROR_TOO_SMALL = "TOO_SMALL";
395
+ const rule$8 = {
396
+ meta: {
397
+ messages: { [ERROR_TOO_SMALL]: "{{ id }} font size too small. Expected minimum of {{ min }}" },
398
+ docs: {
399
+ description: "Enforce font sizes are no smaller than the given value.",
400
+ url: docsLink(A11Y_MIN_FONT_SIZE)
401
+ }
402
+ },
403
+ defaultOptions: {},
404
+ create({ tokens, options, report }) {
405
+ if (!options.minSizePx && !options.minSizeRem) throw new Error("Must specify at least one of minSizePx or minSizeRem");
406
+ for (const t of Object.values(tokens)) {
407
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
408
+ if (t.aliasOf) continue;
409
+ if (t.$type === "typography" && "fontSize" in t.$value) {
410
+ const fontSize = t.$value.fontSize;
411
+ if (fontSize.unit === "px" && options.minSizePx && fontSize.value < options.minSizePx || fontSize.unit === "rem" && options.minSizeRem && fontSize.value < options.minSizeRem) report({
412
+ messageId: ERROR_TOO_SMALL,
413
+ data: {
414
+ id: t.id,
415
+ min: options.minSizePx ? `${options.minSizePx}px` : `${options.minSizeRem}rem`
416
+ }
417
+ });
418
+ }
419
+ }
420
+ }
421
+ };
422
+ var a11y_min_font_size_default = rule$8;
423
+
424
+ //#endregion
425
+ //#region src/lint/plugin-core/rules/colorspace.ts
426
+ const COLORSPACE = "core/colorspace";
427
+ const ERROR_COLOR$1 = "COLOR";
428
+ const ERROR_BORDER$1 = "BORDER";
429
+ const ERROR_GRADIENT$1 = "GRADIENT";
430
+ const ERROR_SHADOW$1 = "SHADOW";
431
+ const rule$7 = {
432
+ meta: {
433
+ messages: {
434
+ [ERROR_COLOR$1]: "Color {{ id }} not in colorspace {{ colorSpace }}",
435
+ [ERROR_BORDER$1]: "Border {{ id }} not in colorspace {{ colorSpace }}",
436
+ [ERROR_GRADIENT$1]: "Gradient {{ id }} not in colorspace {{ colorSpace }}",
437
+ [ERROR_SHADOW$1]: "Shadow {{ id }} not in colorspace {{ colorSpace }}"
438
+ },
439
+ docs: {
440
+ description: "Enforce that all colors are in a specific colorspace.",
441
+ url: docsLink(COLORSPACE)
442
+ }
443
+ },
444
+ defaultOptions: { colorSpace: "srgb" },
445
+ create({ tokens, options, report }) {
446
+ if (!options.colorSpace) return;
447
+ for (const t of Object.values(tokens)) {
448
+ if (options?.ignore && isTokenMatch(t.id, options.ignore)) continue;
449
+ if (t.aliasOf) continue;
450
+ switch (t.$type) {
451
+ case "color": {
452
+ if (t.$value.colorSpace !== options.colorSpace) report({
453
+ messageId: ERROR_COLOR$1,
454
+ data: {
455
+ id: t.id,
456
+ colorSpace: options.colorSpace
457
+ },
458
+ node: t.source.node
459
+ });
460
+ break;
461
+ }
462
+ case "border": {
463
+ if (!t.partialAliasOf?.color && t.$value.color.colorSpace !== options.colorSpace) report({
464
+ messageId: ERROR_BORDER$1,
465
+ data: {
466
+ id: t.id,
467
+ colorSpace: options.colorSpace
468
+ },
469
+ node: t.source.node
470
+ });
471
+ break;
472
+ }
473
+ case "gradient": {
474
+ for (let stopI = 0; stopI < t.$value.length; stopI++) if (!t.partialAliasOf?.[stopI]?.color && t.$value[stopI].color.colorSpace !== options.colorSpace) report({
475
+ messageId: ERROR_GRADIENT$1,
476
+ data: {
477
+ id: t.id,
478
+ colorSpace: options.colorSpace
479
+ },
480
+ node: t.source.node
481
+ });
482
+ break;
483
+ }
484
+ case "shadow": {
485
+ for (let shadowI = 0; shadowI < t.$value.length; shadowI++) if (!t.partialAliasOf?.[shadowI]?.color && t.$value[shadowI].color.colorSpace !== options.colorSpace) report({
486
+ messageId: ERROR_SHADOW$1,
487
+ data: {
488
+ id: t.id,
489
+ colorSpace: options.colorSpace
490
+ },
491
+ node: t.source.node
492
+ });
493
+ break;
494
+ }
495
+ }
496
+ }
497
+ }
498
+ };
499
+ var colorspace_default = rule$7;
500
+
501
+ //#endregion
502
+ //#region ../../node_modules/.pnpm/scule@1.3.0/node_modules/scule/dist/index.mjs
503
+ const NUMBER_CHAR_RE = /\d/;
504
+ const STR_SPLITTERS = [
505
+ "-",
506
+ "_",
507
+ "/",
508
+ "."
509
+ ];
510
+ function isUppercase(char = "") {
511
+ if (NUMBER_CHAR_RE.test(char)) return void 0;
512
+ return char !== char.toLowerCase();
513
+ }
514
+ function splitByCase(str, separators) {
515
+ const splitters = separators ?? STR_SPLITTERS;
516
+ const parts = [];
517
+ if (!str || typeof str !== "string") return parts;
518
+ let buff = "";
519
+ let previousUpper;
520
+ let previousSplitter;
521
+ for (const char of str) {
522
+ const isSplitter = splitters.includes(char);
523
+ if (isSplitter === true) {
524
+ parts.push(buff);
525
+ buff = "";
526
+ previousUpper = void 0;
527
+ continue;
528
+ }
529
+ const isUpper = isUppercase(char);
530
+ if (previousSplitter === false) {
531
+ if (previousUpper === false && isUpper === true) {
532
+ parts.push(buff);
533
+ buff = char;
534
+ previousUpper = isUpper;
535
+ continue;
536
+ }
537
+ if (previousUpper === true && isUpper === false && buff.length > 1) {
538
+ const lastChar = buff.at(-1);
539
+ parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
540
+ buff = lastChar + char;
541
+ previousUpper = isUpper;
542
+ continue;
543
+ }
544
+ }
545
+ buff += char;
546
+ previousUpper = isUpper;
547
+ previousSplitter = isSplitter;
548
+ }
549
+ parts.push(buff);
550
+ return parts;
551
+ }
552
+ function upperFirst(str) {
553
+ return str ? str[0].toUpperCase() + str.slice(1) : "";
554
+ }
555
+ function lowerFirst(str) {
556
+ return str ? str[0].toLowerCase() + str.slice(1) : "";
557
+ }
558
+ function pascalCase(str, opts) {
559
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
560
+ }
561
+ function camelCase(str, opts) {
562
+ return lowerFirst(pascalCase(str || "", opts));
563
+ }
564
+ function kebabCase(str, joiner) {
565
+ return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
566
+ }
567
+ function snakeCase(str) {
568
+ return kebabCase(str || "", "_");
569
+ }
570
+
571
+ //#endregion
572
+ //#region src/lint/plugin-core/rules/consistent-naming.ts
573
+ const CONSISTENT_NAMING = "core/consistent-naming";
574
+ const ERROR_WRONG_FORMAT = "ERROR_WRONG_FORMAT";
575
+ const rule$6 = {
576
+ meta: {
577
+ messages: { [ERROR_WRONG_FORMAT]: "{{ id }} doesn’t match format {{ format }}" },
578
+ docs: {
579
+ description: "Enforce consistent naming for tokens.",
580
+ url: docsLink(CONSISTENT_NAMING)
581
+ }
582
+ },
583
+ defaultOptions: { format: "kebab-case" },
584
+ create({ tokens, options, report }) {
585
+ const basicFormatter = {
586
+ "kebab-case": kebabCase,
587
+ camelCase,
588
+ PascalCase: pascalCase,
589
+ snake_case: snakeCase,
590
+ SCREAMING_SNAKE_CASE: (name) => snakeCase(name).toLocaleUpperCase()
591
+ }[String(options.format)];
592
+ for (const t of Object.values(tokens)) if (basicFormatter) {
593
+ const parts = t.id.split(".");
594
+ if (!parts.every((part) => basicFormatter(part) === part)) report({
595
+ messageId: ERROR_WRONG_FORMAT,
596
+ data: {
597
+ id: t.id,
598
+ format: options.format
599
+ },
600
+ node: t.source.node
601
+ });
602
+ } else if (typeof options.format === "function") {
603
+ const result = options.format(t.id);
604
+ if (result) report({
605
+ messageId: ERROR_WRONG_FORMAT,
606
+ data: {
607
+ id: t.id,
608
+ format: "(custom)"
609
+ },
610
+ node: t.source.node
611
+ });
612
+ }
613
+ }
614
+ };
615
+ var consistent_naming_default = rule$6;
616
+
617
+ //#endregion
618
+ //#region src/lint/plugin-core/rules/descriptions.ts
619
+ const DESCRIPTIONS = "core/descriptions";
620
+ const ERROR_MISSING_DESCRIPTION = "MISSING_DESCRIPTION";
621
+ const rule$5 = {
622
+ meta: {
623
+ messages: { [ERROR_MISSING_DESCRIPTION]: "{{ id }} missing description" },
624
+ docs: {
625
+ description: "Enforce tokens have descriptions.",
626
+ url: docsLink(DESCRIPTIONS)
627
+ }
628
+ },
629
+ defaultOptions: {},
630
+ create({ tokens, options, report }) {
631
+ for (const t of Object.values(tokens)) {
632
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
633
+ if (!t.$description) report({
634
+ messageId: ERROR_MISSING_DESCRIPTION,
635
+ data: { id: t.id },
636
+ node: t.source.node
637
+ });
638
+ }
639
+ }
640
+ };
641
+ var descriptions_default = rule$5;
642
+
643
+ //#endregion
644
+ //#region src/lint/plugin-core/rules/duplicate-values.ts
645
+ const DUPLICATE_VALUES = "core/duplicate-values";
646
+ const ERROR_DUPLICATE_VALUE = "ERROR_DUPLICATE_VALUE";
647
+ const rule$4 = {
648
+ meta: {
649
+ messages: { [ERROR_DUPLICATE_VALUE]: "{{ id }} declared a duplicate value" },
650
+ docs: {
651
+ description: "Enforce tokens can’t redeclare the same value (excludes aliases).",
652
+ url: docsLink(DUPLICATE_VALUES)
653
+ }
654
+ },
655
+ defaultOptions: {},
656
+ create({ report, tokens, options }) {
657
+ const values = {};
658
+ for (const t of Object.values(tokens)) {
659
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
660
+ if (!values[t.$type]) values[t.$type] = /* @__PURE__ */ new Set();
661
+ if (t.$type === "boolean" || t.$type === "duration" || t.$type === "fontWeight" || t.$type === "link" || t.$type === "number" || t.$type === "string") {
662
+ if (typeof t.aliasOf === "string" && isAlias(t.aliasOf)) continue;
663
+ if (values[t.$type]?.has(t.$value)) report({
664
+ messageId: ERROR_DUPLICATE_VALUE,
665
+ data: { id: t.id },
666
+ node: t.source.node
667
+ });
668
+ values[t.$type]?.add(t.$value);
669
+ } else {
670
+ for (const v of values[t.$type].values() ?? []) if (JSON.stringify(t.$value) === JSON.stringify(v)) {
671
+ report({
672
+ messageId: ERROR_DUPLICATE_VALUE,
673
+ data: { id: t.id },
674
+ node: t.source.node
675
+ });
676
+ break;
677
+ }
678
+ values[t.$type].add(t.$value);
679
+ }
680
+ }
681
+ }
682
+ };
683
+ var duplicate_values_default = rule$4;
684
+
685
+ //#endregion
686
+ //#region src/lint/plugin-core/rules/max-gamut.ts
687
+ const MAX_GAMUT = "core/max-gamut";
688
+ const TOLERANCE = 1e-6;
689
+ /** is a Culori-parseable color within the specified gamut? */
690
+ function isWithinGamut(color, gamut) {
691
+ const parsed = tokenToCulori(color);
692
+ if (!parsed) return false;
693
+ if ([
694
+ "rgb",
695
+ "hsl",
696
+ "hsv",
697
+ "hwb"
698
+ ].includes(parsed.mode)) return true;
699
+ const clamped = clampChroma(parsed, parsed.mode, gamut === "srgb" ? "rgb" : gamut);
700
+ return isWithinThreshold(parsed, clamped);
701
+ }
702
+ /** is Color A close enough to Color B? */
703
+ function isWithinThreshold(a, b, tolerance = TOLERANCE) {
704
+ for (const k in a) {
705
+ if (k === "mode" || k === "alpha") continue;
706
+ if (!(k in b)) throw new Error(`Can’t compare ${a.mode} to ${b.mode}`);
707
+ if (Math.abs(a[k] - b[k]) > tolerance) return false;
708
+ }
709
+ return true;
710
+ }
711
+ const ERROR_COLOR = "COLOR";
712
+ const ERROR_BORDER = "BORDER";
713
+ const ERROR_GRADIENT = "GRADIENT";
714
+ const ERROR_SHADOW = "SHADOW";
715
+ const rule$3 = {
716
+ meta: {
717
+ messages: {
718
+ [ERROR_COLOR]: "Color {{ id }} is outside {{ gamut }} gamut",
719
+ [ERROR_BORDER]: "Border {{ id }} is outside {{ gamut }} gamut",
720
+ [ERROR_GRADIENT]: "Gradient {{ id }} is outside {{ gamut }} gamut",
721
+ [ERROR_SHADOW]: "Shadow {{ id }} is outside {{ gamut }} gamut"
722
+ },
723
+ docs: {
724
+ description: "Enforce colors are within the specified gamut.",
725
+ url: docsLink(MAX_GAMUT)
726
+ }
727
+ },
728
+ defaultOptions: { gamut: "rec2020" },
729
+ create({ tokens, options, report }) {
730
+ if (!options?.gamut) return;
731
+ if (options.gamut !== "srgb" && options.gamut !== "p3" && options.gamut !== "rec2020") throw new Error(`Unknown gamut "${options.gamut}". Options are "srgb", "p3", or "rec2020"`);
732
+ for (const t of Object.values(tokens)) {
733
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
734
+ if (t.aliasOf) continue;
735
+ switch (t.$type) {
736
+ case "color": {
737
+ if (!isWithinGamut(t.$value, options.gamut)) report({
738
+ messageId: ERROR_COLOR,
739
+ data: {
740
+ id: t.id,
741
+ gamut: options.gamut
742
+ },
743
+ node: t.source.node
744
+ });
745
+ break;
746
+ }
747
+ case "border": {
748
+ if (!t.partialAliasOf?.color && !isWithinGamut(t.$value.color, options.gamut)) report({
749
+ messageId: ERROR_BORDER,
750
+ data: {
751
+ id: t.id,
752
+ gamut: options.gamut
753
+ },
754
+ node: t.source.node
755
+ });
756
+ break;
757
+ }
758
+ case "gradient": {
759
+ for (let stopI = 0; stopI < t.$value.length; stopI++) if (!t.partialAliasOf?.[stopI]?.color && !isWithinGamut(t.$value[stopI].color, options.gamut)) report({
760
+ messageId: ERROR_GRADIENT,
761
+ data: {
762
+ id: t.id,
763
+ gamut: options.gamut
764
+ },
765
+ node: t.source.node
766
+ });
767
+ break;
768
+ }
769
+ case "shadow": {
770
+ for (let shadowI = 0; shadowI < t.$value.length; shadowI++) if (!t.partialAliasOf?.[shadowI]?.color && !isWithinGamut(t.$value[shadowI].color, options.gamut)) report({
771
+ messageId: ERROR_SHADOW,
772
+ data: {
773
+ id: t.id,
774
+ gamut: options.gamut
775
+ },
776
+ node: t.source.node
777
+ });
778
+ break;
779
+ }
780
+ }
781
+ }
782
+ }
783
+ };
784
+ var max_gamut_default = rule$3;
785
+
786
+ //#endregion
787
+ //#region src/lint/plugin-core/rules/required-children.ts
788
+ const REQUIRED_CHILDREN = "core/required-children";
789
+ const ERROR_EMPTY_MATCH = "EMPTY_MATCH";
790
+ const ERROR_MISSING_REQUIRED_TOKENS = "MISSING_REQUIRED_TOKENS";
791
+ const ERROR_MISSING_REQUIRED_GROUP = "MISSING_REQUIRED_GROUP";
792
+ const rule$2 = {
793
+ meta: {
794
+ messages: {
795
+ [ERROR_EMPTY_MATCH]: "No tokens matched {{ matcher }}",
796
+ [ERROR_MISSING_REQUIRED_TOKENS]: "Match {{ index }}: some groups missing required token \"{{ token }}\"",
797
+ [ERROR_MISSING_REQUIRED_GROUP]: "Match {{ index }}: some tokens missing required group \"{{ group }}\""
798
+ },
799
+ docs: {
800
+ description: "Enforce token groups have specific children, whether tokens and/or groups.",
801
+ url: docsLink(REQUIRED_CHILDREN)
802
+ }
803
+ },
804
+ defaultOptions: { matches: [] },
805
+ create({ tokens, options, report }) {
806
+ if (!options.matches?.length) throw new Error("Invalid config. Missing `matches: […]`");
807
+ for (let matchI = 0; matchI < options.matches.length; matchI++) {
808
+ const { match, requiredTokens, requiredGroups } = options.matches[matchI];
809
+ if (!match.length) throw new Error(`Match ${matchI}: must declare \`match: […]\``);
810
+ if (!requiredTokens?.length && !requiredGroups?.length) throw new Error(`Match ${matchI}: must declare either \`requiredTokens: […]\` or \`requiredGroups: […]\``);
811
+ const matchGroups = [];
812
+ const matchTokens = [];
813
+ let tokensMatched = false;
814
+ for (const t of Object.values(tokens)) {
815
+ if (!isTokenMatch(t.id, match)) continue;
816
+ tokensMatched = true;
817
+ const groups = t.id.split(".");
818
+ matchTokens.push(groups.pop());
819
+ matchGroups.push(...groups);
820
+ }
821
+ if (!tokensMatched) {
822
+ report({
823
+ messageId: ERROR_EMPTY_MATCH,
824
+ data: { matcher: JSON.stringify(match) }
825
+ });
826
+ continue;
827
+ }
828
+ if (requiredTokens) {
829
+ for (const id of requiredTokens) if (!matchTokens.includes(id)) report({
830
+ messageId: ERROR_MISSING_REQUIRED_TOKENS,
831
+ data: {
832
+ index: matchI,
833
+ token: id
834
+ }
835
+ });
836
+ }
837
+ if (requiredGroups) {
838
+ for (const groupName of requiredGroups) if (!matchGroups.includes(groupName)) report({
839
+ messageId: ERROR_MISSING_REQUIRED_GROUP,
840
+ data: {
841
+ index: matchI,
842
+ group: groupName
843
+ }
844
+ });
845
+ }
846
+ }
847
+ }
848
+ };
849
+ var required_children_default = rule$2;
850
+
851
+ //#endregion
852
+ //#region src/lint/plugin-core/rules/required-modes.ts
853
+ const REQUIRED_MODES = "core/required-modes";
854
+ const rule$1 = {
855
+ meta: { docs: {
856
+ description: "Enforce certain tokens have specific modes.",
857
+ url: docsLink(REQUIRED_MODES)
858
+ } },
859
+ defaultOptions: { matches: [] },
860
+ create({ tokens, options, report }) {
861
+ if (!options?.matches?.length) throw new Error("Invalid config. Missing `matches: […]`");
862
+ for (let matchI = 0; matchI < options.matches.length; matchI++) {
863
+ const { match, modes } = options.matches[matchI];
864
+ if (!match.length) throw new Error(`Match ${matchI}: must declare \`match: […]\``);
865
+ if (!modes?.length) throw new Error(`Match ${matchI}: must declare \`modes: […]\``);
866
+ let tokensMatched = false;
867
+ for (const t of Object.values(tokens)) {
868
+ if (!isTokenMatch(t.id, match)) continue;
869
+ tokensMatched = true;
870
+ for (const mode of modes) if (!t.mode?.[mode]) report({
871
+ message: `Token ${t.id}: missing required mode "${mode}"`,
872
+ node: t.source.node
873
+ });
874
+ if (!tokensMatched) report({
875
+ message: `Match "${matchI}": no tokens matched ${JSON.stringify(match)}`,
876
+ node: t.source.node
877
+ });
878
+ }
879
+ }
880
+ }
881
+ };
882
+ var required_modes_default = rule$1;
883
+
884
+ //#endregion
885
+ //#region src/lint/plugin-core/rules/required-typography-properties.ts
886
+ const REQUIRED_TYPOGRAPHY_PROPERTIES = "core/required-typography-properties";
887
+ const rule = {
888
+ meta: { docs: {
889
+ description: "Enforce typography tokens have required properties.",
890
+ url: docsLink(REQUIRED_TYPOGRAPHY_PROPERTIES)
891
+ } },
892
+ defaultOptions: { properties: [] },
893
+ create({ tokens, options, report }) {
894
+ if (!options) return;
895
+ if (!options.properties.length) throw new Error(`"properties" can’t be empty`);
896
+ for (const t of Object.values(tokens)) {
897
+ if (options.ignore && isTokenMatch(t.id, options.ignore)) continue;
898
+ if (t.$type !== "typography") continue;
899
+ if (t.aliasOf) continue;
900
+ for (const p of options.properties) if (!t.partialAliasOf?.[p] && !(p in t.$value)) report({
901
+ message: `${t.id} missing required typographic property "${p}"`,
902
+ node: t.source.node
903
+ });
904
+ }
905
+ }
906
+ };
907
+ var required_typography_properties_default = rule;
908
+
909
+ //#endregion
910
+ //#region src/lint/plugin-core/index.ts
911
+ function coreLintPlugin() {
912
+ return {
913
+ name: "@terrazzo/plugin-lint-core",
914
+ lint() {
915
+ return {
916
+ [COLORSPACE]: colorspace_default,
917
+ [CONSISTENT_NAMING]: consistent_naming_default,
918
+ [DESCRIPTIONS]: descriptions_default,
919
+ [DUPLICATE_VALUES]: duplicate_values_default,
920
+ [MAX_GAMUT]: max_gamut_default,
921
+ [REQUIRED_CHILDREN]: required_children_default,
922
+ [REQUIRED_MODES]: required_modes_default,
923
+ [REQUIRED_TYPOGRAPHY_PROPERTIES]: required_typography_properties_default,
924
+ [A11Y_MIN_CONTRAST]: a11y_min_contrast_default,
925
+ [A11Y_MIN_FONT_SIZE]: a11y_min_font_size_default
926
+ };
927
+ }
928
+ };
929
+ }
930
+
931
+ //#endregion
932
+ //#region src/config.ts
933
+ const TRAILING_SLASH_RE = /\/*$/;
934
+ /**
935
+ * Validate and normalize a config
936
+ */
937
+ function defineConfig(rawConfig, { logger = new Logger(), cwd } = {}) {
938
+ const configStart = performance.now();
939
+ if (!cwd) logger.error({
940
+ group: "config",
941
+ label: "core",
942
+ message: "defineConfig() missing `cwd` for JS API"
943
+ });
944
+ const config = merge({}, rawConfig);
945
+ normalizeTokens({
946
+ rawConfig,
947
+ config,
948
+ logger,
949
+ cwd
950
+ });
951
+ normalizeOutDir({
952
+ config,
953
+ cwd,
954
+ logger
955
+ });
956
+ normalizePlugins({
957
+ config,
958
+ logger
959
+ });
960
+ normalizeLint({
961
+ config,
962
+ logger
963
+ });
964
+ normalizeIgnore({
965
+ config,
966
+ logger
967
+ });
968
+ for (const plugin of config.plugins) plugin.config?.({ ...config });
969
+ logger.debug({
970
+ group: "parser",
971
+ label: "config",
972
+ message: "Finish config validation",
973
+ timing: performance.now() - configStart
974
+ });
975
+ return config;
976
+ }
977
+ /** Normalize config.tokens */
978
+ function normalizeTokens({ rawConfig, config, logger, cwd }) {
979
+ if (rawConfig.tokens === void 0) config.tokens = ["./tokens.json"];
980
+ else if (typeof rawConfig.tokens === "string") config.tokens = [rawConfig.tokens];
981
+ else if (Array.isArray(rawConfig.tokens)) {
982
+ config.tokens = [];
983
+ for (const file of rawConfig.tokens) if (typeof file === "string" || file instanceof URL) config.tokens.push(file);
984
+ else logger.error({
985
+ group: "config",
986
+ label: "tokens",
987
+ message: `Expected array of strings, encountered ${JSON.stringify(file)}`
988
+ });
989
+ } else logger.error({
990
+ group: "config",
991
+ label: "tokens",
992
+ message: `Expected string or array of strings, received ${typeof rawConfig.tokens}`
993
+ });
994
+ for (let i = 0; i < config.tokens.length; i++) {
995
+ const filepath = config.tokens[i];
996
+ if (filepath instanceof URL) continue;
997
+ try {
998
+ config.tokens[i] = new URL(filepath, cwd);
999
+ } catch (err) {
1000
+ logger.error({
1001
+ group: "config",
1002
+ label: "tokens",
1003
+ message: `Invalid URL ${filepath}`
1004
+ });
1005
+ }
1006
+ }
1007
+ }
1008
+ /** Normalize config.outDir */
1009
+ function normalizeOutDir({ config, cwd, logger }) {
1010
+ if (config.outDir instanceof URL) {} else if (typeof config.outDir === "undefined") config.outDir = new URL("./tokens/", cwd);
1011
+ else if (typeof config.outDir !== "string") logger.error({
1012
+ group: "config",
1013
+ label: "outDir",
1014
+ message: `Expected string, received ${JSON.stringify(config.outDir)}`
1015
+ });
1016
+ else {
1017
+ config.outDir = new URL(config.outDir, cwd);
1018
+ config.outDir = new URL(config.outDir.href.replace(TRAILING_SLASH_RE, "/"));
1019
+ }
1020
+ }
1021
+ /** Normalize config.plugins */
1022
+ function normalizePlugins({ config, logger }) {
1023
+ if (typeof config.plugins === "undefined") config.plugins = [];
1024
+ if (!Array.isArray(config.plugins)) logger.error({
1025
+ group: "config",
1026
+ label: "plugins",
1027
+ message: `Expected array of plugins, received ${JSON.stringify(config.plugins)}`
1028
+ });
1029
+ config.plugins.push(coreLintPlugin());
1030
+ for (let n = 0; n < config.plugins.length; n++) {
1031
+ const plugin = config.plugins[n];
1032
+ if (typeof plugin !== "object") logger.error({
1033
+ group: "config",
1034
+ label: `plugin[${n}]`,
1035
+ message: `Expected output plugin, received ${JSON.stringify(plugin)}`
1036
+ });
1037
+ else if (!plugin.name) logger.error({
1038
+ group: "config",
1039
+ label: `plugin[${n}]`,
1040
+ message: `Missing "name"`
1041
+ });
1042
+ }
1043
+ config.plugins.sort((a, b) => {
1044
+ if (a.enforce === "pre" && b.enforce !== "pre") return -1;
1045
+ else if (a.enforce === "post" && b.enforce !== "post") return 1;
1046
+ return 0;
1047
+ });
1048
+ }
1049
+ function normalizeLint({ config, logger }) {
1050
+ if (config.lint !== void 0) {
1051
+ if (config.lint === null || typeof config.lint !== "object" || Array.isArray(config.lint)) logger.error({
1052
+ group: "config",
1053
+ label: "lint",
1054
+ message: "Must be an object"
1055
+ });
1056
+ if (!config.lint.build) config.lint.build = { enabled: true };
1057
+ if (config.lint.build.enabled !== void 0) {
1058
+ if (typeof config.lint.build.enabled !== "boolean") logger.error({
1059
+ group: "config",
1060
+ label: "lint › build › enabled",
1061
+ message: `Expected boolean, received ${JSON.stringify(config.lint.build)}`
1062
+ });
1063
+ } else config.lint.build.enabled = true;
1064
+ if (config.lint.rules === void 0) config.lint.rules = {};
1065
+ else {
1066
+ if (config.lint.rules === null || typeof config.lint.rules !== "object" || Array.isArray(config.lint.rules)) {
1067
+ logger.error({
1068
+ group: "config",
1069
+ label: "lint › rules",
1070
+ message: `Expected object, received ${JSON.stringify(config.lint.rules)}`
1071
+ });
1072
+ return;
1073
+ }
1074
+ const allRules = /* @__PURE__ */ new Map();
1075
+ for (const plugin of config.plugins) {
1076
+ if (typeof plugin.lint !== "function") continue;
1077
+ const pluginRules = plugin.lint();
1078
+ if (!pluginRules || Array.isArray(pluginRules) || typeof pluginRules !== "object") {
1079
+ logger.error({
1080
+ group: "config",
1081
+ label: `plugin › ${plugin.name}`,
1082
+ message: `Expected object for lint() received ${JSON.stringify(pluginRules)}`
1083
+ });
1084
+ continue;
1085
+ }
1086
+ for (const rule$10 of Object.keys(pluginRules)) {
1087
+ if (allRules.get(rule$10) && allRules.get(rule$10) !== plugin.name) logger.error({
1088
+ group: "config",
1089
+ label: `plugin › ${plugin.name}`,
1090
+ message: `Duplicate rule ${rule$10} already registered by plugin ${allRules.get(rule$10)}`
1091
+ });
1092
+ allRules.set(rule$10, plugin.name);
1093
+ }
1094
+ }
1095
+ for (const id of Object.keys(config.lint.rules)) {
1096
+ if (!allRules.has(id)) logger.error({
1097
+ group: "config",
1098
+ label: `lint › rule › ${id}`,
1099
+ message: "Unknown rule. Is the plugin installed?"
1100
+ });
1101
+ const value = config.lint.rules[id];
1102
+ let severity = "off";
1103
+ let options;
1104
+ if (typeof value === "number" || typeof value === "string") severity = value;
1105
+ else if (Array.isArray(value)) {
1106
+ severity = value[0];
1107
+ options = value[1];
1108
+ } else if (value !== void 0) logger.error({
1109
+ group: "config",
1110
+ label: `lint › rule › ${id}`,
1111
+ message: `Invalid eyntax. Expected \`string | number | Array\`, received ${JSON.stringify(value)}}`
1112
+ });
1113
+ config.lint.rules[id] = [severity, options];
1114
+ if (typeof severity === "number") {
1115
+ if (severity !== 0 && severity !== 1 && severity !== 2) logger.error({
1116
+ group: "config",
1117
+ label: `lint › rule › ${id}`,
1118
+ message: `Invalid number ${severity}. Specify 0 (off), 1 (warn), or 2 (error).`
1119
+ });
1120
+ config.lint.rules[id][0] = [
1121
+ "off",
1122
+ "warn",
1123
+ "error"
1124
+ ][severity];
1125
+ } else if (typeof severity === "string") {
1126
+ if (severity !== "off" && severity !== "warn" && severity !== "error") logger.error({
1127
+ group: "config",
1128
+ label: `lint › rule › ${id}`,
1129
+ message: `Invalid string ${JSON.stringify(severity)}. Specify "off", "warn", or "error".`
1130
+ });
1131
+ } else if (value !== null) logger.error({
1132
+ group: "config",
1133
+ label: `lint › rule › ${id}`,
1134
+ message: `Expected string or number, received ${JSON.stringify(value)}`
1135
+ });
1136
+ }
1137
+ }
1138
+ } else config.lint = {
1139
+ build: { enabled: true },
1140
+ rules: {}
1141
+ };
1142
+ }
1143
+ function normalizeIgnore({ config, logger }) {
1144
+ if (!config.ignore) config.ignore = {};
1145
+ config.ignore.tokens ??= [];
1146
+ config.ignore.deprecated ??= false;
1147
+ if (!Array.isArray(config.ignore.tokens) || config.ignore.tokens.some((x) => typeof x !== "string")) logger.error({
1148
+ group: "config",
1149
+ label: "ignore › tokens",
1150
+ message: `Expected array of strings, received ${JSON.stringify(config.ignore.tokens)}`
1151
+ });
1152
+ if (typeof config.ignore.deprecated !== "boolean") logger.error({
1153
+ group: "config",
1154
+ label: "ignore › deprecated",
1155
+ message: `Expected boolean, received ${JSON.stringify(config.ignore.deprecated)}`
1156
+ });
1157
+ }
1158
+ /** Merge configs */
1159
+ function mergeConfigs(a, b) {
1160
+ return merge(a, b);
1161
+ }
1162
+
1163
+ //#endregion
1164
+ //#region src/lint/index.ts
1165
+ const listFormat$1 = new Intl.ListFormat("en-us");
1166
+ async function lintRunner({ tokens, filename, config = {}, src, logger }) {
1167
+ const { plugins = [], lint } = config;
1168
+ const unusedLintRules = Object.keys(lint?.rules ?? {});
1169
+ for (const plugin of plugins) if (typeof plugin.lint === "function") {
1170
+ const s = performance.now();
1171
+ const linter = plugin.lint();
1172
+ const errors = [];
1173
+ const warnings = [];
1174
+ await Promise.all(Object.entries(linter).map(async ([id, rule$10]) => {
1175
+ if (!(id in lint.rules) || lint.rules[id] === null) return;
1176
+ const [severity, options] = lint.rules[id];
1177
+ if (severity === "off") return;
1178
+ await rule$10.create({
1179
+ id,
1180
+ report(descriptor) {
1181
+ let message = "";
1182
+ if (!descriptor.message && !descriptor.messageId) logger.error({
1183
+ group: "lint",
1184
+ label: `${plugin.name} › lint › ${id}`,
1185
+ message: "Unable to report error: missing message or messageId"
1186
+ });
1187
+ if (descriptor.message) message = descriptor.message;
1188
+ else {
1189
+ if (!(descriptor.messageId in (rule$10.meta?.messages ?? {}))) logger.error({
1190
+ group: "lint",
1191
+ label: `${plugin.name} › lint › ${id}`,
1192
+ message: `messageId "${descriptor.messageId}" does not exist`
1193
+ });
1194
+ message = rule$10.meta?.messages?.[descriptor.messageId] ?? "";
1195
+ }
1196
+ if (descriptor.data && typeof descriptor.data === "object") for (const [k, v] of Object.entries(descriptor.data)) {
1197
+ const formatted = [
1198
+ "string",
1199
+ "number",
1200
+ "boolean"
1201
+ ].includes(typeof v) ? String(v) : JSON.stringify(v);
1202
+ message = message.replace(/{{[^}]+}}/g, (inner) => {
1203
+ const key = inner.substring(2, inner.length - 2).trim();
1204
+ return key === k ? formatted : inner;
1205
+ });
1206
+ }
1207
+ (severity === "error" ? errors : warnings).push({
1208
+ group: "lint",
1209
+ label: id,
1210
+ message,
1211
+ filename,
1212
+ node: descriptor.node,
1213
+ src: descriptor.source?.src
1214
+ });
1215
+ },
1216
+ tokens,
1217
+ filename,
1218
+ src,
1219
+ options: merge(rule$10.meta?.defaultOptions ?? [], rule$10.defaultOptions ?? [], options)
1220
+ });
1221
+ const unusedLintRuleI = unusedLintRules.indexOf(id);
1222
+ if (unusedLintRuleI !== -1) unusedLintRules.splice(unusedLintRuleI, 1);
1223
+ }));
1224
+ for (const error of errors) logger.error({
1225
+ ...error,
1226
+ continueOnError: true
1227
+ });
1228
+ for (const warning of warnings) logger.warn(warning);
1229
+ logger.debug({
1230
+ group: "lint",
1231
+ label: plugin.name,
1232
+ message: "Finished",
1233
+ timing: performance.now() - s
1234
+ });
1235
+ if (errors.length) {
1236
+ const counts = [pluralize(errors.length, "error", "errors")];
1237
+ if (warnings.length) counts.push(pluralize(warnings.length, "warning", "warnings"));
1238
+ logger.error({
1239
+ group: "lint",
1240
+ message: `Lint failed with ${listFormat$1.format(counts)}`,
1241
+ label: plugin.name,
1242
+ continueOnError: false
1243
+ });
1244
+ }
1245
+ }
1246
+ for (const unusedRule of unusedLintRules) logger.warn({
1247
+ group: "lint",
1248
+ label: "lint",
1249
+ message: `Unknown lint rule "${unusedRule}"`
1250
+ });
1251
+ }
1252
+
1253
+ //#endregion
1254
+ //#region ../../node_modules/.pnpm/@humanwhocodes+momoa@3.3.8/node_modules/@humanwhocodes/momoa/dist/momoa.js
1255
+ /**
1256
+ * @fileoverview Character codes.
1257
+ * @author Nicholas C. Zakas
1258
+ */
1259
+ const CHAR_0 = 48;
1260
+ const CHAR_1 = 49;
1261
+ const CHAR_9 = 57;
1262
+ const CHAR_BACKSLASH = 92;
1263
+ const CHAR_DOLLAR = 36;
1264
+ const CHAR_DOT = 46;
1265
+ const CHAR_DOUBLE_QUOTE = 34;
1266
+ const CHAR_LOWER_A = 97;
1267
+ const CHAR_LOWER_E = 101;
1268
+ const CHAR_LOWER_F = 102;
1269
+ const CHAR_LOWER_N = 110;
1270
+ const CHAR_LOWER_T = 116;
1271
+ const CHAR_LOWER_U = 117;
1272
+ const CHAR_LOWER_X = 120;
1273
+ const CHAR_LOWER_Z = 122;
1274
+ const CHAR_MINUS = 45;
1275
+ const CHAR_NEWLINE = 10;
1276
+ const CHAR_PLUS = 43;
1277
+ const CHAR_RETURN = 13;
1278
+ const CHAR_SINGLE_QUOTE = 39;
1279
+ const CHAR_SLASH = 47;
1280
+ const CHAR_SPACE = 32;
1281
+ const CHAR_TAB = 9;
1282
+ const CHAR_UNDERSCORE = 95;
1283
+ const CHAR_UPPER_A = 65;
1284
+ const CHAR_UPPER_E = 69;
1285
+ const CHAR_UPPER_F = 70;
1286
+ const CHAR_UPPER_N = 78;
1287
+ const CHAR_UPPER_X = 88;
1288
+ const CHAR_UPPER_Z = 90;
1289
+ const CHAR_LOWER_B = 98;
1290
+ const CHAR_LOWER_R = 114;
1291
+ const CHAR_LOWER_V = 118;
1292
+ const CHAR_LINE_SEPARATOR = 8232;
1293
+ const CHAR_PARAGRAPH_SEPARATOR = 8233;
1294
+ const CHAR_UPPER_I = 73;
1295
+ const CHAR_STAR = 42;
1296
+ const CHAR_VTAB = 11;
1297
+ const CHAR_FORM_FEED = 12;
1298
+ const CHAR_NBSP = 160;
1299
+ const CHAR_BOM = 65279;
1300
+ const CHAR_NON_BREAKING_SPACE = 160;
1301
+ const CHAR_EN_QUAD = 8192;
1302
+ const CHAR_EM_QUAD = 8193;
1303
+ const CHAR_EN_SPACE = 8194;
1304
+ const CHAR_EM_SPACE = 8195;
1305
+ const CHAR_THREE_PER_EM_SPACE = 8196;
1306
+ const CHAR_FOUR_PER_EM_SPACE = 8197;
1307
+ const CHAR_SIX_PER_EM_SPACE = 8198;
1308
+ const CHAR_FIGURE_SPACE = 8199;
1309
+ const CHAR_PUNCTUATION_SPACE = 8200;
1310
+ const CHAR_THIN_SPACE = 8201;
1311
+ const CHAR_HAIR_SPACE = 8202;
1312
+ const CHAR_NARROW_NO_BREAK_SPACE = 8239;
1313
+ const CHAR_MEDIUM_MATHEMATICAL_SPACE = 8287;
1314
+ const CHAR_IDEOGRAPHIC_SPACE = 12288;
1315
+ /**
1316
+ * @fileoverview JSON syntax helpers
1317
+ * @author Nicholas C. Zakas
1318
+ */
1319
+ /** @typedef {import("./typedefs.js").TokenType} TokenType */
1320
+ const LBRACKET = "[";
1321
+ const RBRACKET = "]";
1322
+ const LBRACE = "{";
1323
+ const RBRACE = "}";
1324
+ const COLON = ":";
1325
+ const COMMA = ",";
1326
+ const TRUE = "true";
1327
+ const FALSE = "false";
1328
+ const NULL = "null";
1329
+ const NAN$1 = "NaN";
1330
+ const INFINITY$1 = "Infinity";
1331
+ const QUOTE = "\"";
1332
+ const escapeToChar = new Map([
1333
+ [CHAR_DOUBLE_QUOTE, QUOTE],
1334
+ [CHAR_BACKSLASH, "\\"],
1335
+ [CHAR_SLASH, "/"],
1336
+ [CHAR_LOWER_B, "\b"],
1337
+ [CHAR_LOWER_N, "\n"],
1338
+ [CHAR_LOWER_F, "\f"],
1339
+ [CHAR_LOWER_R, "\r"],
1340
+ [CHAR_LOWER_T, " "]
1341
+ ]);
1342
+ const json5EscapeToChar = new Map([
1343
+ ...escapeToChar,
1344
+ [CHAR_LOWER_V, "\v"],
1345
+ [CHAR_0, "\0"]
1346
+ ]);
1347
+ const charToEscape = new Map([
1348
+ [QUOTE, QUOTE],
1349
+ ["\\", "\\"],
1350
+ ["/", "/"],
1351
+ ["\b", "b"],
1352
+ ["\n", "n"],
1353
+ ["\f", "f"],
1354
+ ["\r", "r"],
1355
+ [" ", "t"]
1356
+ ]);
1357
+ const json5CharToEscape = new Map([
1358
+ ...charToEscape,
1359
+ ["\v", "v"],
1360
+ ["\0", "0"],
1361
+ ["\u2028", "u2028"],
1362
+ ["\u2029", "u2029"]
1363
+ ]);
1364
+ /** @type {Map<string,TokenType>} */
1365
+ const knownTokenTypes = new Map([
1366
+ [LBRACKET, "LBracket"],
1367
+ [RBRACKET, "RBracket"],
1368
+ [LBRACE, "LBrace"],
1369
+ [RBRACE, "RBrace"],
1370
+ [COLON, "Colon"],
1371
+ [COMMA, "Comma"],
1372
+ [TRUE, "Boolean"],
1373
+ [FALSE, "Boolean"],
1374
+ [NULL, "Null"]
1375
+ ]);
1376
+ /** @type {Map<string,TokenType>} */
1377
+ const knownJSON5TokenTypes = new Map([
1378
+ ...knownTokenTypes,
1379
+ [NAN$1, "Number"],
1380
+ [INFINITY$1, "Number"]
1381
+ ]);
1382
+ const json5LineTerminators = new Set([
1383
+ CHAR_NEWLINE,
1384
+ CHAR_RETURN,
1385
+ CHAR_LINE_SEPARATOR,
1386
+ CHAR_PARAGRAPH_SEPARATOR
1387
+ ]);
1388
+ /**
1389
+ * @fileoverview JSON tokenization/parsing errors
1390
+ * @author Nicholas C. Zakas
1391
+ */
1392
+ /** @typedef {import("./typedefs.js").Location} Location */
1393
+ /** @typedef {import("./typedefs.js").Token} Token */
1394
+ /**
1395
+ * Base class that attaches location to an error.
1396
+ */
1397
+ var ErrorWithLocation = class extends Error {
1398
+ /**
1399
+ * Creates a new instance.
1400
+ * @param {string} message The error message to report.
1401
+ * @param {Location} loc The location information for the error.
1402
+ */
1403
+ constructor(message, { line, column, offset }) {
1404
+ super(`${message} (${line}:${column})`);
1405
+ /**
1406
+ * The line on which the error occurred.
1407
+ * @type {number}
1408
+ */
1409
+ this.line = line;
1410
+ /**
1411
+ * The column on which the error occurred.
1412
+ * @type {number}
1413
+ */
1414
+ this.column = column;
1415
+ /**
1416
+ * The index into the string where the error occurred.
1417
+ * @type {number}
1418
+ */
1419
+ this.offset = offset;
1420
+ }
1421
+ };
1422
+ /**
1423
+ * Error thrown when an unexpected character is found during tokenizing.
1424
+ */
1425
+ var UnexpectedChar = class extends ErrorWithLocation {
1426
+ /**
1427
+ * Creates a new instance.
1428
+ * @param {number} unexpected The character that was found.
1429
+ * @param {Location} loc The location information for the found character.
1430
+ */
1431
+ constructor(unexpected, loc) {
1432
+ super(`Unexpected character '${String.fromCharCode(unexpected)}' found.`, loc);
1433
+ }
1434
+ };
1435
+ /**
1436
+ * Error thrown when an unexpected identifier is found during tokenizing.
1437
+ */
1438
+ var UnexpectedIdentifier = class extends ErrorWithLocation {
1439
+ /**
1440
+ * Creates a new instance.
1441
+ * @param {string} unexpected The character that was found.
1442
+ * @param {Location} loc The location information for the found character.
1443
+ */
1444
+ constructor(unexpected, loc) {
1445
+ super(`Unexpected identifier '${unexpected}' found.`, loc);
1446
+ }
1447
+ };
1448
+ /**
1449
+ * Error thrown when an unexpected token is found during parsing.
1450
+ */
1451
+ var UnexpectedToken = class extends ErrorWithLocation {
1452
+ /**
1453
+ * Creates a new instance.
1454
+ * @param {Token} token The token that was found.
1455
+ */
1456
+ constructor(token) {
1457
+ super(`Unexpected token ${token.type} found.`, token.loc.start);
1458
+ }
1459
+ };
1460
+ /**
1461
+ * Error thrown when the end of input is found where it isn't expected.
1462
+ */
1463
+ var UnexpectedEOF = class extends ErrorWithLocation {
1464
+ /**
1465
+ * Creates a new instance.
1466
+ * @param {Location} loc The location information for the found character.
1467
+ */
1468
+ constructor(loc) {
1469
+ super("Unexpected end of input found.", loc);
1470
+ }
1471
+ };
1472
+ 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]/;
1473
+ 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]/;
1474
+ /**
1475
+ * @fileoverview A charactor code reader.
1476
+ * @author Nicholas C. Zakas
1477
+ */
1478
+ const CHAR_CR = 13;
1479
+ const CHAR_LF = 10;
1480
+ /**
1481
+ * A reader that reads character codes from a string.
1482
+ */
1483
+ var CharCodeReader = class {
1484
+ /**
1485
+ * The text to read from.
1486
+ * @type {string}
1487
+ */
1488
+ #text = "";
1489
+ /**
1490
+ * The current line number.
1491
+ * @type {number}
1492
+ */
1493
+ #line = 1;
1494
+ /**
1495
+ * The current column number.
1496
+ * @type {number}
1497
+ */
1498
+ #column = 0;
1499
+ /**
1500
+ * The current offset in the text.
1501
+ * @type {number}
1502
+ */
1503
+ #offset = -1;
1504
+ /**
1505
+ * Whether the last character read was a new line.
1506
+ * @type {boolean}
1507
+ */
1508
+ #newLine = false;
1509
+ /**
1510
+ * The last character code read.
1511
+ * @type {number}
1512
+ */
1513
+ #last = -1;
1514
+ /**
1515
+ * Whether the reader has ended.
1516
+ * @type {boolean}
1517
+ */
1518
+ #ended = false;
1519
+ /**
1520
+ * Creates a new instance.
1521
+ * @param {string} text The text to read from
1522
+ */
1523
+ constructor(text) {
1524
+ this.#text = text;
1525
+ }
1526
+ /**
1527
+ * Ends the reader.
1528
+ * @returns {void}
1529
+ */
1530
+ #end() {
1531
+ if (this.#ended) return;
1532
+ this.#column++;
1533
+ this.#offset++;
1534
+ this.#last = -1;
1535
+ this.#ended = true;
1536
+ }
1537
+ /**
1538
+ * Returns the current position of the reader.
1539
+ * @returns {Location} An object with line, column, and offset properties.
1540
+ */
1541
+ locate() {
1542
+ return {
1543
+ line: this.#line,
1544
+ column: this.#column,
1545
+ offset: this.#offset
1546
+ };
1547
+ }
1548
+ /**
1549
+ * Reads the next character code in the text.
1550
+ * @returns {number} The next character code, or -1 if there are no more characters.
1551
+ */
1552
+ next() {
1553
+ if (this.#offset >= this.#text.length - 1) {
1554
+ this.#end();
1555
+ return -1;
1556
+ }
1557
+ this.#offset++;
1558
+ const charCode = this.#text.charCodeAt(this.#offset);
1559
+ if (this.#newLine) {
1560
+ this.#line++;
1561
+ this.#column = 1;
1562
+ this.#newLine = false;
1563
+ } else this.#column++;
1564
+ if (charCode === CHAR_CR) {
1565
+ this.#newLine = true;
1566
+ if (this.peek() === CHAR_LF) this.#offset++;
1567
+ } else if (charCode === CHAR_LF) this.#newLine = true;
1568
+ this.#last = charCode;
1569
+ return charCode;
1570
+ }
1571
+ /**
1572
+ * Peeks at the next character code in the text.
1573
+ * @returns {number} The next character code, or -1 if there are no more characters.
1574
+ */
1575
+ peek() {
1576
+ if (this.#offset === this.#text.length - 1) return -1;
1577
+ return this.#text.charCodeAt(this.#offset + 1);
1578
+ }
1579
+ /**
1580
+ * Determines if the next character code in the text matches a specific character code.
1581
+ * @param {(number) => boolean} fn A function to call on the next character.
1582
+ * @returns {boolean} True if the next character code matches, false if not.
1583
+ */
1584
+ match(fn) {
1585
+ if (fn(this.peek())) {
1586
+ this.next();
1587
+ return true;
1588
+ }
1589
+ return false;
1590
+ }
1591
+ /**
1592
+ * Returns the last character code read.
1593
+ * @returns {number} The last character code read.
1594
+ */
1595
+ current() {
1596
+ return this.#last;
1597
+ }
1598
+ };
1599
+ /**
1600
+ * @fileoverview JSON tokenizer
1601
+ * @author Nicholas C. Zakas
1602
+ */
1603
+ /** @typedef {import("./typedefs.js").Range} Range */
1604
+ /** @typedef {import("./typedefs.js").TokenizeOptions} TokenizeOptions */
1605
+ const INFINITY = "Infinity";
1606
+ const NAN = "NaN";
1607
+ const keywordStarts = new Set([
1608
+ CHAR_LOWER_T,
1609
+ CHAR_LOWER_F,
1610
+ CHAR_LOWER_N
1611
+ ]);
1612
+ const whitespace = new Set([
1613
+ CHAR_SPACE,
1614
+ CHAR_TAB,
1615
+ CHAR_NEWLINE,
1616
+ CHAR_RETURN
1617
+ ]);
1618
+ const json5Whitespace = new Set([
1619
+ ...whitespace,
1620
+ CHAR_VTAB,
1621
+ CHAR_FORM_FEED,
1622
+ CHAR_NBSP,
1623
+ CHAR_LINE_SEPARATOR,
1624
+ CHAR_PARAGRAPH_SEPARATOR,
1625
+ CHAR_BOM,
1626
+ CHAR_NON_BREAKING_SPACE,
1627
+ CHAR_EN_QUAD,
1628
+ CHAR_EM_QUAD,
1629
+ CHAR_EN_SPACE,
1630
+ CHAR_EM_SPACE,
1631
+ CHAR_THREE_PER_EM_SPACE,
1632
+ CHAR_FOUR_PER_EM_SPACE,
1633
+ CHAR_SIX_PER_EM_SPACE,
1634
+ CHAR_FIGURE_SPACE,
1635
+ CHAR_PUNCTUATION_SPACE,
1636
+ CHAR_THIN_SPACE,
1637
+ CHAR_HAIR_SPACE,
1638
+ CHAR_NARROW_NO_BREAK_SPACE,
1639
+ CHAR_MEDIUM_MATHEMATICAL_SPACE,
1640
+ CHAR_IDEOGRAPHIC_SPACE
1641
+ ]);
1642
+ /** @type {TokenizeOptions} */
1643
+ const DEFAULT_OPTIONS$1 = {
1644
+ mode: "json",
1645
+ ranges: false
1646
+ };
1647
+ const jsonKeywords = new Set([
1648
+ "true",
1649
+ "false",
1650
+ "null"
1651
+ ]);
1652
+ const tt = {
1653
+ EOF: 0,
1654
+ Number: 1,
1655
+ String: 2,
1656
+ Boolean: 3,
1657
+ Null: 4,
1658
+ NaN: 5,
1659
+ Infinity: 6,
1660
+ Identifier: 7,
1661
+ Colon: 20,
1662
+ LBrace: 21,
1663
+ RBrace: 22,
1664
+ LBracket: 23,
1665
+ RBracket: 24,
1666
+ Comma: 25,
1667
+ LineComment: 40,
1668
+ BlockComment: 41
1669
+ };
1670
+ /**
1671
+ * Determines if a given character is a decimal digit.
1672
+ * @param {number} c The character to check.
1673
+ * @returns {boolean} `true` if the character is a digit.
1674
+ */
1675
+ function isDigit(c) {
1676
+ return c >= CHAR_0 && c <= CHAR_9;
1677
+ }
1678
+ /**
1679
+ * Determines if a given character is a hexadecimal digit.
1680
+ * @param {number} c The character to check.
1681
+ * @returns {boolean} `true` if the character is a hexadecimal digit.
1682
+ */
1683
+ function isHexDigit(c) {
1684
+ return isDigit(c) || c >= CHAR_UPPER_A && c <= CHAR_UPPER_F || c >= CHAR_LOWER_A && c <= CHAR_LOWER_F;
1685
+ }
1686
+ /**
1687
+ * Determines if a given character is a positive digit (1-9).
1688
+ * @param {number} c The character to check.
1689
+ * @returns {boolean} `true` if the character is a positive digit.
1690
+ */
1691
+ function isPositiveDigit(c) {
1692
+ return c >= CHAR_1 && c <= CHAR_9;
1693
+ }
1694
+ /**
1695
+ * Determines if a given character is the start of a keyword.
1696
+ * @param {number} c The character to check.
1697
+ * @returns {boolean} `true` if the character is the start of a keyword.
1698
+ */
1699
+ function isKeywordStart(c) {
1700
+ return keywordStarts.has(c);
1701
+ }
1702
+ /**
1703
+ * Determines if a given character is the start of a number.
1704
+ * @param {number} c The character to check.
1705
+ * @returns {boolean} `true` if the character is the start of a number.
1706
+ */
1707
+ function isNumberStart(c) {
1708
+ return isDigit(c) || c === CHAR_DOT || c === CHAR_MINUS;
1709
+ }
1710
+ /**
1711
+ * Determines if a given character is the start of a JSON5 number.
1712
+ * @param {number} c The character to check.
1713
+ * @returns {boolean} `true` if the character is the start of a JSON5 number.
1714
+ */
1715
+ function isJSON5NumberStart(c) {
1716
+ return isNumberStart(c) || c === CHAR_PLUS;
1717
+ }
1718
+ /**
1719
+ * Determines if a given character is the start of a string.
1720
+ * @param {number} c The character to check.
1721
+ * @param {boolean} json5 `true` if JSON5 mode is enabled.
1722
+ * @returns {boolean} `true` if the character is the start of a string.
1723
+ */
1724
+ function isStringStart(c, json5) {
1725
+ return c === CHAR_DOUBLE_QUOTE || json5 && c === CHAR_SINGLE_QUOTE;
1726
+ }
1727
+ /**
1728
+ * Tests that a given character is a valid first character of a
1729
+ * JSON5 identifier
1730
+ * @param {number} c The character to check.
1731
+ * @returns {boolean} `true` if the character is a valid first character.
1732
+ */
1733
+ function isJSON5IdentifierStart(c) {
1734
+ if (c === CHAR_DOLLAR || c === CHAR_UNDERSCORE || c === CHAR_BACKSLASH) return true;
1735
+ if (c >= CHAR_LOWER_A && c <= CHAR_LOWER_Z || c >= CHAR_UPPER_A && c <= CHAR_UPPER_Z) return true;
1736
+ if (c === 8204 || c === 8205) return true;
1737
+ const ct = String.fromCharCode(c);
1738
+ return ID_Start.test(ct);
1739
+ }
1740
+ /**
1741
+ * Tests that a given character is a valid part of a JSON5 identifier.
1742
+ * @param {number} c The character to check.
1743
+ * @returns {boolean} `true` if the character is a valid part of an identifier.
1744
+ */
1745
+ function isJSON5IdentifierPart(c) {
1746
+ if (isJSON5IdentifierStart(c) || isDigit(c)) return true;
1747
+ const ct = String.fromCharCode(c);
1748
+ return ID_Continue.test(ct);
1749
+ }
1750
+ var Tokenizer = class {
1751
+ /**
1752
+ * Options for the tokenizer.
1753
+ * @type {TokenizeOptions}
1754
+ */
1755
+ #options;
1756
+ /**
1757
+ * The source text to tokenize.
1758
+ * @type {string}
1759
+ */
1760
+ #text;
1761
+ /**
1762
+ * The reader for the source text.
1763
+ * @type {CharCodeReader}
1764
+ */
1765
+ #reader;
1766
+ /**
1767
+ * Indicates if the tokenizer is in JSON5 mode.
1768
+ * @type {boolean}
1769
+ */
1770
+ #json5;
1771
+ /**
1772
+ * Indicates if comments are allowed.
1773
+ * @type {boolean}
1774
+ */
1775
+ #allowComments;
1776
+ /**
1777
+ * Indicates if ranges should be included in the tokens.
1778
+ * @type {boolean}
1779
+ */
1780
+ #ranges;
1781
+ /**
1782
+ * The last token type read.
1783
+ * @type {Token}
1784
+ */
1785
+ #token;
1786
+ /**
1787
+ * Determines if a character is an escaped character.
1788
+ * @type {(c:number) => boolean}
1789
+ */
1790
+ #isEscapedCharacter;
1791
+ /**
1792
+ * Determines if a character is a JSON5 line terminator.
1793
+ * @type {(c:number) => boolean}
1794
+ */
1795
+ #isJSON5LineTerminator;
1796
+ /**
1797
+ * Determines if a character is a JSON5 hex escape.
1798
+ * @type {(c:number) => boolean}
1799
+ */
1800
+ #isJSON5HexEscape;
1801
+ /**
1802
+ * Determines if a character is whitespace.
1803
+ * @type {(c:number) => boolean}
1804
+ */
1805
+ #isWhitespace;
1806
+ /**
1807
+ * Creates a new instance of the tokenizer.
1808
+ * @param {string} text The source text
1809
+ * @param {TokenizeOptions} [options] Options for the tokenizer.
1810
+ */
1811
+ constructor(text, options) {
1812
+ this.#text = text;
1813
+ this.#options = {
1814
+ ...DEFAULT_OPTIONS$1,
1815
+ ...options
1816
+ };
1817
+ this.#reader = new CharCodeReader(text);
1818
+ this.#json5 = this.#options.mode === "json5";
1819
+ this.#allowComments = this.#options.mode !== "json";
1820
+ this.#ranges = this.#options.ranges;
1821
+ this.#isEscapedCharacter = this.#json5 ? json5EscapeToChar.has.bind(json5EscapeToChar) : escapeToChar.has.bind(escapeToChar);
1822
+ this.#isJSON5LineTerminator = this.#json5 ? json5LineTerminators.has.bind(json5LineTerminators) : () => false;
1823
+ this.#isJSON5HexEscape = this.#json5 ? (c) => c === CHAR_LOWER_X : () => false;
1824
+ this.#isWhitespace = this.#json5 ? json5Whitespace.has.bind(json5Whitespace) : whitespace.has.bind(whitespace);
1825
+ }
1826
+ /**
1827
+ * Convenience function for throwing unexpected character errors.
1828
+ * @param {number} c The unexpected character.
1829
+ * @param {Location} [loc] The location of the unexpected character.
1830
+ * @returns {never}
1831
+ * @throws {UnexpectedChar} always.
1832
+ */
1833
+ #unexpected(c, loc = this.#reader.locate()) {
1834
+ throw new UnexpectedChar(c, loc);
1835
+ }
1836
+ /**
1837
+ * Convenience function for throwing unexpected identifier errors.
1838
+ * @param {string} identifier The unexpected identifier.
1839
+ * @param {Location} [loc] The location of the unexpected identifier.
1840
+ * @returns {never}
1841
+ * @throws {UnexpectedIdentifier} always.
1842
+ */
1843
+ #unexpectedIdentifier(identifier, loc = this.#reader.locate()) {
1844
+ throw new UnexpectedIdentifier(identifier, loc);
1845
+ }
1846
+ /**
1847
+ * Convenience function for throwing unexpected EOF errors.
1848
+ * @returns {never}
1849
+ * @throws {UnexpectedEOF} always.
1850
+ */
1851
+ #unexpectedEOF() {
1852
+ throw new UnexpectedEOF(this.#reader.locate());
1853
+ }
1854
+ /**
1855
+ * Creates a new token.
1856
+ * @param {TokenType} tokenType The type of token to create.
1857
+ * @param {number} length The length of the token.
1858
+ * @param {Location} startLoc The start location for the token.
1859
+ * @param {Location} [endLoc] The end location for the token.
1860
+ * @returns {Token} The token.
1861
+ */
1862
+ #createToken(tokenType, length, startLoc, endLoc) {
1863
+ const endOffset = startLoc.offset + length;
1864
+ let range = this.#options.ranges ? { range: [startLoc.offset, endOffset] } : void 0;
1865
+ return {
1866
+ type: tokenType,
1867
+ loc: {
1868
+ start: startLoc,
1869
+ end: endLoc || {
1870
+ line: startLoc.line,
1871
+ column: startLoc.column + length,
1872
+ offset: endOffset
1873
+ }
1874
+ },
1875
+ ...range
1876
+ };
1877
+ }
1878
+ /**
1879
+ * Reads in a specific number of hex digits.
1880
+ * @param {number} count The number of hex digits to read.
1881
+ * @returns {string} The hex digits read.
1882
+ */
1883
+ #readHexDigits(count) {
1884
+ let value = "";
1885
+ let c;
1886
+ for (let i = 0; i < count; i++) {
1887
+ c = this.#reader.peek();
1888
+ if (isHexDigit(c)) {
1889
+ this.#reader.next();
1890
+ value += String.fromCharCode(c);
1891
+ continue;
1892
+ }
1893
+ this.#unexpected(c);
1894
+ }
1895
+ return value;
1896
+ }
1897
+ /**
1898
+ * Reads in a JSON5 identifier. Also used for JSON but we validate
1899
+ * the identifier later.
1900
+ * @param {number} c The first character of the identifier.
1901
+ * @returns {string} The identifier read.
1902
+ * @throws {UnexpectedChar} when the identifier cannot be read.
1903
+ */
1904
+ #readIdentifier(c) {
1905
+ let value = "";
1906
+ do {
1907
+ value += String.fromCharCode(c);
1908
+ if (c === CHAR_BACKSLASH) {
1909
+ c = this.#reader.next();
1910
+ if (c !== CHAR_LOWER_U) this.#unexpected(c);
1911
+ value += String.fromCharCode(c);
1912
+ const hexDigits = this.#readHexDigits(4);
1913
+ const charCode = parseInt(hexDigits, 16);
1914
+ if (value.length === 2 && !isJSON5IdentifierStart(charCode)) {
1915
+ const loc = this.#reader.locate();
1916
+ this.#unexpected(CHAR_BACKSLASH, {
1917
+ line: loc.line,
1918
+ column: loc.column - 5,
1919
+ offset: loc.offset - 5
1920
+ });
1921
+ } else if (!isJSON5IdentifierPart(charCode)) {
1922
+ const loc = this.#reader.locate();
1923
+ this.#unexpected(charCode, {
1924
+ line: loc.line,
1925
+ column: loc.column - 5,
1926
+ offset: loc.offset - 5
1927
+ });
1928
+ }
1929
+ value += hexDigits;
1930
+ }
1931
+ c = this.#reader.peek();
1932
+ if (!isJSON5IdentifierPart(c)) break;
1933
+ this.#reader.next();
1934
+ } while (true);
1935
+ return value;
1936
+ }
1937
+ /**
1938
+ * Reads in a string. Works for both JSON and JSON5.
1939
+ * @param {number} c The first character of the string (either " or ').
1940
+ * @returns {number} The length of the string.
1941
+ * @throws {UnexpectedChar} when the string cannot be read.
1942
+ * @throws {UnexpectedEOF} when EOF is reached before the string is finalized.
1943
+ */
1944
+ #readString(c) {
1945
+ const delimiter = c;
1946
+ let length = 1;
1947
+ c = this.#reader.peek();
1948
+ while (c !== -1 && c !== delimiter) {
1949
+ this.#reader.next();
1950
+ length++;
1951
+ if (c === CHAR_BACKSLASH) {
1952
+ c = this.#reader.peek();
1953
+ if (this.#isEscapedCharacter(c) || this.#isJSON5LineTerminator(c)) {
1954
+ this.#reader.next();
1955
+ length++;
1956
+ } else if (c === CHAR_LOWER_U) {
1957
+ this.#reader.next();
1958
+ length++;
1959
+ const result = this.#readHexDigits(4);
1960
+ length += result.length;
1961
+ } else if (this.#isJSON5HexEscape(c)) {
1962
+ this.#reader.next();
1963
+ length++;
1964
+ const result = this.#readHexDigits(2);
1965
+ length += result.length;
1966
+ } else if (this.#json5) {
1967
+ this.#reader.next();
1968
+ length++;
1969
+ } else this.#unexpected(c);
1970
+ }
1971
+ c = this.#reader.peek();
1972
+ }
1973
+ if (c === -1) {
1974
+ this.#reader.next();
1975
+ this.#unexpectedEOF();
1976
+ }
1977
+ this.#reader.next();
1978
+ length++;
1979
+ return length;
1980
+ }
1981
+ /**
1982
+ * Reads a number. Works for both JSON and JSON5.
1983
+ * @param {number} c The first character of the number.
1984
+ * @returns {number} The length of the number.
1985
+ * @throws {UnexpectedChar} when the number cannot be read.
1986
+ * @throws {UnexpectedEOF} when EOF is reached before the number is finalized.
1987
+ */
1988
+ #readNumber(c) {
1989
+ let length = 1;
1990
+ if (c === CHAR_MINUS || this.#json5 && c === CHAR_PLUS) {
1991
+ c = this.#reader.peek();
1992
+ if (this.#json5) {
1993
+ if (c === CHAR_UPPER_I || c === CHAR_UPPER_N) {
1994
+ this.#reader.next();
1995
+ const identifier = this.#readIdentifier(c);
1996
+ if (identifier !== INFINITY && identifier !== NAN) this.#unexpected(c);
1997
+ return length + identifier.length;
1998
+ }
1999
+ }
2000
+ if (!isDigit(c)) this.#unexpected(c);
2001
+ this.#reader.next();
2002
+ length++;
2003
+ }
2004
+ if (c === CHAR_0) {
2005
+ c = this.#reader.peek();
2006
+ if (this.#json5 && (c === CHAR_LOWER_X || c === CHAR_UPPER_X)) {
2007
+ this.#reader.next();
2008
+ length++;
2009
+ c = this.#reader.peek();
2010
+ if (!isHexDigit(c)) {
2011
+ this.#reader.next();
2012
+ this.#unexpected(c);
2013
+ }
2014
+ do {
2015
+ this.#reader.next();
2016
+ length++;
2017
+ c = this.#reader.peek();
2018
+ } while (isHexDigit(c));
2019
+ } else if (isDigit(c)) this.#unexpected(c);
2020
+ } else if (!this.#json5 || c !== CHAR_DOT) {
2021
+ if (!isPositiveDigit(c)) this.#unexpected(c);
2022
+ c = this.#reader.peek();
2023
+ while (isDigit(c)) {
2024
+ this.#reader.next();
2025
+ length++;
2026
+ c = this.#reader.peek();
2027
+ }
2028
+ }
2029
+ if (c === CHAR_DOT) {
2030
+ let digitCount = -1;
2031
+ this.#reader.next();
2032
+ length++;
2033
+ digitCount++;
2034
+ c = this.#reader.peek();
2035
+ while (isDigit(c)) {
2036
+ this.#reader.next();
2037
+ length++;
2038
+ digitCount++;
2039
+ c = this.#reader.peek();
2040
+ }
2041
+ if (!this.#json5 && digitCount === 0) {
2042
+ this.#reader.next();
2043
+ if (c) this.#unexpected(c);
2044
+ else this.#unexpectedEOF();
2045
+ }
2046
+ }
2047
+ if (c === CHAR_LOWER_E || c === CHAR_UPPER_E) {
2048
+ this.#reader.next();
2049
+ length++;
2050
+ c = this.#reader.peek();
2051
+ if (c === CHAR_PLUS || c === CHAR_MINUS) {
2052
+ this.#reader.next();
2053
+ length++;
2054
+ c = this.#reader.peek();
2055
+ }
2056
+ if (c === -1) {
2057
+ this.#reader.next();
2058
+ this.#unexpectedEOF();
2059
+ }
2060
+ if (!isDigit(c)) {
2061
+ this.#reader.next();
2062
+ this.#unexpected(c);
2063
+ }
2064
+ while (isDigit(c)) {
2065
+ this.#reader.next();
2066
+ length++;
2067
+ c = this.#reader.peek();
2068
+ }
2069
+ }
2070
+ return length;
2071
+ }
2072
+ /**
2073
+ * Reads a comment. Works for both JSON and JSON5.
2074
+ * @param {number} c The first character of the comment.
2075
+ * @returns {{length: number, multiline: boolean}} The length of the comment, and whether the comment is multi-line.
2076
+ * @throws {UnexpectedChar} when the comment cannot be read.
2077
+ * @throws {UnexpectedEOF} when EOF is reached before the comment is finalized.
2078
+ */
2079
+ #readComment(c) {
2080
+ let length = 1;
2081
+ c = this.#reader.peek();
2082
+ if (c === CHAR_SLASH) {
2083
+ do {
2084
+ this.#reader.next();
2085
+ length += 1;
2086
+ c = this.#reader.peek();
2087
+ } while (c > -1 && c !== CHAR_RETURN && c !== CHAR_NEWLINE);
2088
+ return {
2089
+ length,
2090
+ multiline: false
2091
+ };
2092
+ }
2093
+ if (c === CHAR_STAR) {
2094
+ this.#reader.next();
2095
+ length += 1;
2096
+ while (c > -1) {
2097
+ c = this.#reader.peek();
2098
+ if (c === CHAR_STAR) {
2099
+ this.#reader.next();
2100
+ length += 1;
2101
+ c = this.#reader.peek();
2102
+ if (c === CHAR_SLASH) {
2103
+ this.#reader.next();
2104
+ length += 1;
2105
+ return {
2106
+ length,
2107
+ multiline: true
2108
+ };
2109
+ }
2110
+ } else {
2111
+ this.#reader.next();
2112
+ length += 1;
2113
+ }
2114
+ }
2115
+ this.#reader.next();
2116
+ this.#unexpectedEOF();
2117
+ }
2118
+ this.#reader.next();
2119
+ this.#unexpected(c);
2120
+ }
2121
+ /**
2122
+ * Returns the next token in the source text.
2123
+ * @returns {number} The code for the next token.
2124
+ */
2125
+ next() {
2126
+ let c = this.#reader.next();
2127
+ while (this.#isWhitespace(c)) c = this.#reader.next();
2128
+ if (c === -1) return tt.EOF;
2129
+ const start = this.#reader.locate();
2130
+ const ct = String.fromCharCode(c);
2131
+ if (this.#json5) if (knownJSON5TokenTypes.has(ct)) this.#token = this.#createToken(knownJSON5TokenTypes.get(ct), 1, start);
2132
+ else if (isJSON5IdentifierStart(c)) {
2133
+ const value = this.#readIdentifier(c);
2134
+ if (knownJSON5TokenTypes.has(value)) this.#token = this.#createToken(knownJSON5TokenTypes.get(value), value.length, start);
2135
+ else this.#token = this.#createToken("Identifier", value.length, start);
2136
+ } else if (isJSON5NumberStart(c)) {
2137
+ const result = this.#readNumber(c);
2138
+ this.#token = this.#createToken("Number", result, start);
2139
+ } else if (isStringStart(c, this.#json5)) {
2140
+ const result = this.#readString(c);
2141
+ const lastCharLoc = this.#reader.locate();
2142
+ this.#token = this.#createToken("String", result, start, {
2143
+ line: lastCharLoc.line,
2144
+ column: lastCharLoc.column + 1,
2145
+ offset: lastCharLoc.offset + 1
2146
+ });
2147
+ } else if (c === CHAR_SLASH && this.#allowComments) {
2148
+ const result = this.#readComment(c);
2149
+ const lastCharLoc = this.#reader.locate();
2150
+ this.#token = this.#createToken(!result.multiline ? "LineComment" : "BlockComment", result.length, start, {
2151
+ line: lastCharLoc.line,
2152
+ column: lastCharLoc.column + 1,
2153
+ offset: lastCharLoc.offset + 1
2154
+ });
2155
+ } else this.#unexpected(c);
2156
+ else if (knownTokenTypes.has(ct)) this.#token = this.#createToken(knownTokenTypes.get(ct), 1, start);
2157
+ else if (isKeywordStart(c)) {
2158
+ const value = this.#readIdentifier(c);
2159
+ if (!jsonKeywords.has(value)) this.#unexpectedIdentifier(value, start);
2160
+ this.#token = this.#createToken(knownTokenTypes.get(value), value.length, start);
2161
+ } else if (isNumberStart(c)) {
2162
+ const result = this.#readNumber(c);
2163
+ this.#token = this.#createToken("Number", result, start);
2164
+ } else if (isStringStart(c, this.#json5)) {
2165
+ const result = this.#readString(c);
2166
+ this.#token = this.#createToken("String", result, start);
2167
+ } else if (c === CHAR_SLASH && this.#allowComments) {
2168
+ const result = this.#readComment(c);
2169
+ const lastCharLoc = this.#reader.locate();
2170
+ this.#token = this.#createToken(!result.multiline ? "LineComment" : "BlockComment", result.length, start, {
2171
+ line: lastCharLoc.line,
2172
+ column: lastCharLoc.column + 1,
2173
+ offset: lastCharLoc.offset + 1
2174
+ });
2175
+ } else this.#unexpected(c);
2176
+ return tt[this.#token.type];
2177
+ }
2178
+ /**
2179
+ * Returns the current token in the source text.
2180
+ * @returns {Token} The current token.
2181
+ */
2182
+ get token() {
2183
+ return this.#token;
2184
+ }
2185
+ };
2186
+ /**
2187
+ * @fileoverview JSON AST types
2188
+ * @author Nicholas C. Zakas
2189
+ */
2190
+ /** @typedef {import("./typedefs.js").NodeParts} NodeParts */
2191
+ /** @typedef {import("./typedefs.js").DocumentNode} DocumentNode */
2192
+ /** @typedef {import("./typedefs.js").StringNode} StringNode */
2193
+ /** @typedef {import("./typedefs.js").NumberNode} NumberNode */
2194
+ /** @typedef {import("./typedefs.js").BooleanNode} BooleanNode */
2195
+ /** @typedef {import("./typedefs.js").MemberNode} MemberNode */
2196
+ /** @typedef {import("./typedefs.js").ObjectNode} ObjectNode */
2197
+ /** @typedef {import("./typedefs.js").ElementNode} ElementNode */
2198
+ /** @typedef {import("./typedefs.js").ArrayNode} ArrayNode */
2199
+ /** @typedef {import("./typedefs.js").NullNode} NullNode */
2200
+ /** @typedef {import("./typedefs.js").ValueNode} ValueNode */
2201
+ /** @typedef {import("./typedefs.js").IdentifierNode} IdentifierNode */
2202
+ /** @typedef {import("./typedefs.js").NaNNode} NaNNode */
2203
+ /** @typedef {import("./typedefs.js").InfinityNode} InfinityNode */
2204
+ /** @typedef {import("./typedefs.js").Sign} Sign */
2205
+ const types = {
2206
+ document(body, parts = {}) {
2207
+ return {
2208
+ type: "Document",
2209
+ body,
2210
+ loc: parts.loc,
2211
+ ...parts
2212
+ };
2213
+ },
2214
+ string(value, parts = {}) {
2215
+ return {
2216
+ type: "String",
2217
+ value,
2218
+ loc: parts.loc,
2219
+ ...parts
2220
+ };
2221
+ },
2222
+ number(value, parts = {}) {
2223
+ return {
2224
+ type: "Number",
2225
+ value,
2226
+ loc: parts.loc,
2227
+ ...parts
2228
+ };
2229
+ },
2230
+ boolean(value, parts = {}) {
2231
+ return {
2232
+ type: "Boolean",
2233
+ value,
2234
+ loc: parts.loc,
2235
+ ...parts
2236
+ };
2237
+ },
2238
+ null(parts = {}) {
2239
+ return {
2240
+ type: "Null",
2241
+ loc: parts.loc,
2242
+ ...parts
2243
+ };
2244
+ },
2245
+ array(elements, parts = {}) {
2246
+ return {
2247
+ type: "Array",
2248
+ elements,
2249
+ loc: parts.loc,
2250
+ ...parts
2251
+ };
2252
+ },
2253
+ element(value, parts = {}) {
2254
+ return {
2255
+ type: "Element",
2256
+ value,
2257
+ loc: parts.loc,
2258
+ ...parts
2259
+ };
2260
+ },
2261
+ object(members, parts = {}) {
2262
+ return {
2263
+ type: "Object",
2264
+ members,
2265
+ loc: parts.loc,
2266
+ ...parts
2267
+ };
2268
+ },
2269
+ member(name, value, parts = {}) {
2270
+ return {
2271
+ type: "Member",
2272
+ name,
2273
+ value,
2274
+ loc: parts.loc,
2275
+ ...parts
2276
+ };
2277
+ },
2278
+ identifier(name, parts = {}) {
2279
+ return {
2280
+ type: "Identifier",
2281
+ name,
2282
+ loc: parts.loc,
2283
+ ...parts
2284
+ };
2285
+ },
2286
+ nan(sign = "", parts = {}) {
2287
+ return {
2288
+ type: "NaN",
2289
+ sign,
2290
+ loc: parts.loc,
2291
+ ...parts
2292
+ };
2293
+ },
2294
+ infinity(sign = "", parts = {}) {
2295
+ return {
2296
+ type: "Infinity",
2297
+ sign,
2298
+ loc: parts.loc,
2299
+ ...parts
2300
+ };
2301
+ }
2302
+ };
2303
+ /**
2304
+ * @fileoverview JSON parser
2305
+ * @author Nicholas C. Zakas
2306
+ */
2307
+ /** @typedef {import("./typedefs.js").Node} Node */
2308
+ /** @typedef {import("./typedefs.js").Mode} Mode */
2309
+ /** @typedef {import("./typedefs.js").ParseOptions} ParseOptions */
2310
+ /** @type {ParseOptions} */
2311
+ const DEFAULT_OPTIONS = {
2312
+ mode: "json",
2313
+ ranges: false,
2314
+ tokens: false,
2315
+ allowTrailingCommas: false
2316
+ };
2317
+ const UNICODE_SEQUENCE = /\\u[\da-fA-F]{4}/gu;
2318
+ /**
2319
+ * Normalizes a JSON5 identifier by converting Unicode escape sequences into
2320
+ * their corresponding characters.
2321
+ * @param {string} identifier The identifier to normalize.
2322
+ * @returns {string} The normalized identifier.
2323
+ */
2324
+ function normalizeIdentifier(identifier) {
2325
+ return identifier.replace(UNICODE_SEQUENCE, (unicodeEscape) => {
2326
+ return String.fromCharCode(parseInt(unicodeEscape.slice(2), 16));
2327
+ });
2328
+ }
2329
+ /**
2330
+ * Converts a JSON-encoded string into a JavaScript string, interpreting each
2331
+ * escape sequence.
2332
+ * @param {string} value The text for the token.
2333
+ * @param {Token} token The string token to convert into a JavaScript string.
2334
+ * @param {boolean} json5 `true` if parsing JSON5, `false` otherwise.
2335
+ * @returns {string} A JavaScript string.
2336
+ */
2337
+ function getStringValue(value, token, json5 = false) {
2338
+ let result = "";
2339
+ let escapeIndex = value.indexOf("\\");
2340
+ let lastIndex = 0;
2341
+ while (escapeIndex >= 0) {
2342
+ result += value.slice(lastIndex, escapeIndex);
2343
+ const escapeChar = value.charAt(escapeIndex + 1);
2344
+ const escapeCharCode = escapeChar.charCodeAt(0);
2345
+ if (json5 && json5EscapeToChar.has(escapeCharCode)) {
2346
+ result += json5EscapeToChar.get(escapeCharCode);
2347
+ lastIndex = escapeIndex + 2;
2348
+ } else if (escapeToChar.has(escapeCharCode)) {
2349
+ result += escapeToChar.get(escapeCharCode);
2350
+ lastIndex = escapeIndex + 2;
2351
+ } else if (escapeChar === "u") {
2352
+ const hexCode = value.slice(escapeIndex + 2, escapeIndex + 6);
2353
+ if (hexCode.length < 4 || /[^0-9a-f]/i.test(hexCode)) throw new ErrorWithLocation(`Invalid unicode escape \\u${hexCode}.`, {
2354
+ line: token.loc.start.line,
2355
+ column: token.loc.start.column + escapeIndex,
2356
+ offset: token.loc.start.offset + escapeIndex
2357
+ });
2358
+ result += String.fromCharCode(parseInt(hexCode, 16));
2359
+ lastIndex = escapeIndex + 6;
2360
+ } else if (json5 && escapeChar === "x") {
2361
+ const hexCode = value.slice(escapeIndex + 2, escapeIndex + 4);
2362
+ if (hexCode.length < 2 || /[^0-9a-f]/i.test(hexCode)) throw new ErrorWithLocation(`Invalid hex escape \\x${hexCode}.`, {
2363
+ line: token.loc.start.line,
2364
+ column: token.loc.start.column + escapeIndex,
2365
+ offset: token.loc.start.offset + escapeIndex
2366
+ });
2367
+ result += String.fromCharCode(parseInt(hexCode, 16));
2368
+ lastIndex = escapeIndex + 4;
2369
+ } else if (json5 && json5LineTerminators.has(escapeCharCode)) {
2370
+ lastIndex = escapeIndex + 2;
2371
+ if (escapeChar === "\r" && value.charAt(lastIndex) === "\n") lastIndex++;
2372
+ } else if (json5) {
2373
+ result += escapeChar;
2374
+ lastIndex = escapeIndex + 2;
2375
+ } else throw new ErrorWithLocation(`Invalid escape \\${escapeChar}.`, {
2376
+ line: token.loc.start.line,
2377
+ column: token.loc.start.column + escapeIndex,
2378
+ offset: token.loc.start.offset + escapeIndex
2379
+ });
2380
+ escapeIndex = value.indexOf("\\", lastIndex);
2381
+ }
2382
+ result += value.slice(lastIndex);
2383
+ return result;
2384
+ }
2385
+ /**
2386
+ * Gets the JavaScript value represented by a JSON token.
2387
+ * @param {string} value The text value of the token.
2388
+ * @param {Token} token The JSON token to get a value for.
2389
+ * @param {boolean} json5 `true` if parsing JSON5, `false` otherwise.
2390
+ * @returns {string|boolean|number} A number, string, or boolean.
2391
+ * @throws {TypeError} If an unknown token type is found.
2392
+ */
2393
+ function getLiteralValue(value, token, json5 = false) {
2394
+ switch (token.type) {
2395
+ case "Boolean": return value === "true";
2396
+ case "Number":
2397
+ if (json5) {
2398
+ if (value.charCodeAt(0) === 45) return -Number(value.slice(1));
2399
+ if (value.charCodeAt(0) === 43) return Number(value.slice(1));
2400
+ }
2401
+ return Number(value);
2402
+ case "String": return getStringValue(value.slice(1, -1), token, json5);
2403
+ default: throw new TypeError(`Unknown token type "${token.type}.`);
2404
+ }
2405
+ }
2406
+ /**
2407
+ *
2408
+ * @param {string} text The text to parse.
2409
+ * @param {ParseOptions} [options] The options object.
2410
+ * @returns {DocumentNode} The AST representing the parsed JSON.
2411
+ * @throws {Error} When there is a parsing error.
2412
+ */
2413
+ function parse$1(text, options) {
2414
+ options = Object.freeze({
2415
+ ...DEFAULT_OPTIONS,
2416
+ ...options
2417
+ });
2418
+ const tokens = [];
2419
+ const tokenizer = new Tokenizer(text, {
2420
+ mode: options.mode,
2421
+ ranges: options.ranges
2422
+ });
2423
+ const json5 = options.mode === "json5";
2424
+ const allowTrailingCommas = options.allowTrailingCommas || json5;
2425
+ /**
2426
+ * Returns the next token knowing there are no comments.
2427
+ * @returns {number} The next token type or 0 if no next token.
2428
+ */
2429
+ function nextNoComments() {
2430
+ const nextType = tokenizer.next();
2431
+ if (nextType && options.tokens) tokens.push(tokenizer.token);
2432
+ return nextType;
2433
+ }
2434
+ /**
2435
+ * Returns the next token knowing there are comments to skip.
2436
+ * @returns {number} The next token type or 0 if no next token.
2437
+ */
2438
+ function nextSkipComments() {
2439
+ const nextType = tokenizer.next();
2440
+ if (nextType && options.tokens) tokens.push(tokenizer.token);
2441
+ if (nextType >= tt.LineComment) return nextSkipComments();
2442
+ return nextType;
2443
+ }
2444
+ const next = options.mode === "json" ? nextNoComments : nextSkipComments;
2445
+ /**
2446
+ * Asserts a token has the given type.
2447
+ * @param {number} token The token to check.
2448
+ * @param {number} type The token type.
2449
+ * @throws {UnexpectedToken} If the token type isn't expected.
2450
+ * @returns {void}
2451
+ */
2452
+ function assertTokenType(token, type) {
2453
+ if (token !== type) throw new UnexpectedToken(tokenizer.token);
2454
+ }
2455
+ /**
2456
+ * Asserts a token has one of the given types.
2457
+ * @param {number} token The token to check.
2458
+ * @param {number[]} types The token types.
2459
+ * @returns {void}
2460
+ * @throws {UnexpectedToken} If the token type isn't expected.
2461
+ */
2462
+ function assertTokenTypes(token, types$1) {
2463
+ if (!types$1.includes(token)) throw new UnexpectedToken(tokenizer.token);
2464
+ }
2465
+ /**
2466
+ * Creates a range only if ranges are specified.
2467
+ * @param {Location} start The start offset for the range.
2468
+ * @param {Location} end The end offset for the range.
2469
+ * @returns {{range:[number,number]}|undefined} An object with a
2470
+ */
2471
+ function createRange(start, end) {
2472
+ return options.ranges ? { range: [start.offset, end.offset] } : void 0;
2473
+ }
2474
+ /**
2475
+ * Creates a node for a string, boolean, or number.
2476
+ * @param {number} tokenType The token representing the literal.
2477
+ * @returns {StringNode|NumberNode|BooleanNode} The node representing
2478
+ * the value.
2479
+ */
2480
+ function createLiteralNode(tokenType) {
2481
+ const token = tokenizer.token;
2482
+ const range = createRange(token.loc.start, token.loc.end);
2483
+ const value = getLiteralValue(text.slice(token.loc.start.offset, token.loc.end.offset), token, json5);
2484
+ const loc = {
2485
+ start: { ...token.loc.start },
2486
+ end: { ...token.loc.end }
2487
+ };
2488
+ const parts = {
2489
+ loc,
2490
+ ...range
2491
+ };
2492
+ switch (tokenType) {
2493
+ case tt.String: return types.string(value, parts);
2494
+ case tt.Number: return types.number(value, parts);
2495
+ case tt.Boolean: return types.boolean(value, parts);
2496
+ default: throw new TypeError(`Unknown token type ${token.type}.`);
2497
+ }
2498
+ }
2499
+ /**
2500
+ * Creates a node for a JSON5 identifier.
2501
+ * @param {Token} token The token representing the identifer.
2502
+ * @returns {NaNNode|InfinityNode|IdentifierNode} The node representing
2503
+ * the value.
2504
+ */
2505
+ function createJSON5IdentifierNode(token) {
2506
+ const range = createRange(token.loc.start, token.loc.end);
2507
+ const identifier = text.slice(token.loc.start.offset, token.loc.end.offset);
2508
+ const loc = {
2509
+ start: { ...token.loc.start },
2510
+ end: { ...token.loc.end }
2511
+ };
2512
+ const parts = {
2513
+ loc,
2514
+ ...range
2515
+ };
2516
+ if (token.type !== "Identifier") {
2517
+ let sign = "";
2518
+ if (identifier[0] === "+" || identifier[0] === "-") sign = identifier[0];
2519
+ return types[identifier.includes("NaN") ? "nan" : "infinity"](sign, parts);
2520
+ }
2521
+ return types.identifier(normalizeIdentifier(identifier), parts);
2522
+ }
2523
+ /**
2524
+ * Creates a node for a null.
2525
+ * @param {Token} token The token representing null.
2526
+ * @returns {NullNode} The node representing null.
2527
+ */
2528
+ function createNullNode(token) {
2529
+ const range = createRange(token.loc.start, token.loc.end);
2530
+ return types.null({
2531
+ loc: {
2532
+ start: { ...token.loc.start },
2533
+ end: { ...token.loc.end }
2534
+ },
2535
+ ...range
2536
+ });
2537
+ }
2538
+ /**
2539
+ * Parses a property in an object.
2540
+ * @param {number} tokenType The token representing the property.
2541
+ * @returns {MemberNode} The node representing the property.
2542
+ * @throws {UnexpectedToken} When an unexpected token is found.
2543
+ * @throws {UnexpectedEOF} When the end of the file is reached.
2544
+ */
2545
+ function parseProperty(tokenType) {
2546
+ if (json5) assertTokenTypes(tokenType, [
2547
+ tt.String,
2548
+ tt.Identifier,
2549
+ tt.Number
2550
+ ]);
2551
+ else assertTokenType(tokenType, tt.String);
2552
+ const token = tokenizer.token;
2553
+ if (json5 && tokenType === tt.Number && /[+\-0-9]/.test(text[token.loc.start.offset])) throw new UnexpectedToken(token);
2554
+ let key = tokenType === tt.String ? createLiteralNode(tokenType) : createJSON5IdentifierNode(token);
2555
+ if (json5 && (key.type === "NaN" || key.type === "Infinity")) {
2556
+ if (key.sign !== "") throw new UnexpectedToken(tokenizer.token);
2557
+ key = types.identifier(key.type, {
2558
+ loc: key.loc,
2559
+ ...createRange(key.loc.start, key.loc.end)
2560
+ });
2561
+ }
2562
+ tokenType = next();
2563
+ assertTokenType(tokenType, tt.Colon);
2564
+ const value = parseValue();
2565
+ const range = createRange(key.loc.start, value.loc.end);
2566
+ return types.member(key, value, {
2567
+ loc: {
2568
+ start: { ...key.loc.start },
2569
+ end: { ...value.loc.end }
2570
+ },
2571
+ ...range
2572
+ });
2573
+ }
2574
+ /**
2575
+ * Parses an object literal.
2576
+ * @param {number} firstTokenType The first token type in the object.
2577
+ * @returns {ObjectNode} The object node.
2578
+ * @throws {UnexpectedEOF} When the end of the file is reached.
2579
+ * @throws {UnexpectedToken} When an unexpected token is found.
2580
+ */
2581
+ function parseObject(firstTokenType) {
2582
+ assertTokenType(firstTokenType, tt.LBrace);
2583
+ const firstToken = tokenizer.token;
2584
+ const members = [];
2585
+ let tokenType = next();
2586
+ if (tokenType !== tt.RBrace) do {
2587
+ members.push(parseProperty(tokenType));
2588
+ tokenType = next();
2589
+ if (!tokenType) throw new UnexpectedEOF(members[members.length - 1].loc.end);
2590
+ if (tokenType === tt.Comma) {
2591
+ tokenType = next();
2592
+ if (allowTrailingCommas && tokenType === tt.RBrace) break;
2593
+ } else break;
2594
+ } while (tokenType);
2595
+ assertTokenType(tokenType, tt.RBrace);
2596
+ const lastToken = tokenizer.token;
2597
+ const range = createRange(firstToken.loc.start, lastToken.loc.end);
2598
+ return types.object(members, {
2599
+ loc: {
2600
+ start: { ...firstToken.loc.start },
2601
+ end: { ...lastToken.loc.end }
2602
+ },
2603
+ ...range
2604
+ });
2605
+ }
2606
+ /**
2607
+ * Parses an array literal.
2608
+ * @param {number} firstTokenType The first token in the array.
2609
+ * @returns {ArrayNode} The array node.
2610
+ * @throws {UnexpectedToken} When an unexpected token is found.
2611
+ * @throws {UnexpectedEOF} When the end of the file is reached.
2612
+ */
2613
+ function parseArray(firstTokenType) {
2614
+ assertTokenType(firstTokenType, tt.LBracket);
2615
+ const firstToken = tokenizer.token;
2616
+ const elements = [];
2617
+ let tokenType = next();
2618
+ if (tokenType !== tt.RBracket) do {
2619
+ const value = parseValue(tokenType);
2620
+ elements.push(types.element(value, { loc: value.loc }));
2621
+ tokenType = next();
2622
+ if (tokenType === tt.Comma) {
2623
+ tokenType = next();
2624
+ if (allowTrailingCommas && tokenType === tt.RBracket) break;
2625
+ } else break;
2626
+ } while (tokenType);
2627
+ assertTokenType(tokenType, tt.RBracket);
2628
+ const lastToken = tokenizer.token;
2629
+ const range = createRange(firstToken.loc.start, lastToken.loc.end);
2630
+ return types.array(elements, {
2631
+ loc: {
2632
+ start: { ...firstToken.loc.start },
2633
+ end: { ...lastToken.loc.end }
2634
+ },
2635
+ ...range
2636
+ });
2637
+ }
2638
+ /**
2639
+ * Parses a JSON value.
2640
+ * @param {number} [tokenType] The token type to parse.
2641
+ * @returns {ValueNode|IdentifierNode} The node representing the value.
2642
+ */
2643
+ function parseValue(tokenType) {
2644
+ tokenType = tokenType ?? next();
2645
+ const token = tokenizer.token;
2646
+ switch (tokenType) {
2647
+ case tt.String:
2648
+ case tt.Boolean: return createLiteralNode(tokenType);
2649
+ case tt.Number:
2650
+ if (json5) {
2651
+ let tokenText = text.slice(token.loc.start.offset, token.loc.end.offset);
2652
+ if (tokenText[0] === "+" || tokenText[0] === "-") tokenText = tokenText.slice(1);
2653
+ if (tokenText === "NaN" || tokenText === "Infinity") return createJSON5IdentifierNode(token);
2654
+ }
2655
+ return createLiteralNode(tokenType);
2656
+ case tt.Null: return createNullNode(token);
2657
+ case tt.LBrace: return parseObject(tokenType);
2658
+ case tt.LBracket: return parseArray(tokenType);
2659
+ default: throw new UnexpectedToken(token);
2660
+ }
2661
+ }
2662
+ const docBody = parseValue();
2663
+ const unexpectedToken = next();
2664
+ if (unexpectedToken) throw new UnexpectedToken(tokenizer.token);
2665
+ const docParts = { loc: {
2666
+ start: {
2667
+ line: 1,
2668
+ column: 1,
2669
+ offset: 0
2670
+ },
2671
+ end: { ...docBody.loc.end }
2672
+ } };
2673
+ if (options.tokens) docParts.tokens = tokens;
2674
+ if (options.ranges) docParts.range = [docParts.loc.start.offset, docParts.loc.end.offset];
2675
+ return types.document(docBody, docParts);
2676
+ }
2677
+ /**
2678
+ * @fileoverview Evaluator for Momoa AST.
2679
+ * @author Nicholas C. Zakas
2680
+ */
2681
+ /** @typedef {import("./typedefs.js").AnyNode} AnyNode */
2682
+ /** @typedef {import("./typedefs.js").JSONValue} JSONValue */
2683
+ /**
2684
+ * Evaluates a Momoa AST node into a JavaScript value.
2685
+ * @param {AnyNode} node The node to interpet.
2686
+ * @returns {JSONValue} The JavaScript value for the node.
2687
+ */
2688
+ function evaluate(node) {
2689
+ switch (node.type) {
2690
+ case "String": return node.value;
2691
+ case "Number": return node.value;
2692
+ case "Boolean": return node.value;
2693
+ case "Null": return null;
2694
+ case "NaN": return NaN;
2695
+ case "Infinity": return node.sign === "-" ? -Infinity : Infinity;
2696
+ case "Identifier": return node.name;
2697
+ case "Array": return node.elements.map((element) => evaluate(element.value));
2698
+ case "Object": {
2699
+ /** @type {{[property: string]: JSONValue}} */
2700
+ const object = {};
2701
+ node.members.forEach((member) => {
2702
+ object[evaluate(member.name)] = evaluate(member.value);
2703
+ });
2704
+ return object;
2705
+ }
2706
+ case "Document": return evaluate(node.body);
2707
+ case "Element": throw new Error("Cannot evaluate array element outside of an array.");
2708
+ case "Member": throw new Error("Cannot evaluate object member outside of an object.");
2709
+ default: throw new Error(`Unknown node type ${node.type}.`);
2710
+ }
2711
+ }
2712
+ /**
2713
+ * @fileoverview Printer for Momoa AST.
2714
+ * @author Nicholas C. Zakas
2715
+ */
2716
+ /**
2717
+ * Prints the string representation of a Boolean node.
2718
+ * @param {BooleanNode} node The node to print.
2719
+ * @returns {string} The boolean value.
2720
+ */
2721
+ function printBoolean(node) {
2722
+ return node.value ? "true" : "false";
2723
+ }
2724
+ /**
2725
+ * Prints the string representation of a null node.
2726
+ * @returns {string} The string "null".
2727
+ */
2728
+ function printNull() {
2729
+ return "null";
2730
+ }
2731
+ /**
2732
+ * Prints the string representation of a number node.
2733
+ * @param {NumberNode} node The node to print.
2734
+ * @returns {string} The number value.
2735
+ */
2736
+ function printNumber(node) {
2737
+ return node.value.toString();
2738
+ }
2739
+ /**
2740
+ * Prints the string representation of a NaN node.
2741
+ * @returns {string} The string "NaN".
2742
+ */
2743
+ function printNaN() {
2744
+ return "NaN";
2745
+ }
2746
+ /**
2747
+ * Prints the string representation of an Infinity node.
2748
+ * @param {InfinityNode} node The node to print.
2749
+ * @returns {string} The string "Infinity" or "-Infinity".
2750
+ */
2751
+ function printInfinity(node) {
2752
+ return node.sign + "Infinity";
2753
+ }
2754
+ /**
2755
+ * Prints the string representation of a string node.
2756
+ * @param {StringNode} node The node to print.
2757
+ * @returns {string} The string value.
2758
+ */
2759
+ function printString(node) {
2760
+ let result = "\"";
2761
+ for (const c of node.value) {
2762
+ const newChar = json5CharToEscape.get(c);
2763
+ if (newChar) {
2764
+ result += "\\" + newChar;
2765
+ continue;
2766
+ }
2767
+ if (c === "\"") {
2768
+ result += "\\\"";
2769
+ continue;
2770
+ }
2771
+ if (c < " " || c === "") {
2772
+ const hex = c.codePointAt(0).toString(16).toUpperCase();
2773
+ result += `\\u${"0000".substring(hex.length)}${hex}`;
2774
+ continue;
2775
+ }
2776
+ result += c;
2777
+ }
2778
+ return result + "\"";
2779
+ }
2780
+ /**
2781
+ * Prints the string representation of an identifier node.
2782
+ * @param {IdentifierNode} node The node to print.
2783
+ * @returns {string} The identifier name.
2784
+ */
2785
+ function printIdentifier(node) {
2786
+ return node.name;
2787
+ }
2788
+ /**
2789
+ * Prints the string representation of an array node.
2790
+ * @param {ArrayNode} node The node to print.
2791
+ * @param {string} indent The string to use for indentation.
2792
+ * @param {number} indentLevel The current level of indentation.
2793
+ * @returns {string} The array value.
2794
+ */
2795
+ function printArray(node, indent, indentLevel) {
2796
+ const newLine = indent ? "\n" : "";
2797
+ const indentString = indent.repeat(indentLevel);
2798
+ const elementIndentString = indent.repeat(indentLevel + 1);
2799
+ return `[${newLine}${node.elements.map((element) => `${elementIndentString}${printValue(element.value, indent, indentLevel + 1)}`).join(`,${newLine}`)}${newLine}${indentString}]`;
2800
+ }
2801
+ /**
2802
+ * Prints the string representation of a member node.
2803
+ * @param {MemberNode} node The node to print.
2804
+ * @param {string} indent The string to use for indentation.
2805
+ * @param {number} indentLevel The current level of indentation.
2806
+ * @returns {string} The member value.
2807
+ */
2808
+ function printMember(node, indent, indentLevel) {
2809
+ const space = indent ? " " : "";
2810
+ return `${printValue(node.name, indent, indentLevel)}:${space}${printValue(node.value, indent, indentLevel + 1)}`;
2811
+ }
2812
+ /**
2813
+ * Prints the string representation of an object node.
2814
+ * @param {ObjectNode} node The node to print.
2815
+ * @param {string} indent The string to use for indentation.
2816
+ * @param {number} indentLevel The current level of indentation.
2817
+ * @returns {string} The object value.
2818
+ */
2819
+ function printObject(node, indent, indentLevel) {
2820
+ const newLine = indent ? "\n" : "";
2821
+ const indentString = indent.repeat(indentLevel);
2822
+ const memberIndentString = indent.repeat(indentLevel + 1);
2823
+ return `{${newLine}${node.members.map((member) => `${memberIndentString}${printMember(member, indent, indentLevel)}`).join(`,${newLine}`)}${newLine}${indentString}}`;
2824
+ }
2825
+ /**
2826
+ * Prints the string representation of a node.
2827
+ * @param {AnyNode} node The node to print.
2828
+ * @param {string} indentString The string to use for indentation.
2829
+ * @param {number} indentLevel The current level of indentation.
2830
+ * @returns {string} The string representation of the node.
2831
+ * @throws {TypeError} If the node type is unknown.
2832
+
2833
+ */
2834
+ function printValue(node, indentString, indentLevel) {
2835
+ switch (node.type) {
2836
+ case "String": return printString(node);
2837
+ case "Number": return printNumber(node);
2838
+ case "Boolean": return printBoolean(node);
2839
+ case "Null": return printNull();
2840
+ case "NaN": return printNaN();
2841
+ case "Infinity": return printInfinity(node);
2842
+ case "Identifier": return printIdentifier(node);
2843
+ case "Array": return printArray(node, indentString, indentLevel);
2844
+ case "Object": return printObject(node, indentString, indentLevel);
2845
+ case "Document": return printValue(node.body, indentString, indentLevel);
2846
+ default: throw new TypeError(`Unknown node type: ${node.type}`);
2847
+ }
2848
+ }
2849
+ /**
2850
+ * Converts a Momoa AST back into a JSON string.
2851
+ * @param {AnyNode} node The node to print.
2852
+ * @param {Object} options Options for the print.
2853
+ * @param {number} [options.indent=0] The number of spaces to indent each line. If
2854
+ * greater than 0, then newlines and indents will be added to output.
2855
+ * @returns {string} The JSON representation of the AST.
2856
+ */
2857
+ function print(node, { indent = 0 } = {}) {
2858
+ const indentLevel = 0;
2859
+ const indentString = " ".repeat(indent);
2860
+ return printValue(node, indentString, indentLevel);
2861
+ }
2862
+
2863
+ //#endregion
2864
+ //#region src/parse/json.ts
2865
+ const CHILD_KEYS = {
2866
+ Document: ["body"],
2867
+ Object: ["members"],
2868
+ Member: ["name", "value"],
2869
+ Element: ["value"],
2870
+ Array: ["elements"],
2871
+ String: [],
2872
+ Number: [],
2873
+ Boolean: [],
2874
+ Null: [],
2875
+ Identifier: [],
2876
+ NaN: [],
2877
+ Infinity: []
2878
+ };
2879
+ /** Determines if a given value is an AST node. */
2880
+ function isNode(value) {
2881
+ return !!value && typeof value === "object" && "type" in value && typeof value.type === "string";
2882
+ }
2883
+ /** Get ObjectNode members as object */
2884
+ function getObjMembers(node) {
2885
+ const members = {};
2886
+ if (node.type !== "Object") return members;
2887
+ for (let i = 0; i < node.members.length; i++) {
2888
+ const m = node.members[i];
2889
+ if (m.name.type !== "String") continue;
2890
+ members[m.name.value] = {
2891
+ ...m.value,
2892
+ index: i
2893
+ };
2894
+ }
2895
+ return members;
2896
+ }
2897
+ /** Inject members to ObjectNode */
2898
+ function injectObjMembers(node, members = []) {
2899
+ if (node.type !== "Object") return;
2900
+ node.members.push(...members);
2901
+ }
2902
+ /** Replace an ObjectNode’s contents outright with another */
2903
+ function replaceObjMembers(a, b) {
2904
+ a.members = b.type === "Document" && b.body?.members || b.members;
2905
+ }
2906
+ /**
2907
+ * Variation of Momoa’s traverse(), which keeps track of global path.
2908
+ * Allows mutation of AST (along with any consequences)
2909
+ */
2910
+ function traverse(root, visitor) {
2911
+ /**
2912
+ * Recursively visits a node.
2913
+ * @param {AnyNode} node The node to visit.
2914
+ * @param {AnyNode} [parent] The parent of the node to visit.
2915
+ * @return {void}
2916
+ */
2917
+ function visitNode(node, parent, path = []) {
2918
+ const nextPath = [...path];
2919
+ if (node.type === "Member") {
2920
+ const { name } = node;
2921
+ nextPath.push("value" in name ? name.value : String(name));
2922
+ }
2923
+ visitor.enter?.(node, parent, nextPath);
2924
+ const childNode = CHILD_KEYS[node.type];
2925
+ for (const key of childNode ?? []) {
2926
+ const value = node[key];
2927
+ if (!value) continue;
2928
+ if (Array.isArray(value)) for (let i = 0; i < value.length; i++) visitNode(value[i], node, key === "elements" ? [...nextPath, String(i)] : nextPath);
2929
+ else if (isNode(value)) visitNode(value, node, nextPath);
2930
+ }
2931
+ visitor.exit?.(node, parent, nextPath);
2932
+ }
2933
+ visitNode(root, void 0, []);
2934
+ }
2935
+ /** Determine if an input is likely a JSON string */
2936
+ function maybeRawJSON(input) {
2937
+ return typeof input === "string" && input.trim().startsWith("{");
2938
+ }
2939
+ /** Find Momoa node by traversing paths */
2940
+ function findNode(node, path) {
2941
+ if (!path.length) return;
2942
+ let nextNode;
2943
+ switch (node.type) {
2944
+ case "Document": return findNode(node.body, path);
2945
+ case "Object": {
2946
+ const [member, ...rest] = path;
2947
+ nextNode = node.members.find((m) => m.name.type === "String" && m.name.value === member)?.value;
2948
+ if (nextNode && rest.length) return findNode(nextNode, path.slice(1));
2949
+ break;
2950
+ }
2951
+ case "Array": {
2952
+ const [_index, ...rest] = path;
2953
+ const index = Number.parseInt(_index, 10);
2954
+ nextNode = node.elements[index]?.value;
2955
+ if (nextNode && rest.length) return findNode(nextNode, path.slice(1));
2956
+ break;
2957
+ }
2958
+ }
2959
+ return nextNode;
2960
+ }
2961
+ function toMomoa(input, { continueOnError, filename, logger, yamlToMomoa }) {
2962
+ let src = "";
2963
+ if (typeof input === "string") src = input;
2964
+ let document = {};
2965
+ if (typeof input === "string" && !maybeRawJSON(input)) if (yamlToMomoa) try {
2966
+ document = yamlToMomoa(input);
2967
+ } catch (err) {
2968
+ logger.error({
2969
+ group: "parser",
2970
+ label: "json",
2971
+ message: String(err),
2972
+ filename,
2973
+ src: input,
2974
+ continueOnError
2975
+ });
2976
+ }
2977
+ else logger.error({
2978
+ group: "parser",
2979
+ label: "yaml",
2980
+ message: `Install \`yaml-to-momoa\` package to parse YAML, and pass in as option, e.g.:
2981
+
2982
+ import { parse } from '@terrazzo/parser';
2983
+ import yamlToMomoa from 'yaml-to-momoa';
2984
+
2985
+ parse(yamlString, { yamlToMomoa });`,
2986
+ continueOnError: false
2987
+ });
2988
+ else document = parseJSON(input);
2989
+ if (!src) src = print(document, { indent: 2 });
2990
+ return {
2991
+ src,
2992
+ document
2993
+ };
2994
+ }
2995
+ /** Momoa, just with default options pre-set */
2996
+ function parseJSON(input, options) {
2997
+ return parse$1(typeof input === "string" ? input : JSON.stringify(input, void 0, 2), {
2998
+ mode: "jsonc",
2999
+ ranges: true,
3000
+ tokens: true,
3001
+ ...options
3002
+ });
3003
+ }
3004
+
3005
+ //#endregion
3006
+ //#region src/parse/alias.ts
3007
+ /**
3008
+ * Resolve aliases and update the token nodes.
3009
+ *
3010
+ * Data structures are in an awkward in-between phase, where they have
3011
+ * placeholders for data but we still need to resolve everything. As such,
3012
+ * TypeScript will raise errors expecting the final shape.
3013
+ *
3014
+ * This is also a bit tricky because different token types alias slightly
3015
+ * differently. For example, color tokens and other “primitive” tokens behave
3016
+ * as-expected. But composite tokens like Typography, Gradient, Border, etc. can
3017
+ * either fully- or partially-alias their values. Then we add modes to the mix,
3018
+ * and we have to do the work all over again for each mode declared.
3019
+ *
3020
+ * All that to say, there are a generous amount of TypeScript overrides here rather
3021
+ * than try to codify indeterminate shapes.
3022
+ */
3023
+ function applyAliases(token, options) {
3024
+ token.mode["."] ??= {};
3025
+ token.mode["."].$value = token.$value;
3026
+ token.mode["."].originalValue ??= token.originalValue.$value;
3027
+ token.mode["."].source ??= token.source;
3028
+ if (typeof token.$value === "string" && isAlias(token.$value)) {
3029
+ const { aliasChain, resolvedToken } = resolveAlias(token.$value, {
3030
+ ...options,
3031
+ token
3032
+ });
3033
+ token.aliasOf = resolvedToken.id;
3034
+ token.aliasChain = aliasChain;
3035
+ token.$value = resolvedToken.$value;
3036
+ }
3037
+ for (const mode of Object.keys(token.mode)) {
3038
+ const modeValue = token.mode[mode].$value;
3039
+ if (typeof modeValue === "string" && isAlias(modeValue)) {
3040
+ const expectedType = [token.$type];
3041
+ const { aliasChain, resolvedToken } = resolveAlias(modeValue, {
3042
+ ...options,
3043
+ token,
3044
+ expectedType,
3045
+ node: token.mode[mode].source?.node || options.node
3046
+ });
3047
+ token.mode[mode].aliasOf = resolvedToken.id;
3048
+ token.mode[mode].aliasChain = aliasChain;
3049
+ token.mode[mode].$value = resolvedToken.$value;
3050
+ continue;
3051
+ }
3052
+ if (typeof token.$value === "object" && typeof token.mode[mode].$value === "object" && !Array.isArray(token.$value)) {
3053
+ for (const [k, v] of Object.entries(token.$value)) if (!(k in token.mode[mode].$value)) token.mode[mode].$value[k] = v;
3054
+ }
3055
+ const node = getObjMembers(options.node).$value || options.node;
3056
+ switch (token.$type) {
3057
+ case "border": {
3058
+ applyBorderPartialAlias(token, mode, {
3059
+ ...options,
3060
+ node
3061
+ });
3062
+ break;
3063
+ }
3064
+ case "gradient": {
3065
+ applyGradientPartialAlias(token, mode, {
3066
+ ...options,
3067
+ node
3068
+ });
3069
+ break;
3070
+ }
3071
+ case "shadow": {
3072
+ applyShadowPartialAlias(token, mode, {
3073
+ ...options,
3074
+ node
3075
+ });
3076
+ break;
3077
+ }
3078
+ case "strokeStyle": {
3079
+ applyStrokeStylePartialAlias(token, mode, {
3080
+ ...options,
3081
+ node
3082
+ });
3083
+ break;
3084
+ }
3085
+ case "transition": {
3086
+ applyTransitionPartialAlias(token, mode, {
3087
+ ...options,
3088
+ node
3089
+ });
3090
+ break;
3091
+ }
3092
+ case "typography": {
3093
+ applyTypographyPartialAlias(token, mode, {
3094
+ ...options,
3095
+ node
3096
+ });
3097
+ break;
3098
+ }
3099
+ }
3100
+ }
3101
+ }
3102
+ const LIST_FORMAT = new Intl.ListFormat("en-us", { type: "disjunction" });
3103
+ /**
3104
+ * Resolve alias. Also add info on root node if it’s the root token (has .id)
3105
+ */
3106
+ function resolveAlias(alias, options) {
3107
+ const baseMessage = {
3108
+ group: "parser",
3109
+ label: "alias",
3110
+ node: options?.node,
3111
+ filename: options.filename,
3112
+ src: options.src
3113
+ };
3114
+ const { logger, token, tokensSet } = options;
3115
+ const shallowAliasID = parseAlias(alias);
3116
+ const { token: resolvedToken, chain } = _resolveAliasInner(shallowAliasID, options);
3117
+ if (!tokensSet[token.id].$type) tokensSet[token.id].$type = resolvedToken.$type;
3118
+ const expectedType = [...options.expectedType ?? []];
3119
+ if (token.$type && !expectedType?.length) expectedType.push(token.$type);
3120
+ if (expectedType?.length && !expectedType.includes(resolvedToken.$type)) logger.error({
3121
+ ...baseMessage,
3122
+ message: `Invalid alias: expected $type: ${LIST_FORMAT.format(expectedType)}, received $type: ${resolvedToken.$type}.`,
3123
+ node: options.node?.type === "Object" && getObjMembers(options.node).$value || baseMessage.node
3124
+ });
3125
+ if (chain?.length && resolvedToken) {
3126
+ let needsSort = false;
3127
+ for (const id of chain) {
3128
+ if (id !== resolvedToken.id && !resolvedToken.aliasedBy?.includes(id)) {
3129
+ resolvedToken.aliasedBy ??= [];
3130
+ resolvedToken.aliasedBy.push(id);
3131
+ needsSort = true;
3132
+ }
3133
+ if (token && !resolvedToken.aliasedBy?.includes(token.id)) {
3134
+ resolvedToken.aliasedBy ??= [];
3135
+ resolvedToken.aliasedBy.push(token.id);
3136
+ needsSort = true;
3137
+ }
3138
+ }
3139
+ if (needsSort) resolvedToken.aliasedBy.sort((a, b) => a.localeCompare(b, "en-us", { numeric: true }));
3140
+ }
3141
+ return {
3142
+ resolvedToken,
3143
+ aliasChain: chain
3144
+ };
3145
+ }
3146
+ function _resolveAliasInner(alias, { scanned = [],...options }) {
3147
+ const { logger, filename, src, node, tokensSet } = options;
3148
+ const baseMessage = {
3149
+ group: "parser",
3150
+ label: "alias",
3151
+ filename,
3152
+ src,
3153
+ node
3154
+ };
3155
+ const id = parseAlias(alias);
3156
+ if (!tokensSet[id]) logger.error({
3157
+ ...baseMessage,
3158
+ message: `Alias {${alias}} not found.`
3159
+ });
3160
+ if (scanned.includes(id)) logger.error({
3161
+ ...baseMessage,
3162
+ message: `Circular alias detected from ${alias}.`
3163
+ });
3164
+ const token = tokensSet[id];
3165
+ scanned.push(id);
3166
+ if (typeof token.originalValue.$value !== "string" || !isAlias(token.originalValue.$value)) return {
3167
+ token,
3168
+ chain: scanned
3169
+ };
3170
+ return _resolveAliasInner(token.originalValue.$value, {
3171
+ ...options,
3172
+ scanned
3173
+ });
3174
+ }
3175
+ function applyBorderPartialAlias(token, mode, options) {
3176
+ for (const [k, v] of Object.entries(token.mode[mode].$value)) if (typeof v === "string" && isAlias(v)) {
3177
+ token.mode[mode].partialAliasOf ??= {};
3178
+ const node = getObjMembers(options.node)[k] || options.node;
3179
+ const { resolvedToken } = resolveAlias(v, {
3180
+ ...options,
3181
+ token,
3182
+ expectedType: {
3183
+ color: ["color"],
3184
+ width: ["dimension"],
3185
+ style: ["strokeStyle"]
3186
+ }[k],
3187
+ node
3188
+ });
3189
+ token.mode[mode].partialAliasOf[k] = parseAlias(v);
3190
+ if (mode === ".") {
3191
+ token.partialAliasOf ??= {};
3192
+ token.partialAliasOf[k] = parseAlias(v);
3193
+ }
3194
+ token.mode[mode].$value[k] = resolvedToken.$value;
3195
+ }
3196
+ }
3197
+ function applyGradientPartialAlias(token, mode, options) {
3198
+ for (let i = 0; i < token.mode[mode].$value.length; i++) {
3199
+ const step = token.mode[mode].$value[i];
3200
+ for (const [k, v] of Object.entries(step)) if (typeof v === "string" && isAlias(v)) {
3201
+ token.mode[mode].partialAliasOf ??= [];
3202
+ token.mode[mode].partialAliasOf[i] ??= {};
3203
+ const expectedType = {
3204
+ color: ["color"],
3205
+ position: ["number"]
3206
+ }[k];
3207
+ let node = options.node?.elements?.[i]?.value || options.node;
3208
+ if (node.type === "Object") node = getObjMembers(node)[k] || node;
3209
+ const { resolvedToken } = resolveAlias(v, {
3210
+ ...options,
3211
+ token,
3212
+ expectedType,
3213
+ node
3214
+ });
3215
+ token.mode[mode].partialAliasOf[i][k] = parseAlias(v);
3216
+ if (mode === ".") {
3217
+ token.partialAliasOf ??= [];
3218
+ token.partialAliasOf[i] ??= {};
3219
+ token.partialAliasOf[i][k] = parseAlias(v);
3220
+ }
3221
+ step[k] = resolvedToken.$value;
3222
+ }
3223
+ }
3224
+ }
3225
+ function applyShadowPartialAlias(token, mode, options) {
3226
+ if (!Array.isArray(token.mode[mode].$value)) token.mode[mode].$value = [token.mode[mode].$value];
3227
+ for (let i = 0; i < token.mode[mode].$value.length; i++) {
3228
+ const layer = token.mode[mode].$value[i];
3229
+ for (const [k, v] of Object.entries(layer)) if (typeof v === "string" && isAlias(v)) {
3230
+ token.mode[mode].partialAliasOf ??= [];
3231
+ token.mode[mode].partialAliasOf[i] ??= {};
3232
+ const expectedType = {
3233
+ offsetX: ["dimension"],
3234
+ offsetY: ["dimension"],
3235
+ blur: ["dimension"],
3236
+ spread: ["dimension"],
3237
+ color: ["color"],
3238
+ inset: ["boolean"]
3239
+ }[k];
3240
+ let node = options.node?.elements?.[i] || options.node;
3241
+ if (node.type === "Object") node = getObjMembers(node)[k] || node;
3242
+ const { resolvedToken } = resolveAlias(v, {
3243
+ ...options,
3244
+ token,
3245
+ expectedType,
3246
+ node
3247
+ });
3248
+ token.mode[mode].partialAliasOf[i][k] = parseAlias(v);
3249
+ if (mode === ".") {
3250
+ token.partialAliasOf ??= [];
3251
+ token.partialAliasOf[i] ??= {};
3252
+ token.partialAliasOf[i][k] = parseAlias(v);
3253
+ }
3254
+ layer[k] = resolvedToken.$value;
3255
+ }
3256
+ }
3257
+ }
3258
+ function applyStrokeStylePartialAlias(token, mode, options) {
3259
+ if (typeof token.mode[mode].$value !== "object" || !("dashArray" in token.mode[mode].$value)) return;
3260
+ for (let i = 0; i < token.mode[mode].$value.dashArray.length; i++) {
3261
+ const dash = token.mode[mode].$value.dashArray[i];
3262
+ if (typeof dash === "string" && isAlias(dash)) {
3263
+ let node = getObjMembers(options.node).dashArray || options.node;
3264
+ if (node.type === "Array") node = node?.elements?.[i]?.value || node;
3265
+ const { resolvedToken } = resolveAlias(dash, {
3266
+ ...options,
3267
+ token,
3268
+ expectedType: ["dimension"],
3269
+ node
3270
+ });
3271
+ token.mode[mode].$value.dashArray[i] = resolvedToken.$value;
3272
+ }
3273
+ }
3274
+ }
3275
+ function applyTransitionPartialAlias(token, mode, options) {
3276
+ for (const [k, v] of Object.entries(token.mode[mode].$value)) if (typeof v === "string" && isAlias(v)) {
3277
+ token.mode[mode].partialAliasOf ??= {};
3278
+ const expectedType = {
3279
+ duration: ["duration"],
3280
+ delay: ["duration"],
3281
+ timingFunction: ["cubicBezier"]
3282
+ }[k];
3283
+ const node = getObjMembers(options.node)[k] || options.node;
3284
+ const { resolvedToken } = resolveAlias(v, {
3285
+ ...options,
3286
+ token,
3287
+ expectedType,
3288
+ node
3289
+ });
3290
+ token.mode[mode].partialAliasOf[k] = parseAlias(v);
3291
+ if (mode === ".") {
3292
+ token.partialAliasOf ??= {};
3293
+ token.partialAliasOf[k] = parseAlias(v);
3294
+ }
3295
+ token.mode[mode].$value[k] = resolvedToken.$value;
3296
+ }
3297
+ }
3298
+ function applyTypographyPartialAlias(token, mode, options) {
3299
+ for (const [k, v] of Object.entries(token.mode[mode].$value)) if (typeof v === "string" && isAlias(v)) {
3300
+ token.partialAliasOf ??= {};
3301
+ token.mode[mode].partialAliasOf ??= {};
3302
+ const expectedType = {
3303
+ fontFamily: ["fontFamily"],
3304
+ fontSize: ["dimension"],
3305
+ fontWeight: ["fontWeight"],
3306
+ letterSpacing: ["dimension"],
3307
+ lineHeight: ["dimension", "number"]
3308
+ }[k] || ["string"];
3309
+ const node = getObjMembers(options.node)[k] || options.node;
3310
+ const { resolvedToken } = resolveAlias(v, {
3311
+ ...options,
3312
+ token,
3313
+ expectedType,
3314
+ node
3315
+ });
3316
+ token.mode[mode].partialAliasOf[k] = parseAlias(v);
3317
+ if (mode === ".") token.partialAliasOf[k] = parseAlias(v);
3318
+ token.mode[mode].$value[k] = resolvedToken.$value;
3319
+ }
3320
+ }
3321
+
3322
+ //#endregion
3323
+ //#region src/parse/normalize.ts
3324
+ const FONT_WEIGHT_MAP = {
3325
+ thin: 100,
3326
+ hairline: 100,
3327
+ "extra-light": 200,
3328
+ "ultra-light": 200,
3329
+ light: 300,
3330
+ normal: 400,
3331
+ regular: 400,
3332
+ book: 400,
3333
+ medium: 500,
3334
+ "semi-bold": 600,
3335
+ "demi-bold": 600,
3336
+ bold: 700,
3337
+ "extra-bold": 800,
3338
+ "ultra-bold": 800,
3339
+ black: 900,
3340
+ heavy: 900,
3341
+ "extra-black": 950,
3342
+ "ultra-black": 950
3343
+ };
3344
+ const NUMBER_WITH_UNIT_RE = /(-?\d*\.?\d+)(.*)/;
3345
+ /** Fill in defaults, and return predictable shapes for tokens */
3346
+ function normalizeValue(token) {
3347
+ if (typeof token.$value === "string" && isAlias(token.$value)) return token.$value;
3348
+ switch (token.$type) {
3349
+ case "boolean": return !!token.$value;
3350
+ case "border": {
3351
+ if (typeof token.$value === "string") return token.$value;
3352
+ return {
3353
+ color: normalizeValue({
3354
+ $type: "color",
3355
+ $value: token.$value.color ?? "#000000"
3356
+ }),
3357
+ style: normalizeValue({
3358
+ $type: "strokeStyle",
3359
+ $value: token.$value.style ?? "solid"
3360
+ }),
3361
+ width: normalizeValue({
3362
+ $type: "dimension",
3363
+ $value: token.$value.width
3364
+ })
3365
+ };
3366
+ }
3367
+ case "color": {
3368
+ if (typeof token.$value === "string") return parseColor(token.$value);
3369
+ const newValue = {
3370
+ colorSpace: token.$value.colorSpace,
3371
+ components: token.$value.components ?? token.$value.channels,
3372
+ alpha: token.$value.alpha ?? 1
3373
+ };
3374
+ if ("hex" in token.$value) newValue.hex = token.$value.hex;
3375
+ return newValue;
3376
+ }
3377
+ case "cubicBezier": {
3378
+ if (typeof token.$value === "string") return token.$value;
3379
+ return token.$value.map((value) => typeof value === "number" ? normalizeValue({
3380
+ $type: "number",
3381
+ $value: value
3382
+ }) : value);
3383
+ }
3384
+ case "dimension": {
3385
+ if (token.$value === 0) return {
3386
+ value: 0,
3387
+ unit: "px"
3388
+ };
3389
+ if (typeof token.$value === "string") {
3390
+ const match = token.$value.match(NUMBER_WITH_UNIT_RE);
3391
+ return {
3392
+ value: Number.parseFloat(match?.[1] || token.$value),
3393
+ unit: match?.[2] || "px"
3394
+ };
3395
+ }
3396
+ return token.$value;
3397
+ }
3398
+ case "duration": {
3399
+ if (token.$value === 0) return {
3400
+ value: 0,
3401
+ unit: "ms"
3402
+ };
3403
+ if (typeof token.$value === "string") {
3404
+ const match = token.$value.match(NUMBER_WITH_UNIT_RE);
3405
+ return {
3406
+ value: Number.parseFloat(match?.[1] || token.$value),
3407
+ unit: match?.[2] || "ms"
3408
+ };
3409
+ }
3410
+ return token.$value;
3411
+ }
3412
+ case "fontFamily": return Array.isArray(token.$value) ? token.$value : [token.$value];
3413
+ case "fontWeight": {
3414
+ if (typeof token.$value === "string" && FONT_WEIGHT_MAP[token.$value]) return FONT_WEIGHT_MAP[token.$value];
3415
+ return Math.min(999, Math.max(1, typeof token.$value === "string" ? Number.parseInt(token.$value) : token.$value));
3416
+ }
3417
+ case "gradient": {
3418
+ if (typeof token.$value === "string") return token.$value;
3419
+ const output = [];
3420
+ for (let i = 0; i < token.$value.length; i++) {
3421
+ const stop = structuredClone(token.$value[i]);
3422
+ stop.color = normalizeValue({
3423
+ $type: "color",
3424
+ $value: stop.color
3425
+ });
3426
+ if (stop.position === void 0) stop.position = i / (token.$value.length - 1);
3427
+ output.push(stop);
3428
+ }
3429
+ return output;
3430
+ }
3431
+ case "number": return typeof token.$value === "number" ? token.$value : Number.parseFloat(token.$value);
3432
+ case "shadow": {
3433
+ if (typeof token.$value === "string") return token.$value;
3434
+ return (Array.isArray(token.$value) ? token.$value : [token.$value]).map((layer) => ({
3435
+ color: normalizeValue({
3436
+ $type: "color",
3437
+ $value: layer.color
3438
+ }),
3439
+ offsetX: normalizeValue({
3440
+ $type: "dimension",
3441
+ $value: layer.offsetX ?? {
3442
+ value: 0,
3443
+ unit: "px"
3444
+ }
3445
+ }),
3446
+ offsetY: normalizeValue({
3447
+ $type: "dimension",
3448
+ $value: layer.offsetY ?? {
3449
+ value: 0,
3450
+ unit: "px"
3451
+ }
3452
+ }),
3453
+ blur: normalizeValue({
3454
+ $type: "dimension",
3455
+ $value: layer.blur ?? {
3456
+ value: 0,
3457
+ unit: "px"
3458
+ }
3459
+ }),
3460
+ spread: normalizeValue({
3461
+ $type: "dimension",
3462
+ $value: layer.spread ?? {
3463
+ value: 0,
3464
+ unit: "px"
3465
+ }
3466
+ }),
3467
+ inset: layer.inset === true
3468
+ }));
3469
+ }
3470
+ case "strokeStyle": return token.$value;
3471
+ case "string": return String(token.$value);
3472
+ case "transition": {
3473
+ if (typeof token.$value === "string") return token.$value;
3474
+ return {
3475
+ duration: normalizeValue({
3476
+ $type: "duration",
3477
+ $value: token.$value.duration ?? 0
3478
+ }),
3479
+ delay: normalizeValue({
3480
+ $type: "duration",
3481
+ $value: token.$value.delay ?? 0
3482
+ }),
3483
+ timingFunction: normalizeValue({
3484
+ $type: "cubicBezier",
3485
+ $value: token.$value.timingFunction
3486
+ })
3487
+ };
3488
+ }
3489
+ case "typography": {
3490
+ if (typeof token.$value === "string") return token.$value;
3491
+ const output = {};
3492
+ for (const [k, $value] of Object.entries(token.$value)) switch (k) {
3493
+ case "fontFamily": {
3494
+ output[k] = normalizeValue({
3495
+ $type: "fontFamily",
3496
+ $value
3497
+ });
3498
+ break;
3499
+ }
3500
+ case "fontSize":
3501
+ case "letterSpacing": {
3502
+ output[k] = normalizeValue({
3503
+ $type: "dimension",
3504
+ $value
3505
+ });
3506
+ break;
3507
+ }
3508
+ case "lineHeight": {
3509
+ output[k] = normalizeValue({
3510
+ $type: typeof token.$value === "number" ? "number" : "dimension",
3511
+ $value
3512
+ });
3513
+ break;
3514
+ }
3515
+ default: {
3516
+ output[k] = $value;
3517
+ break;
3518
+ }
3519
+ }
3520
+ return output;
3521
+ }
3522
+ default: return token.$value;
3523
+ }
3524
+ }
3525
+
3526
+ //#endregion
3527
+ //#region src/parse/validate.ts
3528
+ const listFormat = new Intl.ListFormat("en-us", { type: "disjunction" });
3529
+ const VALID_COLORSPACES = new Set([
3530
+ "adobe-rgb",
3531
+ "display-p3",
3532
+ "hsl",
3533
+ "hwb",
3534
+ "lab",
3535
+ "lch",
3536
+ "oklab",
3537
+ "oklch",
3538
+ "prophoto",
3539
+ "rec2020",
3540
+ "srgb",
3541
+ "srgb-linear",
3542
+ "xyz",
3543
+ "xyz-d50",
3544
+ "xyz-d65"
3545
+ ]);
3546
+ const FONT_WEIGHT_VALUES = new Set([
3547
+ "thin",
3548
+ "hairline",
3549
+ "extra-light",
3550
+ "ultra-light",
3551
+ "light",
3552
+ "normal",
3553
+ "regular",
3554
+ "book",
3555
+ "medium",
3556
+ "semi-bold",
3557
+ "demi-bold",
3558
+ "bold",
3559
+ "extra-bold",
3560
+ "ultra-bold",
3561
+ "black",
3562
+ "heavy",
3563
+ "extra-black",
3564
+ "ultra-black"
3565
+ ]);
3566
+ const STROKE_STYLE_VALUES = new Set([
3567
+ "solid",
3568
+ "dashed",
3569
+ "dotted",
3570
+ "double",
3571
+ "groove",
3572
+ "ridge",
3573
+ "outset",
3574
+ "inset"
3575
+ ]);
3576
+ const STROKE_STYLE_LINE_CAP_VALUES = new Set([
3577
+ "round",
3578
+ "butt",
3579
+ "square"
3580
+ ]);
3581
+ /** Distinct from isAlias() in that this accepts malformed aliases */
3582
+ function isMaybeAlias(node) {
3583
+ if (node?.type === "String") return node.value.startsWith("{");
3584
+ return false;
3585
+ }
3586
+ /** Assert object members match given types */
3587
+ function validateMembersAs($value, properties, node, { filename, src, logger }) {
3588
+ const members = getObjMembers($value);
3589
+ for (const [name, value] of Object.entries(properties)) {
3590
+ const { validator, required } = value;
3591
+ if (!members[name]) {
3592
+ if (required) logger.error({
3593
+ group: "parser",
3594
+ label: "validate",
3595
+ message: `Missing required property "${name}"`,
3596
+ filename,
3597
+ node: $value,
3598
+ src
3599
+ });
3600
+ continue;
3601
+ }
3602
+ const memberValue = members[name];
3603
+ if (isMaybeAlias(memberValue)) validateAliasSyntax(memberValue, node, {
3604
+ filename,
3605
+ src,
3606
+ logger
3607
+ });
3608
+ else validator(memberValue, node, {
3609
+ filename,
3610
+ src,
3611
+ logger
3612
+ });
3613
+ }
3614
+ }
3615
+ /** Verify an Alias $value is formatted correctly */
3616
+ function validateAliasSyntax($value, _node, { filename, src, logger }) {
3617
+ if ($value.type !== "String" || !isAlias($value.value)) logger.error({
3618
+ group: "parser",
3619
+ label: "validate",
3620
+ message: `Invalid alias: ${print($value)}`,
3621
+ filename,
3622
+ node: $value,
3623
+ src
3624
+ });
3625
+ }
3626
+ /** Verify a Border token is valid */
3627
+ function validateBorder($value, node, { filename, src, logger }) {
3628
+ if ($value.type !== "Object") logger.error({
3629
+ group: "parser",
3630
+ label: "validate",
3631
+ message: `Expected object, received ${$value.type}`,
3632
+ filename,
3633
+ node: $value,
3634
+ src
3635
+ });
3636
+ else validateMembersAs($value, {
3637
+ color: {
3638
+ validator: validateColor,
3639
+ required: true
3640
+ },
3641
+ style: {
3642
+ validator: validateStrokeStyle,
3643
+ required: true
3644
+ },
3645
+ width: {
3646
+ validator: validateDimension,
3647
+ required: true
3648
+ }
3649
+ }, node, {
3650
+ filename,
3651
+ src,
3652
+ logger
3653
+ });
3654
+ }
3655
+ /** Verify a Color token is valid */
3656
+ function validateColor($value, node, { filename, src, logger }) {
3657
+ const baseMessage = {
3658
+ group: "parser",
3659
+ label: "validate",
3660
+ filename,
3661
+ node: $value,
3662
+ src
3663
+ };
3664
+ if ($value.type === "String") {
3665
+ if ($value.value === "") logger.error({
3666
+ ...baseMessage,
3667
+ message: "Expected color, received empty string"
3668
+ });
3669
+ } else if ($value.type === "Object") {
3670
+ const channelMemberI = $value.members.findIndex((m) => m.name.type === "String" && m.name.value === "channels");
3671
+ if (channelMemberI !== -1) {
3672
+ logger.warn({
3673
+ ...baseMessage,
3674
+ message: "\"channels\" is deprecated; rename \"channels\" to \"components\""
3675
+ });
3676
+ $value.members[channelMemberI].name.value = "components";
3677
+ }
3678
+ validateMembersAs($value, {
3679
+ colorSpace: {
3680
+ validator: (v) => {
3681
+ if (v.type !== "String") logger.error({
3682
+ ...baseMessage,
3683
+ message: `Expected string, received ${print(v)}`,
3684
+ node: v
3685
+ });
3686
+ if (!VALID_COLORSPACES.has(v.value)) logger.error({
3687
+ ...baseMessage,
3688
+ message: `Unsupported colorspace ${print(v)}`,
3689
+ node: v
3690
+ });
3691
+ },
3692
+ required: true
3693
+ },
3694
+ components: {
3695
+ validator: (v) => {
3696
+ if (v.type !== "Array") logger.error({
3697
+ ...baseMessage,
3698
+ message: `Expected array, received ${print(v)}`,
3699
+ node: v
3700
+ });
3701
+ else {
3702
+ if (v.elements?.length !== 3) logger.error({
3703
+ ...baseMessage,
3704
+ message: `Expected 3 components, received ${v.elements?.length ?? 0}`,
3705
+ node: v
3706
+ });
3707
+ for (const element of v.elements) if (element.value.type !== "Number") logger.error({
3708
+ ...baseMessage,
3709
+ message: `Expected number, received ${print(element.value)}`,
3710
+ node: element
3711
+ });
3712
+ }
3713
+ },
3714
+ required: true
3715
+ },
3716
+ hex: { validator: (v) => {
3717
+ if (v.type !== "String" || v.value.length === 6 || v.value.length === 8 || !/^#[a-f0-9]{3,8}$/i.test(v.value)) logger.error({
3718
+ ...baseMessage,
3719
+ message: `Invalid hex color ${print(v)}`,
3720
+ node: v
3721
+ });
3722
+ } },
3723
+ alpha: { validator: validateNumber }
3724
+ }, node, {
3725
+ filename,
3726
+ src,
3727
+ logger
3728
+ });
3729
+ } else logger.error({
3730
+ ...baseMessage,
3731
+ message: `Expected object, received ${$value.type}`,
3732
+ node: $value
3733
+ });
3734
+ }
3735
+ /** Verify a Cubic Bézier token is valid */
3736
+ function validateCubicBezier($value, _node, { filename, src, logger }) {
3737
+ const baseMessage = {
3738
+ group: "parser",
3739
+ label: "validate",
3740
+ filename,
3741
+ node: $value,
3742
+ src
3743
+ };
3744
+ if ($value.type !== "Array") logger.error({
3745
+ ...baseMessage,
3746
+ message: `Expected array of numbers, received ${print($value)}`
3747
+ });
3748
+ else if (!$value.elements.every((e) => e.value.type === "Number")) logger.error({
3749
+ ...baseMessage,
3750
+ message: "Expected an array of 4 numbers, received some non-numbers"
3751
+ });
3752
+ else if ($value.elements.length !== 4) logger.error({
3753
+ ...baseMessage,
3754
+ message: `Expected an array of 4 numbers, received ${$value.elements.length}`
3755
+ });
3756
+ }
3757
+ /** Verify a Dimension token is valid */
3758
+ function validateDimension($value, _node, { filename, src, logger }) {
3759
+ if ($value.type === "Number" && $value.value === 0) return;
3760
+ const baseMessage = {
3761
+ group: "parser",
3762
+ label: "validate",
3763
+ filename,
3764
+ node: $value,
3765
+ src
3766
+ };
3767
+ if ($value.type === "Object") {
3768
+ const { value: value$1, unit: unit$1 } = getObjMembers($value);
3769
+ if (!value$1) logger.error({
3770
+ ...baseMessage,
3771
+ message: "Missing required property \"value\"."
3772
+ });
3773
+ if (!unit$1) logger.error({
3774
+ ...baseMessage,
3775
+ message: "Missing required property \"unit\"."
3776
+ });
3777
+ if (value$1.type !== "Number") logger.error({
3778
+ ...baseMessage,
3779
+ message: `Expected number, received ${value$1.type}`,
3780
+ node: value$1
3781
+ });
3782
+ if (![
3783
+ "px",
3784
+ "em",
3785
+ "rem"
3786
+ ].includes(unit$1.value)) logger.error({
3787
+ ...baseMessage,
3788
+ message: `Expected unit "px", "em", or "rem", received ${print(unit$1)}`,
3789
+ node: unit$1
3790
+ });
3791
+ return;
3792
+ }
3793
+ if ($value.type !== "String") logger.error({
3794
+ ...baseMessage,
3795
+ message: `Expected string, received ${$value.type}`
3796
+ });
3797
+ const value = $value.value.match(/^-?[0-9.]+/)?.[0];
3798
+ const unit = $value.value.replace(value, "");
3799
+ if ($value.value === "") logger.error({
3800
+ ...baseMessage,
3801
+ message: "Expected dimension, received empty string"
3802
+ });
3803
+ else if (![
3804
+ "px",
3805
+ "em",
3806
+ "rem"
3807
+ ].includes(unit)) logger.error({
3808
+ ...baseMessage,
3809
+ message: `Expected unit "px", "em", or "rem", received ${JSON.stringify(unit || $value.value)}`
3810
+ });
3811
+ else if (!Number.isFinite(Number.parseFloat(value))) logger.error({
3812
+ ...baseMessage,
3813
+ message: `Expected dimension with units, received ${print($value)}`
3814
+ });
3815
+ }
3816
+ /** Verify a Duration token is valid */
3817
+ function validateDuration($value, _node, { filename, src, logger }) {
3818
+ if ($value.type === "Number" && $value.value === 0) return;
3819
+ const baseMessage = {
3820
+ group: "parser",
3821
+ label: "validate",
3822
+ filename,
3823
+ node: $value,
3824
+ src
3825
+ };
3826
+ if ($value.type === "Object") {
3827
+ const { value: value$1, unit: unit$1 } = getObjMembers($value);
3828
+ if (!value$1) logger.error({
3829
+ ...baseMessage,
3830
+ message: "Missing required property \"value\"."
3831
+ });
3832
+ if (!unit$1) logger.error({
3833
+ ...baseMessage,
3834
+ message: "Missing required property \"unit\"."
3835
+ });
3836
+ if (value$1?.type !== "Number") logger.error({
3837
+ ...baseMessage,
3838
+ message: `Expected number, received ${value$1?.type}`,
3839
+ node: value$1
3840
+ });
3841
+ if (!["ms", "s"].includes(unit$1.value)) logger.error({
3842
+ ...baseMessage,
3843
+ message: `Expected unit "ms" or "s", received ${print(unit$1)}`,
3844
+ node: unit$1
3845
+ });
3846
+ return;
3847
+ }
3848
+ if ($value.type !== "String") logger.error({
3849
+ ...baseMessage,
3850
+ message: `Expected string, received ${$value.type}`
3851
+ });
3852
+ const value = $value.value.match(/^-?[0-9.]+/)?.[0];
3853
+ const unit = $value.value.replace(value, "");
3854
+ if ($value.value === "") logger.error({
3855
+ ...baseMessage,
3856
+ message: "Expected duration, received empty string"
3857
+ });
3858
+ else if (!["ms", "s"].includes(unit)) logger.error({
3859
+ ...baseMessage,
3860
+ message: `Expected unit "ms" or "s", received ${JSON.stringify(unit || $value.value)}`
3861
+ });
3862
+ else if (!Number.isFinite(Number.parseFloat(value))) logger.error({
3863
+ ...baseMessage,
3864
+ message: `Expected duration with units, received ${print($value)}`
3865
+ });
3866
+ }
3867
+ /** Verify a Font Family token is valid */
3868
+ function validateFontFamily($value, _node, { filename, src, logger }) {
3869
+ const baseMessage = {
3870
+ group: "parser",
3871
+ label: "validate",
3872
+ filename,
3873
+ node: $value,
3874
+ src
3875
+ };
3876
+ if ($value.type !== "String" && $value.type !== "Array") logger.error({
3877
+ ...baseMessage,
3878
+ message: `Expected string or array of strings, received ${$value.type}`
3879
+ });
3880
+ if ($value.type === "String" && $value.value === "") logger.error({
3881
+ ...baseMessage,
3882
+ message: "Expected font family name, received empty string"
3883
+ });
3884
+ if ($value.type === "Array" && !$value.elements.every((e) => e.value.type === "String" && e.value.value !== "")) logger.error({
3885
+ ...baseMessage,
3886
+ message: "Expected an array of strings, received some non-strings or empty strings"
3887
+ });
3888
+ }
3889
+ /** Verify a Font Weight token is valid */
3890
+ function validateFontWeight($value, _node, { filename, src, logger }) {
3891
+ const baseMessage = {
3892
+ group: "parser",
3893
+ label: "validate",
3894
+ filename,
3895
+ node: $value,
3896
+ src
3897
+ };
3898
+ if ($value.type !== "String" && $value.type !== "Number") logger.error({
3899
+ ...baseMessage,
3900
+ message: `Expected a font weight name or number 0–1000, received ${$value.type}`
3901
+ });
3902
+ if ($value.type === "String" && !FONT_WEIGHT_VALUES.has($value.value)) logger.error({
3903
+ ...baseMessage,
3904
+ message: `Unknown font weight ${print($value)}. Expected one of: ${listFormat.format([...FONT_WEIGHT_VALUES])}.`
3905
+ });
3906
+ if ($value.type === "Number" && ($value.value < 0 || $value.value > 1e3)) logger.error({
3907
+ ...baseMessage,
3908
+ message: `Expected number 0–1000, received ${print($value)}`
3909
+ });
3910
+ }
3911
+ /** Verify a Gradient token is valid */
3912
+ function validateGradient($value, _node, { filename, src, logger }) {
3913
+ const baseMessage = {
3914
+ group: "parser",
3915
+ label: "validate",
3916
+ filename,
3917
+ node: $value,
3918
+ src
3919
+ };
3920
+ if ($value.type !== "Array") logger.error({
3921
+ ...baseMessage,
3922
+ message: `Expected array of gradient stops, received ${$value.type}`
3923
+ });
3924
+ else for (let i = 0; i < $value.elements.length; i++) {
3925
+ const element = $value.elements[i];
3926
+ if (element.value.type !== "Object") {
3927
+ logger.error({
3928
+ ...baseMessage,
3929
+ message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
3930
+ node: element
3931
+ });
3932
+ break;
3933
+ }
3934
+ validateMembersAs(element.value, {
3935
+ color: {
3936
+ validator: validateColor,
3937
+ required: true
3938
+ },
3939
+ position: {
3940
+ validator: validateNumber,
3941
+ required: true
3942
+ }
3943
+ }, element, {
3944
+ filename,
3945
+ src,
3946
+ logger
3947
+ });
3948
+ }
3949
+ }
3950
+ /** Verify a Number token is valid */
3951
+ function validateNumber($value, _node, { filename, src, logger }) {
3952
+ if ($value.type !== "Number") logger.error({
3953
+ group: "parser",
3954
+ label: "validate",
3955
+ message: `Expected number, received ${$value.type}`,
3956
+ filename,
3957
+ node: $value,
3958
+ src
3959
+ });
3960
+ }
3961
+ /** Verify a Boolean token is valid */
3962
+ function validateBoolean($value, _node, { filename, src, logger }) {
3963
+ if ($value.type !== "Boolean") logger.error({
3964
+ group: "parser",
3965
+ label: "validate",
3966
+ message: `Expected boolean, received ${$value.type}`,
3967
+ filename,
3968
+ node: $value,
3969
+ src
3970
+ });
3971
+ }
3972
+ /** Verify a Shadow token’s value is valid */
3973
+ function validateShadowLayer($value, node, { filename, src, logger }) {
3974
+ if ($value.type !== "Object") logger.error({
3975
+ group: "parser",
3976
+ label: "validate",
3977
+ message: `Expected Object, received ${$value.type}`,
3978
+ filename,
3979
+ node: $value,
3980
+ src
3981
+ });
3982
+ else validateMembersAs($value, {
3983
+ color: {
3984
+ validator: validateColor,
3985
+ required: true
3986
+ },
3987
+ offsetX: {
3988
+ validator: validateDimension,
3989
+ required: true
3990
+ },
3991
+ offsetY: {
3992
+ validator: validateDimension,
3993
+ required: true
3994
+ },
3995
+ blur: { validator: validateDimension },
3996
+ spread: { validator: validateDimension },
3997
+ inset: { validator: validateBoolean }
3998
+ }, node, {
3999
+ filename,
4000
+ src,
4001
+ logger
4002
+ });
4003
+ }
4004
+ /** Verify a Stroke Style token is valid. */
4005
+ function validateStrokeStyle($value, node, { filename, src, logger }) {
4006
+ const baseMessage = {
4007
+ group: "parser",
4008
+ label: "validate",
4009
+ filename,
4010
+ node: $value,
4011
+ src
4012
+ };
4013
+ if ($value.type === "String") {
4014
+ if (!STROKE_STYLE_VALUES.has($value.value)) logger.error({
4015
+ ...baseMessage,
4016
+ message: `Unknown stroke style ${print($value)}. Expected one of: ${listFormat.format([...STROKE_STYLE_VALUES])}.`
4017
+ });
4018
+ } else if ($value.type === "Object") {
4019
+ const strokeMembers = getObjMembers($value);
4020
+ for (const property of ["dashArray", "lineCap"]) if (!strokeMembers[property]) logger.error({
4021
+ ...baseMessage,
4022
+ message: `Missing required property "${property}"`
4023
+ });
4024
+ const { lineCap, dashArray } = strokeMembers;
4025
+ if (lineCap?.type !== "String" || !STROKE_STYLE_LINE_CAP_VALUES.has(lineCap.value)) logger.error({
4026
+ ...baseMessage,
4027
+ message: `Unknown lineCap value ${print(lineCap)}. Expected one of: ${listFormat.format([...STROKE_STYLE_LINE_CAP_VALUES])}.`,
4028
+ node
4029
+ });
4030
+ 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, {
4031
+ logger,
4032
+ src
4033
+ });
4034
+ else validateDimension(element.value, node, {
4035
+ logger,
4036
+ src
4037
+ });
4038
+ else logger.error({
4039
+ ...baseMessage,
4040
+ message: "Expected array of strings, recieved some non-strings or empty strings.",
4041
+ node: element
4042
+ });
4043
+ else logger.error({
4044
+ ...baseMessage,
4045
+ message: `Expected array of strings, received ${dashArray.type}`
4046
+ });
4047
+ } else logger.error({
4048
+ ...baseMessage,
4049
+ message: `Expected string or object, received ${$value.type}`
4050
+ });
4051
+ }
4052
+ /** Verify a Transition token is valid */
4053
+ function validateTransition($value, node, { filename, src, logger }) {
4054
+ if ($value.type !== "Object") logger.error({
4055
+ group: "parser",
4056
+ label: "validate",
4057
+ message: `Expected object, received ${$value.type}`,
4058
+ filename,
4059
+ node: $value,
4060
+ src
4061
+ });
4062
+ else validateMembersAs($value, {
4063
+ duration: {
4064
+ validator: validateDuration,
4065
+ required: true
4066
+ },
4067
+ delay: {
4068
+ validator: validateDuration,
4069
+ required: false
4070
+ },
4071
+ timingFunction: {
4072
+ validator: validateCubicBezier,
4073
+ required: true
4074
+ }
4075
+ }, node, {
4076
+ filename,
4077
+ src,
4078
+ logger
4079
+ });
4080
+ }
4081
+ /**
4082
+ * Validate a MemberNode (the entire token object, plus its key in the parent
4083
+ * object) to see if it’s a valid DTCG token or not. Keeping the parent key
4084
+ * really helps in debug messages.
4085
+ */
4086
+ function validateTokenMemberNode(node, { filename, src, logger }) {
4087
+ const baseMessage = {
4088
+ group: "parser",
4089
+ label: "validate",
4090
+ filename,
4091
+ node,
4092
+ src
4093
+ };
4094
+ if (node.type !== "Member" && node.type !== "Object") logger.error({
4095
+ ...baseMessage,
4096
+ message: `Expected Object, received ${JSON.stringify(node.type)}`
4097
+ });
4098
+ const rootMembers = node.value.type === "Object" ? getObjMembers(node.value) : {};
4099
+ const $value = rootMembers.$value;
4100
+ const $type = rootMembers.$type;
4101
+ if (!$value) logger.error({
4102
+ ...baseMessage,
4103
+ message: "Token missing $value"
4104
+ });
4105
+ if (isMaybeAlias($value)) {
4106
+ validateAliasSyntax($value, node, {
4107
+ logger,
4108
+ src
4109
+ });
4110
+ return;
4111
+ }
4112
+ if (!$type) logger.error({
4113
+ ...baseMessage,
4114
+ message: "Token missing $type"
4115
+ });
4116
+ switch ($type.value) {
4117
+ case "color": {
4118
+ validateColor($value, node, {
4119
+ logger,
4120
+ src
4121
+ });
4122
+ break;
4123
+ }
4124
+ case "cubicBezier": {
4125
+ validateCubicBezier($value, node, {
4126
+ logger,
4127
+ src
4128
+ });
4129
+ break;
4130
+ }
4131
+ case "dimension": {
4132
+ validateDimension($value, node, {
4133
+ logger,
4134
+ src
4135
+ });
4136
+ break;
4137
+ }
4138
+ case "duration": {
4139
+ validateDuration($value, node, {
4140
+ logger,
4141
+ src
4142
+ });
4143
+ break;
4144
+ }
4145
+ case "fontFamily": {
4146
+ validateFontFamily($value, node, {
4147
+ logger,
4148
+ src
4149
+ });
4150
+ break;
4151
+ }
4152
+ case "fontWeight": {
4153
+ validateFontWeight($value, node, {
4154
+ logger,
4155
+ src
4156
+ });
4157
+ break;
4158
+ }
4159
+ case "number": {
4160
+ validateNumber($value, node, {
4161
+ logger,
4162
+ src
4163
+ });
4164
+ break;
4165
+ }
4166
+ case "shadow": {
4167
+ if ($value.type === "Object") validateShadowLayer($value, node, {
4168
+ logger,
4169
+ src
4170
+ });
4171
+ else if ($value.type === "Array") for (const element of $value.elements) validateShadowLayer(element.value, $value, {
4172
+ logger,
4173
+ src
4174
+ });
4175
+ else logger.error({
4176
+ ...baseMessage,
4177
+ message: `Expected shadow object or array of shadow objects, received ${$value.type}`,
4178
+ node: $value
4179
+ });
4180
+ break;
4181
+ }
4182
+ case "boolean": {
4183
+ if ($value.type !== "Boolean") logger.error({
4184
+ ...baseMessage,
4185
+ message: `Expected boolean, received ${$value.type}`,
4186
+ node: $value
4187
+ });
4188
+ break;
4189
+ }
4190
+ case "link": {
4191
+ if ($value.type !== "String") logger.error({
4192
+ ...baseMessage,
4193
+ message: `Expected string, received ${$value.type}`,
4194
+ node: $value
4195
+ });
4196
+ else if ($value.value === "") logger.error({
4197
+ ...baseMessage,
4198
+ message: "Expected URL, received empty string",
4199
+ node: $value
4200
+ });
4201
+ break;
4202
+ }
4203
+ case "string": {
4204
+ if ($value.type !== "String") logger.error({
4205
+ ...baseMessage,
4206
+ message: `Expected string, received ${$value.type}`,
4207
+ node: $value
4208
+ });
4209
+ break;
4210
+ }
4211
+ case "border": {
4212
+ validateBorder($value, node, {
4213
+ filename,
4214
+ src,
4215
+ logger
4216
+ });
4217
+ break;
4218
+ }
4219
+ case "gradient": {
4220
+ validateGradient($value, node, {
4221
+ filename,
4222
+ src,
4223
+ logger
4224
+ });
4225
+ break;
4226
+ }
4227
+ case "strokeStyle": {
4228
+ validateStrokeStyle($value, node, {
4229
+ filename,
4230
+ src,
4231
+ logger
4232
+ });
4233
+ break;
4234
+ }
4235
+ case "transition": {
4236
+ validateTransition($value, node, {
4237
+ filename,
4238
+ src,
4239
+ logger
4240
+ });
4241
+ break;
4242
+ }
4243
+ case "typography": {
4244
+ if ($value.type !== "Object") {
4245
+ logger.error({
4246
+ ...baseMessage,
4247
+ message: `Expected object, received ${$value.type}`,
4248
+ node: $value
4249
+ });
4250
+ break;
4251
+ }
4252
+ if ($value.members.length === 0) logger.error({
4253
+ ...baseMessage,
4254
+ message: "Empty typography token. Must contain at least 1 property.",
4255
+ node: $value
4256
+ });
4257
+ validateMembersAs($value, {
4258
+ fontFamily: { validator: validateFontFamily },
4259
+ fontWeight: { validator: validateFontWeight }
4260
+ }, node, {
4261
+ filename,
4262
+ src,
4263
+ logger
4264
+ });
4265
+ break;
4266
+ }
4267
+ default: break;
4268
+ }
4269
+ }
4270
+ /** Return any token node with its inherited $type */
4271
+ function getInheritedType(node, { subpath, $typeInheritance }) {
4272
+ if (node.value.type !== "Object") return;
4273
+ const $type = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$type");
4274
+ const $value = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$value");
4275
+ if ($typeInheritance && $type && !$value) $typeInheritance[subpath.join(".") || "."] = $type;
4276
+ const id = subpath.join(".");
4277
+ let parent$type;
4278
+ let longestPath = "";
4279
+ for (const [k, v] of Object.entries($typeInheritance ?? {})) if (k === "." || id.startsWith(k)) {
4280
+ if (k.length > longestPath.length) {
4281
+ parent$type = v;
4282
+ longestPath = k;
4283
+ }
4284
+ }
4285
+ return parent$type;
4286
+ }
4287
+ /**
4288
+ * Validate does a little more than validate; it also converts to TokenNormalized
4289
+ * and sets up the basic data structure. But aliases are unresolved, and we need
4290
+ * a 2nd normalization pass afterward.
4291
+ */
4292
+ function validateTokenNode(node, { config, filename, logger, parent, inheritedTypeNode, src, subpath }) {
4293
+ if (subpath.includes("$value") || node.value.type !== "Object") return;
4294
+ const members = getObjMembers(node.value);
4295
+ if (!members.$value || subpath.includes("$extensions") || subpath.includes("$deps")) return;
4296
+ const id = subpath.join(".");
4297
+ if (!subpath.includes(".$value") && members.value) logger.warn({
4298
+ group: "parser",
4299
+ label: "validate",
4300
+ message: `Group ${id} has "value". Did you mean "$value"?`,
4301
+ filename,
4302
+ node,
4303
+ src
4304
+ });
4305
+ const nodeWithType = structuredClone(node);
4306
+ let $type = members.$type?.type === "String" && members.$type.value || void 0;
4307
+ if (inheritedTypeNode && !members.$type) {
4308
+ injectObjMembers(nodeWithType.value, [inheritedTypeNode]);
4309
+ $type = inheritedTypeNode.value.value;
4310
+ }
4311
+ validateTokenMemberNode(nodeWithType, {
4312
+ filename,
4313
+ src,
4314
+ logger
4315
+ });
4316
+ const $deprecated = members.$deprecated && evaluate(members.$deprecated);
4317
+ if (config.ignore.deprecated && $deprecated || config.ignore.tokens && isTokenMatch(id, config.ignore.tokens)) return;
4318
+ const group = {
4319
+ id: splitID(id).group,
4320
+ tokens: []
4321
+ };
4322
+ if (inheritedTypeNode && inheritedTypeNode.value.type === "String") group.$type = inheritedTypeNode.value.value;
4323
+ const groupMembers = getObjMembers(parent);
4324
+ if (groupMembers.$description) group.$description = evaluate(groupMembers.$description);
4325
+ if (groupMembers.$extensions) group.$extensions = evaluate(groupMembers.$extensions);
4326
+ const $value = evaluate(members.$value);
4327
+ const token = {
4328
+ $type,
4329
+ $value,
4330
+ id,
4331
+ mode: {},
4332
+ originalValue: evaluate(node.value),
4333
+ group,
4334
+ source: {
4335
+ loc: filename?.href,
4336
+ node: nodeWithType.value
4337
+ }
4338
+ };
4339
+ if (members.$description?.type === "String" && members.$description.value) token.$description = members.$description.value;
4340
+ const extensions = members.$extensions ? getObjMembers(members.$extensions) : void 0;
4341
+ const modeValues = extensions?.mode ? getObjMembers(extensions.mode) : {};
4342
+ for (const mode of [".", ...Object.keys(modeValues)]) {
4343
+ const modeValue = mode === "." ? token.$value : evaluate(modeValues[mode]);
4344
+ token.mode[mode] = {
4345
+ $value: modeValue,
4346
+ originalValue: modeValue,
4347
+ source: {
4348
+ loc: filename?.href,
4349
+ node: modeValues[mode]
4350
+ }
4351
+ };
4352
+ }
4353
+ return token;
4354
+ }
4355
+
4356
+ //#endregion
4357
+ //#region src/parse/index.ts
4358
+ /** Parse */
4359
+ async function parse(_input, { logger = new Logger(), skipLint = false, config = {}, continueOnError = false, yamlToMomoa, transform, _sources = {} } = {}) {
4360
+ const input = Array.isArray(_input) ? _input : [_input];
4361
+ let tokensSet = {};
4362
+ if (!Array.isArray(input)) logger.error({
4363
+ group: "parser",
4364
+ label: "init",
4365
+ message: "Input must be an array of input objects."
4366
+ });
4367
+ await Promise.all(input.map(async (src, i) => {
4368
+ if (!src || typeof src !== "object") logger.error({
4369
+ group: "parser",
4370
+ label: "init",
4371
+ message: `Input (${i}) must be an object.`
4372
+ });
4373
+ if (!src.src || typeof src.src !== "string" && typeof src.src !== "object") logger.error({
4374
+ message: `Input (${i}) missing "src" with a JSON/YAML string, or JSON object.`,
4375
+ group: "parser",
4376
+ label: "init"
4377
+ });
4378
+ if (src.filename) {
4379
+ if (!(src.filename instanceof URL)) logger.error({
4380
+ message: `Input (${i}) "filename" must be a URL (remote or file URL).`,
4381
+ group: "parser",
4382
+ label: "init"
4383
+ });
4384
+ if (_sources[src.filename.href]) return;
4385
+ }
4386
+ const result = await parseSingle(src.src, {
4387
+ filename: src.filename,
4388
+ logger,
4389
+ config,
4390
+ skipLint,
4391
+ continueOnError,
4392
+ yamlToMomoa,
4393
+ transform
4394
+ });
4395
+ tokensSet = Object.assign(tokensSet, result.tokens);
4396
+ if (src.filename) _sources[src.filename.href] = {
4397
+ filename: src.filename,
4398
+ src: result.src,
4399
+ document: result.document
4400
+ };
4401
+ }));
4402
+ const totalStart = performance.now();
4403
+ const aliasesStart = performance.now();
4404
+ let aliasCount = 0;
4405
+ for (const [id, token] of Object.entries(tokensSet)) {
4406
+ applyAliases(token, {
4407
+ tokensSet,
4408
+ filename: _sources[token.source.loc]?.filename,
4409
+ src: _sources[token.source.loc]?.src,
4410
+ node: getObjMembers(token.source.node).$value || token.source.node,
4411
+ logger
4412
+ });
4413
+ aliasCount++;
4414
+ const { group: parentGroup } = splitID(id);
4415
+ if (parentGroup) for (const siblingID of Object.keys(tokensSet)) {
4416
+ const { group: siblingGroup } = splitID(siblingID);
4417
+ if (siblingGroup?.startsWith(parentGroup)) token.group.tokens.push(siblingID);
4418
+ }
4419
+ }
4420
+ logger.debug({
4421
+ message: `Resolved ${aliasCount} aliases`,
4422
+ group: "parser",
4423
+ label: "alias",
4424
+ timing: performance.now() - aliasesStart
4425
+ });
4426
+ logger.debug({
4427
+ message: "Finish all parser tasks",
4428
+ group: "parser",
4429
+ label: "core",
4430
+ timing: performance.now() - totalStart
4431
+ });
4432
+ if (continueOnError) {
4433
+ const { errorCount } = logger.stats();
4434
+ if (errorCount > 0) logger.error({
4435
+ group: "parser",
4436
+ message: `Parser encountered ${errorCount} ${pluralize(errorCount, "error", "errors")}. Exiting.`
4437
+ });
4438
+ }
4439
+ return {
4440
+ tokens: tokensSet,
4441
+ sources: Object.values(_sources)
4442
+ };
4443
+ }
4444
+ /** Parse a single input */
4445
+ async function parseSingle(input, { filename, logger, config, skipLint, continueOnError = false, transform, yamlToMomoa }) {
4446
+ const startParsing = performance.now();
4447
+ let { src, document } = toMomoa(input, {
4448
+ filename,
4449
+ logger,
4450
+ continueOnError,
4451
+ yamlToMomoa
4452
+ });
4453
+ logger.debug({
4454
+ group: "parser",
4455
+ label: "json",
4456
+ message: "Finish JSON parsing",
4457
+ timing: performance.now() - startParsing
4458
+ });
4459
+ const tokensSet = {};
4460
+ if (transform?.root) {
4461
+ const json = typeof input === "string" ? JSON.parse(input) : input;
4462
+ const result = transform?.root(json, ".", document);
4463
+ if (result) {
4464
+ const reRunResult = toMomoa(result, {
4465
+ filename,
4466
+ logger,
4467
+ continueOnError
4468
+ });
4469
+ src = reRunResult.src;
4470
+ document = reRunResult.document;
4471
+ }
4472
+ }
4473
+ let tokenCount = 0;
4474
+ const startValidate = performance.now();
4475
+ const $typeInheritance = {};
4476
+ traverse(document, { enter(node, parent, subpath) {
4477
+ if (node.type === "Document" && node.body.type === "Object" && node.body.members) {
4478
+ const { members: rootMembers } = node.body;
4479
+ const root$type = rootMembers.find((m) => m.name.type === "String" && m.name.value === "$type");
4480
+ const root$value = rootMembers.find((m) => m.name.type === "String" && m.name.value === "$value");
4481
+ if (root$type && !root$value) $typeInheritance["."] = root$type;
4482
+ }
4483
+ if (node.type === "Object" && subpath.length && !node.members.some((m) => m.name.type === "String" && m.name.value === "$value") && !subpath.includes("$value") && !subpath.includes("$extensions")) {
4484
+ if (transform?.group) {
4485
+ const newJSON = transform?.group(evaluate(node), subpath.join("."), node);
4486
+ if (newJSON) replaceObjMembers(node, parseJSON(newJSON));
4487
+ }
4488
+ }
4489
+ if (node.type === "Member") {
4490
+ const inheritedTypeNode = getInheritedType(node, {
4491
+ subpath,
4492
+ $typeInheritance
4493
+ });
4494
+ if (node.value.type === "Object") {
4495
+ const $type = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$type") || inheritedTypeNode;
4496
+ const $value = node.value.members.find((m) => m.name.type === "String" && m.name.value === "$value");
4497
+ if ($value && $type?.value.type === "String" && transform?.[$type.value.value]) {
4498
+ const result = transform[$type.value.value]?.(evaluate(node.value), subpath.join("."), node);
4499
+ if (result) node.value = parseJSON(result).body;
4500
+ }
4501
+ const token = validateTokenNode(node, {
4502
+ filename,
4503
+ src,
4504
+ config,
4505
+ logger,
4506
+ parent,
4507
+ subpath,
4508
+ transform,
4509
+ inheritedTypeNode
4510
+ });
4511
+ if (token) {
4512
+ tokensSet[token.id] = token;
4513
+ tokenCount++;
4514
+ }
4515
+ }
4516
+ }
4517
+ } });
4518
+ logger.debug({
4519
+ message: `Validated ${tokenCount} tokens`,
4520
+ group: "parser",
4521
+ label: "validate",
4522
+ timing: performance.now() - startValidate
4523
+ });
4524
+ const normalizeStart = performance.now();
4525
+ for (const [id, token] of Object.entries(tokensSet)) {
4526
+ try {
4527
+ tokensSet[id].$value = normalizeValue(token);
4528
+ } catch (err) {
4529
+ let { node } = token.source;
4530
+ const members = getObjMembers(node);
4531
+ if (members.$value) node = members.$value;
4532
+ logger.error({
4533
+ group: "parser",
4534
+ label: "normalize",
4535
+ message: err.message,
4536
+ filename,
4537
+ src,
4538
+ node,
4539
+ continueOnError
4540
+ });
4541
+ }
4542
+ for (const [mode, modeValue] of Object.entries(token.mode)) {
4543
+ if (mode === ".") continue;
4544
+ try {
4545
+ tokensSet[id].mode[mode].$value = normalizeValue({
4546
+ $type: token.$type,
4547
+ ...modeValue
4548
+ });
4549
+ } catch (err) {
4550
+ let { node } = token.source;
4551
+ const members = getObjMembers(node);
4552
+ if (members.$value) node = members.$value;
4553
+ logger.error({
4554
+ group: "parser",
4555
+ label: "normalize",
4556
+ message: err.message,
4557
+ filename,
4558
+ src,
4559
+ node: modeValue.source.node,
4560
+ continueOnError
4561
+ });
4562
+ }
4563
+ }
4564
+ }
4565
+ logger.debug({
4566
+ message: `Normalized ${tokenCount} tokens`,
4567
+ group: "parser",
4568
+ label: "normalize",
4569
+ timing: performance.now() - normalizeStart
4570
+ });
4571
+ if (!skipLint && config?.plugins?.length) {
4572
+ const lintStart = performance.now();
4573
+ await lintRunner({
4574
+ tokens: tokensSet,
4575
+ src,
4576
+ config,
4577
+ logger
4578
+ });
4579
+ logger.debug({
4580
+ message: `Linted ${tokenCount} tokens`,
4581
+ group: "parser",
4582
+ label: "lint",
4583
+ timing: performance.now() - lintStart
4584
+ });
4585
+ } else logger.debug({
4586
+ message: "Linting skipped",
4587
+ group: "parser",
4588
+ label: "lint"
4589
+ });
4590
+ return {
4591
+ tokens: tokensSet,
4592
+ document,
4593
+ src
4594
+ };
4595
+ }
4596
+
4597
+ //#endregion
4598
+ 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
4599
  //# sourceMappingURL=index.js.map