@terrazzo/parser 2.0.0-alpha.7 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/index.d.ts +39 -6
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +578 -512
  4. package/dist/index.js.map +1 -1
  5. package/package.json +3 -3
  6. package/src/build/index.ts +0 -209
  7. package/src/config.ts +0 -304
  8. package/src/index.ts +0 -95
  9. package/src/lib/code-frame.ts +0 -177
  10. package/src/lib/momoa.ts +0 -10
  11. package/src/lib/resolver-utils.ts +0 -35
  12. package/src/lint/index.ts +0 -142
  13. package/src/lint/plugin-core/index.ts +0 -103
  14. package/src/lint/plugin-core/lib/docs.ts +0 -3
  15. package/src/lint/plugin-core/rules/a11y-min-contrast.ts +0 -91
  16. package/src/lint/plugin-core/rules/a11y-min-font-size.ts +0 -66
  17. package/src/lint/plugin-core/rules/colorspace.ts +0 -108
  18. package/src/lint/plugin-core/rules/consistent-naming.ts +0 -65
  19. package/src/lint/plugin-core/rules/descriptions.ts +0 -43
  20. package/src/lint/plugin-core/rules/duplicate-values.ts +0 -85
  21. package/src/lint/plugin-core/rules/max-gamut.ts +0 -144
  22. package/src/lint/plugin-core/rules/required-children.ts +0 -106
  23. package/src/lint/plugin-core/rules/required-modes.ts +0 -75
  24. package/src/lint/plugin-core/rules/required-type.ts +0 -28
  25. package/src/lint/plugin-core/rules/required-typography-properties.ts +0 -65
  26. package/src/lint/plugin-core/rules/valid-boolean.ts +0 -41
  27. package/src/lint/plugin-core/rules/valid-border.ts +0 -57
  28. package/src/lint/plugin-core/rules/valid-color.ts +0 -265
  29. package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +0 -83
  30. package/src/lint/plugin-core/rules/valid-dimension.ts +0 -199
  31. package/src/lint/plugin-core/rules/valid-duration.ts +0 -123
  32. package/src/lint/plugin-core/rules/valid-font-family.ts +0 -68
  33. package/src/lint/plugin-core/rules/valid-font-weight.ts +0 -89
  34. package/src/lint/plugin-core/rules/valid-gradient.ts +0 -79
  35. package/src/lint/plugin-core/rules/valid-link.ts +0 -41
  36. package/src/lint/plugin-core/rules/valid-number.ts +0 -63
  37. package/src/lint/plugin-core/rules/valid-shadow.ts +0 -67
  38. package/src/lint/plugin-core/rules/valid-string.ts +0 -41
  39. package/src/lint/plugin-core/rules/valid-stroke-style.ts +0 -104
  40. package/src/lint/plugin-core/rules/valid-transition.ts +0 -61
  41. package/src/lint/plugin-core/rules/valid-typography.ts +0 -67
  42. package/src/logger.ts +0 -213
  43. package/src/parse/index.ts +0 -124
  44. package/src/parse/load.ts +0 -172
  45. package/src/parse/normalize.ts +0 -163
  46. package/src/parse/process.ts +0 -251
  47. package/src/parse/token.ts +0 -553
  48. package/src/resolver/create-synthetic-resolver.ts +0 -86
  49. package/src/resolver/index.ts +0 -7
  50. package/src/resolver/load.ts +0 -215
  51. package/src/resolver/normalize.ts +0 -133
  52. package/src/resolver/validate.ts +0 -375
  53. package/src/types.ts +0 -468
