@terrazzo/parser 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,302 +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
30
  /**
38
- * Resolve alias
39
- * Note: to save work during resolution (and reduce memory), this will inject
40
- * `aliasedBy` while it traverses. Resolution isn’t normally associated with
41
- * mutation, but for our usecase it is preferable.
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.
42
45
  */
43
- export function resolveAlias(
44
- alias: string,
45
- {
46
- tokens,
47
- logger,
48
- filename,
49
- src,
50
- node,
51
- scanned = [],
52
- }: {
53
- tokens: Record<string, TokenNormalized>;
54
- logger: Logger;
55
- filename?: URL;
56
- src: string;
57
- node: AnyNode;
58
- scanned?: string[];
59
- },
60
- ): { id: string; chain: string[] } {
61
- const { id } = parseAlias(alias);
62
- if (!tokens[id]) {
63
- logger.error({ group: 'parser', label: 'alias', message: `Alias "${alias}" not found.`, filename, src, node });
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;
64
59
  }
65
- if (scanned.includes(id)) {
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)) {
66
152
  logger.error({
67
- group: 'parser',
68
- label: 'alias',
69
- message: `Circular alias detected from "${alias}".`,
70
- filename,
71
- src,
72
- node,
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,
73
156
  });
74
157
  }
75
- const token = tokens[id]!;
76
- // important: use originalValue to trace the full alias path correctly
77
- if (!isAlias(token.originalValue.$value)) {
78
- return { id, chain: scanned.concat(id) };
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
+ }
79
177
  }
80
- return resolveAlias(token.originalValue.$value as string, {
81
- tokens,
82
- logger,
83
- filename,
84
- node,
85
- src,
86
- scanned: scanned.concat(id),
87
- });
178
+ return { resolvedToken: resolvedToken!, aliasChain: chain };
88
179
  }
89
180
 
