@terrazzo/parser 2.0.0-alpha.4 → 2.0.0-alpha.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrazzo/parser",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.5",
4
4
  "description": "Parser/validator for the Design Tokens Community Group (DTCG) standard.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,7 +44,15 @@
44
44
  "scule": "^1.3.0",
45
45
  "wildcard-match": "^5.1.4",
46
46
  "@terrazzo/json-schema-tools": "^0.1.0-alpha.0",
47
- "@terrazzo/token-tools": "^2.0.0-alpha.4"
47
+ "@terrazzo/token-tools": "^2.0.0-alpha.5"
48
+ },
49
+ "peerDependencies": {
50
+ "yaml-to-momoa": "0.0.8"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "yaml-to-momoa": {
54
+ "optional": true
55
+ }
48
56
  },
49
57
  "devDependencies": {
50
58
  "yaml-to-momoa": "0.0.8"
@@ -52,6 +60,7 @@
52
60
  "scripts": {
53
61
  "build": "rolldown -c && attw --profile esm-only --pack .",
54
62
  "dev": "rolldown -w -c",
63
+ "format": "biome check --fix --unsafe .",
55
64
  "lint": "pnpm --filter @terrazzo/parser run \"/^lint:(js|ts)/\"",
56
65
  "lint:js": "biome check .",
57
66
  "lint:ts": "tsc --noEmit",
@@ -2,11 +2,12 @@ import type { InputSourceWithDocument } from '@terrazzo/json-schema-tools';
2
2
  import type { TokenNormalized } from '@terrazzo/token-tools';
3
3
  import wcmatch from 'wildcard-match';
4
4
  import Logger, { type LogEntry } from '../logger.js';
5
- import type { BuildRunnerResult, ConfigInit, TokenTransformed, TransformParams } from '../types.js';
5
+ import type { BuildRunnerResult, ConfigInit, Resolver, TokenTransformed, TransformParams } from '../types.js';
6
6
 
7
7
  export interface BuildRunnerOptions {
8
8
  sources: InputSourceWithDocument[];
9
9
  config: ConfigInit;
10
+ resolver: Resolver;
10
11
  logger?: Logger;
11
12
  }
12
13
  export const SINGLE_VALUE = 'SINGLE_VALUE';
@@ -48,7 +49,7 @@ function validateTransformParams({
48
49
  /** Run build stage */
49
50
  export default async function build(
50
51
  tokens: Record<string, TokenNormalized>,
51
- { sources, logger = new Logger(), config }: BuildRunnerOptions,
52
+ { resolver, sources, logger = new Logger(), config }: BuildRunnerOptions,
52
53
  ): Promise<BuildRunnerResult> {
53
54
  const formats: Record<string, TokenTransformed[]> = {};
54
55
  const result: BuildRunnerResult = { outputFiles: [] };
@@ -133,6 +134,7 @@ export default async function build(
133
134
  formats[params.format]![foundTokenI]!.type = typeof cleanValue === 'string' ? SINGLE_VALUE : MULTI_VALUE;
134
135
  }
135
136
  },
137
+ resolver,
136
138
  });
137
139
  }
138
140
  }
@@ -155,6 +157,7 @@ export default async function build(
155
157
  tokens,
156
158
  sources,
157
159
  getTransforms,
160
+ resolver,
158
161
  outputFile(filename, contents) {
159
162
  const resolved = new URL(filename, config.outDir);
160
163
  if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) {
package/src/config.ts CHANGED
@@ -29,7 +29,7 @@ export default function defineConfig(
29
29
 
30
30
  // 2. Start build by calling config()
31
31
  for (const plugin of config.plugins) {
32
- plugin.config?.({ ...config });
32
+ plugin.config?.({ ...config }, { logger });
33
33
  }
34
34
 
35
35
  // 3. finish
@@ -267,13 +267,6 @@ function normalizeLint({ config, logger }: { config: ConfigInit; logger: Logger
267
267
  });
268
268
  }
269
269
  }
270
-
271
- // Apply recommended config in places user hasn’t explicitly opted-out
272
- for (const [id, severity] of Object.entries(RECOMMENDED_CONFIG)) {
273
- if (!(id in config.lint.rules)) {
274
- config.lint.rules[id] = severity;
275
- }
276
- }
277
270
  }
