@terrazzo/parser 0.10.4 → 2.0.0-alpha.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.
- package/CHANGELOG.md +67 -0
- package/dist/index.d.ts +112 -325
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2194 -3621
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/build/index.ts +42 -42
- package/src/config.ts +13 -6
- package/src/lib/code-frame.ts +3 -0
- package/src/lib/momoa.ts +10 -0
- package/src/lint/index.ts +41 -37
- package/src/lint/plugin-core/index.ts +73 -16
- package/src/lint/plugin-core/rules/colorspace.ts +4 -0
- package/src/lint/plugin-core/rules/duplicate-values.ts +2 -0
- package/src/lint/plugin-core/rules/max-gamut.ts +24 -4
- package/src/lint/plugin-core/rules/no-type-on-alias.ts +29 -0
- package/src/lint/plugin-core/rules/required-modes.ts +2 -0
- package/src/lint/plugin-core/rules/required-typography-properties.ts +13 -3
- package/src/lint/plugin-core/rules/valid-boolean.ts +41 -0
- package/src/lint/plugin-core/rules/valid-border.ts +57 -0
- package/src/lint/plugin-core/rules/valid-color.ts +265 -0
- package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +83 -0
- package/src/lint/plugin-core/rules/valid-dimension.ts +199 -0
- package/src/lint/plugin-core/rules/valid-duration.ts +123 -0
- package/src/lint/plugin-core/rules/valid-font-family.ts +68 -0
- package/src/lint/plugin-core/rules/valid-font-weight.ts +89 -0
- package/src/lint/plugin-core/rules/valid-gradient.ts +79 -0
- package/src/lint/plugin-core/rules/valid-link.ts +41 -0
- package/src/lint/plugin-core/rules/valid-number.ts +63 -0
- package/src/lint/plugin-core/rules/valid-shadow.ts +67 -0
- package/src/lint/plugin-core/rules/valid-string.ts +41 -0
- package/src/lint/plugin-core/rules/valid-stroke-style.ts +104 -0
- package/src/lint/plugin-core/rules/valid-transition.ts +61 -0
- package/src/lint/plugin-core/rules/valid-typography.ts +67 -0
- package/src/logger.ts +70 -59
- package/src/parse/index.ts +23 -328
- package/src/parse/load.ts +257 -0
- package/src/parse/normalize.ts +134 -170
- package/src/parse/token.ts +530 -0
- package/src/types.ts +106 -28
- package/src/parse/alias.ts +0 -369
- package/src/parse/json.ts +0 -211
- package/src/parse/validate.ts +0 -961
package/src/types.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
2
|
import type { TokenNormalized } from '@terrazzo/token-tools';
|
|
3
|
+
import type ytm from 'yaml-to-momoa';
|
|
3
4
|
import type Logger from './logger.js';
|
|
4
5
|
|
|
6
|
+
export interface PluginHookContext {
|
|
7
|
+
logger: Logger;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export interface BuildHookOptions {
|
|
11
|
+
/** Plugin hook context (provides access to shared logger) */
|
|
12
|
+
context: PluginHookContext;
|
|
6
13
|
/** Map of tokens */
|
|
7
14
|
tokens: Record<string, TokenNormalized>;
|
|
8
15
|
/** Query transformed values */
|
|
@@ -18,10 +25,12 @@ export interface BuildHookOptions {
|
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export interface BuildRunnerResult {
|
|
21
|
-
outputFiles:
|
|
28
|
+
outputFiles: OutputFileExpanded[];
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
export interface BuildEndHookOptions {
|
|
32
|
+
/** Plugin hook context (provides access to shared logger) */
|
|
33
|
+
context: PluginHookContext;
|
|
25
34
|
/** Map of tokens */
|
|
26
35
|
tokens: Record<string, TokenNormalized>;
|
|
27
36
|
/** Query transformed values */
|
|
@@ -68,6 +77,40 @@ export interface Config {
|
|
|
68
77
|
};
|
|
69
78
|
}
|
|
70
79
|
|
|
80
|
+
export interface VisitorContext {
|
|
81
|
+
parent?: momoa.AnyNode;
|
|
82
|
+
filename: URL;
|
|
83
|
+
path: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type Visitor<T extends momoa.AnyNode = momoa.ObjectNode | momoa.DocumentNode> = (
|
|
87
|
+
node: T,
|
|
88
|
+
context: VisitorContext,
|
|
89
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: TS requires void
|
|
90
|
+
) => T | void | null | undefined;
|
|
91
|
+
|
|
92
|
+
export interface TransformVisitors {
|
|
93
|
+
boolean?: Visitor;
|
|
94
|
+
border?: Visitor;
|
|
95
|
+
color?: Visitor;
|
|
96
|
+
cubicBezier?: Visitor;
|
|
97
|
+
dimension?: Visitor;
|
|
98
|
+
duration?: Visitor;
|
|
99
|
+
fontFamily?: Visitor;
|
|
100
|
+
fontWeight?: Visitor;
|
|
101
|
+
gradient?: Visitor;
|
|
102
|
+
group?: Visitor;
|
|
103
|
+
link?: Visitor;
|
|
104
|
+
number?: Visitor;
|
|
105
|
+
root?: Visitor;
|
|
106
|
+
shadow?: Visitor;
|
|
107
|
+
strokeStyle?: Visitor;
|
|
108
|
+
token?: Visitor;
|
|
109
|
+
transition?: Visitor;
|
|
110
|
+
typography?: Visitor;
|
|
111
|
+
[key: string]: Visitor | undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
71
114
|
// normalized, finalized config
|
|
72
115
|
export interface ConfigInit {
|
|
73
116
|
tokens: URL[];
|
|
@@ -92,14 +135,14 @@ export interface ConfigOptions {
|
|
|
92
135
|
export interface InputSource {
|
|
93
136
|
filename?: URL;
|
|
94
137
|
src: any;
|
|
95
|
-
document: DocumentNode;
|
|
138
|
+
document: momoa.DocumentNode;
|
|
96
139
|
}
|
|
97
140
|
|
|
98
141
|
export interface LintNotice {
|
|
99
142
|
/** Lint message shown to the user */
|
|
100
143
|
message: string;
|
|
101
144
|
/** Erring node (used to point to a specific line) */
|
|
102
|
-
node?: AnyNode;
|
|
145
|
+
node?: momoa.AnyNode;
|
|
103
146
|
}
|
|
104
147
|
|
|
105
148
|
export type LintRuleSeverity = 'error' | 'warn' | 'off';
|
|
@@ -113,10 +156,10 @@ export interface LintRuleNormalized<O = any> {
|
|
|
113
156
|
}
|
|
114
157
|
|
|
115
158
|
export type LintReportDescriptor<MessageIds extends string> = {
|
|
116
|
-
/** To error on a specific token source file, provide
|
|
117
|
-
node?: AnyNode;
|
|
118
|
-
/** To
|
|
119
|
-
|
|
159
|
+
/** To error on a specific token source file, provide a Momoa node */
|
|
160
|
+
node?: momoa.AnyNode;
|
|
161
|
+
/** To provide correct line numbers, specify the filename (usually found on `token.source.loc`) */
|
|
162
|
+
filename?: string;
|
|
120
163
|
/** Provide data for messages */
|
|
121
164
|
data?: Record<string, unknown>;
|
|
122
165
|
} & (
|
|
@@ -139,7 +182,7 @@ export type LintReportDescriptor<MessageIds extends string> = {
|
|
|
139
182
|
|
|
140
183
|
export interface LintRule<
|
|
141
184
|
MessageIds extends string,
|
|
142
|
-
LintRuleOptions extends
|
|
185
|
+
LintRuleOptions extends Record<string, any> = Record<string, never>,
|
|
143
186
|
LintRuleDocs = unknown,
|
|
144
187
|
> {
|
|
145
188
|
meta?: LintRuleMetaData<MessageIds, LintRuleOptions, LintRuleDocs>;
|
|
@@ -164,8 +207,11 @@ export interface LintRuleContext<MessageIds extends string, LintRuleOptions exte
|
|
|
164
207
|
options: LintRuleOptions;
|
|
165
208
|
/** The current working directory. */
|
|
166
209
|
cwd?: URL;
|
|
167
|
-
/**
|
|
168
|
-
|
|
210
|
+
/**
|
|
211
|
+
* All source files present in this run. To find the original source, match a
|
|
212
|
+
* token’s `source.loc` filename to one of the source’s `filename`s.
|
|
213
|
+
*/
|
|
214
|
+
sources: InputSource[];
|
|
169
215
|
/** Source file location. */
|
|
170
216
|
filename?: URL;
|
|
171
217
|
/** ID:Token map of all tokens. */
|
|
@@ -221,6 +267,30 @@ export interface OutputFileExpanded extends OutputFile {
|
|
|
221
267
|
time: number;
|
|
222
268
|
}
|
|
223
269
|
|
|
270
|
+
export interface ParseOptions {
|
|
271
|
+
logger?: Logger;
|
|
272
|
+
config: ConfigInit;
|
|
273
|
+
/**
|
|
274
|
+
* Skip lint step
|
|
275
|
+
* @default false
|
|
276
|
+
*/
|
|
277
|
+
skipLint?: boolean;
|
|
278
|
+
/**
|
|
279
|
+
* Continue on error? (Useful for `tz check`)
|
|
280
|
+
* @default false
|
|
281
|
+
*/
|
|
282
|
+
continueOnError?: boolean;
|
|
283
|
+
/** Provide yamlToMomoa module to parse YAML (by default, this isn’t shipped to cut down on package weight) */
|
|
284
|
+
yamlToMomoa?: typeof ytm;
|
|
285
|
+
/**
|
|
286
|
+
* Transform API
|
|
287
|
+
* @see https://terrazzo.app/docs/api/js#transform-api
|
|
288
|
+
*/
|
|
289
|
+
transform?: TransformVisitors;
|
|
290
|
+
/** (internal cache; do not use) */
|
|
291
|
+
_sources?: Record<string, InputSource>;
|
|
292
|
+
}
|
|
293
|
+
|
|
224
294
|
export interface Plugin {
|
|
225
295
|
name: string;
|
|
226
296
|
/** Read config, and optionally modify */
|
|
@@ -237,17 +307,14 @@ export interface Plugin {
|
|
|
237
307
|
lint?(): Record<string, LintRule<any, any, any>>;
|
|
238
308
|
transform?(options: TransformHookOptions): Promise<void>;
|
|
239
309
|
build?(options: BuildHookOptions): Promise<void>;
|
|
240
|
-
buildEnd?(
|
|
310
|
+
buildEnd?(options: BuildEndHookOptions): Promise<void>;
|
|
241
311
|
}
|
|
242
312
|
|
|
243
|
-
|
|
244
|
-
export interface TokenTransformedSingleValue {
|
|
313
|
+
interface TokenTransformedBase {
|
|
245
314
|
/** Original Token ID */
|
|
246
315
|
id: string;
|
|
247
316
|
/** ID unique to this format. */
|
|
248
317
|
localID?: string;
|
|
249
|
-
type: 'SINGLE_VALUE';
|
|
250
|
-
value: string;
|
|
251
318
|
/**
|
|
252
319
|
* The mode of this value
|
|
253
320
|
* @default "."
|
|
@@ -255,23 +322,27 @@ export interface TokenTransformedSingleValue {
|
|
|
255
322
|
mode: string;
|
|
256
323
|
/** The original token. */
|
|
257
324
|
token: TokenNormalized;
|
|
325
|
+
/** Arbitrary metadata set by plugins. */
|
|
326
|
+
meta?: Record<string | number | symbol, unknown> & {
|
|
327
|
+
/**
|
|
328
|
+
* Metadata for the token-listing plugin. Plugins can
|
|
329
|
+
* set this to be the name of a token as it appears in code,
|
|
330
|
+
* and the token-listing plugin will pick it up and use it.
|
|
331
|
+
*/
|
|
332
|
+
'token-listing'?: { name: string | undefined };
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Transformed token with a single value. Note that this may be any type! */
|
|
337
|
+
export interface TokenTransformedSingleValue extends TokenTransformedBase {
|
|
338
|
+
type: 'SINGLE_VALUE';
|
|
339
|
+
value: string;
|
|
258
340
|
}
|
|
259
341
|
|
|
260
342
|
/** Transformed token with multiple values. Note that this may be any type! */
|
|
261
|
-
export interface TokenTransformedMultiValue {
|
|
262
|
-
/** Original Token ID */
|
|
263
|
-
id: string;
|
|
264
|
-
/** ID unique to this format.*/
|
|
265
|
-
localID?: string;
|
|
343
|
+
export interface TokenTransformedMultiValue extends TokenTransformedBase {
|
|
266
344
|
type: 'MULTI_VALUE';
|
|
267
345
|
value: Record<string, string>;
|
|
268
|
-
/**
|
|
269
|
-
* The mode of this value
|
|
270
|
-
* @default "."
|
|
271
|
-
*/
|
|
272
|
-
mode: string;
|
|
273
|
-
/** The original token */
|
|
274
|
-
token: TokenNormalized;
|
|
275
346
|
}
|
|
276
347
|
|
|
277
348
|
export type TokenTransformed = TokenTransformedSingleValue | TokenTransformedMultiValue;
|
|
@@ -291,6 +362,8 @@ export interface TransformParams {
|
|
|
291
362
|
}
|
|
292
363
|
|
|
293
364
|
export interface TransformHookOptions {
|
|
365
|
+
/** Plugin hook context (provides access to shared logger) */
|
|
366
|
+
context: PluginHookContext;
|
|
294
367
|
/** Map of tokens */
|
|
295
368
|
tokens: Record<string, TokenNormalized>;
|
|
296
369
|
/** Query transformed values */
|
|
@@ -303,8 +376,13 @@ export interface TransformHookOptions {
|
|
|
303
376
|
localID?: string;
|
|
304
377
|
value: string | Record<string, string>; // allow looser type for input (`undefined` will just get stripped)
|
|
305
378
|
mode?: string;
|
|
379
|
+
meta?: TokenTransformedBase['meta'];
|
|
306
380
|
},
|
|
307
381
|
): void;
|
|
308
382
|
/** Momoa documents */
|
|
309
383
|
sources: InputSource[];
|
|
310
384
|
}
|
|
385
|
+
|
|
386
|
+
export interface ReferenceObject {
|
|
387
|
+
$ref: string;
|
|
388
|
+
}
|
package/src/parse/alias.ts
DELETED
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
import type { AnyNode, ArrayNode, ObjectNode } from '@humanwhocodes/momoa';
|
|
2
|
-
import {
|
|
3
|
-
type BorderTokenNormalized,
|
|
4
|
-
type GradientTokenNormalized,
|
|
5
|
-
isAlias,
|
|
6
|
-
parseAlias,
|
|
7
|
-
type ShadowTokenNormalized,
|
|
8
|
-
type StrokeStyleTokenNormalized,
|
|
9
|
-
type TokenNormalized,
|
|
10
|
-
type TokenNormalizedSet,
|
|
11
|
-
type TransitionTokenNormalized,
|
|
12
|
-
type TypographyTokenNormalized,
|
|
13
|
-
} from '@terrazzo/token-tools';
|
|
14
|
-
import type Logger from '../logger.js';
|
|
15
|
-
import { getObjMembers } from './json.js';
|
|
16
|
-
|
|
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 }>;
|
|
28
|
-
};
|
|
29
|
-
|
|
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(
|
|
182
|
-
alias: string,
|
|
183
|
-
{
|
|
184
|
-
scanned = [],
|
|
185
|
-
...options
|
|
186
|
-
}: {
|
|
187
|
-
tokensSet: Record<string, TokenNormalized>;
|
|
188
|
-
logger: Logger;
|
|
189
|
-
filename?: URL;
|
|
190
|
-
src: string;
|
|
191
|
-
node: AnyNode;
|
|
192
|
-
scanned?: string[];
|
|
193
|
-
},
|
|
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}.` });
|
|
203
|
-
}
|
|
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 };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// continue resolving
|
|
215
|
-
return _resolveAliasInner(token.originalValue.$value as string, { ...options, scanned });
|
|
216
|
-
}
|
|
217
|
-
|
|
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,
|
|
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;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
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
|
-
}
|
|
260
|
-
}
|
|
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
|
-
}
|
|
271
|
-
|
|
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);
|
|
296
|
-
}
|
|
297
|
-
(layer as any)[k] = resolvedToken.$value;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
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;
|
|
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
|
-
}
|
|
330
|
-
|
|
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);
|
|
365
|
-
}
|
|
366
|
-
(token.mode[mode]!.$value as any)[k] = resolvedToken.$value;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|