90
- /** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
91
- export function applyAliases(
92
- token: TokenNormalized,
181
+ function _resolveAliasInner(
182
+ alias: string,
93
183
  {
94
- tokens,
95
- logger,
96
- filename,
97
- src,
98
- node,
99
- skipReverseAlias,
184
+ scanned = [],
185
+ ...options
100
186
  }: {
101
- tokens: Record<string, TokenNormalized>;
187
+ tokensSet: Record<string, TokenNormalized>;
102
188
  logger: Logger;
103
189
  filename?: URL;
104
190
  src: string;
105
191
  node: AnyNode;
106
- skipReverseAlias?: boolean;
192
+ scanned?: string[];
107
193
  },
108
- ) {
109
- /**
110
- * Add reverse alias lookups. Note this intentionally mutates the root
111
- * references in `tokens` to save work. This is essential to doing everything
112
- * in one pass while still being accurate.
113
- */
114
- function addReverseAliases(resolvedID: string, ids: string[]) {
115
- if (skipReverseAlias || !tokens[resolvedID]) {
116
- return;
117
- }
118
-
119
- // populate entire chain of aliases, so we can redeclare tokens when needed
120
- if (!tokens[resolvedID]!.aliasedBy) {
121
- tokens[resolvedID]!.aliasedBy = [];
122
- }
123
- for (const link of [token.id, ...ids]) {
124
- if (link !== resolvedID && !tokens[resolvedID]!.aliasedBy!.includes(link)) {
125
- tokens[resolvedID]!.aliasedBy.push(link);
126
- }
127
- }
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.` });
200
+ }
201
+ if (scanned.includes(id)) {
202
+ logger.error({ ...baseMessage, message: `Circular alias detected from ${alias}.` });
128
203
  }
129
204
 
130
- const $valueNode =
131
- (node.type === 'Object' && node.members.find((m) => (m.name as StringNode).value === '$value')?.value) || node;
132
- const expectedAliasTypes =
133
- token.$type in COMPOSITE_TYPE_VALUES && COMPOSITE_TYPE_VALUES[token.$type as keyof typeof COMPOSITE_TYPE_VALUES];
134
-
135
- // handle simple aliases
136
- if (isAlias(token.$value)) {
137
- const { id: deepAliasID, chain } = resolveAlias(token.$value as string, { tokens, logger, filename, node, src });
138
- const { mode: aliasMode } = parseAlias(token.$value as string);
139
- const resolvedToken = tokens[deepAliasID]!;
205
+ const token = tokensSet[id]!;
206
+ scanned.push(id);
140
207
 
141
- // resolve value in root token, and add aliasOf
142
- token.aliasOf = deepAliasID;
143
- token.aliasChain = chain;
144
- token.$value = resolvedToken!.mode[aliasMode!]?.$value || resolvedToken.$value;
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 };
212
+ }
145
213
 
146
- addReverseAliases(deepAliasID, chain);
214
+ // continue resolving
215
+ return _resolveAliasInner(token.originalValue.$value as string, { ...options, scanned });
216
+ }
147
217
 
148
- if (token.$type && token.$type !== resolvedToken.$type) {
149
- logger.error({
150
- group: 'parser',
151
- label: 'alias',
152
- message: `Invalid alias: expected $type: "${token.$type}", received $type: "${resolvedToken.$type}".`,
153
- node: $valueNode,
154
- filename,
155
- 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,
156
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;
157
235
  }
158
- token.$type = resolvedToken.$type;
159
236
  }
237
+ }
160
238
 
161
- // handle aliases within array values (e.g. cubicBezier, gradient)
162
- else if (token.$type === 'cubicBezier' || token.$type === 'gradient' || token.$type === 'shadow') {
163
- // some arrays are primitives, some are objects. handle both
164
- for (let i = 0; i < token.$value.length; i++) {
165
- // @ts-ignore
166
- if (isAlias(token.$value[i])) {
167
- if (!token.partialAliasOf) {
168
- token.partialAliasOf = [];
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;
169
250
  }
170
- // @ts-ignore
171
- const { id: deepAliasID } = resolveAlias(token.$value[i], { tokens, logger, filename, node, src });
172
- // @ts-ignore
173
- const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[i]);
174
- token.partialAliasOf![i] = aliasID;
175
- // @ts-ignore
176
- token.$value[i] = (aliasMode && tokens[deepAliasID].mode[aliasMode]?.$value) || tokens[deepAliasID].$value;
177
- } else if (typeof token.$value[i] === 'object') {
178
- for (const [property, subvalue] of Object.entries(token.$value[i]!)) {
179
- if (isAlias(subvalue)) {
180
- if (!token.partialAliasOf) {
181
- token.partialAliasOf = [];
182
- }
183
- if (!token.partialAliasOf[i]) {
184
- token.partialAliasOf[i] = {};
185
- }
186
- const { id: deepAliasID, chain } = resolveAlias(subvalue, { tokens, logger, filename, node, src });
187
- const { id: shallowAliasID, mode: aliasMode } = parseAlias(subvalue);
188
- const resolvedToken = tokens[deepAliasID]!;
189
-
190
- addReverseAliases(deepAliasID, chain);
191
-
192
- const possibleTypes: string[] = expectedAliasTypes?.[property as keyof typeof expectedAliasTypes] || [];
193
- if (possibleTypes.length && !possibleTypes.includes(resolvedToken.$type)) {
194
- const elementNode = ($valueNode as ArrayNode).elements[i]!.value;
195
- logger.error({
196
- group: 'parser',
197
- label: 'alias',
198
- message: `Invalid alias: expected $type: "${possibleTypes.join('" or "')}", received $type: "${resolvedToken.$type}".`,
199
- node: (elementNode as ObjectNode).members.find((m) => (m.name as StringNode).value === property)!.value,
200
- filename,
201
- src,
202
- });
203
- }
204
-
205
- // @ts-ignore
206
- token.partialAliasOf[i][property] = shallowAliasID; // also keep the shallow alias here, too!
207
- // @ts-ignore
208
- token.$value[i][property] = (aliasMode && resolvedToken.mode[aliasMode]?.$value) || resolvedToken.$value;
209
- }
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);
210
257
  }
258
+ (step as any)[k] = resolvedToken.$value;
211
259
  }
212
260
  }
213
261
  }
214
- // handle aliases within object (composite) values (e.g. border, typography, transition)
215
- else if (typeof token.$value === 'object') {
216
- for (const [property, subvalue] of Object.entries(token.$value)) {
217
- if (!Object.hasOwn(token.$value, property)) {
218
- continue;
219
- }
220
-
221
- if (isAlias(subvalue)) {
222
- if (!token.partialAliasOf) {
223
- token.partialAliasOf = {};
224
- }
225
- const { id: deepAliasID, chain } = resolveAlias(subvalue, { tokens, logger, filename, node, src });
226
- const { id: shallowAliasID, mode: aliasMode } = parseAlias(subvalue);
262
+ }
227
263
 
228
- addReverseAliases(deepAliasID, chain);
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
+ }
229
271
 
230
- // @ts-ignore
231
- token.partialAliasOf[property] = shallowAliasID; // keep the shallow alias!
232
- const resolvedToken = tokens[deepAliasID];
233
- // @ts-ignore
234
- if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(resolvedToken!.$type)) {
235
- logger.error({
236
- group: 'parser',
237
- label: 'alias',
238
- // @ts-ignore
239
- message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${resolvedToken.$type}".`,
240
- // @ts-ignore
241
- node: $valueNode.members.find((m) => m.name.value === property).value,
242
- filename,
243
- src,
244
- });
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;
289
+ }
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);
245
296
  }
