@terrazzo/parser 0.4.0 → 0.6.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 (80) hide show
  1. package/CHANGELOG.md +62 -12
  2. package/dist/build/index.d.ts +1 -0
  3. package/dist/build/index.d.ts.map +1 -0
  4. package/dist/build/index.js +13 -14
  5. package/dist/build/index.js.map +1 -1
  6. package/dist/config.d.ts +1 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +42 -21
  9. package/dist/config.js.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/lib/code-frame.d.ts +1 -0
  13. package/dist/lib/code-frame.d.ts.map +1 -0
  14. package/dist/lint/index.d.ts +1 -0
  15. package/dist/lint/index.d.ts.map +1 -0
  16. package/dist/lint/index.js +8 -5
  17. package/dist/lint/index.js.map +1 -1
  18. package/dist/lint/plugin-core/index.d.ts +1 -0
  19. package/dist/lint/plugin-core/index.d.ts.map +1 -0
  20. package/dist/lint/plugin-core/lib/docs.d.ts +1 -0
  21. package/dist/lint/plugin-core/lib/docs.d.ts.map +1 -0
  22. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +1 -0
  23. package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts.map +1 -0
  24. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +1 -0
  25. package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts.map +1 -0
  26. package/dist/lint/plugin-core/rules/colorspace.d.ts +1 -0
  27. package/dist/lint/plugin-core/rules/colorspace.d.ts.map +1 -0
  28. package/dist/lint/plugin-core/rules/consistent-naming.d.ts +1 -0
  29. package/dist/lint/plugin-core/rules/consistent-naming.d.ts.map +1 -0
  30. package/dist/lint/plugin-core/rules/descriptions.d.ts +1 -0
  31. package/dist/lint/plugin-core/rules/descriptions.d.ts.map +1 -0
  32. package/dist/lint/plugin-core/rules/duplicate-values.d.ts +1 -0
  33. package/dist/lint/plugin-core/rules/duplicate-values.d.ts.map +1 -0
  34. package/dist/lint/plugin-core/rules/duplicate-values.js +1 -1
  35. package/dist/lint/plugin-core/rules/duplicate-values.js.map +1 -1
  36. package/dist/lint/plugin-core/rules/max-gamut.d.ts +1 -0
  37. package/dist/lint/plugin-core/rules/max-gamut.d.ts.map +1 -0
  38. package/dist/lint/plugin-core/rules/required-children.d.ts +1 -0
  39. package/dist/lint/plugin-core/rules/required-children.d.ts.map +1 -0
  40. package/dist/lint/plugin-core/rules/required-modes.d.ts +1 -0
  41. package/dist/lint/plugin-core/rules/required-modes.d.ts.map +1 -0
  42. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +1 -0
  43. package/dist/lint/plugin-core/rules/required-typography-properties.d.ts.map +1 -0
  44. package/dist/logger.d.ts +4 -3
  45. package/dist/logger.d.ts.map +1 -0
  46. package/dist/logger.js +25 -14
  47. package/dist/logger.js.map +1 -1
  48. package/dist/parse/alias.d.ts +31 -48
  49. package/dist/parse/alias.d.ts.map +1 -0
  50. package/dist/parse/alias.js +281 -175
  51. package/dist/parse/alias.js.map +1 -1
  52. package/dist/parse/index.d.ts +1 -0
  53. package/dist/parse/index.d.ts.map +1 -0
  54. package/dist/parse/index.js +59 -70
  55. package/dist/parse/index.js.map +1 -1
  56. package/dist/parse/json.d.ts +3 -3
  57. package/dist/parse/json.d.ts.map +1 -0
  58. package/dist/parse/json.js +5 -7
  59. package/dist/parse/json.js.map +1 -1
  60. package/dist/parse/normalize.d.ts +1 -0
  61. package/dist/parse/normalize.d.ts.map +1 -0
  62. package/dist/parse/normalize.js +13 -7
  63. package/dist/parse/normalize.js.map +1 -1
  64. package/dist/parse/validate.d.ts +6 -0
  65. package/dist/parse/validate.d.ts.map +1 -0
  66. package/dist/parse/validate.js +205 -125
  67. package/dist/parse/validate.js.map +1 -1
  68. package/dist/types.d.ts +1 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/package.json +2 -2
  71. package/src/build/index.ts +13 -14
  72. package/src/config.ts +42 -22
  73. package/src/lint/index.ts +8 -6
  74. package/src/lint/plugin-core/rules/duplicate-values.ts +1 -1
  75. package/src/logger.ts +30 -20
  76. package/src/parse/alias.ts +330 -194
  77. package/src/parse/index.ts +59 -73
  78. package/src/parse/json.ts +6 -8
  79. package/src/parse/normalize.ts +14 -7
  80. package/src/parse/validate.ts +215 -128