278
271
  } else {
279
272
  config.lint = {
package/src/logger.ts CHANGED
@@ -26,6 +26,8 @@ export interface LogEntry {
26
26
  node?: momoa.AnyNode;
27
27
  /** To show a code frame, provide the original source code */
28
28
  src?: string;
29
+ /** Display performance timing */
30
+ timing?: number;
29
31
  }
30
32
 
31
33
  export interface DebugEntry {
@@ -61,6 +63,9 @@ export function formatMessage(entry: LogEntry, severity: LogSeverity) {
61
63
  if (severity in MESSAGE_COLOR) {
62
64
  message = MESSAGE_COLOR[severity]!(message);
63
65
  }
66
+ if (typeof entry.timing === 'number') {
67
+ message = `${message} ${formatTiming(entry.timing)}`;
68
+ }
64
69
  if (entry.node) {
65
70
  const start = entry.node?.loc?.start ?? { line: 0, column: 0 };
66
71
  // strip "file://" protocol, but not href
@@ -139,6 +144,7 @@ export default class Logger {
139
144
  return;
140
145
  }
141
146
  const message = formatMessage(entry, 'warn');
147
+
142
148
  // biome-ignore lint/suspicious/noConsole: this is a logger
143
149
  console.warn(message);
144
150
  }
@@ -168,13 +174,7 @@ export default class Logger {
168
174
 
169
175
  message = `${pc.dim(timeFormatter.format(performance.now()))} ${message}`;
170
176
  if (typeof entry.timing === 'number') {
171
- let timing = '';
172
- if (entry.timing < 1_000) {
173
- timing = `${Math.round(entry.timing * 100) / 100}ms`;
174
- } else if (entry.timing < 60_000) {
175
- timing = `${Math.round(entry.timing * 100) / 100_000}s`;
176
- }
177
- message = `${message}${timing ? pc.dim(` [${timing}]`) : ''}`;
177
+ message = `${message} ${formatTiming(entry.timing)}`;
178
178
  }
179
179
 
180
180
  // biome-ignore lint/suspicious/noConsole: this is a logger
@@ -193,6 +193,18 @@ export default class Logger {
193
193
  }
194
194
  }
195
195
 
196
+ function formatTiming(timing: number): string {
197
+ let output = '';
198
+ if (timing < 1_000) {
199
+ output = `${Math.round(timing * 100) / 100}ms`;
200
+ } else if (timing < 60_000) {
201
+ output = `${Math.round(timing) / 1_000}s`;
202
+ } else {
203
+ output = `${Math.round(timing / 1_000) / 60}m`;
204
+ }
205
+ return pc.dim(`[${output}]`);
206
+ }
207
+
196
208
  export class TokensJSONError extends Error {
197
209
  constructor(message: string) {
198
210
  super(message);
@@ -73,6 +73,15 @@ export default async function parse(
73
73
  });
74
74
  }
75
75
 
76
+ const resolverTiming = performance.now();
77
+ const finalResolver = resolver || (await createSyntheticResolver(tokens, { config, logger, req, sources }));
78
+ logger.debug({
79
+ message: 'Resolver finalized',
80
+ group: 'parser',
81
+ label: 'core',
82
+ timing: performance.now() - resolverTiming,
83
+ });
84
+
76
85
  logger.debug({
77
86
  message: 'Finish all parser tasks',
78
87
  group: 'parser',
@@ -93,7 +102,7 @@ export default async function parse(
93
102
  return {
94
103
  tokens,
95
104
  sources,
96
- resolver: resolver || (await createSyntheticResolver(tokens, { config, logger, req, sources })),
105
+ resolver: finalResolver,
97
106
  };
98
107
  }
99
108
 
@@ -44,12 +44,11 @@ export function processTokens(
44
44
  if (node.type !== 'Object') {
45
45
  return;
46
46
  }
47
- const path = isResolver ? filterResolverPaths(rawPath) : rawPath;
48
- groupFromNode(node, { path, groups });
47
+ groupFromNode(node, { path: isResolver ? filterResolverPaths(rawPath) : rawPath, groups });
49
48
  const token = tokenFromNode(node, {
50
49
  groups,
51
50
  ignore: config.ignore,
52
- path,
51
+ path: isResolver ? filterResolverPaths(rawPath) : rawPath,
53
52
  source: rootSource,
54
53
  });
55
54
  if (token) {
@@ -35,7 +35,7 @@ export function tokenFromNode(
35
35
  node: momoa.AnyNode,
36
36
  { groups, path, source, ignore }: TokenFromNodeOptions,
37
37
  ): TokenNormalized | undefined {
38
- const isToken = node.type === 'Object' && getObjMember(node, '$value') && !path.includes('$extensions');
38
+ const isToken = node.type === 'Object' && !!getObjMember(node, '$value') && !path.includes('$extensions');
39
39
  if (!isToken) {
40
40
  return undefined;
41
41
  }
@@ -59,6 +59,7 @@ export function tokenFromNode(
59
59
  $deprecated: originalToken.$deprecated ?? group.$deprecated ?? undefined, // ⚠️ MUST use ?? here to inherit false correctly
60
60
  $value: originalToken.$value,
61
61
  $extensions: originalToken.$extensions || undefined,
62
+ $extends: originalToken.$extends || undefined,
62
63
  aliasChain: undefined,
63
64
  aliasedBy: undefined,
64
65
  aliasOf: undefined,
@@ -94,7 +95,7 @@ export function tokenFromNode(
94
95
  const $extensions = getObjMember(node, '$extensions');
95
96
  if ($extensions) {
96
97
  const modeNode = getObjMember($extensions as momoa.ObjectNode, 'mode') as momoa.ObjectNode;
97
- for (const mode of Object.keys((token.$extensions as any).mode)) {
98
+ for (const mode of Object.keys((token.$extensions as any).mode ?? {})) {
98
99
  const modeValue = (token.$extensions as any).mode[mode];
99
100
  token.mode[mode] = {
100
101
  $value: modeValue,
@@ -1,4 +1,4 @@
1
- import * as momoa from '@humanwhocodes/momoa';
1
+ import type * as momoa from '@humanwhocodes/momoa';
2
2
  import { type InputSource, type InputSourceWithDocument, maybeRawJSON } from '@terrazzo/json-schema-tools';
3
3
  import type { TokenNormalizedSet } from '@terrazzo/token-tools';
4
4
  import { merge } from 'merge-anything';
@@ -160,7 +160,7 @@ export function createResolver(
160
160
  const tokens = processTokens(rootSource, {
161
161
  config,
162
162
  logger,
163
- sourceByFilename: {},
163
+ sourceByFilename: { [resolverSource._source.filename!.href]: rootSource },
164
164
  refMap: {},
165
165
  sources,
166
166
  });
@@ -1,4 +1,4 @@
1
- import type * as momoa from '@humanwhocodes/momoa';
1
+ import * as momoa from '@humanwhocodes/momoa';
2
2
  import { getObjMember, getObjMembers } from '@terrazzo/json-schema-tools';
3
3
  import type { LogEntry, default as Logger } from '../logger.js';
4
4
 
@@ -24,7 +24,7 @@ export function isLikelyResolver(doc: momoa.DocumentNode): boolean {
24
24
  case 'description':
25
25
  case 'version': {
26
26
  // 1. name, description, or version are a string
27
- if (member.name.type === 'String') {
27
+ if (member.value.type === 'String') {
28
28
  return true;
29
29
  }
30
30
  break;
@@ -176,6 +176,7 @@ export function validateResolver(node: momoa.DocumentNode, { logger, src }: Vali
176
176
  errors.push({ ...entry, message: `Expected object`, node: member.value });
177
177
  }
178
178
  break;
179
+ case '$schema':
179
180
  case '$ref': {
180
181
  if (member.value.type !== 'String') {
181
182
  errors.push({ ...entry, message: `Expected string`, node: member.value });
@@ -329,6 +330,17 @@ export function validateModifier(
329
330
  }
330
331
  break;
331
332
  }
333
+ case 'default': {
334
+ if (member.value.type !== 'String') {
335
+ errors.push({ ...entry, message: `Expected string`, node: member.value });
336
+ } else {
337
+ const contexts = getObjMember(node, 'contexts') as momoa.ObjectNode | undefined;
338
+ if (!contexts || !getObjMember(contexts, member.value.value)) {
339
+ errors.push({ ...entry, message: 'Invalid default context', node: member.value });
340
+ }
341
+ }
342
+ break;
343
+ }
332
344
  case '$defs':
333
345
  case '$extensions':
334
346
  if (member.value.type !== 'Object') {
package/src/types.ts CHANGED
@@ -32,6 +32,8 @@ export interface BuildHookOptions {
32
32
  getTransforms(params: TransformParams): TokenTransformed[];
33
33
  /** Momoa documents */
34
34
  sources: InputSourceWithDocument[];
35
+ /** Resolver */
36
+ resolver: Resolver;
35
37
  outputFile: (
36
38
  /** Filename to output (relative to outDir) */
37
39
  filename: string,
@@ -311,7 +313,7 @@ export interface Plugin {
311
313
  name: string;
312
314
  /** Read config, and optionally modify */
313
315
  // biome-ignore lint/suspicious/noConfusingVoidType format: this helps plugins be a little looser on their typing
314
- config?(config: ConfigInit): void | ConfigInit | undefined;
316
+ config?(config: ConfigInit, context: PluginHookContext): void | ConfigInit | undefined;
315
317
  /**
316
318
  * Declare:
317
319
  * - `"pre"`: run this plugin BEFORE all others
@@ -452,6 +454,8 @@ export interface TransformHookOptions {
452
454
  meta?: TokenTransformedBase['meta'];
453
455
  },
454
456
  ): void;
457
+ /** Resolver */
458
+ resolver: Resolver;
455
459
  /** Momoa documents */
456
460
  sources: InputSourceWithDocument[];
457
461
  }