package/src/parse/load.ts DELETED
@@ -1,172 +0,0 @@
1
- import * as momoa from '@humanwhocodes/momoa';
2
- import {
3
- type BundleOptions,
4
- bundle,
5
- encodeFragment,
6
- getObjMember,
7
- type InputSource,
8
- type InputSourceWithDocument,
9
- replaceNode,
10
- traverse,
11
- } from '@terrazzo/json-schema-tools';
12
- import type { TokenNormalized, TokenNormalizedSet } from '@terrazzo/token-tools';
13
- import { toMomoa } from '../lib/momoa.js';
14
- import { filterResolverPaths } from '../lib/resolver-utils.js';
15
- import type Logger from '../logger.js';
16
- import { isLikelyResolver } from '../resolver/validate.js';
17
- import type { ParseOptions, TransformVisitors } from '../types.js';
18
- import { processTokens } from './process.js';
19
-
20
- /** Ephemeral format that only exists while parsing the document. This is not confirmed to be DTCG yet. */
21
- export interface IntermediaryToken {
22
- id: string;
23
- /** Was this token aliasing another? */
24
- $ref?: string;
25
- $type?: string;
26
- $description?: string;
27
- $deprecated?: string | boolean;
28
- $value: unknown;
29
- $extensions?: Record<string, unknown>;
30
- group: TokenNormalized['group'];
31
- aliasOf?: string;
32
- partialAliasOf?: Record<string, any> | any[];
33
- mode: Record<
34
- string,
35
- {
36
- $type?: string;
37
- $value: unknown;
38
- aliasOf?: string;
39
- partialAliasOf?: Record<string, any> | any[];
40
- source?: { filename?: URL; node: momoa.ObjectNode };
41
- }
42
- >;
43
- source: {
44
- filename?: URL;
45
- node: momoa.ObjectNode;
46
- };
47
- }
48
-
49
- export interface LoadOptions extends Pick<ParseOptions, 'config' | 'continueOnError' | 'yamlToMomoa' | 'transform'> {
50
- req: NonNullable<ParseOptions['req']>;
51
- logger: Logger;
52
- }
53
-
54
- export interface LoadSourcesResult {
55
- tokens: TokenNormalizedSet;
56
- sources: InputSourceWithDocument[];
57
- }
58
-
59
- /** Load from multiple entries, while resolving remote files */
60
- export async function loadSources(
61
- inputs: InputSource[],
62
- { config, logger, req, continueOnError, yamlToMomoa, transform }: LoadOptions,
63
- ): Promise<LoadSourcesResult> {
64
- const entry = { group: 'parser' as const, label: 'init' };
65
-
66
- // 1. Bundle root documents together
67
- const firstLoad = performance.now();
68
- let document = {} as momoa.DocumentNode;
69
-
70
- /** The original user inputs, in original order, with parsed ASTs */
71
- const sources = inputs.map((input, i) => ({
72
- ...input,
73
- document: {} as momoa.DocumentNode,
74
- filename: input.filename || new URL(`virtual:${i}`), // for objects created in memory, an index-based ID helps associate tokens with these
75
- }));
76
- /** The sources array, indexed by filename */
77
- let sourceByFilename: Record<string, InputSourceWithDocument> = {};
78
- try {
79
- const result = await bundle(sources, {
80
- req,
81
- parse: transform ? transformer(transform) : undefined,
82
- yamlToMomoa,
83
- });
84
- document = result.document;
85
- sourceByFilename = result.sources;
86
- for (const [filename, source] of Object.entries(result.sources)) {
87
- const i = sources.findIndex((s) => s.filename.href === filename);
88
- if (i === -1) {
89
- sources.push(source);
90
- } else {
91
- sources[i]!.src = source.src; // this is a sanitized source that is easier to work with
92
- sources[i]!.document = source.document;
93
- }
94
- }
95
- } catch (err) {
96
- let src = sources.find((s) => s.filename.href === (err as any).filename)?.src;
97
- if (src && typeof src !== 'string') {
98
- src = JSON.stringify(src, undefined, 2);
99
- }
100
- logger.error({
101
- ...entry,
102
- continueOnError,
103
- message: (err as Error).message,
104
- node: (err as any).node,
105
- src,
106
- });
107
- }
108
- logger.debug({ ...entry, message: `JSON loaded`, timing: performance.now() - firstLoad });
109
-
110
- const rootSource = {
111
- filename: sources[0]!.filename!,
112
- document,
113
- src: momoa.print(document, { indent: 2 }).replace(/\\\//g, '/'),
114
- };
115
-
116
- return {
117
- tokens: processTokens(rootSource, { config, logger, sources, sourceByFilename }),
118
- sources,
119
- };
120
- }
121
-
122
- function transformer(transform: TransformVisitors): BundleOptions['parse'] {
123
- return async (src, filename) => {
124
- let document = toMomoa(src);
125
- let lastPath = '#/';
126
- let last$type: string | undefined;
127
-
128
- if (transform.root) {
129
- const result = transform.root(document, { filename, parent: undefined, path: [] });
130
- if (result) {
131
- document = result as momoa.DocumentNode;
132
- }
133
- }
134
-
135
- const isResolver = isLikelyResolver(document);
136
- traverse(document, {
137
- enter(node, parent, rawPath) {
138
- const path = isResolver ? filterResolverPaths(rawPath) : rawPath;
139
- if (node.type !== 'Object' || !path.length) {
140
- return;
141
- }
142
- const ctx = { filename, parent, path };
143
- const next$type = getObjMember(node, '$type');
144
- if (next$type?.type === 'String') {
145
- const jsonPath = encodeFragment(path);
146
- if (jsonPath.startsWith(lastPath)) {
147
- last$type = next$type.value;
148
- }
149
- lastPath = jsonPath;
150
- }
151
- if (getObjMember(node, '$value')) {
152
- let result: any = transform.token?.(structuredClone(node), ctx);
153
- if (result) {
154
- replaceNode(node, result);
155
- result = undefined;
156
- }
157
- result = transform[last$type as keyof typeof transform]?.(structuredClone(node as any), ctx);
158
- if (result) {
159
- replaceNode(node, result);
160
- }
161
- } else if (!path.includes('$value')) {
162
- const result = transform.group?.(structuredClone(node), ctx);
163
- if (result) {
164
- replaceNode(node, result);
165
- }
166
- }
167
- },
168
- });
169
-
170
- return document;
171
- };
172
- }
@@ -1,163 +0,0 @@
1
- import type * as momoa from '@humanwhocodes/momoa';
2
- import { getObjMember } from '@terrazzo/json-schema-tools';
3
- import { FONT_WEIGHTS, isAlias, parseColor } from '@terrazzo/token-tools';
4
- import type Logger from '../logger.js';
5
-
6
- interface PreValidatedToken {
7
- id: string;
8
- $type: string;
9
- $value: unknown;
10
- mode: Record<string, { $value: unknown; source: { node: any; filename: string | undefined } }>;
11
- }
12
-
13
- /**
14
- * Normalize token value.
15
- * The reason for the “any” typing is this aligns various user-provided inputs to the type
16
- */
17
- export function normalize(token: PreValidatedToken, { logger, src }: { logger: Logger; src: string }) {
18
- const entry = { group: 'parser' as const, label: 'init', src };
19
-
20
- function normalizeFontFamily(value: unknown): string[] {
21
- return typeof value === 'string' ? [value] : (value as string[]);
22
- }
23
-
24
- function normalizeFontWeight(value: unknown): number {
25
- return (typeof value === 'string' && FONT_WEIGHTS[value as keyof typeof FONT_WEIGHTS]) || (value as number);
26
- }
27
-
28
- function normalizeColor(value: unknown, node: momoa.AnyNode | undefined) {
29
- if (typeof value === 'string' && !isAlias(value)) {
30
- logger.warn({
31
- ...entry,
32
- node,
33
- message: `${token.id}: string colors will be deprecated in a future version. Please update to object notation`,
34
- });
35
- try {
36
- return parseColor(value);
37
- } catch {
38
- return { colorSpace: 'srgb', components: [0, 0, 0], alpha: 1 };
39
- }
40
- } else if (value && typeof value === 'object') {
41
- if ((value as any).alpha === undefined) {
42
- (value as any).alpha = 1;
43
- }
44
- }
45
- return value;
46
- }
47
-
48
- switch (token.$type) {
49
- case 'color': {
50
- for (const mode of Object.keys(token.mode)) {
51
- token.mode[mode]!.$value = normalizeColor(token.mode[mode]!.$value, token.mode[mode]!.source.node);
52
- }
53
- token.$value = token.mode['.']!.$value;
54
- break;
55
- }
56
-
57
- case 'fontFamily': {
58
- for (const mode of Object.keys(token.mode)) {
59
- token.mode[mode]!.$value = normalizeFontFamily(token.mode[mode]!.$value);
60
- }
61
- token.$value = token.mode['.']!.$value;
62
- break;
63
- }
64
-
65
- case 'fontWeight': {
66
- for (const mode of Object.keys(token.mode)) {
67
- token.mode[mode]!.$value = normalizeFontWeight(token.mode[mode]!.$value);
68
- }
69
- token.$value = token.mode['.']!.$value;
70
- break;
71
- }
72
-
73
- case 'border': {
74
- for (const mode of Object.keys(token.mode)) {
75
- const border = token.mode[mode]!.$value as any;
76
- if (!border || typeof border !== 'object') {
77
- continue;
78
- }
79
- if (border.color) {
80
- border.color = normalizeColor(
81
- border.color,
82
- getObjMember(token.mode[mode]!.source.node as momoa.ObjectNode, 'color'),
83
- );
84
- }
85
- }
86
- token.$value = token.mode['.']!.$value;
87
- break;
88
- }
89
-
90
- case 'shadow': {
91
- for (const mode of Object.keys(token.mode)) {
92
- // normalize to array
93
- if (!Array.isArray(token.mode[mode]!.$value)) {
94
- token.mode[mode]!.$value = [token.mode[mode]!.$value];
95
- }
96
- const $value = token.mode[mode]!.$value as any[];
97
- for (let i = 0; i < $value.length; i++) {
98
- const shadow = $value[i]!;
99
- if (!shadow || typeof shadow !== 'object') {
100
- continue;
101
- }
102
- const shadowNode = (
103
- token.mode[mode]!.source.node.type === 'Array'
104
- ? token.mode[mode]!.source.node.elements[i]!.value
105
- : token.mode[mode]!.source.node
106
- ) as momoa.ObjectNode;
107
- if (shadow.color) {
108
- shadow.color = normalizeColor(shadow.color, getObjMember(shadowNode, 'color'));
109
- }
110
- if (!('inset' in shadow)) {
111
- shadow.inset = false;
112
- }
113
- }
114
- }
115
- token.$value = token.mode['.']!.$value;
116
- break;
117
- }
118
-
119
- case 'gradient': {
120
- for (const mode of Object.keys(token.mode)) {
121
- if (!Array.isArray(token.mode[mode]!.$value)) {
122
- continue;
123
- }
124
- const $value = token.mode[mode]!.$value as any[];
125
- for (let i = 0; i < $value.length; i++) {
126
- const stop = $value[i]!;
127
- if (!stop || typeof stop !== 'object') {
128
- continue;
129
- }
130
- const stopNode = (token.mode[mode]!.source.node as momoa.ArrayNode)?.elements?.[i]?.value as momoa.ObjectNode;
131
- if (stop.color) {
132
- stop.color = normalizeColor(stop.color, getObjMember(stopNode, 'color'));
133
- }
134
- }
135
- }
136
- token.$value = token.mode['.']!.$value;
137
- break;
138
- }
139
-
140
- case 'typography': {
141
- for (const mode of Object.keys(token.mode)) {
142
- const $value = token.mode[mode]!.$value as any;
143
- if (typeof $value !== 'object') {
144
- return;
145
- }
146
- for (const [k, v] of Object.entries($value)) {
147
- switch (k) {
148
- case 'fontFamily': {
149
- $value[k] = normalizeFontFamily(v);
150
- break;
151
- }
152
- case 'fontWeight': {
153
- $value[k] = normalizeFontWeight(v);
154
- break;
155
- }
156
- }
157
- }
158
- }
159
- token.$value = token.mode['.']!.$value;
160
- break;
161
- }
162
- }
163
- }
@@ -1,251 +0,0 @@
1
- import * as momoa from '@humanwhocodes/momoa';
2
- import {
3
- encodeFragment,
4
- findNode,
5
- getObjMember,
6
- type InputSourceWithDocument,
7
- mergeObjects,
8
- parseRef,
9
- replaceNode,
10
- traverse,
11
- } from '@terrazzo/json-schema-tools';
12
- import { type GroupNormalized, isAlias, type TokenNormalizedSet } from '@terrazzo/token-tools';
13
- import { filterResolverPaths } from '../lib/resolver-utils.js';
14
- import type Logger from '../logger.js';
15
- import { isLikelyResolver } from '../resolver/validate.js';
16
- import type { ConfigInit, RefMap } from '../types.js';
17
- import { normalize } from './normalize.js';
18
- import {
19
- aliasToGroupRef,
20
- graphAliases,
21
- groupFromNode,
22
- refToTokenID,
23
- resolveAliases,
24
- tokenFromNode,
25
- tokenRawValuesFromNode,
26
- } from './token.js';
27
-
28
- export interface ProcessTokensOptions {
29
- config: ConfigInit;
30
- logger: Logger;
31
- sourceByFilename: Record<string, InputSourceWithDocument>;
32
- sources: InputSourceWithDocument[];
33
- }
34
-
35
- export function processTokens(
36
- rootSource: InputSourceWithDocument,
37
- { config, logger, sourceByFilename }: ProcessTokensOptions,
38
- ): TokenNormalizedSet {
39
- const entry = { group: 'parser' as const, label: 'init' };
40
-
41
- // 1. Inline $refs to discover any additional tokens
42
- const refMap: RefMap = {};
43
- function resolveRef(node: momoa.StringNode, chain: string[]): momoa.AnyNode {
44
- const { subpath } = parseRef(node.value);
45
- if (!subpath) {
46
- logger.error({ ...entry, message: 'Can’t resolve $ref', node, src: rootSource.src });
47
- // exit
48
- }
49
- const next = findNode(rootSource.document, subpath);
50
- if (next?.type === 'Object') {
51
- const next$ref = getObjMember(next, '$ref');
52
- if (next$ref && next$ref.type === 'String') {
53
- if (chain.includes(next$ref.value)) {
54
- logger.error({
55
- ...entry,
56
- message: `Circular $ref detected: ${JSON.stringify(next$ref.value)}`,
57
- node: next$ref,
58
- src: rootSource.src,
59
- });
60
- }
61
- chain.push(next$ref.value);
62
- return resolveRef(next$ref, chain);
63
- }
64
- }
65
- return next;
66
- }
67
- const inlineStart = performance.now();
68
- traverse(rootSource.document, {
69
- enter(node, _parent, rawPath) {
70
- if (rawPath.includes('$extensions') || node.type !== 'Object') {
71
- return;
72
- }
73
- const $ref = node.type === 'Object' ? (getObjMember(node, '$ref') as momoa.StringNode) : undefined;
74
- if (!$ref) {
75
- return;
76
- }
77
- if ($ref.type !== 'String') {
78
- logger.error({ ...entry, message: 'Invalid $ref. Expected string.', node: $ref, src: rootSource.src });
79
- }
80
- const jsonID = encodeFragment(rawPath);
81
- refMap[jsonID] = { filename: rootSource.filename.href, refChain: [$ref.value] };
82
- const resolved = resolveRef($ref, refMap[jsonID]!.refChain);
83
- if (resolved.type === 'Object') {
84
- node.members.splice(
85
- node.members.findIndex((m) => m.name.type === 'String' && m.name.value === '$ref'),
86
- 1,
87
- );
88
- replaceNode(node, mergeObjects(resolved, node));
89
- } else {
90
- replaceNode(node, resolved);
91
- }
92
- },
93
- });
94
- logger.debug({ ...entry, message: 'Inline aliases', timing: performance.now() - inlineStart });
95
-
96
- // 2. Resolve $extends to discover any more additional tokens
97
- function flatten$extends(node: momoa.ObjectNode, chain: string[]) {
98
- const memberKeys = node.members.map((m) => m.name.type === 'String' && m.name.value).filter(Boolean) as string[];
99
-
100
- let extended: momoa.ObjectNode | undefined;
101
-
102
- if (memberKeys.includes('$extends')) {
103
- const $extends = getObjMember(node, '$extends') as momoa.StringNode;
104
- if ($extends.type !== 'String') {
105
- logger.error({ ...entry, message: '$extends must be a string', node: $extends, src: rootSource.src });
106
- }
107
- if (memberKeys.includes('$value')) {
108
- logger.error({ ...entry, message: '$extends can’t exist within a token', node: $extends, src: rootSource.src });
109
- }
110
- const next = isAlias($extends.value) ? aliasToGroupRef($extends.value) : undefined;
111
- if (!next) {
112
- logger.error({ ...entry, message: '$extends must be a valid alias', node: $extends, src: rootSource.src });
113
- }
114
-
115
- if (
116
- chain.includes(next!.$ref) ||
117
- // Check that $extends is not importing from higher up (could go in either direction, which is why we check both ways)
118
- chain.some((value) => value.startsWith(next!.$ref) || next!.$ref.startsWith(value))
119
- ) {
120
- logger.error({ ...entry, message: 'Circular $extends detected', node: $extends, src: rootSource.src });
121
- }
122
-
123
- chain.push(next!.$ref);
124
- extended = findNode(rootSource.document, parseRef(next!.$ref).subpath ?? []);
125
- if (!extended) {
126
- logger.error({ ...entry, message: 'Could not resolve $extends', node: $extends, src: rootSource.src });
127
- }
128
- if (extended!.type !== 'Object') {
129
- logger.error({ ...entry, message: '$extends must resolve to a group of tokens', node });
130
- }
131
-
132
- // To ensure this is resolvable, try and flatten this node first (will catch circular refs)
133
- flatten$extends(extended!, chain);
134
-
135
- replaceNode(node, mergeObjects(extended!, node));
136
- }
137
-
138
- // Deeply-traverse for any interior $extends (even if it wasn’t at the top level)
139
- for (const member of node.members) {
140
- if (
141
- member.value.type === 'Object' &&
142
- member.name.type === 'String' &&
143
- !['$value', '$extensions'].includes(member.name.value)
144
- ) {
145
- traverse(member.value, {
146
- enter(subnode, _parent) {
147
- if (subnode.type === 'Object') {
148
- flatten$extends(subnode, chain);
149
- }
150
- },
151
- });
152
- }
153
- }
154
- }
155
-
156
- const extendsStart = performance.now();
157
- const extendsChain: string[] = [];
158
- flatten$extends(rootSource.document.body as momoa.ObjectNode, extendsChain);
159
- logger.debug({ ...entry, message: 'Resolving $extends', timing: performance.now() - extendsStart });
160
-
161
- // 3. Parse discovered tokens
162
- const firstPass = performance.now();
163
- const tokens: TokenNormalizedSet = {};
164
- // micro-optimization: while we’re iterating over tokens, keeping a “hot”
165
- // array in memory saves recreating arrays from object keys over and over again.
166
- // it does produce a noticeable speedup > 1,000 tokens.
167
- const tokenIDs: string[] = [];
168
- const groups: Record<string, GroupNormalized> = {};
169
-
170
- // 3a. Token & group population
171
- const isResolver = isLikelyResolver(rootSource.document);
172
- traverse(rootSource.document, {
173
- enter(node, _parent, rawPath) {
174
- if (node.type !== 'Object') {
175
- return;
176
- }
177
- groupFromNode(node, { path: isResolver ? filterResolverPaths(rawPath) : rawPath, groups });
178
- const token = tokenFromNode(node, {
179
- groups,
180
- ignore: config.ignore,
181
- path: isResolver ? filterResolverPaths(rawPath) : rawPath,
182
- source: rootSource,
183
- });
184
- if (token) {
185
- tokenIDs.push(token.jsonID);
186
- tokens[token.jsonID] = token;
187
- }
188
- },
189
- });
190
-
191
- logger.debug({ ...entry, message: 'Parsing: 1st pass', timing: performance.now() - firstPass });
192
- const secondPass = performance.now();
193
-
194
- // 3b. Resolve originalValue and original sources
195
- for (const source of Object.values(sourceByFilename)) {
196
- traverse(source.document, {
197
- enter(node, _parent, path) {
198
- if (node.type !== 'Object') {
199
- return;
200
- }
201
-
202
- const tokenRawValues = tokenRawValuesFromNode(node, { filename: source.filename!.href, path });
203
- if (tokenRawValues && tokens[tokenRawValues?.jsonID]) {
204
- tokens[tokenRawValues.jsonID]!.originalValue = tokenRawValues.originalValue;
205
- tokens[tokenRawValues.jsonID]!.source = tokenRawValues.source;
206
- for (const mode of Object.keys(tokenRawValues.mode)) {
207
- tokens[tokenRawValues.jsonID]!.mode[mode]!.originalValue = tokenRawValues.mode[mode]!.originalValue;
208
- tokens[tokenRawValues.jsonID]!.mode[mode]!.source = tokenRawValues.mode[mode]!.source;
209
- }
210
- }
211
- },
212
- });
213
- }
214
-
215
- // 3c. DTCG alias resolution
216
- // Unlike $refs which can be resolved as we go, these can’t happen until the final, flattened set
217
- resolveAliases(tokens, { logger, sources: sourceByFilename, refMap });
218
- logger.debug({ ...entry, message: 'Parsing: 2nd pass', timing: performance.now() - secondPass });
219
-
220
- // 4. Alias graph
221
- // We’ve resolved aliases, but we need this pass for reverse linking i.e. “aliasedBy”
222
- const aliasStart = performance.now();
223
- graphAliases(refMap, { tokens, logger, sources: sourceByFilename });
224
- logger.debug({ ...entry, message: 'Alias graph built', timing: performance.now() - aliasStart });
225
-
226
- // 5. normalize
227
- // Allow for some minor variance in inputs, and be nice to folks.
228
- const normalizeStart = performance.now();
229
- for (const id of tokenIDs) {
230
- const token = tokens[id]!;
231
- normalize(token as any, { logger, src: sourceByFilename[token.source.filename!]?.src });
232
- }
233
- logger.debug({ ...entry, message: 'Normalized values', timing: performance.now() - normalizeStart });
234
-
235
- // 6. alphabetize & filter
236
- // This can’t happen until the last step, where we’re 100% sure we’ve resolved everything.
237
- const sortStart = performance.now();
238
- const tokensSorted: TokenNormalizedSet = {};
239
- tokenIDs.sort((a, b) => a.localeCompare(b, 'en-us', { numeric: true }));
240
- for (const path of tokenIDs) {
241
- const id = refToTokenID(path)!;
242
- tokensSorted[id] = tokens[path]!;
243
- }
244
- // Sort group IDs once, too
245
- for (const group of Object.values(groups)) {
246
- group.tokens.sort((a, b) => a.localeCompare(b, 'en-us', { numeric: true }));
247
- }
248
- logger.debug({ ...entry, message: 'Sorted tokens', timing: performance.now() - sortStart });
249
-
250
- return tokensSorted;
251
- }