@terrazzo/parser 0.1.3 → 0.2.1
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +23 -0
- package/CONTRIBUTING.md +0 -12
- package/dist/build/index.d.ts +19 -0
- package/dist/build/index.js +165 -0
- package/dist/build/index.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +269 -0
- package/dist/config.js.map +1 -0
- package/{index.d.ts → dist/index.d.ts} +1 -5
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/code-frame.d.ts +30 -0
- package/dist/lib/code-frame.js +108 -0
- package/dist/lib/code-frame.js.map +1 -0
- package/dist/lint/index.d.ts +11 -0
- package/dist/lint/index.js +102 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/plugin-core/index.d.ts +12 -0
- package/dist/lint/plugin-core/index.js +40 -0
- package/dist/lint/plugin-core/index.js.map +1 -0
- package/dist/lint/plugin-core/lib/docs.d.ts +1 -0
- package/dist/lint/plugin-core/lib/docs.js +4 -0
- package/dist/lint/plugin-core/lib/docs.js.map +1 -0
- package/dist/lint/plugin-core/rules/a11y-min-contrast.d.ts +39 -0
- package/dist/lint/plugin-core/rules/a11y-min-contrast.js +58 -0
- package/dist/lint/plugin-core/rules/a11y-min-contrast.js.map +1 -0
- package/dist/lint/plugin-core/rules/a11y-min-font-size.d.ts +13 -0
- package/dist/lint/plugin-core/rules/a11y-min-font-size.js +45 -0
- package/dist/lint/plugin-core/rules/a11y-min-font-size.js.map +1 -0
- package/dist/lint/plugin-core/rules/colorspace.d.ts +14 -0
- package/dist/lint/plugin-core/rules/colorspace.js +85 -0
- package/dist/lint/plugin-core/rules/colorspace.js.map +1 -0
- package/dist/lint/plugin-core/rules/consistent-naming.d.ts +11 -0
- package/dist/lint/plugin-core/rules/consistent-naming.js +49 -0
- package/dist/lint/plugin-core/rules/consistent-naming.js.map +1 -0
- package/dist/lint/plugin-core/rules/descriptions.d.ts +9 -0
- package/dist/lint/plugin-core/rules/descriptions.js +32 -0
- package/dist/lint/plugin-core/rules/descriptions.js.map +1 -0
- package/dist/lint/plugin-core/rules/duplicate-values.d.ts +9 -0
- package/dist/lint/plugin-core/rules/duplicate-values.js +65 -0
- package/dist/lint/plugin-core/rules/duplicate-values.js.map +1 -0
- package/dist/lint/plugin-core/rules/max-gamut.d.ts +14 -0
- package/dist/lint/plugin-core/rules/max-gamut.js +101 -0
- package/dist/lint/plugin-core/rules/max-gamut.js.map +1 -0
- package/dist/lint/plugin-core/rules/required-children.d.ts +18 -0
- package/dist/lint/plugin-core/rules/required-children.js +78 -0
- package/dist/lint/plugin-core/rules/required-children.js.map +1 -0
- package/dist/lint/plugin-core/rules/required-modes.d.ts +13 -0
- package/dist/lint/plugin-core/rules/required-modes.js +52 -0
- package/dist/lint/plugin-core/rules/required-modes.js.map +1 -0
- package/dist/lint/plugin-core/rules/required-typography-properties.d.ts +10 -0
- package/dist/lint/plugin-core/rules/required-typography-properties.js +38 -0
- package/dist/lint/plugin-core/rules/required-typography-properties.js.map +1 -0
- package/dist/logger.d.ts +76 -0
- package/dist/logger.js +123 -0
- package/dist/logger.js.map +1 -0
- package/dist/parse/alias.d.ts +51 -0
- package/dist/parse/alias.js +188 -0
- package/dist/parse/alias.js.map +1 -0
- package/dist/parse/index.d.ts +27 -0
- package/dist/parse/index.js +379 -0
- package/dist/parse/index.js.map +1 -0
- package/dist/parse/json.d.ts +36 -0
- package/dist/parse/json.js +88 -0
- package/dist/parse/json.js.map +1 -0
- package/dist/parse/normalize.d.ts +23 -0
- package/dist/parse/normalize.js +163 -0
- package/dist/parse/normalize.js.map +1 -0
- package/dist/parse/validate.d.ts +45 -0
- package/dist/parse/validate.js +601 -0
- package/dist/parse/validate.js.map +1 -0
- package/dist/types.d.ts +264 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +7 -7
- package/{build/index.js → src/build/index.ts} +47 -63
- package/src/config.ts +280 -0
- package/src/index.ts +18 -0
- package/{lib/code-frame.js → src/lib/code-frame.ts} +41 -8
- package/src/lint/index.ts +135 -0
- package/src/lint/plugin-core/index.ts +47 -0
- package/src/lint/plugin-core/lib/docs.ts +3 -0
- package/src/lint/plugin-core/rules/a11y-min-contrast.ts +91 -0
- package/src/lint/plugin-core/rules/a11y-min-font-size.ts +64 -0
- package/src/lint/plugin-core/rules/colorspace.ts +101 -0
- package/src/lint/plugin-core/rules/consistent-naming.ts +65 -0
- package/src/lint/plugin-core/rules/descriptions.ts +41 -0
- package/src/lint/plugin-core/rules/duplicate-values.ts +80 -0
- package/src/lint/plugin-core/rules/max-gamut.ts +121 -0
- package/src/lint/plugin-core/rules/required-children.ts +104 -0
- package/src/lint/plugin-core/rules/required-modes.ts +71 -0
- package/src/lint/plugin-core/rules/required-typography-properties.ts +53 -0
- package/{logger.js → src/logger.ts} +55 -16
- package/src/parse/alias.ts +224 -0
- package/src/parse/index.ts +457 -0
- package/src/parse/json.ts +106 -0
- package/{parse/normalize.js → src/parse/normalize.ts} +70 -24
- package/{parse/validate.js → src/parse/validate.ts} +154 -236
- package/src/types.ts +310 -0
- package/build/index.d.ts +0 -113
- package/config.d.ts +0 -64
- package/config.js +0 -206
- package/index.js +0 -35
- package/lib/code-frame.d.ts +0 -56
- package/lint/index.d.ts +0 -44
- package/lint/index.js +0 -59
- package/lint/plugin-core/index.d.ts +0 -3
- package/lint/plugin-core/index.js +0 -12
- package/lint/plugin-core/rules/duplicate-values.d.ts +0 -10
- package/lint/plugin-core/rules/duplicate-values.js +0 -68
- package/logger.d.ts +0 -71
- package/parse/index.d.ts +0 -45
- package/parse/index.js +0 -592
- package/parse/json.d.ts +0 -30
- package/parse/json.js +0 -94
- package/parse/normalize.d.ts +0 -3
- package/parse/validate.d.ts +0 -43
package/parse/index.js
DELETED
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
import { evaluate, parse as parseJSON, print } from '@humanwhocodes/momoa';
|
|
2
|
-
import { isAlias, parseAlias, pluralize, splitID } from '@terrazzo/token-tools';
|
|
3
|
-
import lintRunner from '../lint/index.js';
|
|
4
|
-
import Logger from '../logger.js';
|
|
5
|
-
import normalize from './normalize.js';
|
|
6
|
-
import validate from './validate.js';
|
|
7
|
-
import { getObjMembers, injectObjMembers, traverse } from './json.js';
|
|
8
|
-
|
|
9
|
-
export * from './validate.js';
|
|
10
|
-
|
|
11
|
-
/** @typedef {import("@humanwhocodes/momoa").DocumentNode} DocumentNode */
|
|
12
|
-
/** @typedef {import("../config.js").Plugin} Plugin */
|
|
13
|
-
/** @typedef {import("../types.js").TokenNormalized} TokenNormalized */
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {object} ParseResult
|
|
16
|
-
* @property {Record<string, TokenNormalized} tokens
|
|
17
|
-
* @property {Object[]} src
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* @typedef {object} ParseInput
|
|
21
|
-
* @property {string | object} src
|
|
22
|
-
* @property {URL} [filename]
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* @typedef {object} ParseOptions
|
|
26
|
-
* @property {Logger} logger
|
|
27
|
-
* @property {import("../config.js").Config} config
|
|
28
|
-
* @property {import("yamlToMomoa")} yamlToMomoa
|
|
29
|
-
* @property {boolean} [skipLint=false]
|
|
30
|
-
* @property {boolean} [continueOnError=false]
|
|
31
|
-
*/
|
|
32
|
-
/**
|
|
33
|
-
* Parse
|
|
34
|
-
* @param {ParseInput[]} input
|
|
35
|
-
* @param {ParseOptions} [options]
|
|
36
|
-
* @return {Promise<ParseResult>}
|
|
37
|
-
*/
|
|
38
|
-
export default async function parse(
|
|
39
|
-
input,
|
|
40
|
-
{ logger = new Logger(), skipLint = false, config = {}, continueOnError = false, yamlToMomoa } = {},
|
|
41
|
-
) {
|
|
42
|
-
let tokens = {};
|
|
43
|
-
// note: only keeps track of sources with locations on disk; in-memory sources are discarded
|
|
44
|
-
// (it’s only for reporting line numbers, which doesn’t mean as much for dynamic sources)
|
|
45
|
-
const sources = {};
|
|
46
|
-
|
|
47
|
-
if (!Array.isArray(input)) {
|
|
48
|
-
logger.error({ group: 'parser', task: 'init', message: 'Input must be an array of input objects.' });
|
|
49
|
-
}
|
|
50
|
-
for (let i = 0; i < input.length; i++) {
|
|
51
|
-
if (!input[i] || typeof input[i] !== 'object') {
|
|
52
|
-
logger.error({ group: 'parser', task: 'init', message: `Input (${i}) must be an object.` });
|
|
53
|
-
}
|
|
54
|
-
if (!input[i].src || (typeof input[i].src !== 'string' && typeof input[i].src !== 'object')) {
|
|
55
|
-
logger.error({
|
|
56
|
-
group: 'parser',
|
|
57
|
-
task: 'init',
|
|
58
|
-
message: `Input (${i}) missing "src" with a JSON/YAML string, or JSON object.`,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
if (input[i].filename && !(input[i].filename instanceof URL)) {
|
|
62
|
-
logger.error({
|
|
63
|
-
group: 'parser',
|
|
64
|
-
task: 'init',
|
|
65
|
-
message: `Input (${i}) "filename" must be a URL (remote or file URL).`,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const result = await parseSingle(input[i].src, {
|
|
70
|
-
filename: input[i].filename,
|
|
71
|
-
logger,
|
|
72
|
-
config,
|
|
73
|
-
skipLint,
|
|
74
|
-
continueOnError,
|
|
75
|
-
yamlToMomoa,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
tokens = Object.assign(tokens, result.tokens);
|
|
79
|
-
if (input[i].filename) {
|
|
80
|
-
sources[input[i].filename.protocol === 'file:' ? input[i].filename.href : input[i].filename.href] = {
|
|
81
|
-
filename: input[i].filename,
|
|
82
|
-
src: result.src,
|
|
83
|
-
document: result.document,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const totalStart = performance.now();
|
|
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], {
|
|
135
|
-
tokens,
|
|
136
|
-
node: tokens[id].mode[mode].source.node,
|
|
137
|
-
logger,
|
|
138
|
-
src: sources[tokens[id].source.loc]?.src,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
logger.debug({
|
|
143
|
-
group: 'parser',
|
|
144
|
-
task: 'modes',
|
|
145
|
-
message: 'Finish token modes',
|
|
146
|
-
timing: performance.now() - modesStart,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
logger.debug({
|
|
150
|
-
group: 'parser',
|
|
151
|
-
task: 'core',
|
|
152
|
-
message: 'Finish all parser tasks',
|
|
153
|
-
timing: performance.now() - totalStart,
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
if (continueOnError) {
|
|
157
|
-
const { errorCount } = logger.stats();
|
|
158
|
-
if (errorCount > 0) {
|
|
159
|
-
logger.error({
|
|
160
|
-
message: `Parser encountered ${errorCount} ${pluralize(errorCount, 'error', 'errors')}. Exiting.`,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
tokens,
|
|
167
|
-
sources,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Parse a single input
|
|
173
|
-
* @param {string | object} input
|
|
174
|
-
* @param {object} options
|
|
175
|
-
* @param {URL} [options.filename]
|
|
176
|
-
* @param {Logger} [options.logger]
|
|
177
|
-
* @param {import("../config.js").Config} [options.config]
|
|
178
|
-
* @param {boolean} [options.skipLint]
|
|
179
|
-
*/
|
|
180
|
-
async function parseSingle(input, { filename, logger, config, skipLint, continueOnError = false, yamlToMomoa }) {
|
|
181
|
-
// 1. Build AST
|
|
182
|
-
let src;
|
|
183
|
-
if (typeof input === 'string') {
|
|
184
|
-
src = input;
|
|
185
|
-
}
|
|
186
|
-
const startParsing = performance.now();
|
|
187
|
-
logger.debug({ group: 'parser', task: 'parse', message: 'Start tokens parsing' });
|
|
188
|
-
let document;
|
|
189
|
-
if (typeof input === 'string' && !maybeJSONString(input)) {
|
|
190
|
-
if (yamlToMomoa) {
|
|
191
|
-
try {
|
|
192
|
-
document = yamlToMomoa(input); // if string, but not JSON, attempt YAML
|
|
193
|
-
} catch (err) {
|
|
194
|
-
logger.error({ message: String(err), filename, src: input, continueOnError });
|
|
195
|
-
}
|
|
196
|
-
} else {
|
|
197
|
-
logger.error({
|
|
198
|
-
group: 'parser',
|
|
199
|
-
task: 'parse',
|
|
200
|
-
message: `Install \`yaml-to-momoa\` package to parse YAML, and pass in as option, e.g.:
|
|
201
|
-
|
|
202
|
-
import { parse } from '@terrazzo/parser';
|
|
203
|
-
import yamlToMomoa from 'yaml-to-momoa';
|
|
204
|
-
|
|
205
|
-
parse(yamlString, { yamlToMomoa });`,
|
|
206
|
-
continueOnError: false, // fail here; no point in continuing
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
} else {
|
|
210
|
-
document = parseJSON(
|
|
211
|
-
typeof input === 'string' ? input : JSON.stringify(input, undefined, 2), // everything else: assert it’s JSON-serializable
|
|
212
|
-
{
|
|
213
|
-
mode: 'jsonc',
|
|
214
|
-
},
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
if (!src) {
|
|
218
|
-
src = print(document, { indent: 2 });
|
|
219
|
-
}
|
|
220
|
-
logger.debug({
|
|
221
|
-
group: 'parser',
|
|
222
|
-
task: 'parse',
|
|
223
|
-
message: 'Finish tokens parsing',
|
|
224
|
-
timing: performance.now() - startParsing,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const tokens = {};
|
|
228
|
-
|
|
229
|
-
// 2. Walk AST once to validate tokens
|
|
230
|
-
const startValidation = performance.now();
|
|
231
|
-
logger.debug({ group: 'parser', task: 'validate', message: 'Start tokens validation' });
|
|
232
|
-
const $typeInheritance = {};
|
|
233
|
-
traverse(document, {
|
|
234
|
-
enter(node, parent, path) {
|
|
235
|
-
if (node.type === 'Member' && node.value.type === 'Object' && node.value.members) {
|
|
236
|
-
const members = getObjMembers(node.value);
|
|
237
|
-
|
|
238
|
-
// keep track of $types
|
|
239
|
-
if (members.$type && members.$type.type === 'String' && !members.$value) {
|
|
240
|
-
$typeInheritance[path.join('.') || '.'] = node.value.members.find((m) => m.name.value === '$type');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const id = path.join('.');
|
|
244
|
-
|
|
245
|
-
if (members.$value) {
|
|
246
|
-
const extensions = members.$extensions ? getObjMembers(members.$extensions) : undefined;
|
|
247
|
-
const sourceNode = structuredClone(node);
|
|
248
|
-
|
|
249
|
-
// get parent type by taking the closest-scoped $type (length === closer)
|
|
250
|
-
let parent$type;
|
|
251
|
-
let longestPath = '';
|
|
252
|
-
for (const [k, v] of Object.entries($typeInheritance)) {
|
|
253
|
-
if (k === '.' || id.startsWith(k)) {
|
|
254
|
-
if (k.length > longestPath.length) {
|
|
255
|
-
parent$type = v;
|
|
256
|
-
longestPath = k;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
if (parent$type && !members.$type) {
|
|
261
|
-
sourceNode.value = injectObjMembers(sourceNode.value, [parent$type]);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
validate(sourceNode, { filename, src, logger });
|
|
265
|
-
|
|
266
|
-
const group = { id: splitID(id).group, tokens: [] };
|
|
267
|
-
if (parent$type) {
|
|
268
|
-
group.$type = parent$type.value.value;
|
|
269
|
-
}
|
|
270
|
-
// note: this will also include sibling tokens, so be selective about only accessing group-specific properties
|
|
271
|
-
const groupMembers = getObjMembers(parent);
|
|
272
|
-
if (groupMembers.$description) {
|
|
273
|
-
group.$description = evaluate(groupMembers.$description);
|
|
274
|
-
}
|
|
275
|
-
if (groupMembers.$extensions) {
|
|
276
|
-
group.$extensions = evaluate(groupMembers.$extensions);
|
|
277
|
-
}
|
|
278
|
-
const token = {
|
|
279
|
-
$type: members.$type?.value ?? parent$type?.value.value,
|
|
280
|
-
$value: evaluate(members.$value),
|
|
281
|
-
id,
|
|
282
|
-
mode: {},
|
|
283
|
-
originalValue: evaluate(node.value),
|
|
284
|
-
group,
|
|
285
|
-
source: {
|
|
286
|
-
loc: filename ? filename.href : undefined,
|
|
287
|
-
node: sourceNode.value,
|
|
288
|
-
},
|
|
289
|
-
};
|
|
290
|
-
if (members.$description?.value) {
|
|
291
|
-
token.$description = members.$description.value;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// handle modes
|
|
295
|
-
// note that circular refs are avoided here, such as not duplicating `modes`
|
|
296
|
-
const modeValues = extensions?.mode ? getObjMembers(extensions.mode) : {};
|
|
297
|
-
for (const mode of ['.', ...Object.keys(modeValues)]) {
|
|
298
|
-
token.mode[mode] = {
|
|
299
|
-
id: token.id,
|
|
300
|
-
$type: token.$type,
|
|
301
|
-
$value: mode === '.' ? token.$value : evaluate(modeValues[mode]),
|
|
302
|
-
source: {
|
|
303
|
-
loc: filename ? filename.href : undefined,
|
|
304
|
-
node: mode === '.' ? structuredClone(token.source.node) : modeValues[mode],
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
if (token.$description) {
|
|
308
|
-
token.mode[mode].$description = token.$description;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
tokens[id] = token;
|
|
313
|
-
} else if (!id.includes('.$value') && members.value) {
|
|
314
|
-
logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, filename, node, src });
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// edge case: if $type appears at root of tokens.json, collect it
|
|
319
|
-
if (node.type === 'Document' && node.body.type === 'Object' && node.body.members) {
|
|
320
|
-
const members = getObjMembers(node.body);
|
|
321
|
-
if (members.$type && members.$type.type === 'String' && !members.$value) {
|
|
322
|
-
$typeInheritance['.'] = node.body.members.find((m) => m.name.value === '$type');
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
|
-
});
|
|
327
|
-
logger.debug({
|
|
328
|
-
group: 'parser',
|
|
329
|
-
task: 'validate',
|
|
330
|
-
message: 'Finish tokens validation',
|
|
331
|
-
timing: performance.now() - startValidation,
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// 3. Execute lint runner with loaded plugins
|
|
335
|
-
if (!skipLint && config?.plugins?.length) {
|
|
336
|
-
const lintStart = performance.now();
|
|
337
|
-
logger.debug({
|
|
338
|
-
group: 'parser',
|
|
339
|
-
task: 'validate',
|
|
340
|
-
message: 'Start token linting',
|
|
341
|
-
});
|
|
342
|
-
await lintRunner({ document, filename, src, config, logger });
|
|
343
|
-
logger.debug({
|
|
344
|
-
group: 'parser',
|
|
345
|
-
task: 'validate',
|
|
346
|
-
message: 'Finish token linting',
|
|
347
|
-
timing: performance.now() - lintStart,
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// 4. normalize values
|
|
352
|
-
const normalizeStart = performance.now();
|
|
353
|
-
logger.debug({
|
|
354
|
-
group: 'parser',
|
|
355
|
-
task: 'normalize',
|
|
356
|
-
message: 'Start token normalization',
|
|
357
|
-
});
|
|
358
|
-
for (const id in tokens) {
|
|
359
|
-
if (!Object.hasOwn(tokens, id)) {
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
try {
|
|
363
|
-
tokens[id].$value = normalize(tokens[id]);
|
|
364
|
-
} catch (err) {
|
|
365
|
-
let { node } = tokens[id].source;
|
|
366
|
-
const members = getObjMembers(node);
|
|
367
|
-
if (members.$value) {
|
|
368
|
-
node = members.$value;
|
|
369
|
-
}
|
|
370
|
-
logger.error({ message: err.message, filename, src, node, continueOnError });
|
|
371
|
-
}
|
|
372
|
-
for (const mode in tokens[id].mode) {
|
|
373
|
-
if (mode === '.') {
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
try {
|
|
377
|
-
tokens[id].mode[mode].$value = normalize(tokens[id].mode[mode]);
|
|
378
|
-
} catch (err) {
|
|
379
|
-
let { node } = tokens[id].source;
|
|
380
|
-
const members = getObjMembers(node);
|
|
381
|
-
if (members.$value) {
|
|
382
|
-
node = members.$value;
|
|
383
|
-
}
|
|
384
|
-
logger.error({
|
|
385
|
-
message: err.message,
|
|
386
|
-
filename,
|
|
387
|
-
src,
|
|
388
|
-
node: tokens[id].mode[mode].source.node,
|
|
389
|
-
continueOnError,
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
logger.debug({
|
|
395
|
-
group: 'parser',
|
|
396
|
-
task: 'normalize',
|
|
397
|
-
message: 'Finish token normalization',
|
|
398
|
-
timing: performance.now() - normalizeStart,
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
return { tokens, document, src };
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Determine if an input is likely a JSON string
|
|
406
|
-
* @param {string} input
|
|
407
|
-
* @return {boolean}
|
|
408
|
-
*/
|
|
409
|
-
export function maybeJSONString(input) {
|
|
410
|
-
return typeof input === 'string' && input.trim().startsWith('{');
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Resolve alias
|
|
415
|
-
* @param {string} alias
|
|
416
|
-
* @param {Object} options
|
|
417
|
-
* @param {Record<string, TokenNormalized>} options.tokens
|
|
418
|
-
* @param {Logger} options.logger
|
|
419
|
-
* @param {string} [options.filename]
|
|
420
|
-
* @param {AnyNode} [options.node]
|
|
421
|
-
* @param {string} [options.string]
|
|
422
|
-
* @param {string} [options.scanned=[]]
|
|
423
|
-
* @param {string}
|
|
424
|
-
*/
|
|
425
|
-
export function resolveAlias(alias, { tokens, logger, filename, src, node, scanned = [] }) {
|
|
426
|
-
const { id } = parseAlias(alias);
|
|
427
|
-
if (!tokens[id]) {
|
|
428
|
-
logger.error({ message: `Alias "${alias}" not found.`, filename, src, node });
|
|
429
|
-
}
|
|
430
|
-
if (scanned.includes(id)) {
|
|
431
|
-
logger.error({ message: `Circular alias detected from "${alias}".`, filename, src, node });
|
|
432
|
-
}
|
|
433
|
-
const token = tokens[id];
|
|
434
|
-
if (!isAlias(token.$value)) {
|
|
435
|
-
return id;
|
|
436
|
-
}
|
|
437
|
-
return resolveAlias(token.$value, { tokens, logger, filename, node, src, scanned: [...scanned, id] });
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/** Throw error if resolved alias for composite properties doesn’t match expected $type. */
|
|
441
|
-
const COMPOSITE_TYPE_VALUES = {
|
|
442
|
-
border: {
|
|
443
|
-
color: ['color'],
|
|
444
|
-
width: ['dimension'],
|
|
445
|
-
strokeStyle: ['strokeStyle'],
|
|
446
|
-
},
|
|
447
|
-
gradient: {
|
|
448
|
-
color: ['color'],
|
|
449
|
-
position: ['number'],
|
|
450
|
-
},
|
|
451
|
-
shadow: {
|
|
452
|
-
color: ['color'],
|
|
453
|
-
position: ['dimension'],
|
|
454
|
-
},
|
|
455
|
-
strokeStyle: {
|
|
456
|
-
dashArray: ['dimension'],
|
|
457
|
-
},
|
|
458
|
-
transition: {
|
|
459
|
-
duration: ['duration'],
|
|
460
|
-
delay: ['duration'],
|
|
461
|
-
timingFunction: ['cubicBezier'],
|
|
462
|
-
},
|
|
463
|
-
typography: {
|
|
464
|
-
fontFamily: ['fontFamily'],
|
|
465
|
-
fontSize: ['dimension'],
|
|
466
|
-
fontWeight: ['fontWeight'],
|
|
467
|
-
letterSpacing: ['dimension'],
|
|
468
|
-
lineHeight: ['dimension', 'number'],
|
|
469
|
-
},
|
|
470
|
-
};
|
|
471
|
-
|
|
472
|
-
/** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
|
|
473
|
-
function applyAliases(token, { tokens, logger, filename, src, node }) {
|
|
474
|
-
const $valueNode = (node.type === 'Object' && node.members.find((m) => m.name.value === '$value')?.value) || node;
|
|
475
|
-
const expectedAliasTypes = COMPOSITE_TYPE_VALUES[token.$type];
|
|
476
|
-
|
|
477
|
-
// handle simple aliases
|
|
478
|
-
if (isAlias(token.$value)) {
|
|
479
|
-
const aliasOfID = resolveAlias(token.$value, { tokens, logger, filename, node, src });
|
|
480
|
-
const { mode: aliasMode } = parseAlias(token.$value);
|
|
481
|
-
const aliasOf = tokens[aliasOfID];
|
|
482
|
-
token.aliasOf = aliasOfID;
|
|
483
|
-
token.$value = aliasOf.mode[aliasMode]?.$value || aliasOf.$value;
|
|
484
|
-
if (token.$type && token.$type !== aliasOf.$type) {
|
|
485
|
-
logger.error({
|
|
486
|
-
message: `Invalid alias: expected $type: "${token.$type}", received $type: "${aliasOf.$type}".`,
|
|
487
|
-
node: $valueNode,
|
|
488
|
-
filename,
|
|
489
|
-
src,
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
token.$type = aliasOf.$type;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// handle aliases within array values (e.g. cubicBezier, gradient)
|
|
496
|
-
else if (Array.isArray(token.$value)) {
|
|
497
|
-
// some arrays are primitives, some are objects. handle both
|
|
498
|
-
for (let i = 0; i < token.$value.length; i++) {
|
|
499
|
-
if (isAlias(token.$value[i])) {
|
|
500
|
-
if (!token.partialAliasOf) {
|
|
501
|
-
token.partialAliasOf = [];
|
|
502
|
-
}
|
|
503
|
-
const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, filename, node, src });
|
|
504
|
-
const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[i]);
|
|
505
|
-
token.partialAliasOf[i] = aliasID;
|
|
506
|
-
token.$value[i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
|
|
507
|
-
} else if (typeof token.$value[i] === 'object') {
|
|
508
|
-
for (const property in token.$value[i]) {
|
|
509
|
-
if (isAlias(token.$value[i][property])) {
|
|
510
|
-
if (!token.partialAliasOf) {
|
|
511
|
-
token.partialAliasOf = [];
|
|
512
|
-
}
|
|
513
|
-
if (!token.partialAliasOf[i]) {
|
|
514
|
-
token.partialAliasOf[i] = {};
|
|
515
|
-
}
|
|
516
|
-
const aliasOfID = resolveAlias(token.$value[i][property], { tokens, logger, filename, node, src });
|
|
517
|
-
const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[i][property]);
|
|
518
|
-
const aliasToken = tokens[aliasOfID];
|
|
519
|
-
|
|
520
|
-
if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
|
|
521
|
-
const elementNode = $valueNode.elements[i].value;
|
|
522
|
-
logger.error({
|
|
523
|
-
message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
|
|
524
|
-
node: elementNode.members.find((m) => m.name.value === property).value,
|
|
525
|
-
filename,
|
|
526
|
-
src,
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
token.partialAliasOf[i][property] = aliasID; // also keep the shallow alias here, too!
|
|
531
|
-
token.$value[i][property] = aliasToken.mode[aliasMode]?.$value || aliasToken.$value;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
// handle aliases within object (composite) values (e.g. border, typography, transition)
|
|
538
|
-
else if (typeof token.$value === 'object') {
|
|
539
|
-
for (const property in token.$value) {
|
|
540
|
-
if (!Object.hasOwn(token.$value, property)) {
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (isAlias(token.$value[property])) {
|
|
545
|
-
if (!token.partialAliasOf) {
|
|
546
|
-
token.partialAliasOf = {};
|
|
547
|
-
}
|
|
548
|
-
const aliasOfID = resolveAlias(token.$value[property], { tokens, logger, filename, node, src });
|
|
549
|
-
const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[property]);
|
|
550
|
-
token.partialAliasOf[property] = aliasID; // keep the shallow alias!
|
|
551
|
-
const aliasToken = tokens[aliasOfID];
|
|
552
|
-
if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
|
|
553
|
-
logger.error({
|
|
554
|
-
message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
|
|
555
|
-
node: $valueNode.members.find((m) => m.name.value === property).value,
|
|
556
|
-
filename,
|
|
557
|
-
src,
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
token.$value[property] = aliasToken.mode[aliasMode]?.$value || aliasToken.$value;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// strokeStyle has an array within an object
|
|
564
|
-
else if (Array.isArray(token.$value[property])) {
|
|
565
|
-
for (let i = 0; i < token.$value[property].length; i++) {
|
|
566
|
-
if (isAlias(token.$value[property][i])) {
|
|
567
|
-
const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, filename, node, src });
|
|
568
|
-
if (!token.partialAliasOf) {
|
|
569
|
-
token.partialAliasOf = {};
|
|
570
|
-
}
|
|
571
|
-
if (!token.partialAliasOf[property]) {
|
|
572
|
-
token.partialAliasOf[property] = [];
|
|
573
|
-
}
|
|
574
|
-
const { id: aliasID, mode: aliasMode } = parseAlias(token.$value[property][i]);
|
|
575
|
-
token.partialAliasOf[property][i] = aliasID; // keep the shallow alias!
|
|
576
|
-
const aliasToken = tokens[aliasOfID];
|
|
577
|
-
if (expectedAliasTypes?.[property] && !expectedAliasTypes[property].includes(aliasToken.$type)) {
|
|
578
|
-
const arrayNode = $valueNode.members.find((m) => m.name.value === property).value;
|
|
579
|
-
logger.error({
|
|
580
|
-
message: `Invalid alias: expected $type: "${expectedAliasTypes[property].join('" or "')}", received $type: "${aliasToken.$type}".`,
|
|
581
|
-
node: arrayNode.elements[i],
|
|
582
|
-
filename,
|
|
583
|
-
src,
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
token.$value[property][i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
}
|
package/parse/json.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { AnyNode, MemberNode, ObjectNode, ValueNode } from '@humanwhocodes/momoa';
|
|
2
|
-
|
|
3
|
-
export interface Visitor {
|
|
4
|
-
enter?: (node: AnyNode, parent: AnyNode | undefined, path: string[]) => void;
|
|
5
|
-
exit?: (node: AnyNode, parent: AnyNode | undefined, path: string[]) => void;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
declare const CHILD_KEYS: {
|
|
9
|
-
Document: readonly ['body'];
|
|
10
|
-
Object: readonly ['members'];
|
|
11
|
-
Member: readonly ['name', 'value'];
|
|
12
|
-
Element: readonly ['value'];
|
|
13
|
-
Array: readonly ['elements'];
|
|
14
|
-
String: readonly [];
|
|
15
|
-
Number: readonly [];
|
|
16
|
-
Boolean: readonly [];
|
|
17
|
-
Null: readonly [];
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/** Determines if a given value is an AST node. */
|
|
21
|
-
export function isNode(value: unknown): boolean;
|
|
22
|
-
|
|
23
|
-
/** Get ObjectNode members as object */
|
|
24
|
-
export function getObjMembers(node: ObjectNode): Record<string | number, ValueNode | undefined>;
|
|
25
|
-
|
|
26
|
-
/** Inject members to ObjectNode and return a clone */
|
|
27
|
-
export function injectObjMembers(node: ObjectNode, members: MemberNode[]): ObjectNode;
|
|
28
|
-
|
|
29
|
-
/** Variation of Momoa’s traverse(), which keeps track of global path */
|
|
30
|
-
export function traverse(root: AnyNode, visitor: Visitor): void;
|