@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.
@@ -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';
@@ -41,7 +41,7 @@ export interface ParseResult {
41
41
 
42
42
  /** Parse */
43
43
  export default async function parse(
44
- input: Omit<InputSource, 'document'>[],
44
+ _input: Omit<InputSource, 'document'> | Omit<InputSource, 'document'>[],
45
45
  {
46
46
  logger = new Logger(),
47
47
  skipLint = false,
@@ -51,7 +51,8 @@ export default async function parse(
51
51
  _sources = {},
52
52
  }: ParseOptions = {} as ParseOptions,
53
53
  ): Promise<ParseResult> {
54
- let tokens: Record<string, TokenNormalized> = {};
54
+ const input = Array.isArray(_input) ? _input : [_input];
55
+ let tokensSet: Record<string, TokenNormalized> = {};
55
56
 
56
57
  if (!Array.isArray(input)) {
57
58
  logger.error({ group: 'parser', label: 'init', message: 'Input must be an array of input objects.' });
@@ -91,7 +92,7 @@ export default async function parse(
91
92
  continueOnError,
92
93
  yamlToMomoa,
93
94
  });
94
- tokens = Object.assign(tokens, result.tokens);
95
+ tokensSet = Object.assign(tokensSet, result.tokens);
95
96
  if (src.filename) {
96
97
  _sources[src.filename.href] = {
97
98
  filename: src.filename,
@@ -107,35 +108,18 @@ export default async function parse(
107
108
  // 5. Resolve aliases and populate groups
108
109
  const aliasesStart = performance.now();
109
110
  let aliasCount = 0;
110
- for (const [id, token] of Object.entries(tokens)) {
111
- if (!Object.hasOwn(tokens, id)) {
112
- continue;
113
- }
111
+ for (const [id, token] of Object.entries(tokensSet)) {
114
112
  applyAliases(token, {
115
- tokens,
113
+ tokensSet,
116
114
  filename: _sources[token.source.loc!]?.filename!,
117
115
  src: _sources[token.source.loc!]?.src as string,
118
- node: token.source.node,
116
+ node: (getObjMembers(token.source.node).$value as any) || token.source.node,
119
117
  logger,
120
118
  });
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
119
  aliasCount++;
136
120
  const { group: parentGroup } = splitID(id);
137
121
  if (parentGroup) {
138
- for (const siblingID of Object.keys(tokens)) {
122
+ for (const siblingID of Object.keys(tokensSet)) {
139
123
  const { group: siblingGroup } = splitID(siblingID);
140
124
  if (siblingGroup?.startsWith(parentGroup)) {
141
125
  token.group.tokens.push(siblingID);
@@ -150,34 +134,6 @@ export default async function parse(
150
134
  timing: performance.now() - aliasesStart,
151
135
  });
152
136
 
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
137
  logger.debug({
182
138
  message: 'Finish all parser tasks',
183
139
  group: 'parser',
@@ -196,7 +152,7 @@ export default async function parse(
196
152
  }
197
153
 
198
154
  return {
199
- tokens,
155
+ tokens: tokensSet,
200
156
  sources: Object.values(_sources),
201
157
  };
202
158
  }
@@ -229,7 +185,7 @@ async function parseSingle(
229
185
  message: 'Finish JSON parsing',
230
186
  timing: performance.now() - startParsing,
231
187
  });
232
- const tokens: Record<string, TokenNormalized> = {};
188
+ const tokensSet: Record<string, TokenNormalized> = {};
233
189
 
234
190
  // 2. Walk AST to validate tokens
235
191
  let tokenCount = 0;
@@ -250,7 +206,7 @@ async function parseSingle(
250
206
  if (node.type === 'Member') {
251
207
  const token = validateTokenNode(node, { filename, src, config, logger, parent, subpath, $typeInheritance });
252
208
  if (token) {
253
- tokens[token.id] = token;
209
+ tokensSet[token.id] = token;
254
210
  tokenCount++;
255
211
  }
256
212
  }
@@ -265,9 +221,9 @@ async function parseSingle(
265
221
 
266
222
  // 3. normalize values
267
223
  const normalizeStart = performance.now();
268
- for (const [id, token] of Object.entries(tokens)) {
224
+ for (const [id, token] of Object.entries(tokensSet)) {
269
225
  try {
270
- tokens[id]!.$value = normalize(token);
226
+ tokensSet[id]!.$value = normalize(token);
271
227
  } catch (err) {
272
228
  let { node } = token.source;
273
229
  const members = getObjMembers(node);
@@ -289,7 +245,7 @@ async function parseSingle(
289
245
  continue;
290
246
  }
291
247
  try {
292
- tokens[id]!.mode[mode]!.$value = normalize(modeValue);
248
+ tokensSet[id]!.mode[mode]!.$value = normalize({ $type: token.$type, ...modeValue });
293
249
  } catch (err) {
294
250
  let { node } = token.source;
295
251
  const members = getObjMembers(node);
@@ -318,7 +274,7 @@ async function parseSingle(
318
274
  // 4. Execute lint runner with loaded plugins
319
275
  if (!skipLint && config?.plugins?.length) {
320
276
  const lintStart = performance.now();
321
- await lintRunner({ tokens, src, config, logger });
277
+ await lintRunner({ tokens: tokensSet, src, config, logger });
322
278
  logger.debug({
323
279
  message: `Linted ${tokenCount} tokens`,
324
280
  group: 'parser',
@@ -330,7 +286,7 @@ async function parseSingle(
330
286
  }
331
287
 
332
288
  return {
333
- tokens,
289
+ tokens: tokensSet,
334
290
  document,
335
291
  src,
336
292
  };
@@ -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({