@terrazzo/parser 0.5.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.
@@ -4,7 +4,7 @@ import type ytm from 'yaml-to-momoa';
4
4
  import lintRunner from '../lint/index.js';
5
5
  import Logger from '../logger.js';
6
6
  import type { ConfigInit, InputSource } from '../types.js';
7
- import { applyAliases } from './alias.js';
7
+ import applyAliases from './alias.js';
8
8
  import { getObjMembers, toMomoa, traverse } from './json.js';
9
9
  import normalize from './normalize.js';
10
10
  import validateTokenNode from './validate.js';
@@ -51,7 +51,7 @@ export default async function parse(
51
51
  _sources = {},
52
52
  }: ParseOptions = {} as ParseOptions,
53
53
  ): Promise<ParseResult> {
54
- let tokens: Record<string, TokenNormalized> = {};
54
+ let tokensSet: Record<string, TokenNormalized> = {};
55
55
 
56
56
  if (!Array.isArray(input)) {
57
57
  logger.error({ group: 'parser', label: 'init', message: 'Input must be an array of input objects.' });
@@ -91,7 +91,7 @@ export default async function parse(
91
91
  continueOnError,
92
92
  yamlToMomoa,
93
93
  });
94
- tokens = Object.assign(tokens, result.tokens);
94
+ tokensSet = Object.assign(tokensSet, result.tokens);
95
95
  if (src.filename) {
96
96
  _sources[src.filename.href] = {
97
97
  filename: src.filename,
@@ -107,35 +107,18 @@ export default async function parse(
107
107
  // 5. Resolve aliases and populate groups
108
108
  const aliasesStart = performance.now();
109
109
  let aliasCount = 0;
110
- for (const [id, token] of Object.entries(tokens)) {
111
- if (!Object.hasOwn(tokens, id)) {
112
- continue;
113
- }
110
+ for (const [id, token] of Object.entries(tokensSet)) {
114
111
  applyAliases(token, {
115
- tokens,
112
+ tokensSet,
116
113
  filename: _sources[token.source.loc!]?.filename!,
117
114
  src: _sources[token.source.loc!]?.src as string,
118
- node: token.source.node,
115
+ node: (getObjMembers(token.source.node).$value as any) || token.source.node,
119
116
  logger,
120
117
  });
121
- token.mode['.']!.$type = token.$type;
122
- token.mode['.']!.$value = token.$value;
123
- if (token.aliasOf) {
124
- token.mode['.']!.aliasOf = token.aliasOf;
125
- }
126
- if (token.aliasChain) {
127
- token.mode['.']!.aliasChain = token.aliasChain;
128
- }
129
- if (token.aliasedBy) {
130
- token.mode['.']!.aliasedBy = token.aliasedBy;
131
- }
132
- if (token.partialAliasOf) {
133
- token.mode['.']!.partialAliasOf = token.partialAliasOf;
134
- }
135
118
  aliasCount++;
136
119
  const { group: parentGroup } = splitID(id);
137
120
  if (parentGroup) {
138
- for (const siblingID of Object.keys(tokens)) {
121
+ for (const siblingID of Object.keys(tokensSet)) {
139
122
  const { group: siblingGroup } = splitID(siblingID);
140
123
  if (siblingGroup?.startsWith(parentGroup)) {
141
124
  token.group.tokens.push(siblingID);
@@ -150,34 +133,6 @@ export default async function parse(
150
133
  timing: performance.now() - aliasesStart,
151
134
  });
152
135
 
153
- // 7. resolve mode aliases
154
- const modesStart = performance.now();
155
- let modeAliasCount = 0;
156
- for (const [id, token] of Object.entries(tokens)) {
157
- if (!Object.hasOwn(tokens, id)) {
158
- continue;
159
- }
160
- for (const [mode, modeValue] of Object.entries(token.mode)) {
161
- if (mode === '.') {
162
- continue; // skip shadow of root value
163
- }
164
- modeAliasCount++;
165
- applyAliases(modeValue, {
166
- tokens,
167
- node: modeValue.source.node,
168
- logger,
169
- src: _sources[token.source.loc!]?.src as string,
170
- skipReverseAlias: true,
171
- });
172
- }
173
- }
174
- logger.debug({
175
- message: `Resolved ${modeAliasCount} mode aliases`,
176
- group: 'parser',
177
- label: 'alias',
178
- timing: performance.now() - modesStart,
179
- });
180
-
181
136
  logger.debug({
182
137
  message: 'Finish all parser tasks',
183
138
  group: 'parser',
@@ -196,7 +151,7 @@ export default async function parse(
196
151
  }
197
152
 
198
153
  return {
199
- tokens,
154
+ tokens: tokensSet,
200
155
  sources: Object.values(_sources),
201
156
  };
202
157
  }
@@ -229,7 +184,7 @@ async function parseSingle(
229
184
  message: 'Finish JSON parsing',
230
185
  timing: performance.now() - startParsing,
231
186
  });
232
- const tokens: Record<string, TokenNormalized> = {};
187
+ const tokensSet: Record<string, TokenNormalized> = {};
233
188
 
234
189
  // 2. Walk AST to validate tokens
235
190
  let tokenCount = 0;
@@ -250,7 +205,7 @@ async function parseSingle(
250
205
  if (node.type === 'Member') {
251
206
  const token = validateTokenNode(node, { filename, src, config, logger, parent, subpath, $typeInheritance });
252
207
  if (token) {
253
- tokens[token.id] = token;
208
+ tokensSet[token.id] = token;
254
209
  tokenCount++;
255
210
  }
256
211
  }
@@ -265,9 +220,9 @@ async function parseSingle(
265
220
 
266
221
  // 3. normalize values
267
222
  const normalizeStart = performance.now();
268
- for (const [id, token] of Object.entries(tokens)) {
223
+ for (const [id, token] of Object.entries(tokensSet)) {
269
224
  try {
270
- tokens[id]!.$value = normalize(token);
225
+ tokensSet[id]!.$value = normalize(token);
271
226
  } catch (err) {
272
227
  let { node } = token.source;
273
228
  const members = getObjMembers(node);
@@ -289,7 +244,7 @@ async function parseSingle(
289
244
  continue;
290
245
  }
291
246
  try {
292
- tokens[id]!.mode[mode]!.$value = normalize(modeValue);
247
+ tokensSet[id]!.mode[mode]!.$value = normalize({ $type: token.$type, ...modeValue });
293
248
  } catch (err) {
294
249
  let { node } = token.source;
295
250
  const members = getObjMembers(node);
@@ -318,7 +273,7 @@ async function parseSingle(
318
273
  // 4. Execute lint runner with loaded plugins
319
274
  if (!skipLint && config?.plugins?.length) {
320
275
  const lintStart = performance.now();
321
- await lintRunner({ tokens, src, config, logger });
276
+ await lintRunner({ tokens: tokensSet, src, config, logger });
322
277
  logger.debug({
323
278
  message: `Linted ${tokenCount} tokens`,
324
279
  group: 'parser',
@@ -330,7 +285,7 @@ async function parseSingle(
330
285
  }
331
286
 
332
287
  return {
333
- tokens,
288
+ tokens: tokensSet,
334
289
  document,
335
290
  src,
336
291
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type CubicBezierValue,
3
3
  type DimensionValue,
4
+ type FontFamilyValue,
4
5
  type GradientStopNormalized,
5
6
  type GradientValueNormalized,
6
7
  type ShadowValueNormalized,
@@ -39,7 +40,7 @@ const NUMBER_WITH_UNIT_RE = /(-?\d*\.?\d+)(.*)/;
39
40
 
40
41
  /** Fill in defaults, and return predictable shapes for tokens */
41
42
  export default function normalizeValue<T extends Token>(token: T): T['$value'] {
42
- if (isAlias(token.$value)) {
43
+ if (typeof token.$value === 'string' && isAlias(token.$value)) {
43
44
  return token.$value;
44
45
  }
45
46
  switch (token.$type) {
@@ -168,6 +169,10 @@ export default function normalizeValue<T extends Token>(token: T): T['$value'] {
168
169
  const output: TypographyValueNormalized = {};
169
170
  for (const [k, $value] of Object.entries(token.$value)) {
170
171
  switch (k) {
172
+ case 'fontFamily': {
173
+ output[k] = normalizeValue({ $type: 'fontFamily', $value: $value as FontFamilyValue });
174
+ break;
175
+ }
171
176
  case 'fontSize':
172
177
  case 'letterSpacing': {
173
178
  output[k] = normalizeValue({ $type: 'dimension', $value: $value as DimensionValue });
@@ -254,10 +254,8 @@ export function validateColor($value: ValueNode, node: AnyNode, { filename, src,
254
254
  export function validateCubicBezier($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions) {
255
255
  const baseMessage = { group: 'parser' as const, label: 'validate', filename, node: $value, src };
256
256
  if ($value.type !== 'Array') {
257
- logger.error({ ...baseMessage, message: `Expected array of strings, received ${print($value)}` });
258
- } else if (
259
- !$value.elements.every((e) => e.value.type === 'Number' || (e.value.type === 'String' && isAlias(e.value.value)))
260
- ) {
257
+ logger.error({ ...baseMessage, message: `Expected array of numbers, received ${print($value)}` });
258
+ } else if (!$value.elements.every((e) => e.value.type === 'Number')) {
261
259
  logger.error({ ...baseMessage, message: 'Expected an array of 4 numbers, received some non-numbers' });
262
260
  } else if ($value.elements.length !== 4) {
263
261
  logger.error({ ...baseMessage, message: `Expected an array of 4 numbers, received ${$value.elements.length}` });
@@ -750,6 +748,11 @@ export interface ValidateTokenNodeOptions {
750
748
  $typeInheritance?: Record<string, Token['$type']>;
751
749
  }
752
750
 
751
+ /**
752
+ * Validate does a little more than validate; it also converts to TokenNormalized
753
+ * and sets up the basic data structure. But aliases are unresolved, and we need
754
+ * a 2nd normalization pass afterward.
755
+ */
753
756
  export default function validateTokenNode(
754
757
  node: MemberNode,
755
758
  { config, filename, logger, parent, src, subpath, $typeInheritance }: ValidateTokenNodeOptions,
@@ -861,28 +864,18 @@ export default function validateTokenNode(
861
864
 
862
865
  // handle modes
863
866
  // note that circular refs are avoided here, such as not duplicating `modes`
864
- const modeValues = extensions?.mode
865
- ? getObjMembers(
866
- // @ts-ignore
867
- extensions.mode,
868
- )
869
- : {};
867
+ const modeValues = extensions?.mode ? getObjMembers(extensions.mode as any) : {};
870
868
  for (const mode of ['.', ...Object.keys(modeValues)]) {
869
+ const modeValue = mode === '.' ? token.$value : (evaluate((modeValues as any)[mode]) as any);
871
870
  token.mode[mode] = {
872
- id: token.id,
873
- // @ts-ignore
874
- $type: token.$type,
875
- // @ts-ignore
876
- $value: mode === '.' ? token.$value : evaluate(modeValues[mode]),
871
+ $value: modeValue,
872
+ originalValue: modeValue,
877
873
  source: {
878
874
  loc: filename ? filename.href : undefined,
879
875
  // @ts-ignore
880
- node: mode === '.' ? structuredClone(token.source.node) : modeValues[mode],
876
+ node: modeValues[mode],
881
877
  },
882
878
  };
883
- if (token.$description) {
884
- token.mode[mode]!.$description = token.$description;
885
- }
886
879
  }
887
880
 
888
881
  // logger.debug({