@terrazzo/parser 0.0.3 → 0.0.4

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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # 💠 @terrazzo/parser
2
+
3
+ JS API for parsing / validating / transforming DTCG tokens.
4
+
5
+ [View Documentation](https://terrazzi-docs-beta.pages.dev)
package/build/index.d.ts CHANGED
@@ -14,6 +14,13 @@ export interface OutputFile {
14
14
  contents: string | Buffer;
15
15
  }
16
16
 
17
+ export interface OutputFileExpanded extends OutputFile {
18
+ /** The `name` of the plugin that produced this file. */
19
+ plugin: string;
20
+ /** How long this output took to make. */
21
+ time: number;
22
+ }
23
+
17
24
  /** Transformed token with a single value. Note that this may be any type! */
18
25
  export interface TokenTransformedSingleValue {
19
26
  /** ID unique to this format. If missing, use `token.id`. */
@@ -44,7 +51,9 @@ export interface TransformParams {
44
51
  /** ID of an existing format */
45
52
  format: string;
46
53
  /** Glob of tokens to select (e.g. `"color.*"` to select all tokens starting with `"color."`) */
47
- select?: string | string[];
54
+ id?: string | string[];
55
+ /** $type(s) to filter for */
56
+ $type?: string | string[];
48
57
  /** Mode name, if selecting a mode (default: `"."`) */
49
58
  mode?: string | string[];
50
59
  }
@@ -95,7 +104,7 @@ export interface BuildEndHookOptions {
95
104
  /** Momoa document */
96
105
  ast: DocumentNode;
97
106
  /** Final files to be written */
98
- outputFiles: OutputFile[];
107
+ outputFiles: OutputFileExpanded[];
99
108
  }
100
109
 
101
110
  export default function build(
package/build/index.js CHANGED
@@ -66,14 +66,24 @@ export default async function build(tokens, { ast, logger = new Logger(), config
66
66
 
67
67
  function getTransforms(params) {
68
68
  return (formats[params.format] ?? []).filter((token) => {
69
+ if (params.$type) {
70
+ if (typeof params.$type === 'string' && token.$type !== params.$type) {
71
+ return false;
72
+ } else if (Array.isArray(params.$type) && !params.$type.some(($type) => token.type === $type)) {
73
+ return false;
74
+ }
75
+ }
69
76
  if (
70
- params.select &&
71
- params.select !== '*' &&
72
- !isTokenMatch(token.token.id, Array.isArray(params.select) ? params.select : [params.select])
77
+ params.id &&
78
+ params.id !== '*' &&
79
+ !isTokenMatch(token.token.id, Array.isArray(params.id) ? params.id : [params.id])
73
80
  ) {
74
81
  return false;
75
82
  }
76
- return !params.mode || wcmatch(params.mode)(token.mode);
83
+ if (params.mode && !wcmatch(params.mode)(token.mode)) {
84
+ return false;
85
+ }
86
+ return true;
77
87
  });
78
88
  }
79
89
 
@@ -137,6 +147,7 @@ export default async function build(tokens, { ast, logger = new Logger(), config
137
147
  logger.debug({ group: 'parser', task: 'build', message: 'Start build' });
138
148
  for (const plugin of config.plugins) {
139
149
  if (typeof plugin.build === 'function') {
150
+ const pluginBuildStart = performance.now();
140
151
  await plugin.build({
141
152
  tokens,
142
153
  ast,
@@ -146,7 +157,12 @@ export default async function build(tokens, { ast, logger = new Logger(), config
146
157
  if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) {
147
158
  logger.error({ message: `Can’t overwrite file "${filename}"`, label: plugin.name });
148
159
  }
149
- result.outputFiles.push({ filename, contents });
160
+ result.outputFiles.push({
161
+ filename,
162
+ contents,
163
+ plugin: plugin.name,
164
+ time: performance.now() - pluginBuildStart,
165
+ });
150
166
  },
151
167
  });
152
168
  }
@@ -166,6 +182,7 @@ export default async function build(tokens, { ast, logger = new Logger(), config
166
182
  await plugin.buildEnd({
167
183
  tokens,
168
184
  ast,
185
+ getTransforms,
169
186
  format: (formatID) => createFormatter(formatID),
170
187
  outputFiles: structruedClone(result.outputFiles),
171
188
  });
package/lint/index.d.ts CHANGED
@@ -27,6 +27,7 @@ export interface LinterOptions<O = any> {
27
27
  severity: LintRuleSeverity;
28
28
  };
29
29
  ast: DocumentNode;
30
+ source?: string;
30
31
  /** Any options the user has declared for this plugin */
31
32
  options?: O;
32
33
  }
package/logger.d.ts CHANGED
@@ -18,12 +18,8 @@ export interface LogEntry {
18
18
  continueOnError?: boolean;
19
19
  /** (optional) Show a code frame for the erring node */
20
20
  node?: AnyNode;
21
- /** (optional) To show code frame, provide entire AST to show which line erred (otherwise it’s floating in space) */
22
- ast?: DocumentNode;
23
- /** (optional) To highlight a specifc part of the code frame, provide line no. (1-based) and col. no. */
24
- loc?: SourceLocation['start'];
25
- /** (optional) MANUAL codeFrame override (only use for non-JSON errors, like YAML) */
26
- code?: string;
21
+ /** (optional) To show a code frame, provide the original source code */
22
+ source?: string;
27
23
  }
28
24
 
29
25
  export interface DebugEntry {
@@ -34,7 +30,7 @@ export interface DebugEntry {
34
30
  /** Error message to be logged */
35
31
  message: string;
36
32
  /** (optional) Show code below message */
37
- codeFrame?: { code: string; line: number; column: number };
33
+ codeFrame?: { source: string; line: number; column: number };
38
34
  /** (optional) Display performance timing */
39
35
  timing?: number;
40
36
  }
package/logger.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { codeFrameColumns } from '@babel/code-frame';
2
- import { print } from '@humanwhocodes/momoa';
3
2
  import color from 'picocolors';
4
3
  import wcmatch from 'wildcard-match';
5
4
 
@@ -24,10 +23,8 @@ export function formatMessage(entry, severity) {
24
23
  if (severity in MESSAGE_COLOR) {
25
24
  message = MESSAGE_COLOR[severity](message);
26
25
  }
27
- if (entry.ast || entry.code) {
28
- message = `${message}\n\n${codeFrameColumns(entry.ast ? print(entry.ast, { indent: 2 }) : entry.code, {
29
- start: entry.loc ?? { line: 1 },
30
- })}`;
26
+ if (entry.source) {
27
+ message = `${message}\n\n${codeFrameColumns(entry.source, { start: entry.node?.loc?.start })}`;
31
28
  }
32
29
  return message;
33
30
  }
@@ -89,10 +86,10 @@ export default class Logger {
89
86
  let message = formatMessage(entry, 'debug');
90
87
 
91
88
  const debugPrefix = `${entry.group}:${entry.task}`;
92
- if (!wcmatch(this.debugScope)(debugPrefix)) {
89
+ if (this.debugScope !== '*' && !wcmatch(this.debugScope)(debugPrefix)) {
93
90
  return;
94
91
  }
95
- message = `${DEBUG_GROUP_COLOR[entry.group || 'core'](debugPrefix)} ${color.dim(
92
+ message = `${(DEBUG_GROUP_COLOR[entry.group] || color.white)(debugPrefix)} ${color.dim(
96
93
  timeFormatter.format(new Date()),
97
94
  )} ${message}`;
98
95
  if (typeof entry.timing === 'number') {
@@ -105,7 +102,8 @@ export default class Logger {
105
102
  message = `${message} ${color.dim(`[${timing}]`)}`;
106
103
  }
107
104
 
108
- console.debug(message);
105
+ // biome-ignore lint/suspicious/noConsoleLog: this is its job
106
+ console.log(message);
109
107
  }
110
108
  }
111
109
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@terrazzo/parser",
3
3
  "description": "Parser/validator for the Design Tokens Community Group (DTCG) standard.",
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
5
  "author": {
6
6
  "name": "Drew Powers",
7
7
  "email": "drew@pow.rs"
package/parse/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { evaluate, parse as parseJSON } from '@humanwhocodes/momoa';
1
+ import { evaluate, parse as parseJSON, print } from '@humanwhocodes/momoa';
2
2
  import { isAlias, parseAlias, splitID } from '@terrazzo/token-tools';
3
3
  import lintRunner from '../lint/index.js';
4
4
  import Logger from '../logger.js';
@@ -34,6 +34,10 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
34
34
  const totalStart = performance.now();
35
35
 
36
36
  // 1. Build AST
37
+ let source;
38
+ if (typeof input === 'string') {
39
+ source = input;
40
+ }
37
41
  const startParsing = performance.now();
38
42
  logger.debug({ group: 'parser', task: 'parse', message: 'Start tokens parsing' });
39
43
  let ast;
@@ -44,6 +48,9 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
44
48
  mode: 'jsonc',
45
49
  }); // everything else: assert it’s JSON-serializable
46
50
  }
51
+ if (!source) {
52
+ source = print(ast, { indent: 2 });
53
+ }
47
54
  logger.debug({
48
55
  group: 'parser',
49
56
  task: 'parse',
@@ -86,7 +93,7 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
86
93
  if (parent$type && !members.$type) {
87
94
  sourceNode.value = injectObjMembers(sourceNode.value, [parent$type]);
88
95
  }
89
- validate(sourceNode, { ast, logger });
96
+ validate(sourceNode, { source, logger });
90
97
 
91
98
  const group = { id: splitID(id).group, tokens: [] };
92
99
  if (parent$type) {
@@ -130,7 +137,7 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
130
137
 
131
138
  tokens[id] = token;
132
139
  } else if (members.value) {
133
- logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, node, ast });
140
+ logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, node, source });
134
141
  }
135
142
  }
136
143
 
@@ -158,7 +165,7 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
158
165
  task: 'validate',
159
166
  message: 'Start token linting',
160
167
  });
161
- await lintRunner({ ast, config, logger });
168
+ await lintRunner({ ast, source, config, logger });
162
169
  logger.debug({
163
170
  group: 'parser',
164
171
  task: 'validate',
@@ -181,7 +188,12 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
181
188
  try {
182
189
  tokens[id].$value = normalize(tokens[id]);
183
190
  } catch (err) {
184
- logger.error({ message: err.message, ast, node: tokens[id].sourceNode });
191
+ let node = tokens[id].sourceNode;
192
+ const members = getObjMembers(node);
193
+ if (members.$value) {
194
+ node = members.$value;
195
+ }
196
+ logger.error({ message: err.message, source, node });
185
197
  }
186
198
  for (const mode in tokens[id].mode) {
187
199
  if (mode === '.') {
@@ -190,7 +202,12 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
190
202
  try {
191
203
  tokens[id].mode[mode].$value = normalize(tokens[id].mode[mode]);
192
204
  } catch (err) {
193
- logger.error({ message: err.message, ast, node: tokens[id].mode[mode].sourceNode });
205
+ let node = tokens[id].sourceNode;
206
+ const members = getObjMembers(node);
207
+ if (members.$value) {
208
+ node = members.$value;
209
+ }
210
+ logger.error({ message: err.message, source, node: tokens[id].mode[mode].sourceNode });
194
211
  }
195
212
  }
196
213
  }
@@ -207,7 +224,7 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
207
224
  continue;
208
225
  }
209
226
  const token = tokens[id];
210
- applyAliases(token, { tokens, ast, node: token.sourceNode, logger });
227
+ applyAliases(token, { tokens, source, node: token.sourceNode, logger });
211
228
  token.mode['.'].$value = token.$value;
212
229
  if (token.aliasOf) {
213
230
  token.mode['.'].aliasOf = token.aliasOf;
@@ -239,7 +256,7 @@ export default async function parse(input, { logger = new Logger(), skipLint = f
239
256
  if (mode === '.') {
240
257
  continue; // skip shadow of root value
241
258
  }
242
- applyAliases(tokens[id].mode[mode], { tokens, ast, node: tokens[id].mode[mode].sourceNode, logger });
259
+ applyAliases(tokens[id].mode[mode], { tokens, source, node: tokens[id].mode[mode].sourceNode, logger });
243
260
  }
244
261
  }
245
262
  logger.debug({
@@ -277,31 +294,31 @@ export function maybeJSONString(input) {
277
294
  * @param {Object} options
278
295
  * @param {Record<string, TokenNormalized>} options.tokens
279
296
  * @param {Logger} options.logger
280
- * @param {AnyNode | undefined} options.node
281
- * @param {DocumentNode | undefined} options.ast
297
+ * @param {AnyNode} [options.node]
298
+ * @param {string} [options.string]
282
299
  * @param {string[]=[]} options.scanned
283
300
  * @param {string}
284
301
  */
285
- export function resolveAlias(alias, { tokens, logger, ast, node, scanned = [] }) {
302
+ export function resolveAlias(alias, { tokens, logger, source, node, scanned = [] }) {
286
303
  const { id } = parseAlias(alias);
287
304
  if (!tokens[id]) {
288
- logger.error({ message: `Alias "${alias}" not found`, ast, node });
305
+ logger.error({ message: `Alias "${alias}" not found`, source, node });
289
306
  }
290
307
  if (scanned.includes(id)) {
291
- logger.error({ message: `Circular alias detected from "${alias}"`, ast, node });
308
+ logger.error({ message: `Circular alias detected from "${alias}"`, source, node });
292
309
  }
293
310
  const token = tokens[id];
294
311
  if (!isAlias(token.$value)) {
295
312
  return id;
296
313
  }
297
- return resolveAlias(alias, { tokens, logger, ast, node, scanned: [...scanned, id] });
314
+ return resolveAlias(alias, { tokens, logger, node, source, scanned: [...scanned, id] });
298
315
  }
299
316
 
300
317
  /** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
301
- function applyAliases(token, { tokens, logger, ast, node }) {
318
+ function applyAliases(token, { tokens, logger, source, node }) {
302
319
  // handle simple aliases
303
320
  if (isAlias(token.$value)) {
304
- const aliasOfID = resolveAlias(token.$value, { tokens, logger, node, ast });
321
+ const aliasOfID = resolveAlias(token.$value, { tokens, logger, node, source });
305
322
  const { mode: aliasMode } = parseAlias(token.$value);
306
323
  const aliasOf = tokens[aliasOfID];
307
324
  token.aliasOf = aliasOfID;
@@ -310,7 +327,6 @@ function applyAliases(token, { tokens, logger, ast, node }) {
310
327
  logger.warn({
311
328
  message: `Token ${token.id} has $type "${token.$type}" but aliased ${aliasOfID} of $type "${aliasOf.$type}"`,
312
329
  node,
313
- ast,
314
330
  });
315
331
  token.$type = aliasOf.$type;
316
332
  } else {
@@ -325,7 +341,7 @@ function applyAliases(token, { tokens, logger, ast, node }) {
325
341
  if (!token.partialAliasOf) {
326
342
  token.partialAliasOf = [];
327
343
  }
328
- const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, node, ast });
344
+ const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, node, source });
329
345
  const { mode: aliasMode } = parseAlias(token.$value[i]);
330
346
  token.partialAliasOf[i] = aliasOfID;
331
347
  token.$value[i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
@@ -338,7 +354,7 @@ function applyAliases(token, { tokens, logger, ast, node }) {
338
354
  if (!token.partialAliasOf[i]) {
339
355
  token.partialAliasOf[i] = {};
340
356
  }
341
- const aliasOfID = resolveAlias(token.$value[i][property], { tokens, logger, node, ast });
357
+ const aliasOfID = resolveAlias(token.$value[i][property], { tokens, logger, node, source });
342
358
  const { mode: aliasMode } = parseAlias(token.$value[i][property]);
343
359
  token.$value[i][property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
344
360
  token.partialAliasOf[i][property] = aliasOfID;
@@ -358,7 +374,7 @@ function applyAliases(token, { tokens, logger, ast, node }) {
358
374
  if (!token.partialAliasOf) {
359
375
  token.partialAliasOf = {};
360
376
  }
361
- const aliasOfID = resolveAlias(token.$value[property], { tokens, logger, node, ast });
377
+ const aliasOfID = resolveAlias(token.$value[property], { tokens, logger, node, source });
362
378
  const { mode: aliasMode } = parseAlias(token.$value[property]);
363
379
  token.partialAliasOf[property] = aliasOfID;
364
380
  token.$value[property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
@@ -367,7 +383,7 @@ function applyAliases(token, { tokens, logger, ast, node }) {
367
383
  else if (Array.isArray(token.$value[property])) {
368
384
  for (let i = 0; i < token.$value[property].length; i++) {
369
385
  if (isAlias(token.$value[property][i])) {
370
- const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, node, ast });
386
+ const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, node, source });
371
387
  if (!token.partialAliasOf) {
372
388
  token.partialAliasOf = {};
373
389
  }
@@ -7,7 +7,7 @@ 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
- ast: DocumentNode;
10
+ source: string;
11
11
  logger: Logger;
12
12
  }
13
13
 
package/parse/validate.js CHANGED
@@ -44,15 +44,6 @@ export const STROKE_STYLE_VALUES = new Set([
44
44
  ]);
45
45
  export const STROKE_STYLE_LINE_CAP_VALUES = new Set(['round', 'butt', 'square']);
46
46
 
47
- /**
48
- * Get the location of a JSON node
49
- * @param {AnyNode} node
50
- * @return {SourceLocation}
51
- */
52
- function getLoc(node) {
53
- return { line: node.loc?.start.line ?? 1, column: node.loc?.start.column };
54
- }
55
-
56
47
  /**
57
48
  * Distinct from isAlias() in that this accepts malformed aliases
58
49
  * @param {AnyNode} node
@@ -73,21 +64,21 @@ function isMaybeAlias(node) {
73
64
  * @param {ValidateOptions} options
74
65
  * @return {void}
75
66
  */
76
- function validateMembersAs($value, properties, node, { ast, logger }) {
67
+ function validateMembersAs($value, properties, node, { source, logger }) {
77
68
  const members = getObjMembers($value);
78
69
  for (const property in properties) {
79
70
  const { validator, required } = properties[property];
80
71
  if (!members[property]) {
81
72
  if (required) {
82
- logger.error({ message: `Missing required property "${property}"`, node, ast, loc: getLoc($value) });
73
+ logger.error({ message: `Missing required property "${property}"`, node: $value, source });
83
74
  }
84
75
  continue;
85
76
  }
86
77
  const value = members[property];
87
78
  if (isMaybeAlias(value)) {
88
- validateAlias(value, node, { ast, logger });
79
+ validateAlias(value, node, { source, logger });
89
80
  } else {
90
- validator(value, node, { ast, logger });
81
+ validator(value, node, { source, logger });
91
82
  }
92
83
  }
93
84
  }
@@ -99,9 +90,9 @@ function validateMembersAs($value, properties, node, { ast, logger }) {
99
90
  * @param {ValidateOptions} options
100
91
  * @return {void}
101
92
  */
102
- export function validateAlias($value, node, { ast, logger }) {
93
+ export function validateAlias($value, node, { source, logger }) {
103
94
  if ($value.type !== 'String' || !isAlias($value.value)) {
104
- logger.error({ message: `Invalid alias: ${print($value)}`, node, ast, loc: getLoc($value) });
95
+ logger.error({ message: `Invalid alias: ${print($value)}`, node: $value, source });
105
96
  }
106
97
  }
107
98
 
@@ -112,9 +103,9 @@ export function validateAlias($value, node, { ast, logger }) {
112
103
  * @param {ValidateOptions} options
113
104
  * @return {void}
114
105
  */
115
- export function validateBorder($value, node, { ast, logger }) {
106
+ export function validateBorder($value, node, { source, logger }) {
116
107
  if ($value.type !== 'Object') {
117
- logger.error({ message: `Expected object, received ${$value.type}`, node, ast, loc: getLoc($value) });
108
+ logger.error({ message: `Expected object, received ${$value.type}`, node: $value, source });
118
109
  return;
119
110
  }
120
111
  validateMembersAs(
@@ -125,7 +116,7 @@ export function validateBorder($value, node, { ast, logger }) {
125
116
  width: { validator: validateDimension, required: true },
126
117
  },
127
118
  node,
128
- { ast, logger },
119
+ { source, logger },
129
120
  );
130
121
  }
131
122
 
@@ -136,11 +127,11 @@ export function validateBorder($value, node, { ast, logger }) {
136
127
  * @param {ValidateOptions} options
137
128
  * @return {void}
138
129
  */
139
- export function validateColor($value, node, { ast, logger }) {
130
+ export function validateColor($value, node, { source, logger }) {
140
131
  if ($value.type !== 'String') {
141
- logger.error({ message: `Expected string, received ${$value.type}`, node, ast, loc: getLoc($value) });
132
+ logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
142
133
  } else if ($value.value === '') {
143
- logger.error({ message: 'Expected color, received empty string', node, ast, loc: getLoc($value) });
134
+ logger.error({ message: 'Expected color, received empty string', node: $value, source });
144
135
  }
145
136
  }
146
137
 
@@ -151,29 +142,22 @@ export function validateColor($value, node, { ast, logger }) {
151
142
  * @param {ValidateOptions} options
152
143
  * @return {void}
153
144
  */
154
- export function validateCubicBézier($value, node, { ast, logger }) {
145
+ export function validateCubicBézier($value, node, { source, logger }) {
155
146
  if ($value.type !== 'Array') {
156
- logger.error({
157
- message: `Expected array of strings, received ${print($value)}`,
158
- node,
159
- ast,
160
- loc: getLoc($value),
161
- });
147
+ logger.error({ message: `Expected array of strings, received ${print($value)}`, node: $value, source });
162
148
  } else if (
163
149
  !$value.elements.every((e) => e.value.type === 'Number' || (e.value.type === 'String' && isAlias(e.value.value)))
164
150
  ) {
165
151
  logger.error({
166
152
  message: 'Expected an array of 4 numbers, received some non-numbers',
167
- node,
168
- ast,
169
- loc: getLoc($value),
153
+ node: $value,
154
+ source,
170
155
  });
171
156
  } else if ($value.elements.length !== 4) {
172
157
  logger.error({
173
158
  message: `Expected an array of 4 numbers, received ${$value.elements.length}`,
174
- node,
175
- ast,
176
- loc: getLoc($value),
159
+ node: $value,
160
+ source,
177
161
  });
178
162
  }
179
163
  }
@@ -185,23 +169,18 @@ export function validateCubicBézier($value, node, { ast, logger }) {
185
169
  * @param {ValidateOptions} options
186
170
  * @return {void}
187
171
  */
188
- export function validateDimension($value, node, { ast, logger }) {
172
+ export function validateDimension($value, node, { source, logger }) {
189
173
  if ($value.type === 'Number' && $value.value === 0) {
190
174
  return; // `0` is a valid number
191
175
  }
192
176
  if ($value.type !== 'String') {
193
- logger.error({ message: `Expected string, received ${$value.type}`, node, ast, loc: getLoc($value) });
177
+ logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
194
178
  } else if ($value.value === '') {
195
- logger.error({ message: 'Expected dimension, received empty string', node, ast, loc: getLoc($value) });
179
+ logger.error({ message: 'Expected dimension, received empty string', node: $value, source });
196
180
  } else if (String(Number($value.value)) === $value.value) {
197
- logger.error({ message: 'Missing units', node, ast, loc: getLoc($value) });
181
+ logger.error({ message: 'Missing units', node: $value, source });
198
182
  } else if (!/^[0-9]+/.test($value.value)) {
199
- logger.error({
200
- message: `Expected dimension with units, received ${print($value)}`,
201
- node,
202
- ast,
203
- loc: getLoc($value),
204
- });
183
+ logger.error({ message: `Expected dimension with units, received ${print($value)}`, node: $value, source });
205
184
  }
206
185
  }
207
186
 
@@ -212,23 +191,18 @@ export function validateDimension($value, node, { ast, logger }) {
212
191
  * @param {ValidateOptions} options
213
192
  * @return {void}
214
193
  */
215
- export function validateDuration($value, node, { ast, logger }) {
194
+ export function validateDuration($value, node, { source, logger }) {
216
195
  if ($value.type === 'Number' && $value.value === 0) {
217
196
  return; // `0` is a valid number
218
197
  }
219
198
  if ($value.type !== 'String') {
220
- logger.error({ message: `Expected string, received ${$value.type}`, node, ast, loc: getLoc($value) });
199
+ logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
221
200
  } else if ($value.value === '') {
222
- logger.error({ message: 'Expected duration, received empty string', node, ast, loc: getLoc($value) });
201
+ logger.error({ message: 'Expected duration, received empty string', node: $value, source });
223
202
  } else if (!/m?s$/.test($value.value)) {
224
- logger.error({ message: 'Missing unit "ms" or "s"', node, ast, loc: getLoc($value) });
203
+ logger.error({ message: 'Missing unit "ms" or "s"', node: $value, source });
225
204
  } else if (!/^[0-9]+/.test($value.value)) {
226
- logger.error({
227
- message: `Expected duration in \`ms\` or \`s\`, received ${print($value)}`,
228
- node,
229
- ast,
230
- loc: getLoc($value),
231
- });
205
+ logger.error({ message: `Expected duration in \`ms\` or \`s\`, received ${print($value)}`, node: $value, source });
232
206
  }
233
207
  }
234
208
 
@@ -239,24 +213,18 @@ export function validateDuration($value, node, { ast, logger }) {
239
213
  * @param {ValidateOptions} options
240
214
  * @return {void}
241
215
  */
242
- export function validateFontFamily($value, node, { ast, logger }) {
216
+ export function validateFontFamily($value, node, { source, logger }) {
243
217
  if ($value.type !== 'String' && $value.type !== 'Array') {
244
- logger.error({
245
- message: `Expected string or array of strings, received ${$value.type}`,
246
- node,
247
- ast,
248
- loc: getLoc($value),
249
- });
218
+ logger.error({ message: `Expected string or array of strings, received ${$value.type}`, node: $value, source });
250
219
  }
251
220
  if ($value.type === 'String' && $value.value === '') {
252
- logger.error({ message: 'Expected font family name, received empty string', node, ast, loc: getLoc($value) });
221
+ logger.error({ message: 'Expected font family name, received empty string', node: $value, source });
253
222
  }
254
223
  if ($value.type === 'Array' && !$value.elements.every((e) => e.value.type === 'String' && e.value.value !== '')) {
255
224
  logger.error({
256
225
  message: 'Expected an array of strings, received some non-strings or empty strings',
257
- node,
258
- ast,
259
- loc: getLoc($value),
226
+ node: $value,
227
+ source,
260
228
  });
261
229
  }
262
230
  }
@@ -268,25 +236,23 @@ export function validateFontFamily($value, node, { ast, logger }) {
268
236
  * @param {ValidateOptions} options
269
237
  * @return {void}
270
238
  */
271
- export function validateFontWeight($value, node, { ast, logger }) {
239
+ export function validateFontWeight($value, node, { source, logger }) {
272
240
  if ($value.type !== 'String' && $value.type !== 'Number') {
273
241
  logger.error({
274
242
  message: `Expected a font weight name or number 0–1000, received ${$value.type}`,
275
- node,
276
- ast,
277
- loc: getLoc($value),
243
+ node: $value,
244
+ source,
278
245
  });
279
246
  }
280
247
  if ($value.type === 'String' && !FONT_WEIGHT_VALUES.has($value.value)) {
281
248
  logger.error({
282
249
  message: `Unknown font weight ${print($value)}. Expected one of: ${listFormat.format([...FONT_WEIGHT_VALUES])}.`,
283
- node,
284
- ast,
285
- loc: getLoc($value),
250
+ node: $value,
251
+ source,
286
252
  });
287
253
  }
288
254
  if ($value.type === 'Number' && ($value.value < 0 || $value.value > 1000)) {
289
- logger.error({ message: `Expected number 0–1000, received ${print($value)}`, node, ast, loc: getLoc($value) });
255
+ logger.error({ message: `Expected number 0–1000, received ${print($value)}`, node: $value, source });
290
256
  }
291
257
  }
292
258
 
@@ -297,14 +263,9 @@ export function validateFontWeight($value, node, { ast, logger }) {
297
263
  * @param {ValidateOptions} options
298
264
  * @return {void}
299
265
  */
300
- export function validateGradient($value, node, { ast, logger }) {
266
+ export function validateGradient($value, node, { source, logger }) {
301
267
  if ($value.type !== 'Array') {
302
- logger.error({
303
- message: `Expected array of gradient stops, received ${$value.type}`,
304
- node,
305
- ast,
306
- loc: getLoc($value),
307
- });
268
+ logger.error({ message: `Expected array of gradient stops, received ${$value.type}`, node: $value, source });
308
269
  return;
309
270
  }
310
271
  for (let i = 0; i < $value.elements.length; i++) {
@@ -312,9 +273,8 @@ export function validateGradient($value, node, { ast, logger }) {
312
273
  if (element.value.type !== 'Object') {
313
274
  logger.error({
314
275
  message: `Stop #${i + 1}: Expected gradient stop, received ${element.value.type}`,
315
- node,
316
- ast,
317
- loc: getLoc(element),
276
+ node: element,
277
+ source,
318
278
  });
319
279
  break;
320
280
  }
@@ -324,8 +284,8 @@ export function validateGradient($value, node, { ast, logger }) {
324
284
  color: { validator: validateColor, required: true },
325
285
  position: { validator: validateNumber, required: true },
326
286
  },
327
- node,
328
- { ast, logger },
287
+ element,
288
+ { source, logger },
329
289
  );
330
290
  }
331
291
  }
@@ -337,9 +297,9 @@ export function validateGradient($value, node, { ast, logger }) {
337
297
  * @param {ValidateOptions} options
338
298
  * @return {void}
339
299
  */
340
- export function validateNumber($value, node, { ast, logger }) {
300
+ export function validateNumber($value, node, { source, logger }) {
341
301
  if ($value.type !== 'Number') {
342
- logger.error({ message: `Expected number, received ${$value.type}`, node, ast, loc: getLoc($value) });
302
+ logger.error({ message: `Expected number, received ${$value.type}`, node: $value, source });
343
303
  }
344
304
  }
345
305
 
@@ -350,9 +310,9 @@ export function validateNumber($value, node, { ast, logger }) {
350
310
  * @param {ValidateOptions} options
351
311
  * @return {void}
352
312
  */
353
- export function validateShadowLayer($value, node, { ast, logger }) {
313
+ export function validateShadowLayer($value, node, { source, logger }) {
354
314
  if ($value.type !== 'Object') {
355
- logger.error({ message: `Expected Object, received ${$value.type}`, node, ast, loc: getLoc($value) });
315
+ logger.error({ message: `Expected Object, received ${$value.type}`, node: $value, source });
356
316
  return;
357
317
  }
358
318
  validateMembersAs(
@@ -365,7 +325,7 @@ export function validateShadowLayer($value, node, { ast, logger }) {
365
325
  spread: { validator: validateDimension },
366
326
  },
367
327
  node,
368
- { ast, logger },
328
+ { source, logger },
369
329
  );
370
330
  }
371
331
 
@@ -376,7 +336,7 @@ export function validateShadowLayer($value, node, { ast, logger }) {
376
336
  * @param {ValidateOptions} options
377
337
  * @return {void}
378
338
  */
379
- export function validateStrokeStyle($value, node, { ast, logger }) {
339
+ export function validateStrokeStyle($value, node, { source, logger }) {
380
340
  // note: strokeStyle’s values are NOT aliasable (unless by string, but that breaks validations)
381
341
  if ($value.type === 'String') {
382
342
  if (!STROKE_STYLE_VALUES.has($value.value)) {
@@ -384,9 +344,8 @@ export function validateStrokeStyle($value, node, { ast, logger }) {
384
344
  message: `Unknown stroke style ${print($value)}. Expected one of: ${listFormat.format([
385
345
  ...STROKE_STYLE_VALUES,
386
346
  ])}.`,
387
- node,
388
- ast,
389
- loc: getLoc($value),
347
+ node: $value,
348
+ source,
390
349
  });
391
350
  }
392
351
  } else if ($value.type === 'Object') {
@@ -395,9 +354,8 @@ export function validateStrokeStyle($value, node, { ast, logger }) {
395
354
  if (!strokeMembers[property]) {
396
355
  logger.error({
397
356
  message: `Missing required property "${property}"`,
398
- node,
399
- ast,
400
- loc: getLoc($value),
357
+ node: $value,
358
+ source,
401
359
  });
402
360
  }
403
361
  }
@@ -413,29 +371,31 @@ export function validateStrokeStyle($value, node, { ast, logger }) {
413
371
  for (const element of dashArray.elements) {
414
372
  if (element.value.type === 'String' && element.value.value !== '') {
415
373
  if (isMaybeAlias(element.value)) {
416
- validateAlias(element.value, node, { logger, ast });
374
+ validateAlias(element.value, node, { logger, source });
417
375
  } else {
418
- validateDimension(element.value, node, { logger, ast });
376
+ validateDimension(element.value, node, { logger, source });
419
377
  }
420
378
  } else {
421
379
  logger.error({
422
380
  message: 'Expected array of strings, recieved some non-strings or empty strings.',
423
- node,
424
- ast,
425
- loc: getLoc(element),
381
+ node: element,
382
+ source,
426
383
  });
427
384
  }
428
385
  }
429
386
  } else {
430
387
  logger.error({
431
388
  message: `Expected array of strings, received ${dashArray.type}`,
432
- node,
433
- ast,
434
- loc: getLoc($value),
389
+ node: $value,
390
+ source,
435
391
  });
436
392
  }
437
393
  } else {
438
- logger.error({ message: `Expected string or object, received ${$value.type}`, node, ast, loc: getLoc($value) });
394
+ logger.error({
395
+ message: `Expected string or object, received ${$value.type}`,
396
+ node: $value,
397
+ source,
398
+ });
439
399
  }
440
400
  }
441
401
 
@@ -446,9 +406,9 @@ export function validateStrokeStyle($value, node, { ast, logger }) {
446
406
  * @param {ValidateOptions} options
447
407
  * @return {void}
448
408
  */
449
- export function validateTransition($value, node, { ast, logger }) {
409
+ export function validateTransition($value, node, { source, logger }) {
450
410
  if ($value.type !== 'Object') {
451
- logger.error({ message: `Expected object, received ${$value.type}`, node, ast, loc: getLoc($value) });
411
+ logger.error({ message: `Expected object, received ${$value.type}`, node: $value, source });
452
412
  return;
453
413
  }
454
414
  validateMembersAs(
@@ -459,7 +419,7 @@ export function validateTransition($value, node, { ast, logger }) {
459
419
  timingFunction: { validator: validateCubicBézier, required: true },
460
420
  },
461
421
  node,
462
- { ast, logger },
422
+ { source, logger },
463
423
  );
464
424
  }
465
425
 
@@ -470,13 +430,12 @@ export function validateTransition($value, node, { ast, logger }) {
470
430
  * @param {ValidateOptions} options
471
431
  * @return {void}
472
432
  */
473
- export default function validate(node, { ast, logger }) {
433
+ export default function validate(node, { source, logger }) {
474
434
  if (node.type !== 'Member' && node.type !== 'Object') {
475
435
  logger.error({
476
436
  message: `Expected Object, received ${JSON.stringify(node.type)}`,
477
437
  node,
478
- ast,
479
- loc: { line: 1 },
438
+ source,
480
439
  });
481
440
  return;
482
441
  }
@@ -486,63 +445,62 @@ export default function validate(node, { ast, logger }) {
486
445
  const $type = rootMembers.$type;
487
446
 
488
447
  if (!$value) {
489
- logger.error({ message: 'Token missing $value', node, ast, loc: getLoc(node) });
448
+ logger.error({ message: 'Token missing $value', node, source });
490
449
  return;
491
450
  }
492
451
  // If top-level value is a valid alias, this is valid (no need for $type)
493
452
  // ⚠️ Important: ALL Object and Array nodes below will need to check for aliases within!
494
453
  if (isMaybeAlias($value)) {
495
- validateAlias($value, node, { logger, ast });
454
+ validateAlias($value, node, { logger, source });
496
455
  return;
497
456
  }
498
457
 
499
458
  if (!$type) {
500
- logger.error({ message: 'Token missing $type', node, ast, loc: getLoc(node) });
459
+ logger.error({ message: 'Token missing $type', node, source });
501
460
  return;
502
461
  }
503
462
 
504
463
  switch ($type.value) {
505
464
  case 'color': {
506
- validateColor($value, node, { logger, ast });
465
+ validateColor($value, node, { logger, source });
507
466
  break;
508
467
  }
509
468
  case 'cubicBezier': {
510
- validateCubicBézier($value, node, { logger, ast });
469
+ validateCubicBézier($value, node, { logger, source });
511
470
  break;
512
471
  }
513
472
  case 'dimension': {
514
- validateDimension($value, node, { logger, ast });
473
+ validateDimension($value, node, { logger, source });
515
474
  break;
516
475
  }
517
476
  case 'duration': {
518
- validateDuration($value, node, { logger, ast });
477
+ validateDuration($value, node, { logger, source });
519
478
  break;
520
479
  }
521
480
  case 'fontFamily': {
522
- validateFontFamily($value, node, { logger, ast });
481
+ validateFontFamily($value, node, { logger, source });
523
482
  break;
524
483
  }
525
484
  case 'fontWeight': {
526
- validateFontWeight($value, node, { logger, ast });
485
+ validateFontWeight($value, node, { logger, source });
527
486
  break;
528
487
  }
529
488
  case 'number': {
530
- validateNumber($value, node, { logger, ast });
489
+ validateNumber($value, node, { logger, source });
531
490
  break;
532
491
  }
533
492
  case 'shadow': {
534
493
  if ($value.type === 'Object') {
535
- validateShadowLayer($value, node, { logger, ast });
494
+ validateShadowLayer($value, node, { logger, source });
536
495
  } else if ($value.type === 'Array') {
537
496
  for (const element of $value.elements) {
538
- validateShadowLayer(element.value, $value, { logger, ast });
497
+ validateShadowLayer(element.value, $value, { logger, source });
539
498
  }
540
499
  } else {
541
500
  logger.error({
542
501
  message: `Expected shadow object or array of shadow objects, received ${$value.type}`,
543
- node,
544
- ast,
545
- loc: getLoc($value),
502
+ node: $value,
503
+ source,
546
504
  });
547
505
  }
548
506
  break;
@@ -551,54 +509,49 @@ export default function validate(node, { ast, logger }) {
551
509
  // extensions
552
510
  case 'boolean': {
553
511
  if ($value.type !== 'Boolean') {
554
- logger.error({ message: `Expected boolean, received ${$value.type}`, node, ast, loc: getLoc($value) });
512
+ logger.error({ message: `Expected boolean, received ${$value.type}`, node: $value, source });
555
513
  }
556
514
  break;
557
515
  }
558
516
  case 'link': {
559
517
  if ($value.type !== 'String') {
560
- logger.error({ message: `Expected string, received ${$value.type}`, node, ast, loc: getLoc($value) });
518
+ logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
561
519
  } else if ($value.value === '') {
562
- logger.error({ message: 'Expected URL, received empty string', node, ast, loc: getLoc($value) });
520
+ logger.error({ message: 'Expected URL, received empty string', node: $value, source });
563
521
  }
564
522
  break;
565
523
  }
566
524
  case 'string': {
567
525
  if ($value.type !== 'String') {
568
- logger.error({ message: `Expected string, received ${$value.type}`, node, ast, loc: getLoc($value) });
526
+ logger.error({ message: `Expected string, received ${$value.type}`, node: $value, source });
569
527
  }
570
528
  break;
571
529
  }
572
530
 
573
531
  // composite types
574
532
  case 'border': {
575
- validateBorder($value, node, { ast, logger });
533
+ validateBorder($value, node, { source, logger });
576
534
  break;
577
535
  }
578
536
  case 'gradient': {
579
- validateGradient($value, node, { ast, logger });
537
+ validateGradient($value, node, { source, logger });
580
538
  break;
581
539
  }
582
540
  case 'strokeStyle': {
583
- validateStrokeStyle($value, node, { ast, logger });
541
+ validateStrokeStyle($value, node, { source, logger });
584
542
  break;
585
543
  }
586
544
  case 'transition': {
587
- validateTransition($value, node, { ast, logger });
545
+ validateTransition($value, node, { source, logger });
588
546
  break;
589
547
  }
590
548
  case 'typography': {
591
549
  if ($value.type !== 'Object') {
592
- logger.error({ message: `Expected object, received ${$value.type}`, node, ast, loc: getLoc($value) });
550
+ logger.error({ message: `Expected object, received ${$value.type}`, node: $value, source });
593
551
  break;
594
552
  }
595
553
  if ($value.members.length === 0) {
596
- logger.error({
597
- message: 'Empty typography token. Must contain at least 1 property.',
598
- node,
599
- ast,
600
- loc: getLoc($value),
601
- });
554
+ logger.error({ message: 'Empty typography token. Must contain at least 1 property.', node: $value, source });
602
555
  }
603
556
  validateMembersAs(
604
557
  $value,
@@ -607,7 +560,7 @@ export default function validate(node, { ast, logger }) {
607
560
  fontWeight: { validator: validateFontWeight },
608
561
  },
609
562
  node,
610
- { ast, logger },
563
+ { source, logger },
611
564
  );
612
565
  break;
613
566
  }
package/parse/yaml.js CHANGED
@@ -36,8 +36,8 @@ export default function yamlToAST(input, { logger }) {
36
36
  logger.error({
37
37
  label: 'parse:yaml',
38
38
  message: `${error.code} ${error.message}`,
39
- code: input,
40
- loc: posToLoc(input, error.pos),
39
+ node: { loc: { start: posToLoc(input, error.pos) } },
40
+ source: input,
41
41
  });
42
42
  }
43
43
  }