246
- // @ts-ignore
247
- token.$value[property] = resolvedToken.mode[aliasMode]?.$value || resolvedToken.$value;
297
+ (layer as any)[k] = resolvedToken.$value;
248
298
  }
299
+ }
300
+ }
301
+ }
249
302
 
250
- // strokeStyle has an array within an object
251
- // @ts-ignore
252
- else if (Array.isArray(token.$value[property])) {
253
- // @ts-ignore
254
- for (let i = 0; i < token.$value[property].length; i++) {
255
- // @ts-ignore
256
- if (isAlias(token.$value[property][i])) {
257
- // @ts-ignore
258
- const { id: deepAliasID, chain } = resolveAlias(token.$value[property][i], {
259
- tokens,
260
- logger,
261
- filename,
262
- node,
263
- src,
264
- });
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
+ }
265
312
 
266
- addReverseAliases(deepAliasID, chain);
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;
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
+ }
267
330
 
268
- if (!token.partialAliasOf) {
269
- token.partialAliasOf = {};
270
- }
271
- // @ts-ignore
272
- if (!token.partialAliasOf[property]) {
273
- // @ts-ignore
274
- token.partialAliasOf[property] = [];
275
- }
276
- // @ts-ignore
277
- const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[property][i]);
278
- // @ts-ignore
279
- token.partialAliasOf[property][i] = aliasID; // keep the shallow alias!
280
- const resolvedToken = tokens[deepAliasID];
281
- // @ts-ignore
282
- if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(resolvedToken.$type)) {
283
- // @ts-ignore
284
- const arrayNode = $valueNode.members.find((m) => m.name.value === property).value;
285
- logger.error({
286
- group: 'parser',
287
- label: 'alias',
288
- // @ts-ignore
289
- message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${resolvedToken.$type}".`,
290
- node: arrayNode.elements[i],
291
- filename,
292
- src,
293
- });
294
- }
295
- // @ts-ignore
296
- token.$value[property][i] = tokens[deepAliasID].mode[aliasMode]?.$value || tokens[deepAliasID].$value;
297
- }
298
- }
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);
342
+ }
343
+ (token.mode[mode]!.$value as any)[k] = resolvedToken.$value;
344
+ }
345
+ }
346
+ }
347
+
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);
299
365
  }
366
+ (token.mode[mode]!.$value as any)[k] = resolvedToken.$value;
300
367
  }
301
368
  }
302
369
  }