@@ -1,233 +1,369 @@
1
- import type { AnyNode, ArrayNode, ObjectNode, StringNode } from '@humanwhocodes/momoa';
2
- import { type TokenNormalized, isAlias, parseAlias } from '@terrazzo/token-tools';
1
+ import type { AnyNode, ArrayNode, ObjectNode } from '@humanwhocodes/momoa';
2
+ import {
3
+ type BorderTokenNormalized,
4
+ type GradientTokenNormalized,
5
+ type ShadowTokenNormalized,
6
+ type StrokeStyleTokenNormalized,
7
+ type TokenNormalized,
8
+ type TokenNormalizedSet,
9
+ type TransitionTokenNormalized,
10
+ type TypographyTokenNormalized,
11
+ isAlias,
12
+ parseAlias,
13
+ } from '@terrazzo/token-tools';
3
14
  import type Logger from '../logger.js';
15
+ import { getObjMembers } from './json.js';
4
16
 
5
- /** Throw error if resolved alias for composite properties doesn’t match expected $type. */
6
- export const COMPOSITE_TYPE_VALUES = {
7
- border: {
8
- color: ['color'],
9
- width: ['dimension'],
10
- strokeStyle: ['strokeStyle'],
11
- },
12
- gradient: {
13
- color: ['color'],
14
- position: ['number'],
15
- },
16
- shadow: {
17
- color: ['color'],
18
- position: ['dimension'],
19
- },
20
- strokeStyle: {
21
- dashArray: ['dimension'],
22
- },
23
- transition: {
24
- duration: ['duration'],
25
- delay: ['duration'],
26
- timingFunction: ['cubicBezier'],
27
- },
28
- typography: {
29
- fontFamily: ['fontFamily'],
30
- fontSize: ['dimension'],
31
- fontWeight: ['fontWeight'],
32
- letterSpacing: ['dimension'],
33
- lineHeight: ['dimension', 'number'],
34
- },
17
+ export interface ApplyAliasOptions {
18
+ tokensSet: TokenNormalizedSet;
19
+ filename: URL;
20
+ src: string;
21
+ node: ObjectNode;
22
+ logger: Logger;
23
+ }
24
+
25
+ export type PreAliased<T extends TokenNormalized> = {
26
+ $value: T['$value'] | string;
27
+ mode: Record<string, T['mode'][string] & { $value: T['$value'] | string }>;
35
28
  };
36
29
 
37
- /** Resolve alias */
38
- export function resolveAlias(
30
+ /**
31
+ * Resolve aliases and update the token nodes.
32
+ *
33
+ * Data structures are in an awkward in-between phase, where they have
34
+ * placeholders for data but we still need to resolve everything. As such,
35
+ * TypeScript will raise errors expecting the final shape.
36
+ *
37
+ * This is also a bit tricky because different token types alias slightly
38
+ * differently. For example, color tokens and other “primitive” tokens behave
39
+ * as-expected. But composite tokens like Typography, Gradient, Border, etc. can
40
+ * either fully- or partially-alias their values. Then we add modes to the mix,
41
+ * and we have to do the work all over again for each mode declared.
42
+ *
43
+ * All that to say, there are a generous amount of TypeScript overrides here rather
44
+ * than try to codify indeterminate shapes.
45
+ */
46
+ export default function applyAliases(token: TokenNormalized, options: ApplyAliasOptions): void {
47
+ // prepopulate default mode (if not set)
48
+ token.mode['.'] ??= {} as any;
49
+ token.mode['.'].$value = token.$value;
50
+ token.mode['.'].originalValue ??= token.originalValue.$value;
51
+ token.mode['.'].source ??= token.source;
52
+
53
+ // resolve root
54
+ if (typeof token.$value === 'string' && isAlias(token.$value)) {
55
+ const { aliasChain, resolvedToken } = resolveAlias(token.$value, { ...options, token });
56
+ token.aliasOf = resolvedToken.id;
57
+ token.aliasChain = aliasChain;
58
+ (token as any).$value = resolvedToken.$value;
59
+ }
60
+
61
+ // resolve modes
62
+ for (const mode of Object.keys(token.mode)) {
63
+ const modeValue = token.mode[mode]!.$value;
64
+
65
+ // if the entire mode value is a simple alias, resolve & continue
66
+ if (typeof modeValue === 'string' && isAlias(modeValue)) {
67
+ const expectedType = [token.$type];
68
+ const { aliasChain, resolvedToken } = resolveAlias(modeValue, {
69
+ ...options,
70
+ token,
71
+ expectedType,
72
+ node: token.mode[mode]!.source?.node || options.node,
73
+ });
74
+ token.mode[mode]!.aliasOf = resolvedToken.id;
75
+ token.mode[mode]!.aliasChain = aliasChain;
76
+ (token.mode[mode] as any).$value = resolvedToken.$value;
77
+ continue;
78
+ }
79
+
80
+ // object types: expand default $value into current mode
81
+ if (
82
+ typeof token.$value === 'object' &&
83
+ typeof token.mode[mode]!.$value === 'object' &&
84
+ !Array.isArray(token.$value)
85
+ ) {
86
+ for (const [k, v] of Object.entries(token.$value)) {
87
+ if (!(k in token.mode[mode]!.$value)) {
88
+ (token.mode[mode]!.$value as any)[k] = v;
89
+ }
90
+ }
91
+ }
92
+
93
+ // if the mode is an object or array that’s partially aliased, do work per-token type
94
+ const node = (getObjMembers(options.node).$value as any) || options.node;
95
+ switch (token.$type) {
96
+ case 'border': {
97
+ applyBorderPartialAlias(token, mode, { ...options, node });
98
+ break;
99
+ }
100
+ case 'gradient': {
101
+ applyGradientPartialAlias(token, mode, { ...options, node });
102
+ break;
103
+ }
104
+ case 'shadow': {
105
+ applyShadowPartialAlias(token, mode, { ...options, node });
106
+ break;
107
+ }
108
+ case 'strokeStyle': {
109
+ applyStrokeStylePartialAlias(token, mode, { ...options, node });
110
+ break;
111
+ }
112
+ case 'transition': {
113
+ applyTransitionPartialAlias(token, mode, { ...options, node });
114
+ break;
115
+ }
116
+ case 'typography': {
117
+ applyTypographyPartialAlias(token, mode, { ...options, node });
118
+ break;
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ const LIST_FORMAT = new Intl.ListFormat('en-us', { type: 'disjunction' });
125
+
126
+ /**
127
+ * Resolve alias. Also add info on root node if it’s the root token (has .id)
128
+ */
129
+ function resolveAlias(alias: string, options: { token: TokenNormalized; expectedType?: string[] } & ApplyAliasOptions) {
130
+ const baseMessage = {
131
+ group: 'parser' as const,
132
+ label: 'alias',
133
+ node: options?.node,
134
+ filename: options.filename,
135
+ src: options.src,
136
+ };
137
+ const { logger, token, tokensSet } = options;
138
+ const shallowAliasID = parseAlias(alias);
139
+ const { token: resolvedToken, chain } = _resolveAliasInner(shallowAliasID, options);
140
+
141
+ // Apply missing $types while resolving
142
+ if (!tokensSet[token.id]!.$type) {
143
+ tokensSet[token.id]!.$type = resolvedToken!.$type;
144
+ }
145
+
146
+ // throw error if expectedType differed
147
+ const expectedType = [...(options.expectedType ?? [])];
148
+ if (token.$type && !expectedType?.length) {
149
+ expectedType.push(token.$type);
150
+ }
151
+ if (expectedType?.length && !expectedType.includes(resolvedToken!.$type)) {
152
+ logger.error({
153
+ ...baseMessage,
154
+ message: `Invalid alias: expected $type: ${LIST_FORMAT.format(expectedType!)}, received $type: ${resolvedToken!.$type}.`,
155
+ node: (options.node?.type === 'Object' && getObjMembers(options.node).$value) || baseMessage.node,
156
+ });
157
+ }
158
+
159
+ // Apply reverse aliases as we’re traversing the graph
160
+ if (chain?.length && resolvedToken) {
161
+ let needsSort = false;
162
+ for (const id of chain) {
163
+ if (id !== resolvedToken.id && !resolvedToken.aliasedBy?.includes(id)) {
164
+ resolvedToken.aliasedBy ??= [];
165
+ resolvedToken.aliasedBy!.push(id);
166
+ needsSort = true;
167
+ }
168
+ if (token && !resolvedToken.aliasedBy?.includes(token.id)) {
169
+ resolvedToken.aliasedBy ??= [];
170
+ resolvedToken.aliasedBy!.push(token.id);
171
+ needsSort = true;
172
+ }
173
+ }
174
+ if (needsSort) {
175
+ resolvedToken.aliasedBy!.sort((a, b) => a.localeCompare(b, 'en-us', { numeric: true }));
176
+ }
177
+ }
178
+ return { resolvedToken: resolvedToken!, aliasChain: chain };
179
+ }
180
+
181
+ function _resolveAliasInner(
39
182
  alias: string,
40
183
  {
41
- tokens,
42
- logger,
43
- filename,
44
- src,
45
- node,
46
184
  scanned = [],
185
+ ...options
47
186
  }: {
48
- tokens: Record<string, TokenNormalized>;
187
+ tokensSet: Record<string, TokenNormalized>;
49
188
  logger: Logger;
50
189
  filename?: URL;
51
190
  src: string;
52
191
  node: AnyNode;
53
192
  scanned?: string[];
54
193
  },
55
- ) {
56
- const { id } = parseAlias(alias);
57
- if (!tokens[id]) {
58
- logger.error({ message: `Alias "${alias}" not found.`, filename, src, node });
194
+ ): { token: TokenNormalized; chain: string[] } {
195
+ const { logger, filename, src, node, tokensSet } = options;
196
+ const baseMessage = { group: 'parser' as const, label: 'alias', filename, src, node };
197
+ const id = parseAlias(alias);
198
+ if (!tokensSet[id]) {
199
+ logger.error({ ...baseMessage, message: `Alias {${alias}} not found.` });
59
200
  }
60
201
  if (scanned.includes(id)) {
61
- logger.error({ message: `Circular alias detected from "${alias}".`, filename, src, node });
202
+ logger.error({ ...baseMessage, message: `Circular alias detected from ${alias}.` });
62
203
  }
63
- const token = tokens[id]!;
64
- if (!isAlias(token.$value)) {
65
- return id;
204
+
205
+ const token = tokensSet[id]!;
206
+ scanned.push(id);
207
+
208
+ // important: use originalValue to trace the full alias path correctly
209
+ // finish resolution
210
+ if (typeof token.originalValue.$value !== 'string' || !isAlias(token.originalValue.$value)) {
211
+ return { token, chain: scanned };
66
212
  }
67
- return resolveAlias(token.$value as string, { tokens, logger, filename, node, src, scanned: [...scanned, id] });
213
+
214
+ // continue resolving
215
+ return _resolveAliasInner(token.originalValue.$value as string, { ...options, scanned });
68
216
  }
69
217
 
70
- /** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
71
- export function applyAliases(
72
- token: TokenNormalized,
73
- {
74
- tokens,
75
- logger,
76
- filename,
77
- src,
78
- node,
79
- }: { tokens: Record<string, TokenNormalized>; logger: Logger; filename?: URL; src: string; node: AnyNode },
80
- ) {
81
- const $valueNode =
82
- (node.type === 'Object' && node.members.find((m) => (m.name as StringNode).value === '$value')?.value) || node;
83
- const expectedAliasTypes =
84
- token.$type in COMPOSITE_TYPE_VALUES && COMPOSITE_TYPE_VALUES[token.$type as keyof typeof COMPOSITE_TYPE_VALUES];
85
-
86
- // handle simple aliases
87
- if (isAlias(token.$value)) {
88
- const aliasOfID = resolveAlias(token.$value as string, { tokens, logger, filename, node, src });
89
- const { mode: aliasMode } = parseAlias(token.$value as string);
90
- const aliasOf = tokens[aliasOfID]!;
91
- token.aliasOf = aliasOfID;
92
- token.$value = aliasOf!.mode[aliasMode!]?.$value || aliasOf.$value;
93
- if (token.$type && token.$type !== aliasOf.$type) {
94
- logger.error({
95
- message: `Invalid alias: expected $type: "${token.$type}", received $type: "${aliasOf.$type}".`,
96
- node: $valueNode,
97
- filename,
98
- src,
218
+ function applyBorderPartialAlias(token: BorderTokenNormalized, mode: string, options: ApplyAliasOptions): void {
219
+ for (const [k, v] of Object.entries(token.mode[mode]!.$value)) {
220
+ if (typeof v === 'string' && isAlias(v)) {
221
+ token.mode[mode]!.partialAliasOf ??= {};
222
+ const node = (getObjMembers(options.node)[k] as any) || options.node;
223
+ const { resolvedToken } = resolveAlias(v, {
224
+ ...options,
225
+ token,
226
+ expectedType: { color: ['color'], width: ['dimension'], style: ['strokeStyle'] }[k],
227
+ node,
99
228
  });
229
+ (token.mode[mode]!.partialAliasOf as any)[k] = parseAlias(v);
230
+ if (mode === '.') {
231
+ token.partialAliasOf ??= {};
232
+ (token.partialAliasOf as any)[k] = parseAlias(v);
233
+ }
234
+ (token.mode[mode]!.$value as any)[k] = resolvedToken.$value;
100
235
  }
101
- token.$type = aliasOf.$type;
236
+ }
237
+ }
102
238
 
103
- // also update aliased token’s .aliasedBy value
104
- if (!tokens[aliasOfID]!.aliasedBy) {
105
- tokens[aliasOfID]!.aliasedBy = [];
106
- }
107
- if (!tokens[aliasOfID]!.aliasedBy.includes(token.id)) {
108
- tokens[aliasOfID]!.aliasedBy.push(token.id);
109
- tokens[aliasOfID]!.aliasedBy.sort((a, b) => a.localeCompare(b, 'en-us', { numeric: true })); // sort to improve downstream plugins’ determinism
239
+ function applyGradientPartialAlias(token: GradientTokenNormalized, mode: string, options: ApplyAliasOptions): void {
240
+ for (let i = 0; i < token.mode[mode]!.$value.length; i++) {
241
+ const step = token.mode[mode]!.$value[i]!;
242
+ for (const [k, v] of Object.entries(step)) {
243
+ if (typeof v === 'string' && isAlias(v)) {
244
+ token.mode[mode]!.partialAliasOf ??= [];
245
+ (token.mode[mode]!.partialAliasOf as any)[i] ??= {};
246
+ const expectedType = { color: ['color'], position: ['number'] }[k];
247
+ let node = ((options.node as unknown as ArrayNode | undefined)?.elements?.[i]?.value as any) || options.node;
248
+ if (node.type === 'Object') {
249
+ node = getObjMembers(node)[k] || node;
250
+ }
251
+ const { resolvedToken } = resolveAlias(v, { ...options, token, expectedType, node });
252
+ (token.mode[mode]!.partialAliasOf[i] as any)[k] = parseAlias(v);
253
+ if (mode === '.') {
254
+ token.partialAliasOf ??= [];
255
+ (token.partialAliasOf as any)[i] ??= {};
256
+ (token.partialAliasOf[i] as any)[k] = parseAlias(v);
257
+ }
258
+ (step as any)[k] = resolvedToken.$value;
259
+ }
110
260
  }
111
261
  }
262
+ }
263
+
264
+ function applyShadowPartialAlias(token: ShadowTokenNormalized, mode: string, options: ApplyAliasOptions): void {
265
+ // shadow-only fix: historically this token type may or may not allow an array
266
+ // of values, and at this stage in parsing, they all might not have been
267
+ // normalized yet.
268
+ if (!Array.isArray(token.mode[mode]!.$value)) {
269
+ token.mode[mode]!.$value = [token.mode[mode]!.$value];
270
+ }
112
271
 
113
- // handle aliases within array values (e.g. cubicBezier, gradient)
114
- else if (token.$type === 'cubicBezier' || token.$type === 'gradient' || token.$type === 'shadow') {
115
- // some arrays are primitives, some are objects. handle both
116
- for (let i = 0; i < token.$value.length; i++) {
117
- // @ts-ignore
118
- if (isAlias(token.$value[i])) {
119
- if (!token.partialAliasOf) {
120
- token.partialAliasOf = [];
272
+ for (let i = 0; i < token.mode[mode]!.$value.length; i++) {
273
+ const layer = token.mode[mode]!.$value[i]!;
274
+ for (const [k, v] of Object.entries(layer)) {
275
+ if (typeof v === 'string' && isAlias(v)) {
276
+ token.mode[mode]!.partialAliasOf ??= [];
277
+ token.mode[mode]!.partialAliasOf[i] ??= {};
278
+ const expectedType = {
279
+ offsetX: ['dimension'],
280
+ offsetY: ['dimension'],
281
+ blur: ['dimension'],
282
+ spread: ['dimension'],
283
+ color: ['color'],
284
+ inset: ['boolean'],
285
+ }[k];
286
+ let node = ((options.node as unknown as ArrayNode | undefined)?.elements?.[i] as any) || options.node;
287
+ if (node.type === 'Object') {
288
+ node = getObjMembers(node)[k] || node;
121
289
  }
122
- // @ts-ignore
123
- const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, filename, node, src });
124
- // @ts-ignore
125
- const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[i]);
126
- token.partialAliasOf![i] = aliasID;
127
- // @ts-ignore
128
- token.$value[i] = (aliasMode && tokens[aliasOfID].mode[aliasMode]?.$value) || tokens[aliasOfID].$value;
129
- } else if (typeof token.$value[i] === 'object') {
130
- for (const [property, subvalue] of Object.entries(token.$value[i]!)) {
131
- if (isAlias(subvalue)) {
132
- if (!token.partialAliasOf) {
133
- token.partialAliasOf = [];
134
- }
135
- if (!token.partialAliasOf[i]) {
136
- token.partialAliasOf[i] = {};
137
- }
138
- const aliasOfID = resolveAlias(subvalue, { tokens, logger, filename, node, src });
139
- const { id: aliasID, mode: aliasMode } = parseAlias(subvalue);
140
- const aliasToken = tokens[aliasOfID]!;
141
- const possibleTypes: string[] = expectedAliasTypes?.[property as keyof typeof expectedAliasTypes] || [];
142
- if (possibleTypes.length && !possibleTypes.includes(aliasToken.$type)) {
143
- const elementNode = ($valueNode as ArrayNode).elements[i]!.value;
144
- logger.error({
145
- message: `Invalid alias: expected $type: "${possibleTypes.join('" or "')}", received $type: "${aliasToken.$type}".`,
146
- node: (elementNode as ObjectNode).members.find((m) => (m.name as StringNode).value === property)!.value,
147
- filename,
148
- src,
149
- });
150
- }
151
-
152
- // @ts-ignore
153
- token.partialAliasOf[i][property] = aliasID; // also keep the shallow alias here, too!
154
- // @ts-ignore
155
- token.$value[i][property] = (aliasMode && aliasToken.mode[aliasMode]?.$value) || aliasToken.$value;
156
- }
290
+ const { resolvedToken } = resolveAlias(v, { ...options, token, expectedType, node });
291
+ (token.mode[mode]!.partialAliasOf[i] as any)[k] = parseAlias(v);
292
+ if (mode === '.') {
293
+ token.partialAliasOf ??= [];
294
+ token.partialAliasOf[i] ??= {};
295
+ (token.partialAliasOf[i] as any)[k] = parseAlias(v);
157
296
  }
297
+ (layer as any)[k] = resolvedToken.$value;
158
298
  }
159
299
  }
160
300
  }
161
- // handle aliases within object (composite) values (e.g. border, typography, transition)
162
- else if (typeof token.$value === 'object') {
163
- for (const [property, subvalue] of Object.entries(token.$value)) {
164
- if (!Object.hasOwn(token.$value, property)) {
165
- continue;
301
+ }
302
+
303
+ function applyStrokeStylePartialAlias(
304
+ token: StrokeStyleTokenNormalized,
305
+ mode: string,
306
+ options: ApplyAliasOptions,
307
+ ): void {
308
+ // only dashArray can be aliased
309
+ if (typeof token.mode[mode]!.$value !== 'object' || !('dashArray' in token.mode[mode]!.$value)) {
310
+ return;
311
+ }
312
+
313
+ for (let i = 0; i < token.mode[mode]!.$value.dashArray.length; i++) {
314
+ const dash = token.mode[mode]!.$value.dashArray[i]!;
315
+ if (typeof dash === 'string' && isAlias(dash)) {
316
+ let node = (getObjMembers(options.node).dashArray as any) || options.node;
317
+ if (node.type === 'Array') {
318
+ node = ((node as unknown as ArrayNode | undefined)?.elements?.[i]?.value as any) || node;
166
319
  }
320
+ const { resolvedToken } = resolveAlias(dash, {
321
+ ...options,
322
+ token,
323
+ expectedType: ['dimension'],
324
+ node,
325
+ });
326
+ (token.mode[mode]!.$value as any).dashArray[i] = resolvedToken.$value;
327
+ }
328
+ }
329
+ }
167
330
 
168
- if (isAlias(subvalue)) {
169
- if (!token.partialAliasOf) {
170
- token.partialAliasOf = {};
171
- }
172
- const aliasOfID = resolveAlias(subvalue, { tokens, logger, filename, node, src });
173
- const { id: aliasID, mode: aliasMode } = parseAlias(subvalue);
174
- // @ts-ignore
175
- token.partialAliasOf[property] = aliasID; // keep the shallow alias!
176
- const aliasToken = tokens[aliasOfID];
177
- // @ts-ignore
178
- if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken!.$type)) {
179
- logger.error({
180
- // @ts-ignore
181
- message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
182
- // @ts-ignore
183
- node: $valueNode.members.find((m) => m.name.value === property).value,
184
- filename,
185
- src,
186
- });
187
- }
188
- // @ts-ignore
189
- token.$value[property] = aliasToken.mode[aliasMode]?.$value || aliasToken.$value;
331
+ function applyTransitionPartialAlias(token: TransitionTokenNormalized, mode: string, options: ApplyAliasOptions): void {
332
+ for (const [k, v] of Object.entries(token.mode[mode]!.$value)) {
333
+ if (typeof v === 'string' && isAlias(v)) {
334
+ token.mode[mode]!.partialAliasOf ??= {};
335
+ const expectedType = { duration: ['duration'], delay: ['duration'], timingFunction: ['cubicBezier'] }[k];
336
+ const node = (getObjMembers(options.node)[k] as any) || options.node;
337
+ const { resolvedToken } = resolveAlias(v, { ...options, token, expectedType, node });
338
+ (token.mode[mode]!.partialAliasOf as any)[k] = parseAlias(v);
339
+ if (mode === '.') {
340
+ token.partialAliasOf ??= {};
341
+ (token.partialAliasOf as any)[k] = parseAlias(v);
190
342
  }
343
+ (token.mode[mode]!.$value as any)[k] = resolvedToken.$value;
344
+ }
345
+ }
346
+ }
191
347
 
192
- // strokeStyle has an array within an object
193
- // @ts-ignore
194
- else if (Array.isArray(token.$value[property])) {
195
- // @ts-ignore
196
- for (let i = 0; i < token.$value[property].length; i++) {
197
- // @ts-ignore
198
- if (isAlias(token.$value[property][i])) {
199
- // @ts-ignore
200
- const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, filename, node, src });
201
- if (!token.partialAliasOf) {
202
- token.partialAliasOf = {};
203
- }
204
- // @ts-ignore
205
- if (!token.partialAliasOf[property]) {
206
- // @ts-ignore
207
- token.partialAliasOf[property] = [];
208
- }
209
- // @ts-ignore
210
- const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[property][i]);
211
- // @ts-ignore
212
- token.partialAliasOf[property][i] = aliasID; // keep the shallow alias!
213
- const aliasToken = tokens[aliasOfID];
214
- // @ts-ignore
215
- if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
216
- // @ts-ignore
217
- const arrayNode = $valueNode.members.find((m) => m.name.value === property).value;
218
- logger.error({
219
- // @ts-ignore
220
- message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
221
- node: arrayNode.elements[i],
222
- filename,
223
- src,
224
- });
225
- }
226
- // @ts-ignore
227
- token.$value[property][i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
228
- }
229
- }
348
+ function applyTypographyPartialAlias(token: TypographyTokenNormalized, mode: string, options: ApplyAliasOptions): void {
349
+ for (const [k, v] of Object.entries(token.mode[mode]!.$value)) {
350
+ if (typeof v === 'string' && isAlias(v)) {
351
+ token.partialAliasOf ??= {};
352
+ token.mode[mode]!.partialAliasOf ??= {};
353
+ const expectedType = {
354
+ fontFamily: ['fontFamily'],
355
+ fontSize: ['dimension'],
356
+ fontWeight: ['fontWeight'],
357
+ letterSpacing: ['dimension'],
358
+ lineHeight: ['dimension', 'number'],
359
+ }[k] || ['string'];
360
+ const node = (getObjMembers(options.node)[k] as any) || options.node;
361
+ const { resolvedToken } = resolveAlias(v, { ...options, token, expectedType, node });
362
+ (token.mode[mode]!.partialAliasOf as any)[k] = parseAlias(v);
363
+ if (mode === '.') {
364
+ token.partialAliasOf[k] = parseAlias(v);
230
365
  }
366
+ (token.mode[mode]!.$value as any)[k] = resolvedToken.$value;
231
367
  }
232
368
  }
233
369
  }