@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
@@ -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,
@@ -105,27 +105,20 @@ export default async function parse(
105
105
  const totalStart = performance.now();
106
106
 
107
107
  // 5. Resolve aliases and populate groups
108
- for (const [id, token] of Object.entries(tokens)) {
109
- if (!Object.hasOwn(tokens, id)) {
110
- continue;
111
- }
108
+ const aliasesStart = performance.now();
109
+ let aliasCount = 0;
110
+ for (const [id, token] of Object.entries(tokensSet)) {
112
111
  applyAliases(token, {
113
- tokens,
112
+ tokensSet,
114
113
  filename: _sources[token.source.loc!]?.filename!,
115
114
  src: _sources[token.source.loc!]?.src as string,
116
- node: token.source.node,
115
+ node: (getObjMembers(token.source.node).$value as any) || token.source.node,
117
116
  logger,
118
117
  });
119
- token.mode['.']!.$value = token.$value;
120
- if (token.aliasOf) {
121
- token.mode['.']!.aliasOf = token.aliasOf;
122
- }
123
- if (token.partialAliasOf) {
124
- token.mode['.']!.partialAliasOf = token.partialAliasOf;
125
- }
118
+ aliasCount++;
126
119
  const { group: parentGroup } = splitID(id);
127
120
  if (parentGroup) {
128
- for (const siblingID of Object.keys(tokens)) {
121
+ for (const siblingID of Object.keys(tokensSet)) {
129
122
  const { group: siblingGroup } = splitID(siblingID);
130
123
  if (siblingGroup?.startsWith(parentGroup)) {
131
124
  token.group.tokens.push(siblingID);
@@ -133,35 +126,11 @@ export default async function parse(
133
126
  }
134
127
  }
135
128
  }
136
-
137
- // 6. resolve mode aliases
138
- const modesStart = performance.now();
139
- logger.debug({
140
- message: 'Start mode resolution',
141
- group: 'parser',
142
- label: 'modes',
143
- });
144
- for (const [id, token] of Object.entries(tokens)) {
145
- if (!Object.hasOwn(tokens, id)) {
146
- continue;
147
- }
148
- for (const [mode, modeValue] of Object.entries(token.mode)) {
149
- if (mode === '.') {
150
- continue; // skip shadow of root value
151
- }
152
- applyAliases(modeValue, {
153
- tokens,
154
- node: modeValue.source.node,
155
- logger,
156
- src: _sources[token.source.loc!]?.src as string,
157
- });
158
- }
159
- }
160
129
  logger.debug({
130
+ message: `Resolved ${aliasCount} aliases`,
161
131
  group: 'parser',
162
- label: 'modes',
163
- message: 'Finish token modes',
164
- timing: performance.now() - modesStart,
132
+ label: 'alias',
133
+ timing: performance.now() - aliasesStart,
165
134
  });
166
135
 
167
136
  logger.debug({
@@ -175,13 +144,14 @@ export default async function parse(
175
144
  const { errorCount } = logger.stats();
176
145
  if (errorCount > 0) {
177
146
  logger.error({
147
+ group: 'parser',
178
148
  message: `Parser encountered ${errorCount} ${pluralize(errorCount, 'error', 'errors')}. Exiting.`,
179
149
  });
180
150
  }
181
151
  }
182
152
 
183
153
  return {
184
- tokens,
154
+ tokens: tokensSet,
185
155
  sources: Object.values(_sources),
186
156
  };
187
157
  }
@@ -207,20 +177,19 @@ async function parseSingle(
207
177
  ): Promise<{ tokens: Record<string, Token>; document: DocumentNode; src?: string }> {
208
178
  // 1. Build AST
209
179
  const startParsing = performance.now();
210
- logger.debug({ group: 'parser', label: 'parse', message: 'Start JSON parsing' });
211
180
  const { src, document } = toMomoa(input, { filename, logger, continueOnError, yamlToMomoa });
212
181
  logger.debug({
213
182
  group: 'parser',
214
- label: 'parse',
183
+ label: 'json',
215
184
  message: 'Finish JSON parsing',
216
185
  timing: performance.now() - startParsing,
217
186
  });
218
- const tokens: Record<string, TokenNormalized> = {};
187
+ const tokensSet: Record<string, TokenNormalized> = {};
219
188
 
220
189
  // 2. Walk AST to validate tokens
190
+ let tokenCount = 0;
221
191
  const startValidate = performance.now();
222
192
  const $typeInheritance: Record<string, Token['$type']> = {};
223
- logger.debug({ message: 'Start token validation', group: 'parser', label: 'validate' });
224
193
  traverse(document, {
225
194
  enter(node, parent, subpath) {
226
195
  // if $type appears at root of tokens.json, collect it
@@ -236,70 +205,87 @@ async function parseSingle(
236
205
  if (node.type === 'Member') {
237
206
  const token = validateTokenNode(node, { filename, src, config, logger, parent, subpath, $typeInheritance });
238
207
  if (token) {
239
- tokens[token.id] = token;
208
+ tokensSet[token.id] = token;
209
+ tokenCount++;
240
210
  }
241
211
  }
242
212
  },
243
213
  });
244
- logger.debug({ message: 'Finish token validation', group: 'parser', label: 'validate', timing: startValidate });
245
-
246
- // 3. normalize values
247
- const normalizeStart = performance.now();
248
214
  logger.debug({
249
- message: 'Start token normalization',
215
+ message: `Validated ${tokenCount} tokens`,
250
216
  group: 'parser',
251
- label: 'normalize',
217
+ label: 'validate',
218
+ timing: performance.now() - startValidate,
252
219
  });
253
- for (const [id, token] of Object.entries(tokens)) {
220
+
221
+ // 3. normalize values
222
+ const normalizeStart = performance.now();
223
+ for (const [id, token] of Object.entries(tokensSet)) {
254
224
  try {
255
- tokens[id]!.$value = normalize(token);
225
+ tokensSet[id]!.$value = normalize(token);
256
226
  } catch (err) {
257
227
  let { node } = token.source;
258
228
  const members = getObjMembers(node);
259
229
  if (members.$value) {
260
230
  node = members.$value as ObjectNode;
261
231
  }
262
- logger.error({ message: (err as Error).message, filename, src, node, continueOnError });
232
+ logger.error({
233
+ group: 'parser',
234
+ label: 'normalize',
235
+ message: (err as Error).message,
236
+ filename,
237
+ src,
238
+ node,
239
+ continueOnError,
240
+ });
263
241
  }
264
242
  for (const [mode, modeValue] of Object.entries(token.mode)) {
265
243
  if (mode === '.') {
266
244
  continue;
267
245
  }
268
246
  try {
269
- tokens[id]!.mode[mode]!.$value = normalize(modeValue);
247
+ tokensSet[id]!.mode[mode]!.$value = normalize({ $type: token.$type, ...modeValue });
270
248
  } catch (err) {
271
249
  let { node } = token.source;
272
250
  const members = getObjMembers(node);
273
251
  if (members.$value) {
274
252
  node = members.$value as ObjectNode;
275
253
  }
276
- logger.error({ message: (err as Error).message, filename, src, node: modeValue.source.node, continueOnError });
254
+ logger.error({
255
+ group: 'parser',
256
+ label: 'normalize',
257
+ message: (err as Error).message,
258
+ filename,
259
+ src,
260
+ node: modeValue.source.node,
261
+ continueOnError,
262
+ });
277
263
  }
278
264
  }
279
265
  }
266
+ logger.debug({
267
+ message: `Normalized ${tokenCount} tokens`,
268
+ group: 'parser',
269
+ label: 'normalize',
270
+ timing: performance.now() - normalizeStart,
271
+ });
280
272
 
281
273
  // 4. Execute lint runner with loaded plugins
282
274
  if (!skipLint && config?.plugins?.length) {
283
275
  const lintStart = performance.now();
284
- logger.debug({ message: 'Start token linting', group: 'parser', label: 'validate' });
285
- await lintRunner({ tokens, src, config, logger });
276
+ await lintRunner({ tokens: tokensSet, src, config, logger });
286
277
  logger.debug({
287
- message: 'Finish token linting',
278
+ message: `Linted ${tokenCount} tokens`,
288
279
  group: 'parser',
289
- label: 'validate',
280
+ label: 'lint',
290
281
  timing: performance.now() - lintStart,
291
282
  });
283
+ } else {
284
+ logger.debug({ message: 'Linting skipped', group: 'parser', label: 'lint' });
292
285
  }
293
286
 
294
- logger.debug({
295
- message: 'Finish token normalization',
296
- group: 'parser',
297
- label: 'normalize',
298
- timing: performance.now() - normalizeStart,
299
- });
300
-
301
287
  return {
302
- tokens,
288
+ tokens: tokensSet,
303
289
  document,
304
290
  src,
305
291
  };
package/src/parse/json.ts CHANGED
@@ -55,18 +55,15 @@ export function getObjMembers(node: ObjectNode): Record<string | number, ValueNo
55
55
  }
56
56
 
57
57
  /**
58
- * Inject members to ObjectNode and return a clone
58
+ * Inject members to ObjectNode
59
59
  * @param {ObjectNode} node
60
60
  * @param {MemberNode[]} members
61
- * @return {ObjectNode}
62
61
  */
63
- export function injectObjMembers(node: ObjectNode, members: MemberNode[] = []): ObjectNode {
62
+ export function injectObjMembers(node: ObjectNode, members: MemberNode[] = []) {
64
63
  if (node.type !== 'Object') {
65
- return node;
64
+ return;
66
65
  }
67
- const newNode = structuredClone(node);
68
- newNode.members.push(...members);
69
- return newNode;
66
+ node.members.push(...members);
70
67
  }
71
68
 
72
69
  /**
@@ -176,11 +173,12 @@ export function toMomoa(
176
173
  try {
177
174
  document = yamlToMomoa(input); // if string, but not JSON, attempt YAML
178
175
  } catch (err) {
179
- logger.error({ message: String(err), filename, src: input, continueOnError });
176
+ logger.error({ group: 'parser', label: 'json', message: String(err), filename, src: input, continueOnError });
180
177
  }
181
178
  } else {
182
179
  logger.error({
183
180
  group: 'parser',
181
+ label: 'yaml',
184
182
  message: `Install \`yaml-to-momoa\` package to parse YAML, and pass in as option, e.g.:
185
183
 
186
184
  import { parse } from '@terrazzo/parser';
@@ -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) {
@@ -110,7 +111,7 @@ export default function normalizeValue<T extends Token>(token: T): T['$value'] {
110
111
  }
111
112
  const output: GradientValueNormalized = [];
112
113
  for (let i = 0; i < token.$value.length; i++) {
113
- const stop = { ...(token.$value[i] as GradientStopNormalized) };
114
+ const stop = structuredClone(token.$value[i] as GradientStopNormalized);
114
115
  stop.color = normalizeValue({ $type: 'color', $value: stop.color! });
115
116
  if (stop.position === undefined) {
116
117
  stop.position = i / (token.$value.length - 1);
@@ -166,22 +167,28 @@ export default function normalizeValue<T extends Token>(token: T): T['$value'] {
166
167
  return token.$value;
167
168
  }
168
169
  const output: TypographyValueNormalized = {};
169
- for (const k in token.$value) {
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
- output[k] = normalizeValue({ $type: 'dimension', $value: token.$value[k] as DimensionValue });
178
+ output[k] = normalizeValue({ $type: 'dimension', $value: $value as DimensionValue });
174
179
  break;
175
180
  }
176
181
  case 'lineHeight': {
177
182
  output[k] = normalizeValue({
178
183
  $type: typeof token.$value === 'number' ? 'number' : 'dimension',
179
- $value: token.$value[k] as any,
184
+ $value: $value as any,
180
185
  });
181
186
  break;
182
187
  }
183
- default:
184
- output[k] = token.$value[k];
188
+ default: {
189
+ output[k] = $value;
190
+ break;
191
+ }
185
192
  }
186
193
  }
187
194
  return output;