@terrazzo/parser 0.0.11 → 0.0.12

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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # @terrazzo/parser
2
+
3
+ ## 0.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - [#285](https://github.com/terrazzoapp/terrazzo/pull/285) [`e8a0df1`](https://github.com/terrazzoapp/terrazzo/commit/e8a0df1f3b50cf7cb292bcc475aae271feae4569) Thanks [@drwpow](https://github.com/drwpow)! - Add support for multiple token files
8
+
9
+ - Updated dependencies [[`e8a0df1`](https://github.com/terrazzoapp/terrazzo/commit/e8a0df1f3b50cf7cb292bcc475aae271feae4569)]:
10
+ - @terrazzo/token-tools@0.0.6
package/build/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import type { ConfigInit } from '../config.js';
4
4
  import type Logger from '../logger.js';
5
5
 
6
6
  export interface BuildRunnerOptions {
7
- ast: DocumentNode;
7
+ sources: { filename?: URL; src: string; document: DocumentNode }[];
8
8
  config: ConfigInit;
9
9
  logger?: Logger;
10
10
  }
@@ -73,8 +73,8 @@ export interface TransformHookOptions {
73
73
  mode?: string;
74
74
  },
75
75
  ): void;
76
- /** Momoa document */
77
- ast: DocumentNode;
76
+ /** Momoa documents */
77
+ sources: { filename?: URL; src: string; document: DocumentNode }[];
78
78
  }
79
79
 
