@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 +10 -0
- package/build/index.d.ts +7 -7
- package/build/index.js +5 -5
- package/lint/index.d.ts +4 -2
- package/logger.d.ts +4 -2
- package/logger.js +4 -2
- package/package.json +2 -2
- package/parse/index.d.ts +11 -3
- package/parse/index.js +200 -113
- package/parse/validate.d.ts +2 -1
- package/parse/validate.js +133 -106
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
|
-
|
|
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
|
|
77
|
-
|
|
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
|
|
86
|
-
|
|
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
|
|
105
|
-
|
|
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 {
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?: {
|
|
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.
|
|
27
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
* @
|
|
22
|
-
* @
|
|
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 {
|
|
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
|
-
|
|
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
|
|
177
|
+
let src;
|
|
41
178
|
if (typeof input === 'string') {
|
|
42
|
-
|
|
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
|
|
183
|
+
let document;
|
|
47
184
|
if (typeof input === 'string' && !maybeJSONString(input)) {
|
|
48
|
-
|
|
185
|
+
document = parseYAML(input, { logger }); // if string, but not JSON, attempt YAML
|
|
49
186
|
} else {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 (!
|
|
55
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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({
|
|
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].
|
|
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,
|
|
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].
|
|
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({
|
|
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
|
-
|
|
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
|
|
398
|
+
* @param {string} [options.scanned=[]]
|
|
312
399
|
* @param {string}
|
|
313
400
|
*/
|
|
314
|
-
export function resolveAlias(alias, { tokens, logger,
|
|
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`,
|
|
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}"`,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
485
|
+
const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, filename, node, src });
|
|
399
486
|
if (!token.partialAliasOf) {
|
|
400
487
|
token.partialAliasOf = {};
|
|
401
488
|
}
|
package/parse/validate.d.ts
CHANGED
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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, {
|
|
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,
|
|
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, {
|
|
95
|
+
validateAlias(value, node, { filename, src, logger });
|
|
98
96
|
} else {
|
|
99
|
-
validator(value, node, {
|
|
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, {
|
|
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,
|
|
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, {
|
|
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,
|
|
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
|
-
{
|
|
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, {
|
|
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
|
-
//
|
|
153
|
+
// src,
|
|
155
154
|
// });
|
|
156
155
|
if ($value.value === '') {
|
|
157
|
-
logger.error({ message: 'Expected color, received empty string', node: $value,
|
|
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,
|
|
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,
|
|
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,
|
|
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({
|
|
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({
|
|
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,
|
|
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
|
-
{
|
|
218
|
+
{ filename, src, logger },
|
|
210
219
|
);
|
|
211
220
|
} else {
|
|
212
|
-
logger.error({ message: `Expected object, received ${$value.type}`, node: $value,
|
|
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, {
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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, {
|
|
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,
|
|
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,
|
|
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,
|
|
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({
|
|
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, {
|
|
310
|
+
export function validateFontFamily($value, node, { filename, src, logger }) {
|
|
295
311
|
if ($value.type !== 'String' && $value.type !== 'Array') {
|
|
296
|
-
logger.error({
|
|
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,
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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, {
|
|
368
|
+
export function validateGradient($value, node, { filename, src, logger }) {
|
|
345
369
|
if ($value.type !== 'Array') {
|
|
346
|
-
logger.error({
|
|
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
|
-
|
|
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
|
-
{
|
|
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, {
|
|
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,
|
|
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, {
|
|
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,
|
|
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
|
-
{
|
|
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, {
|
|
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
|
-
|
|
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,
|
|
479
|
+
validateAlias(element.value, node, { logger, src });
|
|
453
480
|
} else {
|
|
454
|
-
validateDimension(element.value, node, { logger,
|
|
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
|
-
|
|
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, {
|
|
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,
|
|
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
|
-
{
|
|
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, {
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
564
|
+
validateColor($value, node, { logger, src });
|
|
544
565
|
break;
|
|
545
566
|
}
|
|
546
567
|
case 'cubicBezier': {
|
|
547
|
-
validateCubicBezier($value, node, { logger,
|
|
568
|
+
validateCubicBezier($value, node, { logger, src });
|
|
548
569
|
break;
|
|
549
570
|
}
|
|
550
571
|
case 'dimension': {
|
|
551
|
-
validateDimension($value, node, { logger,
|
|
572
|
+
validateDimension($value, node, { logger, src });
|
|
552
573
|
break;
|
|
553
574
|
}
|
|
554
575
|
case 'duration': {
|
|
555
|
-
validateDuration($value, node, { logger,
|
|
576
|
+
validateDuration($value, node, { logger, src });
|
|
556
577
|
break;
|
|
557
578
|
}
|
|
558
579
|
case 'fontFamily': {
|
|
559
|
-
validateFontFamily($value, node, { logger,
|
|
580
|
+
validateFontFamily($value, node, { logger, src });
|
|
560
581
|
break;
|
|
561
582
|
}
|
|
562
583
|
case 'fontWeight': {
|
|
563
|
-
validateFontWeight($value, node, { logger,
|
|
584
|
+
validateFontWeight($value, node, { logger, src });
|
|
564
585
|
break;
|
|
565
586
|
}
|
|
566
587
|
case 'number': {
|
|
567
|
-
validateNumber($value, node, { logger,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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, {
|
|
633
|
+
validateBorder($value, node, { filename, src, logger });
|
|
612
634
|
break;
|
|
613
635
|
}
|
|
614
636
|
case 'gradient': {
|
|
615
|
-
validateGradient($value, node, {
|
|
637
|
+
validateGradient($value, node, { filename, src, logger });
|
|
616
638
|
break;
|
|
617
639
|
}
|
|
618
640
|
case 'strokeStyle': {
|
|
619
|
-
validateStrokeStyle($value, node, {
|
|
641
|
+
validateStrokeStyle($value, node, { filename, src, logger });
|
|
620
642
|
break;
|
|
621
643
|
}
|
|
622
644
|
case 'transition': {
|
|
623
|
-
validateTransition($value, node, {
|
|
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,
|
|
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({
|
|
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
|
-
{
|
|
668
|
+
{ filename, src, logger },
|
|
642
669
|
);
|
|
643
670
|
break;
|
|
644
671
|
}
|