@terrazzo/parser 0.0.8 → 0.0.10

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/config.js CHANGED
@@ -11,12 +11,16 @@ const TRAILING_SLASH_RE = /\/*$/;
11
11
  * @param {Logger} options.logger
12
12
  * @param {URL} options.cwd
13
13
  */
14
- export default function defineConfig(rawConfig, { logger = new Logger(), cwd = import.meta.url } = {}) {
14
+ export default function defineConfig(rawConfig, { logger = new Logger(), cwd } = {}) {
15
15
  const configStart = performance.now();
16
16
 
17
+ if (!cwd) {
18
+ logger.error({ label: 'core', message: 'defineConfig() missing `cwd` for JS API' });
19
+ }
20
+
17
21
  logger.debug({ group: 'parser', task: 'config', message: 'Start config validation' });
18
22
 
19
- const config = { ...rawConfig };
23
+ const config = merge({}, rawConfig);
20
24
 
21
25
  // config.tokens
22
26
  if (rawConfig.tokens === undefined) {
@@ -26,7 +30,7 @@ export default function defineConfig(rawConfig, { logger = new Logger(), cwd = i
26
30
  } else if (Array.isArray(rawConfig.tokens)) {
27
31
  config.tokens = [];
28
32
  for (const file of rawConfig.tokens) {
29
- if (typeof file === 'string') {
33
+ if (typeof file === 'string' || file instanceof URL) {
30
34
  config.tokens.push(file); // will be normalized in next step
31
35
  } else {
32
36
  logger.error({
@@ -43,15 +47,20 @@ export default function defineConfig(rawConfig, { logger = new Logger(), cwd = i
43
47
  }
44
48
  for (let i = 0; i < config.tokens.length; i++) {
45
49
  const filepath = config.tokens[i];
50
+ if (filepath instanceof URL) {
51
+ continue; // skip if already resolved
52
+ }
46
53
  try {
47
54
  config.tokens[i] = new URL(filepath, cwd);
48
- } catch {
55
+ } catch (err) {
49
56
  logger.error({ label: 'config.tokens', message: `Invalid URL ${filepath}` });
50
57
  }
51
58
  }
52
59
 
53
60
  // config.outDir
54
- if (typeof config.outDir === 'undefined') {
61
+ if (config.outDir instanceof URL) {
62
+ // noop
63
+ } else if (typeof config.outDir === 'undefined') {
55
64
  config.outDir = new URL('./tokens/', cwd);
56
65
  } else if (typeof config.outDir !== 'string') {
57
66
  logger.error({ label: 'config.outDir', message: `Expected string, received ${JSON.stringify(config.outDir)}` });
@@ -95,7 +104,6 @@ export default function defineConfig(rawConfig, { logger = new Logger(), cwd = i
95
104
  logger.error({ label: 'config.lint', message: 'Must be an object' });
96
105
  return config;
97
106
  }
98
-
99
107
  if (!config.lint.build) {
100
108
  config.lint.build = { enabled: true };
101
109
  }
@@ -109,15 +117,15 @@ export default function defineConfig(rawConfig, { logger = new Logger(), cwd = i
109
117
  } else {
110
118
  config.lint.build.enabled = true;
111
119
  }
112
-
113
- if (config.lint.rules !== undefined) {
120
+ if (config.lint.rules === undefined) {
121
+ config.lint.rules = {};
122
+ } else {
114
123
  if (config.lint.rules === null || typeof config.lint.rules !== 'object' || Array.isArray(config.lint.rules)) {
115
124
  logger.error({
116
125
  label: 'config.lint.rules',
117
126
  message: `Expected object, received ${JSON.stringify(config.lint.rules)}`,
118
127
  });
119
128
  }
120
-
121
129
  for (const id in config.lint.rules) {
122
130
  if (!Object.hasOwn(config.lint.rules, id)) {
123
131
  continue;
package/logger.d.ts CHANGED
@@ -36,14 +36,6 @@ export interface DebugEntry {
36
36
 
37
37
  export default class Logger {
38
38
  constructor(options?: { level?: LogLevel; debugScope: string });
39
- }
40
-
41
- export class TokensJSONError extends Error {
42
- level: LogLevel;
43
- debugScope: string;
44
- node?: AnyNode;
45
-
46
- constructor(message: string, node: AnyNode);
47
39
 
48
40
  setLevel(level: LogLevel): void;
49
41
 
@@ -58,4 +50,20 @@ export class TokensJSONError extends Error {
58
50
 
59
51
  /** Log a diagnostics message (if logging level permits) */
60
52
  debug(entry: DebugEntry): void;
53
+
54
+ /** Get current stats for logger instance */
55
+ stats(): {
56
+ errorCount: number;
57
+ infoCount: number;
58
+ warnCount: number;
59
+ debugCount: number;
60
+ };
61
+ }
62
+
63
+ export class TokensJSONError extends Error {
64
+ level: LogLevel;
65
+ debugScope: string;
66
+ node?: AnyNode;
67
+
68
+ constructor(message: string, node: AnyNode);
61
69
  }
package/logger.js CHANGED
@@ -32,6 +32,10 @@ export function formatMessage(entry, severity) {
32
32
  export default class Logger {
33
33
  level = 'info';
34
34
  debugScope = '*';
35
+ errorCount = 0;
36
+ warnCount = 0;
37
+ infoCount = 0;
38
+ debugCount = 0;
35
39
 
36
40
  constructor(options) {
37
41
  if (options?.level) {
@@ -48,6 +52,7 @@ export default class Logger {
48
52
 
49
53
  /** Log an error message (always; can’t be silenced) */
50
54
  error(entry) {
55
+ this.errorCount++;
51
56
  const message = formatMessage(entry, 'error');
52
57
  if (entry.continueOnError) {
53
58
  console.error(message);
@@ -64,6 +69,7 @@ export default class Logger {
64
69
 
65
70
  /** Log an info message (if logging level permits) */
66
71
  info(entry) {
72
+ this.infoCount++;
67
73
  if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('info')) {
68
74
  return;
69
75
  }
@@ -73,6 +79,7 @@ export default class Logger {
73
79
 
74
80
  /** Log a warning message (if logging level permits) */
75
81
  warn(entry) {
82
+ this.warnCount++;
76
83
  if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('warn')) {
77
84
  return;
78
85
  }
@@ -81,6 +88,7 @@ export default class Logger {
81
88
 
82
89
  /** Log a diagnostics message (if logging level permits) */
83
90
  debug(entry) {
91
+ this.debugCount++;
84
92
  if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('debug')) {
85
93
  return;
86
94
  }
@@ -107,6 +115,16 @@ export default class Logger {
107
115
  // biome-ignore lint/suspicious/noConsoleLog: this is its job
108
116
  console.log(message);
109
117
  }
118
+
119
+ /** Get stats for current logger instance */
120
+ stats() {
121
+ return {
122
+ errorCount: this.errorCount,
123
+ warnCount: this.warnCount,
124
+ infoCount: this.infoCount,
125
+ debugCount: this.debugCount,
126
+ };
127
+ }
110
128
  }
111
129
 
112
130
  export class TokensJSONError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrazzo/parser",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Parser/validator for the Design Tokens Community Group (DTCG) standard.",
5
5
  "type": "module",
6
6
  "author": {
@@ -31,9 +31,9 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@babel/code-frame": "^7.24.7",
34
- "@humanwhocodes/momoa": "^3.0.6",
34
+ "@humanwhocodes/momoa": "^3.1.1",
35
35
  "@types/babel__code-frame": "^7.0.6",
36
- "@types/culori": "^2.1.0",
36
+ "@types/culori": "^2.1.1",
37
37
  "@types/deep-equal": "^1.0.4",
38
38
  "culori": "^4.0.1",
39
39
  "deep-equal": "^2.2.3",
package/parse/index.d.ts CHANGED
@@ -10,6 +10,8 @@ export interface ParseOptions {
10
10
  /** Skip lint step (default: false) */
11
11
  skipLint?: boolean;
12
12
  config: ConfigInit;
13
+ /** Continue on error? (Useful for `tz check`) (default: false) */
14
+ continueOnError?: boolean;
13
15
  }
14
16
 
15
17
  export interface ParseResult {
package/parse/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { evaluate, parse as parseJSON, print } from '@humanwhocodes/momoa';
2
- import { isAlias, parseAlias, splitID } from '@terrazzo/token-tools';
2
+ import { isAlias, parseAlias, pluralize, splitID } from '@terrazzo/token-tools';
3
3
  import lintRunner from '../lint/index.js';
4
4
  import Logger from '../logger.js';
5
5
  import normalize from './normalize.js';
@@ -28,7 +28,10 @@ export * from './validate.js';
28
28
  * @param {ParseOptions} options
29
29
  * @return {Promise<ParseResult>}
30
30
  */
31
- export default async function parse(input, { logger = new Logger(), skipLint = false, config } = {}) {
31
+ export default async function parse(
32
+ input,
33
+ { logger = new Logger(), skipLint = false, config, continueOnError = false } = {},
34
+ ) {
32
35
  const { plugins } = config;
33
36
 
34
37
  const totalStart = performance.now();
@@ -193,7 +196,7 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
193
196
  if (members.$value) {
194
197
  node = members.$value;
195
198
  }
196
- logger.error({ message: err.message, source, node, continueOnError: true });
199
+ logger.error({ message: err.message, source, node, continueOnError });
197
200
  }
198
201
  for (const mode in tokens[id].mode) {
199
202
  if (mode === '.') {
@@ -207,7 +210,7 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
207
210
  if (members.$value) {
208
211
  node = members.$value;
209
212
  }
210
- logger.error({ message: err.message, source, node: tokens[id].mode[mode].sourceNode, continueOnError: true });
213
+ logger.error({ message: err.message, source, node: tokens[id].mode[mode].sourceNode, continueOnError });
211
214
  }
212
215
  }
213
216
  }
@@ -273,6 +276,15 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
273
276
  timing: performance.now() - totalStart,
274
277
  });
275
278
 
279
+ if (continueOnError) {
280
+ const { errorCount } = logger.stats();
281
+ if (errorCount > 0) {
282
+ logger.error({
283
+ message: `Parser encountered ${errorCount} ${pluralize(errorCount, 'error', 'errors')}. Exiting.`,
284
+ });
285
+ }
286
+ }
287
+
276
288
  return {
277
289
  tokens,
278
290
  ast,
@@ -302,10 +314,10 @@ export function maybeJSONString(input) {
302
314
  export function resolveAlias(alias, { tokens, logger, source, node, scanned = [] }) {
303
315
  const { id } = parseAlias(alias);
304
316
  if (!tokens[id]) {
305
- logger.error({ message: `Alias "${alias}" not found`, source, node, continueOnError: true });
317
+ logger.error({ message: `Alias "${alias}" not found`, source, node });
306
318
  }
307
319
  if (scanned.includes(id)) {
308
- logger.error({ message: `Circular alias detected from "${alias}"`, source, node, continueOnError: true });
320
+ logger.error({ message: `Circular alias detected from "${alias}"`, source, node });
309
321
  }
310
322
  const token = tokens[id];
311
323
  if (!isAlias(token.$value)) {