80
80
  export interface BuildHookOptions {
@@ -82,8 +82,8 @@ export interface BuildHookOptions {
82
82
  tokens: Record<string, TokenNormalized>;
83
83
  /** Query transformed values */
84
84
  getTransforms(params: TransformParams): TokenTransformed[];
85
- /** Momoa document */
86
- ast: DocumentNode;
85
+ /** Momoa documents */
86
+ sources: { filename?: URL; src: string; document: DocumentNode }[];
87
87
  outputFile: (
88
88
  /** Filename to output (relative to outDir) */
89
89
  filename: string,
@@ -101,8 +101,8 @@ export interface BuildEndHookOptions {
101
101
  tokens: Record<string, TokenNormalized>;
102
102
  /** Query transformed values */
103
103
  getTransforms(params: TransformParams): TokenTransformed[];
104
- /** Momoa document */
105
- ast: DocumentNode;
104
+ /** Momoa documents */
105
+ sources: { filename?: URL; src: string; document: DocumentNode }[];
106
106
  /** Final files to be written */
107
107
  outputFiles: OutputFileExpanded[];
108
108
  }
package/build/index.js CHANGED
@@ -5,7 +5,7 @@ import Logger from '../logger.js';
5
5
  /**
6
6
  * @typedef {object} BuildRunnerOptions
7
7
  * @typedef {Record<string, TokenNormalized>} BuildRunnerOptions.tokens
8
- * @typedef {DocumentNode} BuildRunnerOptions.ast
8
+ * @typedef {Array} BuildRunnerOptions.sources
9
9
  * @typedef {ConfigInit} BuildRunnerOptions.config
10
10
  * @typedef {Logger} BuildRunnerOptions.logger
11
11
  * @typedef {import("@humanwhocodes/momoa").DocumentNode} DocumentNode
@@ -60,7 +60,7 @@ function validateTransformParams({ params, token, logger, pluginName }) {
60
60
  * @param {BuildOptions} options
61
61
  * @return {Promise<BuildResult>}
62
62
  */
63
- export default async function build(tokens, { ast, logger = new Logger(), config }) {
63
+ export default async function build(tokens, { sources, logger = new Logger(), config }) {
64
64
  const formats = {};
65
65
  const result = { outputFiles: [] };
66
66
 
@@ -95,7 +95,7 @@ export default async function build(tokens, { ast, logger = new Logger(), config
95
95
  if (typeof plugin.transform === 'function') {
96
96
  await plugin.transform({
97
97
  tokens,
98
- ast,
98
+ sources,
99
99
  getTransforms,
100
100
  setTransform(id, params) {
101
101
  if (transformsLocked) {
@@ -165,7 +165,7 @@ export default async function build(tokens, { ast, logger = new Logger(), config
165
165
  const pluginBuildStart = performance.now();
166
166
  await plugin.build({
167
167
  tokens,
168
- ast,
168
+ sources,
169
169
  getTransforms,
170
170
  outputFile(filename, contents) {
171
171
  const resolved = new URL(filename, config.outDir);
@@ -196,7 +196,7 @@ export default async function build(tokens, { ast, logger = new Logger(), config
196
196
  if (typeof plugin.buildEnd === 'function') {
197
197
  await plugin.buildEnd({
198
198
  tokens,
199
- ast,
199
+ sources,
200
200
  getTransforms,
201
201
  format: (formatID) => createFormatter(formatID),
202
202
  outputFiles: structruedClone(result.outputFiles),
package/lint/index.d.ts CHANGED
@@ -26,7 +26,8 @@ export interface LinterOptions<O = any> {
26
26
  id: string;
27
27
  severity: LintRuleSeverity;
28
28
  };
29
- ast: DocumentNode;
29
+ document: DocumentNode;
30
+ filename?: URL;
30
31
  source?: string;
31
32
  /** Any options the user has declared for this plugin */
32
33
  options?: O;
@@ -34,7 +35,8 @@ export interface LinterOptions<O = any> {
34
35
  export type Linter = (options: LinterOptions) => Promise<LintNotice[] | undefined>;
35
36
 
36
37
  export interface LintRunnerOptions {
37
- ast: DocumentNode;
38
+ document: DocumentNode;
39
+ filename?: URL;
38
40
  config: ConfigInit;
39
41
  logger: Logger;
40
42
  }
package/logger.d.ts CHANGED
@@ -13,12 +13,14 @@ export interface LogEntry {
13
13
  message: string;
14
14
  /** (optional) Prefix message with label */
15
15
  label?: string;
16
+ /** (optional) File in disk */
17
+ filename?: URL;
16
18
  /** Continue on error? (default: false) */
17
19
  continueOnError?: boolean;
18
20
  /** (optional) Show a code frame for the erring node */
19
21
  node?: AnyNode;
20
22
  /** (optional) To show a code frame, provide the original source code */
21
- source?: string;
23
+ src?: string;
22
24
  }
23
25
 
24
26
  export interface DebugEntry {
@@ -29,7 +31,7 @@ export interface DebugEntry {
29
31
  /** Error message to be logged */
30
32
  message: string;
31
33
  /** (optional) Show code below message */
32
- codeFrame?: { source: string; line: number; column: number };
34
+ codeFrame?: { src: string; line: number; column: number };
33
35
  /** (optional) Display performance timing */
34
36
  timing?: number;
35
37
  }
package/logger.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { codeFrameColumns } from '@babel/code-frame';
2
2
  import color from 'picocolors';
3
+ import { fileURLToPath } from 'node:url';
3
4
  import wcmatch from 'wildcard-match';
4
5
 
5
6
  export const LOG_ORDER = ['error', 'warn', 'info', 'debug'];
@@ -23,8 +24,9 @@ export function formatMessage(entry, severity) {
23
24
  if (severity in MESSAGE_COLOR) {
24
25
  message = MESSAGE_COLOR[severity](message);
25
26
  }
26
- if (entry.source) {
27
- message = `${message}\n\n${codeFrameColumns(entry.source, { start: entry.node?.loc?.start })}`;
27
+ if (entry.src) {
28
+ const start = entry.node?.loc?.start;
29
+ message = `${message}\n\n${entry.filename ? `${fileURLToPath(entry.filename)}:${start?.line ?? 0}:${start?.column ?? 0}\n\n` : ''}${codeFrameColumns(entry.src, { start })}`;
28
30
  }
29
31
  return message;
30
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrazzo/parser",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Parser/validator for the Design Tokens Community Group (DTCG) standard.",
5
5
  "type": "module",
6
6
  "author": {
@@ -42,7 +42,7 @@
42
42
  "picocolors": "^1.0.1",
43
43
  "wildcard-match": "^5.1.3",
44
44
  "yaml": "^2.4.5",
45
- "@terrazzo/token-tools": "^0.0.4"
45
+ "@terrazzo/token-tools": "^0.0.6"
46
46
  },
47
47
  "scripts": {
48
48
  "lint": "biome check .",
package/parse/index.d.ts CHANGED
@@ -5,24 +5,32 @@ import type Logger from '../logger.js';
5
5
 
6
6
  export * from './validate.js';
7
7
 
8
+ export interface ParseInput {
9
+ /** Source filename (if read from disk) */
10
+ filename?: URL;
11
+ /** JSON/YAML string, or JSON-serializable object (if already in memory) */
12
+ src: string | object;
13
+ }
14
+
8
15
  export interface ParseOptions {
9
16
  logger?: Logger;
17
+ config: ConfigInit;
10
18
  /** Skip lint step (default: false) */
11
19
  skipLint?: boolean;
12
- config: ConfigInit;
13
20
  /** Continue on error? (Useful for `tz check`) (default: false) */
14
21
  continueOnError?: boolean;
15
22
  }
16
23
 
17
24
  export interface ParseResult {
18
25
  tokens: Record<string, TokenNormalized>;
19
- ast: DocumentNode;
26
+ /** ASTs are returned in order of input array */
27
+ sources: { filename?: URL; src: string; document: DocumentNode }[];
20
28
  }
21
29
 
22
30
  /**
23
31
  * Parse and validate Tokens JSON, and lint it
24
32
  */
25
- export default function parse(input: string | object, options?: ParseOptions): Promise<ParseResult>;
33
+ export default function parse(input: ParseInput[], options?: ParseOptions): Promise<ParseResult>;
26
34
 
27
35
  /** Determine if an input is likely a JSON string */
28
36
  export function maybeJSONString(input: unknown): boolean;
package/parse/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { evaluate, parse as parseJSON, print } from '@humanwhocodes/momoa';
2
2
  import { isAlias, parseAlias, pluralize, splitID } from '@terrazzo/token-tools';
3
+ import { fileURLToPath } from 'node:url';
3
4
  import lintRunner from '../lint/index.js';
4
5
  import Logger from '../logger.js';
5
6
  import normalize from './normalize.js';
@@ -9,50 +10,189 @@ import { getObjMembers, injectObjMembers, traverse } from './json.js';
9
10
 
10
11
  export * from './validate.js';
11
12
 
13
+ /** @typedef {import("@humanwhocodes/momoa").DocumentNode} DocumentNode */
14
+ /** @typedef {import("../config.js").Plugin} Plugin */
15
+ /** @typedef {import("../types.js").TokenNormalized} TokenNormalized */
12
16
  /**
13
- * @typedef {import("@humanwhocodes/momoa").DocumentNode} DocumentNode
14
- * @typedef {import("../config.js").Plugin} Plugin
15
- * @typedef {import("../types.js").TokenNormalized} TokenNormalized
16
- * @typedef {object} ParseOptions
17
- * @typedef {Logger} ParseOptions.logger
18
- * @typedef {boolean=false} ParseOptions.skipLint
19
- * @typedef {Plugin[]} ParseOptions.plugins
20
17
  * @typedef {object} ParseResult
21
- * @typedef {Record<string, TokenNormalized} ParseResult.tokens
22
- * @typedef {DocumentNode} ParseResult.ast
18
+ * @property {Record<string, TokenNormalized} tokens
19
+ * @property {Object[]} src
20
+ */
21
+ /**
22
+ * @typedef {object} ParseInput
23
+ * @property {string | object} src
24
+ * @property {URL} [filename]
25
+ */
26
+ /**
27
+ * @typedef {object} ParseOptions
28
+ * @property {Logger} logger
29
+ * @property {import("../config.js").Config} config
30
+ * @property {boolean} [skipLint=false]
31
+ * @property {boolean} [continueOnError=false]
23
32
  */
24
-
25
33
  /**
26
34
  * Parse
27
- * @param {string | object} input
28
- * @param {ParseOptions} options
35
+ * @param {ParseInput[]} input
36
+ * @param {ParseOptions} [options]
29
37
  * @return {Promise<ParseResult>}
30
38
  */
31
39
  export default async function parse(
32
40
  input,
33
- { logger = new Logger(), skipLint = false, config, continueOnError = false } = {},
41
+ { logger = new Logger(), skipLint = false, config = {}, continueOnError = false } = {},
34
42
  ) {
35
- const { plugins } = config;
43
+ let tokens = {};
44
+ // note: only keeps track of sources with locations on disk; in-memory sources are discarded
45
+ // (it’s only for reporting line numbers, which doesn’t mean as much for dynamic sources)
46
+ const sources = {};
47
+
48
+ if (!Array.isArray(input)) {
49
+ logger.error({ group: 'parser', task: 'init', message: 'Input must be an array of input objects.' });
50
+ }
51
+ for (let i = 0; i < input.length; i++) {
52
+ if (!input[i] || typeof input[i] !== 'object') {
53
+ logger.error({ group: 'parser', task: 'init', message: `Input (${i}) must be an object.` });
54
+ }
55
+ if (!input[i].src || (typeof input[i].src !== 'string' && typeof input[i].src !== 'object')) {
56
+ logger.error({
57
+ group: 'parser',
58
+ task: 'init',
59
+ message: `Input (${i}) missing "src" with a JSON/YAML string, or JSON object.`,
60
+ });
61
+ }
62
+ if (input[i].filename && !(input[i].filename instanceof URL)) {
63
+ logger.error({
64
+ group: 'parser',
65
+ task: 'init',
66
+ message: `Input (${i}) "filename" must be a URL (remote or file URL).`,
67
+ });
68
+ }
69
+
70
+ const result = await parseSingle(input[i].src, {
71
+ filename: input[i].filename,
72
+ logger,
73
+ config,
74
+ skipLint,
75
+ continueOnError,
76
+ });
77
+
78
+ tokens = Object.assign(tokens, result.tokens);
79
+ if (input[i].filename) {
80
+ sources[input[i].filename.protocol === 'file:' ? fileURLToPath(input[i].filename) : input[i].filename.href] = {
81
+ filename: input[i].filename,
82
+ src: result.src,
83
+ document: result.document,
84
+ };
85
+ }
86
+ }
36
87
 
37
88
  const totalStart = performance.now();
38
89
 
90
+ // 5. Resolve aliases and populate groups
91
+ for (const id in tokens) {
92
+ if (!Object.hasOwn(tokens, id)) {
93
+ continue;
94
+ }
95
+ const token = tokens[id];
96
+ applyAliases(token, {
97
+ tokens,
98
+ filename: sources[token.source.loc]?.filename,
99
+ src: sources[token.source.loc]?.src,
100
+ node: token.source.node,
101
+ logger,
102
+ });
103
+ token.mode['.'].$value = token.$value;
104
+ if (token.aliasOf) {
105
+ token.mode['.'].aliasOf = token.aliasOf;
106
+ }
107
+ if (token.partialAliasOf) {
108
+ token.mode['.'].partialAliasOf = token.partialAliasOf;
109
+ }
110
+ const { group: parentGroup } = splitID(id);
111
+ for (const siblingID in tokens) {
112
+ const { group: siblingGroup } = splitID(siblingID);
113
+ if (siblingGroup?.startsWith(parentGroup)) {
114
+ token.group.tokens.push(siblingID);
115
+ }
116
+ }
117
+ }
118
+
119
+ // 6. resolve mode aliases
120
+ const modesStart = performance.now();
121
+ logger.debug({
122
+ group: 'parser',
123
+ task: 'modes',
124
+ message: 'Start mode resolution',
125
+ });
126
+ for (const id in tokens) {
127
+ if (!Object.hasOwn(tokens, id)) {
128
+ continue;
129
+ }
130
+ for (const mode in tokens[id].mode) {
131
+ if (mode === '.') {
132
+ continue; // skip shadow of root value
133
+ }
134
+ applyAliases(tokens[id].mode[mode], { tokens, node: tokens[id].mode[mode].source.node, logger });
135
+ }
136
+ }
137
+ logger.debug({
138
+ group: 'parser',
139
+ task: 'modes',
140
+ message: 'Finish token modes',
141
+ timing: performance.now() - modesStart,
142
+ });
143
+
144
+ logger.debug({
145
+ group: 'parser',
146
+ task: 'core',
147
+ message: 'Finish all parser tasks',
148
+ timing: performance.now() - totalStart,
149
+ });
150
+
151
+ if (continueOnError) {
152
+ const { errorCount } = logger.stats();
153
+ if (errorCount > 0) {
154
+ logger.error({
155
+ message: `Parser encountered ${errorCount} ${pluralize(errorCount, 'error', 'errors')}. Exiting.`,
156
+ });
157
+ }
158
+ }
159
+
160
+ return {
161
+ tokens,
162
+ sources,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Parse a single input
168
+ * @param {string | object} input
169
+ * @param {object} options
170
+ * @param {URL} [options.filename]
171
+ * @param {Logger} [options.logger]
172
+ * @param {import("../config.js").Config} [options.config]
173
+ * @param {boolean} [options.skipLint]
174
+ */
175
+ async function parseSingle(input, { filename, logger, config, skipLint, continueOnError = false }) {
39
176
  // 1. Build AST
40
- let source;
177
+ let src;
41
178
  if (typeof input === 'string') {
42
- source = input;
179
+ src = input;
43
180
  }
44
181
  const startParsing = performance.now();
45
182
  logger.debug({ group: 'parser', task: 'parse', message: 'Start tokens parsing' });
46
- let ast;
183
+ let document;
47
184
  if (typeof input === 'string' && !maybeJSONString(input)) {
48
- ast = parseYAML(input, { logger }); // if string, but not JSON, attempt YAML
185
+ document = parseYAML(input, { logger }); // if string, but not JSON, attempt YAML
49
186
  } else {
50
- ast = parseJSON(typeof input === 'string' ? input : JSON.stringify(input, undefined, 2), {
51
- mode: 'jsonc',
52
- }); // everything else: assert it’s JSON-serializable
187
+ document = parseJSON(
188
+ typeof input === 'string' ? input : JSON.stringify(input, undefined, 2), // everything else: assert it’s JSON-serializable
189
+ {
190
+ mode: 'jsonc',
191
+ },
192
+ );
53
193
  }
54
- if (!source) {
55
- source = print(ast, { indent: 2 });
194
+ if (!src) {
195
+ src = print(document, { indent: 2 });
56
196
  }
57
197
  logger.debug({
58
198
  group: 'parser',
@@ -67,7 +207,7 @@ export default async function parse(
67
207
  const startValidation = performance.now();
68
208
  logger.debug({ group: 'parser', task: 'validate', message: 'Start tokens validation' });
69
209
  const $typeInheritance = {};
70
- traverse(ast, {
210
+ traverse(document, {
71
211
  enter(node, parent, path) {
72
212
  if (node.type === 'Member' && node.value.type === 'Object' && node.value.members) {
73
213
  const members = getObjMembers(node.value);
@@ -96,7 +236,8 @@ export default async function parse(
96
236
  if (parent$type && !members.$type) {
97
237
  sourceNode.value = injectObjMembers(sourceNode.value, [parent$type]);
98
238
  }
99
- validate(sourceNode, { source, logger });
239
+
240
+ validate(sourceNode, { filename, src, logger });
100
241
 
101
242
  const group = { id: splitID(id).group, tokens: [] };
102
243
  if (parent$type) {
@@ -117,7 +258,10 @@ export default async function parse(
117
258
  mode: {},
118
259
  originalValue: evaluate(node.value),
119
260
  group,
120
- sourceNode: sourceNode.value,
261
+ source: {
262
+ loc: filename ? fileURLToPath(filename) : undefined,
263
+ node: sourceNode.value,
264
+ },
121
265
  };
122
266
  if (members.$description?.value) {
123
267
  token.$description = members.$description.value;
@@ -131,7 +275,10 @@ export default async function parse(
131
275
  id: token.id,
132
276
  $type: token.$type,
133
277
  $value: mode === '.' ? token.$value : evaluate(modeValues[mode]),
134
- sourceNode: mode === '.' ? structuredClone(token.sourceNode) : modeValues[mode],
278
+ source: {
279
+ loc: filename ? fileURLToPath(filename) : undefined,
280
+ node: mode === '.' ? structuredClone(token.source.node) : modeValues[mode],
281
+ },
135
282
  };
136
283
  if (token.$description) {
137
284
  token.mode[mode].$description = token.$description;
@@ -140,7 +287,7 @@ export default async function parse(
140
287
 
141
288
  tokens[id] = token;
142
289
  } else if (members.value) {
143
- logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, node, source });
290
+ logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, filename, node, src });
144
291
  }
145
292
  }
146
293
 
@@ -161,14 +308,14 @@ export default async function parse(
161
308
  });
162
309
 
163
310
  // 3. Execute lint runner with loaded plugins
164
- if (!skipLint && plugins?.length) {
311
+ if (!skipLint && config?.plugins?.length) {
165
312
  const lintStart = performance.now();
166
313
  logger.debug({
167
314
  group: 'parser',
168
315
  task: 'validate',
169
316
  message: 'Start token linting',
170
317
  });
171
- await lintRunner({ ast, source, config, logger });
318
+ await lintRunner({ document, filename, src, config, logger });
172
319
  logger.debug({
173
320
  group: 'parser',
174
321
  task: 'validate',
@@ -191,12 +338,12 @@ export default async function parse(
191
338
  try {
192
339
  tokens[id].$value = normalize(tokens[id]);
193
340
  } catch (err) {
194
- let node = tokens[id].sourceNode;
341
+ let { node } = tokens[id].source;
195
342
  const members = getObjMembers(node);
196
343
  if (members.$value) {
197
344
  node = members.$value;
198
345
  }
199
- logger.error({ message: err.message, source, node, continueOnError });
346
+ logger.error({ message: err.message, filename, src, node, continueOnError });
200
347
  }
201
348
  for (const mode in tokens[id].mode) {
202
349
  if (mode === '.') {
@@ -205,12 +352,18 @@ export default async function parse(
205
352
  try {
206
353
  tokens[id].mode[mode].$value = normalize(tokens[id].mode[mode]);
207
354
  } catch (err) {
208
- let node = tokens[id].sourceNode;
355
+ let { node } = tokens[id].source;
209
356
  const members = getObjMembers(node);
210
357
  if (members.$value) {
211
358
  node = members.$value;
212
359
  }
213
- logger.error({ message: err.message, source, node: tokens[id].mode[mode].sourceNode, continueOnError });
360
+ logger.error({
361
+ message: err.message,
362
+ filename,
363
+ src,
364
+ node: tokens[id].mode[mode].source.node,
365
+ continueOnError,
366
+ });
214
367
  }
215
368
  }
216
369
  }
@@ -221,74 +374,7 @@ export default async function parse(
221
374
  timing: performance.now() - normalizeStart,
222
375
  });
223
376
 
224
- // 5. Resolve aliases and populate groups
225
- for (const id in tokens) {
226
- if (!Object.hasOwn(tokens, id)) {
227
- continue;
228
- }
229
- const token = tokens[id];
230
- applyAliases(token, { tokens, source, node: token.sourceNode, logger });
231
- token.mode['.'].$value = token.$value;
232
- if (token.aliasOf) {
233
- token.mode['.'].aliasOf = token.aliasOf;
234
- }
235
- if (token.partialAliasOf) {
236
- token.mode['.'].partialAliasOf = token.partialAliasOf;
237
- }
238
- const { group: parentGroup } = splitID(id);
239
- for (const siblingID in tokens) {
240
- const { group: siblingGroup } = splitID(siblingID);
241
- if (siblingGroup?.startsWith(parentGroup)) {
242
- token.group.tokens.push(siblingID);
243
- }
244
- }
245
- }
246
-
247
- // 6. resolve mode aliases
248
- const modesStart = performance.now();
249
- logger.debug({
250
- group: 'parser',
251
- task: 'modes',
252
- message: 'Start mode resolution',
253
- });
254
- for (const id in tokens) {
255
- if (!Object.hasOwn(tokens, id)) {
256
- continue;
257
- }
258
- for (const mode in tokens[id].mode) {
259
- if (mode === '.') {
260
- continue; // skip shadow of root value
261
- }
262
- applyAliases(tokens[id].mode[mode], { tokens, source, node: tokens[id].mode[mode].sourceNode, logger });
263
- }
264
- }
265
- logger.debug({
266
- group: 'parser',
267
- task: 'modes',
268
- message: 'Finish token modes',
269
- timing: performance.now() - modesStart,
270
- });
271
-
272
- logger.debug({
273
- group: 'parser',
274
- task: 'core',
275
- message: 'Finish all parser tasks',
276
- timing: performance.now() - totalStart,
277
- });
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
-
288
- return {
289
- tokens,
290
- ast,
291
- };
377
+ return { tokens, document, src };
292
378
  }
293
379
 
294
380
  /**
@@ -306,31 +392,32 @@ export function maybeJSONString(input) {
306
392
  * @param {Object} options
307
393
  * @param {Record<string, TokenNormalized>} options.tokens
308
394
  * @param {Logger} options.logger
395
+ * @param {string} [options.filename]
309
396
  * @param {AnyNode} [options.node]
310
397
  * @param {string} [options.string]
311
- * @param {string[]=[]} options.scanned
398
+ * @param {string} [options.scanned=[]]
312
399
  * @param {string}
313
400
  */
314
- export function resolveAlias(alias, { tokens, logger, source, node, scanned = [] }) {
401
+ export function resolveAlias(alias, { tokens, logger, filename, src, node, scanned = [] }) {
315
402
  const { id } = parseAlias(alias);
316
403
  if (!tokens[id]) {
317
- logger.error({ message: `Alias "${alias}" not found`, source, node });
404
+ logger.error({ message: `Alias "${alias}" not found`, filename, src, node });
318
405
  }
319
406
  if (scanned.includes(id)) {
320
- logger.error({ message: `Circular alias detected from "${alias}"`, source, node });
407
+ logger.error({ message: `Circular alias detected from "${alias}"`, filename, src, node });
321
408
  }
322
409
  const token = tokens[id];
323
410
  if (!isAlias(token.$value)) {
324
411
  return id;
325
412
  }
326
- return resolveAlias(token.$value, { tokens, logger, node, source, scanned: [...scanned, id] });
413
+ return resolveAlias(token.$value, { tokens, logger, filename, node, src, scanned: [...scanned, id] });
327
414
  }
328
415
 
329
416
  /** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
330
- function applyAliases(token, { tokens, logger, source, node }) {
417
+ function applyAliases(token, { tokens, logger, filename, src, node }) {
331
418
  // handle simple aliases
332
419
  if (isAlias(token.$value)) {
333
- const aliasOfID = resolveAlias(token.$value, { tokens, logger, node, source });
420
+ const aliasOfID = resolveAlias(token.$value, { tokens, logger, filename, node, src });
334
421
  const { mode: aliasMode } = parseAlias(token.$value);
335
422
  const aliasOf = tokens[aliasOfID];
336
423
  token.aliasOf = aliasOfID;
@@ -353,7 +440,7 @@ function applyAliases(token, { tokens, logger, source, node }) {
353
440
  if (!token.partialAliasOf) {
354
441
  token.partialAliasOf = [];
355
442
  }
356
- const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, node, source });
443
+ const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, filename, node, src });
357
444
  const { mode: aliasMode } = parseAlias(token.$value[i]);
358
445
  token.partialAliasOf[i] = aliasOfID;
359
446
  token.$value[i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
@@ -366,7 +453,7 @@ function applyAliases(token, { tokens, logger, source, node }) {
366
453
  if (!token.partialAliasOf[i]) {
367
454
  token.partialAliasOf[i] = {};
368
455
  }
369
- const aliasOfID = resolveAlias(token.$value[i][property], { tokens, logger, node, source });
456
+ const aliasOfID = resolveAlias(token.$value[i][property], { tokens, logger, filename, node, src });
370
457
  const { mode: aliasMode } = parseAlias(token.$value[i][property]);
371
458
  token.$value[i][property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
372
459
  token.partialAliasOf[i][property] = aliasOfID;
@@ -386,7 +473,7 @@ function applyAliases(token, { tokens, logger, source, node }) {
386
473
  if (!token.partialAliasOf) {
387
474
  token.partialAliasOf = {};
388
475
  }
389
- const aliasOfID = resolveAlias(token.$value[property], { tokens, logger, node, source });
476
+ const aliasOfID = resolveAlias(token.$value[property], { tokens, logger, filename, node, src });
390
477
  const { mode: aliasMode } = parseAlias(token.$value[property]);
391
478
  token.partialAliasOf[property] = aliasOfID;
392
479
  token.$value[property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
@@ -395,7 +482,7 @@ function applyAliases(token, { tokens, logger, source, node }) {
395
482
  else if (Array.isArray(token.$value[property])) {
396
483
  for (let i = 0; i < token.$value[property].length; i++) {
397
484
  if (isAlias(token.$value[property][i])) {
398
- const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, node, source });
485
+ const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, filename, node, src });
399
486
  if (!token.partialAliasOf) {
400
487
  token.partialAliasOf = {};
401
488
  }
@@ -7,7 +7,8 @@ declare const STROKE_STYLE_VALUES: Set<string>;
7
7
  declare const STROKE_STYLE_LINE_CAP_VALUES: Set<string>;
8
8
 
9
9
  export interface ValidateOptions {
10
- source: string;
10
+ filename?: URL;
11
+ src: string;
11
12
  logger: Logger;
12
13
  }
13
14
 
package/parse/validate.js CHANGED
@@ -4,12 +4,10 @@ import { getObjMembers } from './json.js';
4
4
 
5
5
  const listFormat = new Intl.ListFormat('en-us', { type: 'disjunction' });
6
6
 
7
- /**
8
- * @typedef {import("@humanwhocodes/momoa").AnyNode} AnyNode
9
- * @typedef {import("@humanwhocodes/momoa").ObjectNode} ObjectNode
10
- * @typedef {import("@humanwhocodes/momoa").ValueNode} ValueNode
11
- * @typedef {import("@babel/code-frame").SourceLocation} SourceLocation
12
- */
7
+ /** @typedef {import("@humanwhocodes/momoa").AnyNode} AnyNode */
8
+ /** @typedef {import("@humanwhocodes/momoa").ObjectNode} ObjectNode */
9
+ /** @typedef {import("@humanwhocodes/momoa").ValueNode} ValueNode */
10
+ /** @typedef {import("@babel/code-frame").SourceLocation} SourceLocation */
13
11
 
14
12
  export const VALID_COLORSPACES = new Set([
15
13
  'adobe-rgb',
@@ -82,21 +80,21 @@ function isMaybeAlias(node) {
82
80
  * @param {ValidateOptions} options
83
81
  * @return {void}
84
82
  */
85
- function validateMembersAs($value, properties, node, { source, logger }) {
83
+ function validateMembersAs($value, properties, node, { filename, src, logger }) {
86
84
  const members = getObjMembers($value);
87
85
  for (const property in properties) {
88
86
  const { validator, required } = properties[property];
89
87
  if (!members[property]) {
90
88
  if (required) {
91
- logger.error({ message: `Missing required property "${property}"`, node: $value, source });
89
+ logger.error({ message: `Missing required property "${property}"`, filename, node: $value, src });
92
90
  }
93
91
  continue;
94
92
  }
95
93
  const value = members[property];
96
94
  if (isMaybeAlias(value)) {
97
- validateAlias(value, node, { source, logger });
95
+ validateAlias(value, node, { filename, src, logger });
98
96
  } else {
99
- validator(value, node, { source, logger });
97
+ validator(value, node, { filename, src, logger });
100
98
  }
101
99
  }
102
100
  }
@@ -108,9 +106,9 @@ function validateMembersAs($value, properties, node, { source, logger }) {
108
106
  * @param {ValidateOptions} options
109
107
  * @return {void}
110
108
  */
111
- export function validateAlias($value, node, { source, logger }) {
109
+ export function validateAlias($value, node, { filename, src, logger }) {
112
110
  if ($value.type !== 'String' || !isAlias($value.value)) {
113
- logger.error({ message: `Invalid alias: ${print($value)}`, node: $value, source });
111
+ logger.error({ message: `Invalid alias: ${print($value)}`, filename, node: $value, src });
114
112
  }
115
113
  }
116
114
 
@@ -121,9 +119,9 @@ export function validateAlias($value, node, { source, logger }) {
121
119
  * @param {ValidateOptions} options
122
120
  * @return {void}
123
121
  */
124
- export function validateBorder($value, node, { source, logger }) {
122
+ export function validateBorder($value, node, { filename, src, logger }) {
125
123
  if ($value.type !== 'Object') {
126
- logger.error({ message: `Expected object, received ${$value.type}`, node: $value, source });
124
+ logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
127
125
  return;
128
126
  }
129
127
  validateMembersAs(
@@ -134,7 +132,7 @@ export function validateBorder($value, node, { source, logger }) {
134
132
  width: { validator: validateDimension, required: true },
135
133
  },
136
134
  node,
137
- { source, logger },
135
+ { filename, src, logger },
138
136
  );
139
137
  }
140
138
 
@@ -145,16 +143,17 @@ export function validateBorder($value, node, { source, logger }) {
145
143
  * @param {ValidateOptions} options
146
144
  * @return {void}
147
145
  */
148
- export function validateColor($value, node, { source, logger }) {
146
+ export function validateColor($value, node, { filename, src, logger }) {
149
147
  if ($value.type === 'String') {
150
148
  // TODO: enable when object notation is finalized
151
149
  // logger.warn({
150
+ // filename,
152
151
  // message: 'String colors are no longer recommended; please use the object notation instead.',
153
152
  // node: $value,
154
- // source,
153
+ // src,
155
154
  // });
156
155
  if ($value.value === '') {
157
- logger.error({ message: 'Expected color, received empty string', node: $value, source });
156
+ logger.error({ message: 'Expected color, received empty string', filename, node: $value, src });
158
157
  }
159
158
  } else if ($value.type === 'Object') {
160
159
  validateMembersAs(
@@ -163,10 +162,10 @@ export function validateColor($value, node, { source, logger }) {
163
162
  colorSpace: {
164
163
  validator: (v) => {
165
164
  if (v.type !== 'String') {
166
- logger.error({ message: `Expected string, received ${print(v)}`, node: v, source });
165
+ logger.error({ message: `Expected string, received ${print(v)}`, filename, node: v, src });
167
166
  }
168
167
  if (!VALID_COLORSPACES.has(v.value)) {
169
- logger.error({ message: `Unsupported colorspace ${print(v)}`, node: v, source });
168
+ logger.error({ message: `Unsupported colorspace ${print(v)}`, filename, node: v, src });
170
169
  }
171
170
  },
172
171
  required: true,
@@ -174,14 +173,24 @@ export function validateColor($value, node, { source, logger }) {
174
173
  channels: {
175
174
  validator: (v, node) => {
176
175
  if (v.type !== 'Array') {
177
- logger.error({ message: `Expected array, received ${print(v)}`, node: v, source });
176
+ logger.error({ message: `Expected array, received ${print(v)}`, filename, node: v, src });
178
177
  }
179
178
  if (v.elements?.length !== 3) {
180
- logger.error({ message: `Expected 3 channels, received ${v.elements?.length ?? 0}`, node: v, source });
179
+ logger.error({
180
+ message: `Expected 3 channels, received ${v.elements?.length ?? 0}`,
181
+ filename,
182
+ node: v,
183
+ src,
184
+ });
181
185
  }
182
186
  for (const element of v.elements) {
183
187
  if (element.value.type !== 'Number') {
184
- logger.error({ message: `Expected number, received ${print(element.value)}`, node: element, source });
188
+ logger.error({
189
+ message: `Expected number, received ${print(element.value)}`,
190
+ filename,
191
+ node: element,
192
+ src,
193
+ });
185
194
  }
186
195
  }
187
196
  },
@@ -199,17 +208,17 @@ export function validateColor($value, node, { source, logger }) {
199
208
  v.value.length === 7 + 1 ||
200
209
  !/^#[a-f0-9]{3,8}$/i.test(v.value)
201
210
  ) {
202
- logger.error({ message: `Invalid hex color ${print(v)}`, node: v, source });
211
+ logger.error({ message: `Invalid hex color ${print(v)}`, filename, node: v, src });
203
212
  }
204
213
  },
205
214
  },
206
215
  alpha: { validator: validateNumber },
207
216
  },
208
217
  node,
209
- { source, logger },
218
+ { filename, src, logger },
210
219
  );
211
220
  } else {
212
- logger.error({ message: `Expected object, received ${$value.type}`, node: $value, source });
221
+ logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
213
222
  }
214
223
  }
215
224
 
@@ -220,22 +229,24 @@ export function validateColor($value, node, { source, logger }) {
220
229
  * @param {ValidateOptions} options
221
230
  * @return {void}
222
231
  */
223
- export function validateCubicBezier($value, node, { source, logger }) {
232
+ export function validateCubicBezier($value, node, { filename, src, logger }) {
224
233
  if ($value.type !== 'Array') {
225
- logger.error({ message: `Expected array of strings, received ${print($value)}`, node: $value, source });
234
+ logger.error({ message: `Expected array of strings, received ${print($value)}`, filename, node: $value, src });
226
235
  } else if (
227
236
  !$value.elements.every((e) => e.value.type === 'Number' || (e.value.type === 'String' && isAlias(e.value.value)))
228
237
  ) {
229
238
  logger.error({
230
239
  message: 'Expected an array of 4 numbers, received some non-numbers',
240
+ filename,
231
241
  node: $value,
232
- source,
242
+ src,
233
243
  });
234
244
  } else if ($value.elements.length !== 4) {
235
245
  logger.error({
236
246
  message: `Expected an array of 4 numbers, received ${$value.elements.length}`,
247
+ filename,
237
248
  node: $value,
238
- source,
249
+ src,
239
250
  });
240
251
  }
241
252
  }
@@ -247,18 +258,18 @@ export function validateCubicBezier($value, node, { source, logger }) {
247
258
  * @param {ValidateOptions} options
248
259
  * @return {void}
249
260
  */
250
- export function validateDimension($value, node, { source, logger }) {
261
+ export function validateDimension($value, node, { filename, src, logger }) {
251
262
  if ($value.type === 'Number' && $value.value === 0) {
252
263
  return; // `0` is a valid number
253
264
  }
254
265
  if ($value.type !== 'String') {
255
- logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
266
+ logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
256
267
  } else if ($value.value === '') {
257
- logger.error({ message: 'Expected dimension, received empty string', node: $value, source });
268
+ logger.error({ message: 'Expected dimension, received empty string', filename, node: $value, src });
258
269
  } else if (String(Number($value.value)) === $value.value) {
259
- logger.error({ message: 'Missing units', node: $value, source });
270
+ logger.error({ message: 'Missing units', filename, node: $value, src });
260
271
  } else if (!/^[0-9]+/.test($value.value)) {
261
- logger.error({ message: `Expected dimension with units, received ${print($value)}`, node: $value, source });
272
+ logger.error({ message: `Expected dimension with units, received ${print($value)}`, filename, node: $value, src });
262
273
  }
263
274
  }
264
275
 
@@ -269,18 +280,23 @@ export function validateDimension($value, node, { source, logger }) {
269
280
  * @param {ValidateOptions} options
270
281
  * @return {void}
271
282
  */
272
- export function validateDuration($value, node, { source, logger }) {
283
+ export function validateDuration($value, node, { filename, src, logger }) {
273
284
  if ($value.type === 'Number' && $value.value === 0) {
274
285
  return; // `0` is a valid number
275
286
  }
276
287
  if ($value.type !== 'String') {
277
- logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
288
+ logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
278
289
  } else if ($value.value === '') {
279
- logger.error({ message: 'Expected duration, received empty string', node: $value, source });
290
+ logger.error({ message: 'Expected duration, received empty string', filename, node: $value, src });
280
291
  } else if (!/m?s$/.test($value.value)) {
281
- logger.error({ message: 'Missing unit "ms" or "s"', node: $value, source });
292
+ logger.error({ message: 'Missing unit "ms" or "s"', filename, node: $value, src });
282
293
  } else if (!/^[0-9]+/.test($value.value)) {
283
- logger.error({ message: `Expected duration in \`ms\` or \`s\`, received ${print($value)}`, node: $value, source });
294
+ logger.error({
295
+ message: `Expected duration in \`ms\` or \`s\`, received ${print($value)}`,
296
+ filename,
297
+ node: $value,
298
+ src,
299
+ });
284
300
  }
285
301
  }
286
302
 
@@ -291,18 +307,24 @@ export function validateDuration($value, node, { source, logger }) {
291
307
  * @param {ValidateOptions} options
292
308
  * @return {void}
293
309
  */
294
- export function validateFontFamily($value, node, { source, logger }) {
310
+ export function validateFontFamily($value, node, { filename, src, logger }) {
295
311
  if ($value.type !== 'String' && $value.type !== 'Array') {
296
- logger.error({ message: `Expected string or array of strings, received ${$value.type}`, node: $value, source });
312
+ logger.error({
313
+ message: `Expected string or array of strings, received ${$value.type}`,
314
+ filename,
315
+ node: $value,
316
+ src,
317
+ });
297
318
  }
298
319
  if ($value.type === 'String' && $value.value === '') {
299
- logger.error({ message: 'Expected font family name, received empty string', node: $value, source });
320
+ logger.error({ message: 'Expected font family name, received empty string', filename, node: $value, src });
300
321
  }
301
322
  if ($value.type === 'Array' && !$value.elements.every((e) => e.value.type === 'String' && e.value.value !== '')) {
302
323
  logger.error({
303
324
  message: 'Expected an array of strings, received some non-strings or empty strings',
325
+ filename,
304
326
  node: $value,
305
- source,
327
+ src,
306
328
  });
307
329
  }
308
330
  }
@@ -314,23 +336,25 @@ export function validateFontFamily($value, node, { source, logger }) {
314
336
  * @param {ValidateOptions} options
315
337
  * @return {void}
316
338
  */
317
- export function validateFontWeight($value, node, { source, logger }) {
339
+ export function validateFontWeight($value, node, { filename, src, logger }) {
318
340
  if ($value.type !== 'String' && $value.type !== 'Number') {
319
341
  logger.error({
320
342
  message: `Expected a font weight name or number 0–1000, received ${$value.type}`,
343
+ filename,
321
344
  node: $value,
322
- source,
345
+ src,
323
346
  });
324
347
  }
325
348
  if ($value.type === 'String' && !FONT_WEIGHT_VALUES.has($value.value)) {
326
349
  logger.error({
327
350
  message: `Unknown font weight ${print($value)}. Expected one of: ${listFormat.format([...FONT_WEIGHT_VALUES])}.`,
351
+ filename,
328
352
  node: $value,
329
- source,
353
+ src,
330
354
  });
331
355
  }
332
356
  if ($value.type === 'Number' && ($value.value < 0 || $value.value > 1000)) {
333
- logger.error({ message: `Expected number 0–1000, received ${print($value)}`, node: $value, source });
357
+ logger.error({ message: `Expected number 0–1000, received ${print($value)}`, filename, node: $value, src });
334
358
  }
335
359
  }
336
360
 
@@ -341,9 +365,14 @@ export function validateFontWeight($value, node, { source, logger }) {
341
365
  * @param {ValidateOptions} options
342
366
  * @return {void}
343
367
  */
344
- export function validateGradient($value, node, { source, logger }) {
368
+ export function validateGradient($value, node, { filename, src, logger }) {
345
369
  if ($value.type !== 'Array') {
346
- logger.error({ message: `Expected array of gradient stops, received ${$value.type}`, node: $value, source });
370
+ logger.error({
371
+ message: `Expected array of gradient stops, received ${$value.type}`,
372
+ filename,
373
+ node: $value,
374
+ src,
375
+ });
347
376
  return;
348
377
  }
349
378
  for (let i = 0; i < $value.elements.length; i++) {
@@ -351,8 +380,9 @@ export function validateGradient($value, node, { source, logger }) {
351
380
  if (element.value.type !== 'Object') {
352
381
  logger.error({
353
382
  message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
383
+ filename,
354
384
  node: element,
355
- source,
385
+ src,
356
386
  });
357
387
  break;
358
388
  }
@@ -363,7 +393,7 @@ export function validateGradient($value, node, { source, logger }) {
363
393
  position: { validator: validateNumber, required: true },
364
394
  },
365
395
  element,
366
- { source, logger },
396
+ { filename, src, logger },
367
397
  );
368
398
  }
369
399
  }
@@ -375,9 +405,9 @@ export function validateGradient($value, node, { source, logger }) {
375
405
  * @param {ValidateOptions} options
376
406
  * @return {void}
377
407
  */
378
- export function validateNumber($value, node, { source, logger }) {
408
+ export function validateNumber($value, node, { filename, src, logger }) {
379
409
  if ($value.type !== 'Number') {
380
- logger.error({ message: `Expected number, received ${$value.type}`, node: $value, source });
410
+ logger.error({ message: `Expected number, received ${$value.type}`, filename, node: $value, src });
381
411
  }
382
412
  }
383
413
 
@@ -388,9 +418,9 @@ export function validateNumber($value, node, { source, logger }) {
388
418
  * @param {ValidateOptions} options
389
419
  * @return {void}
390
420
  */
391
- export function validateShadowLayer($value, node, { source, logger }) {
421
+ export function validateShadowLayer($value, node, { filename, src, logger }) {
392
422
  if ($value.type !== 'Object') {
393
- logger.error({ message: `Expected Object, received ${$value.type}`, node: $value, source });
423
+ logger.error({ message: `Expected Object, received ${$value.type}`, filename, node: $value, src });
394
424
  return;
395
425
  }
396
426
  validateMembersAs(
@@ -403,7 +433,7 @@ export function validateShadowLayer($value, node, { source, logger }) {
403
433
  spread: { validator: validateDimension },
404
434
  },
405
435
  node,
406
- { source, logger },
436
+ { filename, src, logger },
407
437
  );
408
438
  }
409
439
 
@@ -414,7 +444,7 @@ export function validateShadowLayer($value, node, { source, logger }) {
414
444
  * @param {ValidateOptions} options
415
445
  * @return {void}
416
446
  */
417
- export function validateStrokeStyle($value, node, { source, logger }) {
447
+ export function validateStrokeStyle($value, node, { filename, src, logger }) {
418
448
  // note: strokeStyle’s values are NOT aliasable (unless by string, but that breaks validations)
419
449
  if ($value.type === 'String') {
420
450
  if (!STROKE_STYLE_VALUES.has($value.value)) {
@@ -422,19 +452,16 @@ export function validateStrokeStyle($value, node, { source, logger }) {
422
452
  message: `Unknown stroke style ${print($value)}. Expected one of: ${listFormat.format([
423
453
  ...STROKE_STYLE_VALUES,
424
454
  ])}.`,
455
+ filename,
425
456
  node: $value,
426
- source,
457
+ src,
427
458
  });
428
459
  }
429
460
  } else if ($value.type === 'Object') {
430
461
  const strokeMembers = getObjMembers($value);
431
462
  for (const property of ['dashArray', 'lineCap']) {
432
463
  if (!strokeMembers[property]) {
433
- logger.error({
434
- message: `Missing required property "${property}"`,
435
- node: $value,
436
- source,
437
- });
464
+ logger.error({ message: `Missing required property "${property}"`, filename, node: $value, src });
438
465
  }
439
466
  }
440
467
  const { lineCap, dashArray } = strokeMembers;
@@ -449,31 +476,24 @@ export function validateStrokeStyle($value, node, { source, logger }) {
449
476
  for (const element of dashArray.elements) {
450
477
  if (element.value.type === 'String' && element.value.value !== '') {
451
478
  if (isMaybeAlias(element.value)) {
452
- validateAlias(element.value, node, { logger, source });
479
+ validateAlias(element.value, node, { logger, src });
453
480
  } else {
454
- validateDimension(element.value, node, { logger, source });
481
+ validateDimension(element.value, node, { logger, src });
455
482
  }
456
483
  } else {
457
484
  logger.error({
458
485
  message: 'Expected array of strings, recieved some non-strings or empty strings.',
486
+ filename,
459
487
  node: element,
460
- source,
488
+ src,
461
489
  });
462
490
  }
463
491
  }
464
492
  } else {
465
- logger.error({
466
- message: `Expected array of strings, received ${dashArray.type}`,
467
- node: $value,
468
- source,
469
- });
493
+ logger.error({ message: `Expected array of strings, received ${dashArray.type}`, filename, node: $value, src });
470
494
  }
471
495
  } else {
472
- logger.error({
473
- message: `Expected string or object, received ${$value.type}`,
474
- node: $value,
475
- source,
476
- });
496
+ logger.error({ message: `Expected string or object, received ${$value.type}`, filename, node: $value, src });
477
497
  }
478
498
  }
479
499
 
@@ -484,9 +504,9 @@ export function validateStrokeStyle($value, node, { source, logger }) {
484
504
  * @param {ValidateOptions} options
485
505
  * @return {void}
486
506
  */
487
- export function validateTransition($value, node, { source, logger }) {
507
+ export function validateTransition($value, node, { filename, src, logger }) {
488
508
  if ($value.type !== 'Object') {
489
- logger.error({ message: `Expected object, received ${$value.type}`, node: $value, source });
509
+ logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
490
510
  return;
491
511
  }
492
512
  validateMembersAs(
@@ -497,7 +517,7 @@ export function validateTransition($value, node, { source, logger }) {
497
517
  timingFunction: { validator: validateCubicBezier, required: true },
498
518
  },
499
519
  node,
500
- { source, logger },
520
+ { filename, src, logger },
501
521
  );
502
522
  }
503
523
 
@@ -508,12 +528,13 @@ export function validateTransition($value, node, { source, logger }) {
508
528
  * @param {ValidateOptions} options
509
529
  * @return {void}
510
530
  */
511
- export default function validate(node, { source, logger }) {
531
+ export default function validate(node, { filename, src, logger }) {
512
532
  if (node.type !== 'Member' && node.type !== 'Object') {
513
533
  logger.error({
514
534
  message: `Expected Object, received ${JSON.stringify(node.type)}`,
535
+ filename,
515
536
  node,
516
- source,
537
+ src,
517
538
  });
518
539
  return;
519
540
  }
@@ -523,62 +544,63 @@ export default function validate(node, { source, logger }) {
523
544
  const $type = rootMembers.$type;
524
545
 
525
546
  if (!$value) {
526
- logger.error({ message: 'Token missing $value', node, source });
547
+ logger.error({ message: 'Token missing $value', filename, node, src });
527
548
  return;
528
549
  }
529
550
  // If top-level value is a valid alias, this is valid (no need for $type)
530
551
  // ⚠️ Important: ALL Object and Array nodes below will need to check for aliases within!
531
552
  if (isMaybeAlias($value)) {
532
- validateAlias($value, node, { logger, source });
553
+ validateAlias($value, node, { logger, src });
533
554
  return;
534
555
  }
535
556
 
536
557
  if (!$type) {
537
- logger.error({ message: 'Token missing $type', node, source });
558
+ logger.error({ message: 'Token missing $type', filename, node, src });
538
559
  return;
539
560
  }
540
561
 
541
562
  switch ($type.value) {
542
563
  case 'color': {
543
- validateColor($value, node, { logger, source });
564
+ validateColor($value, node, { logger, src });
544
565
  break;
545
566
  }
546
567
  case 'cubicBezier': {
547
- validateCubicBezier($value, node, { logger, source });
568
+ validateCubicBezier($value, node, { logger, src });
548
569
  break;
549
570
  }
550
571
  case 'dimension': {
551
- validateDimension($value, node, { logger, source });
572
+ validateDimension($value, node, { logger, src });
552
573
  break;
553
574
  }
554
575
  case 'duration': {
555
- validateDuration($value, node, { logger, source });
576
+ validateDuration($value, node, { logger, src });
556
577
  break;
557
578
  }
558
579
  case 'fontFamily': {
559
- validateFontFamily($value, node, { logger, source });
580
+ validateFontFamily($value, node, { logger, src });
560
581
  break;
561
582
  }
562
583
  case 'fontWeight': {
563
- validateFontWeight($value, node, { logger, source });
584
+ validateFontWeight($value, node, { logger, src });
564
585
  break;
565
586
  }
566
587
  case 'number': {
567
- validateNumber($value, node, { logger, source });
588
+ validateNumber($value, node, { logger, src });
568
589
  break;
569
590
  }
570
591
  case 'shadow': {
571
592
  if ($value.type === 'Object') {
572
- validateShadowLayer($value, node, { logger, source });
593
+ validateShadowLayer($value, node, { logger, src });
573
594
  } else if ($value.type === 'Array') {
574
595
  for (const element of $value.elements) {
575
- validateShadowLayer(element.value, $value, { logger, source });
596
+ validateShadowLayer(element.value, $value, { logger, src });
576
597
  }
577
598
  } else {
578
599
  logger.error({
579
600
  message: `Expected shadow object or array of shadow objects, received ${$value.type}`,
601
+ filename,
580
602
  node: $value,
581
- source,
603
+ src,
582
604
  });
583
605
  }
584
606
  break;
@@ -587,49 +609,54 @@ export default function validate(node, { source, logger }) {
587
609
  // extensions
588
610
  case 'boolean': {
589
611
  if ($value.type !== 'Boolean') {
590
- logger.error({ message: `Expected boolean, received ${$value.type}`, node: $value, source });
612
+ logger.error({ message: `Expected boolean, received ${$value.type}`, filename, node: $value, src });
591
613
  }
592
614
  break;
593
615
  }
594
616
  case 'link': {
595
617
  if ($value.type !== 'String') {
596
- logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
618
+ logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
597
619
  } else if ($value.value === '') {
598
- logger.error({ message: 'Expected URL, received empty string', node: $value, source });
620
+ logger.error({ message: 'Expected URL, received empty string', filename, node: $value, src });
599
621
  }
600
622
  break;
601
623
  }
602
624
  case 'string': {
603
625
  if ($value.type !== 'String') {
604
- logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
626
+ logger.error({ message: `Expected string, received ${$value.type}`, filename, node: $value, src });
605
627
  }
606
628
  break;
607
629
  }
608
630
 
609
631
  // composite types
610
632
  case 'border': {
611
- validateBorder($value, node, { source, logger });
633
+ validateBorder($value, node, { filename, src, logger });
612
634
  break;
613
635
  }
614
636
  case 'gradient': {
615
- validateGradient($value, node, { source, logger });
637
+ validateGradient($value, node, { filename, src, logger });
616
638
  break;
617
639
  }
618
640
  case 'strokeStyle': {
619
- validateStrokeStyle($value, node, { source, logger });
641
+ validateStrokeStyle($value, node, { filename, src, logger });
620
642
  break;
621
643
  }
622
644
  case 'transition': {
623
- validateTransition($value, node, { source, logger });
645
+ validateTransition($value, node, { filename, src, logger });
624
646
  break;
625
647
  }
626
648
  case 'typography': {
627
649
  if ($value.type !== 'Object') {
628
- logger.error({ message: `Expected object, received ${$value.type}`, node: $value, source });
650
+ logger.error({ message: `Expected object, received ${$value.type}`, filename, node: $value, src });
629
651
  break;
630
652
  }
631
653
  if ($value.members.length === 0) {
632
- logger.error({ message: 'Empty typography token. Must contain at least 1 property.', node: $value, source });
654
+ logger.error({
655
+ message: 'Empty typography token. Must contain at least 1 property.',
656
+ filename,
657
+ node: $value,
658
+ src,
659
+ });
633
660
  }
634
661
  validateMembersAs(
635
662
  $value,
@@ -638,7 +665,7 @@ export default function validate(node, { source, logger }) {
638
665
  fontWeight: { validator: validateFontWeight },
639
666
  },
640
667
  node,
641
- { source, logger },
668
+ { filename, src, logger },
642
669
  );
643
670
  break;
644
671
  }