reffy 6.2.0 → 6.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/LICENSE +21 -21
- package/README.md +158 -158
- package/index.js +11 -11
- package/package.json +53 -53
- package/reffy.js +248 -248
- package/src/browserlib/canonicalize-url.mjs +50 -50
- package/src/browserlib/create-outline.mjs +352 -352
- package/src/browserlib/extract-cssdfn.mjs +319 -319
- package/src/browserlib/extract-dfns.mjs +686 -686
- package/src/browserlib/extract-elements.mjs +205 -205
- package/src/browserlib/extract-headings.mjs +48 -48
- package/src/browserlib/extract-ids.mjs +28 -28
- package/src/browserlib/extract-links.mjs +28 -28
- package/src/browserlib/extract-references.mjs +203 -203
- package/src/browserlib/extract-webidl.mjs +134 -134
- package/src/browserlib/get-absolute-url.mjs +21 -21
- package/src/browserlib/get-generator.mjs +26 -26
- package/src/browserlib/get-lastmodified-date.mjs +13 -13
- package/src/browserlib/get-title.mjs +11 -11
- package/src/browserlib/informative-selector.mjs +16 -16
- package/src/browserlib/map-ids-to-headings.mjs +136 -136
- package/src/browserlib/reffy.json +53 -53
- package/src/cli/check-missing-dfns.js +609 -609
- package/src/cli/generate-idlnames.js +430 -430
- package/src/cli/generate-idlparsed.js +139 -139
- package/src/cli/merge-crawl-results.js +128 -128
- package/src/cli/parse-webidl.js +430 -430
- package/src/lib/css-grammar-parse-tree.schema.json +109 -109
- package/src/lib/css-grammar-parser.js +440 -440
- package/src/lib/fetch.js +55 -55
- package/src/lib/nock-server.js +119 -119
- package/src/lib/specs-crawler.js +605 -603
- package/src/lib/util.js +898 -898
- package/src/specs/missing-css-rules.json +197 -197
- package/src/specs/spec-equivalents.json +149 -149
- package/src/browserlib/extract-editors.mjs~ +0 -14
- package/src/browserlib/generate-es-dfn-report.sh~ +0 -4
- package/src/cli/csstree-grammar-check.js +0 -28
- package/src/cli/csstree-grammar-check.js~ +0 -10
- package/src/cli/csstree-grammar-parser.js +0 -11
- package/src/cli/csstree-grammar-parser.js~ +0 -1
- package/src/cli/extract-editors.js~ +0 -38
- package/src/cli/process-specs.js~ +0 -28
|
@@ -1,440 +1,440 @@
|
|
|
1
|
-
// Based on https://drafts.csswg.org/css-values-4/#value-defs
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const primitives = new Map([
|
|
5
|
-
["ident", {}],
|
|
6
|
-
["ident-token", {}],
|
|
7
|
-
["declaration-value", {}],
|
|
8
|
-
// the subset below is only used in selectors / MQ
|
|
9
|
-
// probably ought to be removed
|
|
10
|
-
["number-token", {}],
|
|
11
|
-
["hash-token", {}],
|
|
12
|
-
["any-value", {}],
|
|
13
|
-
["string-token", {}],
|
|
14
|
-
["function-token", {}],
|
|
15
|
-
["dimension-token", {}],
|
|
16
|
-
// ----
|
|
17
|
-
["zero", {url: ""}],
|
|
18
|
-
["custom-ident", {url: "https://drafts.csswg.org/css-values-4/#custom-idents"}],
|
|
19
|
-
["string", {url: "https://drafts.csswg.org/css-values-4/#strings"}],
|
|
20
|
-
["url", {url: "https://drafts.csswg.org/css-values-4/#urls"}],
|
|
21
|
-
["integer", {url: "https://drafts.csswg.org/css-values-4/#integers"}],
|
|
22
|
-
["number", {url: "https://drafts.csswg.org/css-values-4/#numbers"}],
|
|
23
|
-
["percentage", {url: "https://drafts.csswg.org/css-values-4/#percentages"}],
|
|
24
|
-
["number-percentage", {url: "https://drafts.csswg.org/css-values-4/#number-percentage"}],
|
|
25
|
-
["length-percentage", {url: "https://drafts.csswg.org/css-values-4/#length-percentage"}],
|
|
26
|
-
["frequency-percentage", {url: "https://drafts.csswg.org/css-values-4/#frequency-percentage"}],
|
|
27
|
-
["angle-percentage", {url: "https://drafts.csswg.org/css-values-4/#angle-percentage"}],
|
|
28
|
-
["time-percentage", {url: "https://drafts.csswg.org/css-values-4/#time-percentage"}],
|
|
29
|
-
["dimension", {url: "https://drafts.csswg.org/css-values-4/#dimensions"}],
|
|
30
|
-
["length", {url: "https://drafts.csswg.org/css-values-4/#lengths"}],
|
|
31
|
-
["angle", {url: "https://drafts.csswg.org/css-values-4/#angles"}],
|
|
32
|
-
["time", {url: "https://drafts.csswg.org/css-values-4/#time"}],
|
|
33
|
-
["frequency", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
|
|
34
|
-
["resolution", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
|
|
35
|
-
["color", {url: "https://drafts.csswg.org/css-color-3/#valuea-def-color"}],
|
|
36
|
-
["image", {url: "https://drafts.csswg.org/css-images-3/#typedef-image"}],
|
|
37
|
-
["position", {url: "https://drafts.csswg.org/css-values-4/#typedef-position"}]
|
|
38
|
-
]);
|
|
39
|
-
|
|
40
|
-
const combinatorsMap = [['&&', 'allOf'],
|
|
41
|
-
['||', 'anyOf'],
|
|
42
|
-
['|', 'oneOf']];
|
|
43
|
-
|
|
44
|
-
const multipliersStarters = ['{', '+', '#', '!', '?', '*'];
|
|
45
|
-
|
|
46
|
-
const unquotedTokens = ['/', ',', '(', ')'];
|
|
47
|
-
|
|
48
|
-
const componentizeByCombinators = (parts, combinators = new Map(combinatorsMap)) => {
|
|
49
|
-
const res = {};
|
|
50
|
-
let combinatorFound = false;
|
|
51
|
-
const combinatorIterator = combinators.entries();
|
|
52
|
-
while (!combinatorFound) {
|
|
53
|
-
const {value: entry, done} = combinatorIterator.next();
|
|
54
|
-
if (done) break;
|
|
55
|
-
const [c,t] = entry;
|
|
56
|
-
if (Array.isArray(parts) && parts.includes(c)) {
|
|
57
|
-
combinatorFound = true;
|
|
58
|
-
// going down into the list of combinators by order of precedence
|
|
59
|
-
const lowerCombinators = new Map(combinators);
|
|
60
|
-
lowerCombinators.delete(c);
|
|
61
|
-
let components = splitByCombinator(parts, c);
|
|
62
|
-
res[t] = components.map(p => componentizeByCombinators(p, lowerCombinators));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (!combinatorFound) {
|
|
66
|
-
if (Array.isArray(parts)) {
|
|
67
|
-
if (parts.length > 1) {
|
|
68
|
-
return {type: "array", items: parts.map(p => componentizeByCombinators(p))};
|
|
69
|
-
} else {
|
|
70
|
-
return componentizeByCombinators(parts[0]);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (parts && parts.type && parts.type === "array")
|
|
74
|
-
return {...parts, items: componentizeByCombinators(parts.items)};
|
|
75
|
-
return parts;
|
|
76
|
-
}
|
|
77
|
-
return res;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const splitByCombinator = (parts, combinator) => {
|
|
81
|
-
const {components} = parts.reduce((a, b, i) => {
|
|
82
|
-
if (b === combinator) {
|
|
83
|
-
a.components.push(a.head.length === 1 ? a.head[0] : a.head);
|
|
84
|
-
a.head = [];
|
|
85
|
-
} else {
|
|
86
|
-
if (Array.isArray(b)) {
|
|
87
|
-
a.head.push(componentizeByCombinators(b));
|
|
88
|
-
} else {
|
|
89
|
-
a.head.push(b);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (i === parts.length - 1) {
|
|
93
|
-
a.components.push(a.head.length === 1 ? a.head[0] : a.head);
|
|
94
|
-
}
|
|
95
|
-
return a;
|
|
96
|
-
}, {head: [], components: []});
|
|
97
|
-
return components;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const parseMultiplierRange = range => {
|
|
101
|
-
if (range[0] !== '{')
|
|
102
|
-
throw new Error(`Expected { at the start of multiplier range ${range}`);
|
|
103
|
-
if (range[range.length - 1] !== '}')
|
|
104
|
-
throw new Error(`Expected } at the end of multiplier range ${range}`);
|
|
105
|
-
const values = range.slice(1,range.length - 1);
|
|
106
|
-
if (values.match(/^[0-9]+$/)) {
|
|
107
|
-
return {minItems: parseInt(values, 10), maxItems: parseInt(values, 10)};
|
|
108
|
-
} else if (values.match(/^[0-9]+,([0-9]+)?$/)) {
|
|
109
|
-
const [min,max] = values.split(',');
|
|
110
|
-
return { ...{minItems: parseInt(min, 10)}, ...(max ? { maxItems: parseInt(max, 10)} : {})};
|
|
111
|
-
} else {
|
|
112
|
-
throw new Error(`Unrecognized range format in multiplier ${range}`);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const applyMultiplier = (multiplier, modifiee) => {
|
|
117
|
-
let ret;
|
|
118
|
-
if (multiplier === '*') {
|
|
119
|
-
return {
|
|
120
|
-
type: "array",
|
|
121
|
-
items: modifiee
|
|
122
|
-
};
|
|
123
|
-
} else if (multiplier === '+') {
|
|
124
|
-
return {
|
|
125
|
-
type: "array",
|
|
126
|
-
items: modifiee,
|
|
127
|
-
minItems: 1
|
|
128
|
-
};
|
|
129
|
-
} else if (multiplier === '#') {
|
|
130
|
-
return {
|
|
131
|
-
type: "array",
|
|
132
|
-
items: modifiee,
|
|
133
|
-
separator: ","
|
|
134
|
-
};
|
|
135
|
-
} else if (multiplier.startsWith('{')) {
|
|
136
|
-
return {
|
|
137
|
-
type: "array",
|
|
138
|
-
items: modifiee,
|
|
139
|
-
...parseMultiplierRange(multiplier)
|
|
140
|
-
};
|
|
141
|
-
} else if (multiplier === '?') {
|
|
142
|
-
if (Array.isArray(modifiee)) {
|
|
143
|
-
return {type: "array", items: modifiee, maxItems: 1};
|
|
144
|
-
} else {
|
|
145
|
-
return {...modifiee, optional: true};
|
|
146
|
-
}
|
|
147
|
-
} else if (multiplier === '!') {
|
|
148
|
-
if (Array.isArray(modifiee)) {
|
|
149
|
-
return {type: "array", items: modifiee, minItems: 1};
|
|
150
|
-
} else {
|
|
151
|
-
throw new Error(`Multiplier "!" applied to non-group ${modifiee}`);
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
throw new Error(`Unrecognized multiplier ${multiplier}`);
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const isMultiplier = s => typeof s === "string" && multipliersStarters.map(starter => s.startsWith(starter)).includes(true);
|
|
159
|
-
|
|
160
|
-
const primitiveMatch = (s, p) => s.match(new RegExp("<(" + p + ")( +\\\[[^\\\]]*\\\])?>"));
|
|
161
|
-
|
|
162
|
-
const parseBracketedRange = s => {
|
|
163
|
-
if (!s || !s.trim()) return undefined;
|
|
164
|
-
const range = s.trim().slice(1, s.length - 2).split(',').map(x => x.trim());
|
|
165
|
-
if (range.length != 2) throw new Error(`Unrecognized range descriptor ${s}`);
|
|
166
|
-
return range;
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const parseTerminals = s => {
|
|
170
|
-
let m;
|
|
171
|
-
if ([...new Map(combinatorsMap).keys()].includes(s) || s === '[' || s.startsWith(']') || isMultiplier(s)) {
|
|
172
|
-
return s;
|
|
173
|
-
} else if (unquotedTokens.includes(s)) {
|
|
174
|
-
return {type: "string", content: s};
|
|
175
|
-
} else if ((m = s.match(/^\'([^\']*)\'$/))) {
|
|
176
|
-
return {type: "string", content: m[1]};
|
|
177
|
-
} else if ((m = s.match(/^<\'([-_a-zA-Z][^\'>]*)\'>$/))) {
|
|
178
|
-
return {type: "propertyref", name: m[1]};
|
|
179
|
-
} else if ((m = [...primitives.keys()].find(p => primitiveMatch(s, p)))) {
|
|
180
|
-
// TODO: parse bracketed range notation
|
|
181
|
-
const [, name, range] = primitiveMatch(s, m);
|
|
182
|
-
return Object.assign({type: "primitive", name}, range ? {range: parseBracketedRange(range)} : {});
|
|
183
|
-
} else if ((m = s.match(/^<[-_a-zA-Z]([^>]*)>$/))) {
|
|
184
|
-
return {type: "valuespace", name: s.slice(1, s.length -1)};
|
|
185
|
-
} else if ((m = s.match(/^[-_a-zA-Z][-_a-zA-Z0-9]*$/))) {
|
|
186
|
-
return {type: "keyword", name: s};
|
|
187
|
-
} else if ((m = s.match(/^[-_a-zA-Z][-_a-zA-Z0-9]*\($/))) {
|
|
188
|
-
return {type: "functionstart", name: s};
|
|
189
|
-
} else { // TODO: add support for functional notations https://drafts.csswg.org/css-values-4/#functional-notation even though they're not recognized as top-level items in the grammar
|
|
190
|
-
throw new Error(`Unrecognized token ${s}`);
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const tokenize = (value) => {
|
|
195
|
-
let i = 0, currentToken='', tokens=[], state = 'new';
|
|
196
|
-
const delimiterStates = ['new', 'keyword', 'pipe'];
|
|
197
|
-
while(i < value.length) {
|
|
198
|
-
const c = value[i];
|
|
199
|
-
if (c.match(/\s/)) {
|
|
200
|
-
if (state === 'labracket') { // bracketed range notation
|
|
201
|
-
currentToken += c;
|
|
202
|
-
} else {
|
|
203
|
-
if (currentToken) tokens.push(currentToken);
|
|
204
|
-
currentToken = '';
|
|
205
|
-
state = 'new';
|
|
206
|
-
}
|
|
207
|
-
} else if (c === '<') {
|
|
208
|
-
if (delimiterStates.includes(state)) {
|
|
209
|
-
if (currentToken) tokens.push(currentToken);
|
|
210
|
-
currentToken = c;
|
|
211
|
-
state = 'labracket';
|
|
212
|
-
} else if (state === 'quote') {
|
|
213
|
-
currentToken += c;
|
|
214
|
-
} else {
|
|
215
|
-
throw new Error(`Unexpected < in ${currentToken} while parsing ${value} in state ${state}`);
|
|
216
|
-
}
|
|
217
|
-
} else if (c === ">") {
|
|
218
|
-
if (state === 'quote') {
|
|
219
|
-
currentToken += c;
|
|
220
|
-
} else if (state === 'rabracket' || state === 'labracket') {
|
|
221
|
-
currentToken += c;
|
|
222
|
-
tokens.push(currentToken);
|
|
223
|
-
currentToken = '';
|
|
224
|
-
state = 'new';
|
|
225
|
-
} else {
|
|
226
|
-
throw new Error(`Unexpected > in ${currentToken} while parsing ${value} in state ${state}`);
|
|
227
|
-
}
|
|
228
|
-
} else if (c === "'") {
|
|
229
|
-
if (state === 'quote') {
|
|
230
|
-
currentToken += c;
|
|
231
|
-
tokens.push(currentToken);
|
|
232
|
-
currentToken = '';
|
|
233
|
-
state = 'new';
|
|
234
|
-
} else if (state === 'labracket') {
|
|
235
|
-
currentToken += c;
|
|
236
|
-
state = 'labracketquote';
|
|
237
|
-
} else if (state === 'labracketquote') {
|
|
238
|
-
currentToken += c;
|
|
239
|
-
state = 'rabracket';
|
|
240
|
-
} else {
|
|
241
|
-
if (currentToken) tokens.push(currentToken);
|
|
242
|
-
currentToken = c;
|
|
243
|
-
state = 'quote';
|
|
244
|
-
}
|
|
245
|
-
} else if (c === "[" || c === "]" || c === "+" || c === "*" || c === "#" || c === "!" || c === '?' || c === '/') {
|
|
246
|
-
if (delimiterStates.includes(state)) {
|
|
247
|
-
if (currentToken) tokens.push(currentToken);
|
|
248
|
-
tokens.push(c);
|
|
249
|
-
currentToken='';
|
|
250
|
-
state = 'new';
|
|
251
|
-
} else if (state === 'quote') {
|
|
252
|
-
currentToken += c;
|
|
253
|
-
} else if (state === 'labracket' && c === '[') {
|
|
254
|
-
// bracketed range notation
|
|
255
|
-
state = 'bracketedrange';
|
|
256
|
-
currentToken += c;
|
|
257
|
-
} else if (state === 'bracketedrange' && c === ']') {
|
|
258
|
-
currentToken += c;
|
|
259
|
-
state = 'labracket';
|
|
260
|
-
} else {
|
|
261
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
262
|
-
}
|
|
263
|
-
} else if ( c === '{' ) {
|
|
264
|
-
if (state === 'quote') {
|
|
265
|
-
currentToken += c;
|
|
266
|
-
} else if (delimiterStates.includes(state)) {
|
|
267
|
-
if (currentToken) tokens.push(currentToken);
|
|
268
|
-
currentToken = c;
|
|
269
|
-
state = 'curlybracket';
|
|
270
|
-
} else {
|
|
271
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
272
|
-
}
|
|
273
|
-
} else if ( c === '}' ) {
|
|
274
|
-
if (state === 'quote') {
|
|
275
|
-
currentToken += c;
|
|
276
|
-
} else if (state === 'curlybracket') {
|
|
277
|
-
currentToken += c;
|
|
278
|
-
tokens.push(currentToken);
|
|
279
|
-
currentToken = '';
|
|
280
|
-
state = 'new';
|
|
281
|
-
} else {
|
|
282
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
283
|
-
}
|
|
284
|
-
} else if ( c === ',') {
|
|
285
|
-
if (delimiterStates.includes(state)) {
|
|
286
|
-
if (currentToken) tokens.push(currentToken);
|
|
287
|
-
tokens.push(c);
|
|
288
|
-
currentToken='';
|
|
289
|
-
state = 'new';
|
|
290
|
-
} else if (state === 'quote' || state === 'curlybracket' || state === 'bracketedrange') {
|
|
291
|
-
currentToken += c;
|
|
292
|
-
} else {
|
|
293
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
294
|
-
}
|
|
295
|
-
} else if ( c === '(') {
|
|
296
|
-
if (state === 'new' || state === 'pipe') {
|
|
297
|
-
if (currentToken) tokens.push(currentToken);
|
|
298
|
-
tokens.push(c);
|
|
299
|
-
currentToken='';
|
|
300
|
-
state = 'new';
|
|
301
|
-
} else if (state === 'quote' || state === 'labracket' || state === 'labracketquote') {
|
|
302
|
-
currentToken += c;
|
|
303
|
-
} else if (state === 'keyword') {
|
|
304
|
-
currentToken += c;
|
|
305
|
-
tokens.push(currentToken);
|
|
306
|
-
currentToken ='';
|
|
307
|
-
state = 'new';
|
|
308
|
-
} else {
|
|
309
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
310
|
-
}
|
|
311
|
-
} else if (c === ")") {
|
|
312
|
-
if (delimiterStates.includes(state)) {
|
|
313
|
-
if (currentToken) tokens.push(currentToken);
|
|
314
|
-
tokens.push(c);
|
|
315
|
-
currentToken='';
|
|
316
|
-
state = 'new';
|
|
317
|
-
} else if (state === 'quote' || state === 'labracket' || state === 'labracketquote') {
|
|
318
|
-
currentToken += c;
|
|
319
|
-
} else {
|
|
320
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
321
|
-
}
|
|
322
|
-
} else if ( c === '&') {
|
|
323
|
-
if (state === 'new' || state === 'keyword') { // 'pipe' can't appear just before ampersand
|
|
324
|
-
if (currentToken) tokens.push(currentToken);
|
|
325
|
-
currentToken=c;
|
|
326
|
-
state = 'ampersand';
|
|
327
|
-
} else if (state === 'quote') {
|
|
328
|
-
currentToken += c;
|
|
329
|
-
} else if (state === 'ampersand') {
|
|
330
|
-
currentToken += c;
|
|
331
|
-
tokens.push(currentToken);
|
|
332
|
-
currentToken='';
|
|
333
|
-
state = 'new';
|
|
334
|
-
} else {
|
|
335
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
336
|
-
}
|
|
337
|
-
} else if (c === '|') {
|
|
338
|
-
if (state === 'new' || state === 'keyword') {
|
|
339
|
-
if (currentToken) tokens.push(currentToken);
|
|
340
|
-
currentToken=c;
|
|
341
|
-
state = 'pipe';
|
|
342
|
-
} else if (state === 'quote') {
|
|
343
|
-
currentToken += c;
|
|
344
|
-
} else if (state === 'pipe') {
|
|
345
|
-
currentToken += c;
|
|
346
|
-
tokens.push(currentToken);
|
|
347
|
-
currentToken='';
|
|
348
|
-
state = 'new';
|
|
349
|
-
} else {
|
|
350
|
-
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
351
|
-
}
|
|
352
|
-
} else {
|
|
353
|
-
if (state === 'pipe') {
|
|
354
|
-
tokens.push(currentToken);
|
|
355
|
-
currentToken = 'c';
|
|
356
|
-
state = 'keyword';
|
|
357
|
-
} else {
|
|
358
|
-
currentToken += c;
|
|
359
|
-
if (state === 'new') state = 'keyword';
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
i++;
|
|
363
|
-
}
|
|
364
|
-
if (state === 'new' || state === 'keyword') {
|
|
365
|
-
if (currentToken) tokens.push(currentToken);
|
|
366
|
-
} else {
|
|
367
|
-
throw new Error(`Unexpected EOF while parsing ${value} in state ${state}`);
|
|
368
|
-
}
|
|
369
|
-
return tokens;
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const parsePropDefValue = (value) => {
|
|
373
|
-
value = value.trim();
|
|
374
|
-
const tokens = tokenize(value);
|
|
375
|
-
let parts = tokens.filter(x => x)
|
|
376
|
-
.map(parseTerminals);
|
|
377
|
-
|
|
378
|
-
// Applying multipliers on terminals
|
|
379
|
-
parts = parts.reduce((arr, item, i) => {
|
|
380
|
-
if (!isMultiplier(item)) {
|
|
381
|
-
arr.push(item);
|
|
382
|
-
return arr;
|
|
383
|
-
}
|
|
384
|
-
if (i === 0)
|
|
385
|
-
throw new Error(`Unexpected multiplier ${item} at the start of ${value}`);
|
|
386
|
-
const prevItem = arr.pop();
|
|
387
|
-
if (prevItem !== ']') {
|
|
388
|
-
arr.push(applyMultiplier(item, prevItem));
|
|
389
|
-
} else {
|
|
390
|
-
arr.push(prevItem);
|
|
391
|
-
arr.push(item);
|
|
392
|
-
}
|
|
393
|
-
return arr;
|
|
394
|
-
}, []);
|
|
395
|
-
|
|
396
|
-
// matching functional notations
|
|
397
|
-
while(parts.findIndex(p => p.type === 'functionstart') !== -1) {
|
|
398
|
-
const funcIdx = parts.findIndex(p => p.type === 'functionstart');
|
|
399
|
-
const matchingClosingFuncIdx = parts.findIndex((p, i) => p.content === ')' && i > funcIdx);
|
|
400
|
-
if (matchingClosingFuncIdx === -1) {
|
|
401
|
-
throw new Error(`Unterminated function notation in ${value}`);
|
|
402
|
-
}
|
|
403
|
-
const name = parts[funcIdx].name;
|
|
404
|
-
const func = { type: "function",
|
|
405
|
-
name: name.slice(0, name.length - 1),
|
|
406
|
-
arguments: parts.slice(funcIdx + 1, matchingClosingFuncIdx)
|
|
407
|
-
};
|
|
408
|
-
parts = parts.slice(0, funcIdx)
|
|
409
|
-
.concat([func])
|
|
410
|
-
.concat(parts.slice(matchingClosingFuncIdx + 1));
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// matching potentially nested bracket-groups
|
|
414
|
-
while(parts.lastIndexOf('[') !== -1) {
|
|
415
|
-
const bracketIdx = parts.lastIndexOf('[');
|
|
416
|
-
|
|
417
|
-
// closing bracket may be more than just ']'
|
|
418
|
-
// since it can be accompanied with multipliers
|
|
419
|
-
const matchingBracketIdx = parts.findIndex((p, i) => p === ']' && i > bracketIdx);
|
|
420
|
-
|
|
421
|
-
if (matchingBracketIdx === -1) {
|
|
422
|
-
throw new Error(`Unterminated bracket-group in ${value}`);
|
|
423
|
-
}
|
|
424
|
-
let group = parts.slice(bracketIdx + 1, matchingBracketIdx);
|
|
425
|
-
let multiplier, i = 0, multiplied = false;
|
|
426
|
-
while ((multiplier = parts.slice(matchingBracketIdx + 1)[i]) && isMultiplier(multiplier)) {
|
|
427
|
-
group = applyMultiplier(multiplier, group);
|
|
428
|
-
multiplied = true;
|
|
429
|
-
i++;
|
|
430
|
-
}
|
|
431
|
-
const multipliedGroup = multiplied ? group : [ group ] ;
|
|
432
|
-
parts = parts.slice(0, bracketIdx)
|
|
433
|
-
.concat(multipliedGroup)
|
|
434
|
-
.concat(parts.slice(matchingBracketIdx + 1 + i));
|
|
435
|
-
}
|
|
436
|
-
const res = componentizeByCombinators(parts);
|
|
437
|
-
return res.length === 1 ? res[0] : res;
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
module.exports.parsePropDefValue = parsePropDefValue;
|
|
1
|
+
// Based on https://drafts.csswg.org/css-values-4/#value-defs
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const primitives = new Map([
|
|
5
|
+
["ident", {}],
|
|
6
|
+
["ident-token", {}],
|
|
7
|
+
["declaration-value", {}],
|
|
8
|
+
// the subset below is only used in selectors / MQ
|
|
9
|
+
// probably ought to be removed
|
|
10
|
+
["number-token", {}],
|
|
11
|
+
["hash-token", {}],
|
|
12
|
+
["any-value", {}],
|
|
13
|
+
["string-token", {}],
|
|
14
|
+
["function-token", {}],
|
|
15
|
+
["dimension-token", {}],
|
|
16
|
+
// ----
|
|
17
|
+
["zero", {url: ""}],
|
|
18
|
+
["custom-ident", {url: "https://drafts.csswg.org/css-values-4/#custom-idents"}],
|
|
19
|
+
["string", {url: "https://drafts.csswg.org/css-values-4/#strings"}],
|
|
20
|
+
["url", {url: "https://drafts.csswg.org/css-values-4/#urls"}],
|
|
21
|
+
["integer", {url: "https://drafts.csswg.org/css-values-4/#integers"}],
|
|
22
|
+
["number", {url: "https://drafts.csswg.org/css-values-4/#numbers"}],
|
|
23
|
+
["percentage", {url: "https://drafts.csswg.org/css-values-4/#percentages"}],
|
|
24
|
+
["number-percentage", {url: "https://drafts.csswg.org/css-values-4/#number-percentage"}],
|
|
25
|
+
["length-percentage", {url: "https://drafts.csswg.org/css-values-4/#length-percentage"}],
|
|
26
|
+
["frequency-percentage", {url: "https://drafts.csswg.org/css-values-4/#frequency-percentage"}],
|
|
27
|
+
["angle-percentage", {url: "https://drafts.csswg.org/css-values-4/#angle-percentage"}],
|
|
28
|
+
["time-percentage", {url: "https://drafts.csswg.org/css-values-4/#time-percentage"}],
|
|
29
|
+
["dimension", {url: "https://drafts.csswg.org/css-values-4/#dimensions"}],
|
|
30
|
+
["length", {url: "https://drafts.csswg.org/css-values-4/#lengths"}],
|
|
31
|
+
["angle", {url: "https://drafts.csswg.org/css-values-4/#angles"}],
|
|
32
|
+
["time", {url: "https://drafts.csswg.org/css-values-4/#time"}],
|
|
33
|
+
["frequency", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
|
|
34
|
+
["resolution", {url: "https://drafts.csswg.org/css-values-4/#frequency"}],
|
|
35
|
+
["color", {url: "https://drafts.csswg.org/css-color-3/#valuea-def-color"}],
|
|
36
|
+
["image", {url: "https://drafts.csswg.org/css-images-3/#typedef-image"}],
|
|
37
|
+
["position", {url: "https://drafts.csswg.org/css-values-4/#typedef-position"}]
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const combinatorsMap = [['&&', 'allOf'],
|
|
41
|
+
['||', 'anyOf'],
|
|
42
|
+
['|', 'oneOf']];
|
|
43
|
+
|
|
44
|
+
const multipliersStarters = ['{', '+', '#', '!', '?', '*'];
|
|
45
|
+
|
|
46
|
+
const unquotedTokens = ['/', ',', '(', ')'];
|
|
47
|
+
|
|
48
|
+
const componentizeByCombinators = (parts, combinators = new Map(combinatorsMap)) => {
|
|
49
|
+
const res = {};
|
|
50
|
+
let combinatorFound = false;
|
|
51
|
+
const combinatorIterator = combinators.entries();
|
|
52
|
+
while (!combinatorFound) {
|
|
53
|
+
const {value: entry, done} = combinatorIterator.next();
|
|
54
|
+
if (done) break;
|
|
55
|
+
const [c,t] = entry;
|
|
56
|
+
if (Array.isArray(parts) && parts.includes(c)) {
|
|
57
|
+
combinatorFound = true;
|
|
58
|
+
// going down into the list of combinators by order of precedence
|
|
59
|
+
const lowerCombinators = new Map(combinators);
|
|
60
|
+
lowerCombinators.delete(c);
|
|
61
|
+
let components = splitByCombinator(parts, c);
|
|
62
|
+
res[t] = components.map(p => componentizeByCombinators(p, lowerCombinators));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!combinatorFound) {
|
|
66
|
+
if (Array.isArray(parts)) {
|
|
67
|
+
if (parts.length > 1) {
|
|
68
|
+
return {type: "array", items: parts.map(p => componentizeByCombinators(p))};
|
|
69
|
+
} else {
|
|
70
|
+
return componentizeByCombinators(parts[0]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (parts && parts.type && parts.type === "array")
|
|
74
|
+
return {...parts, items: componentizeByCombinators(parts.items)};
|
|
75
|
+
return parts;
|
|
76
|
+
}
|
|
77
|
+
return res;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const splitByCombinator = (parts, combinator) => {
|
|
81
|
+
const {components} = parts.reduce((a, b, i) => {
|
|
82
|
+
if (b === combinator) {
|
|
83
|
+
a.components.push(a.head.length === 1 ? a.head[0] : a.head);
|
|
84
|
+
a.head = [];
|
|
85
|
+
} else {
|
|
86
|
+
if (Array.isArray(b)) {
|
|
87
|
+
a.head.push(componentizeByCombinators(b));
|
|
88
|
+
} else {
|
|
89
|
+
a.head.push(b);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (i === parts.length - 1) {
|
|
93
|
+
a.components.push(a.head.length === 1 ? a.head[0] : a.head);
|
|
94
|
+
}
|
|
95
|
+
return a;
|
|
96
|
+
}, {head: [], components: []});
|
|
97
|
+
return components;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const parseMultiplierRange = range => {
|
|
101
|
+
if (range[0] !== '{')
|
|
102
|
+
throw new Error(`Expected { at the start of multiplier range ${range}`);
|
|
103
|
+
if (range[range.length - 1] !== '}')
|
|
104
|
+
throw new Error(`Expected } at the end of multiplier range ${range}`);
|
|
105
|
+
const values = range.slice(1,range.length - 1);
|
|
106
|
+
if (values.match(/^[0-9]+$/)) {
|
|
107
|
+
return {minItems: parseInt(values, 10), maxItems: parseInt(values, 10)};
|
|
108
|
+
} else if (values.match(/^[0-9]+,([0-9]+)?$/)) {
|
|
109
|
+
const [min,max] = values.split(',');
|
|
110
|
+
return { ...{minItems: parseInt(min, 10)}, ...(max ? { maxItems: parseInt(max, 10)} : {})};
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error(`Unrecognized range format in multiplier ${range}`);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const applyMultiplier = (multiplier, modifiee) => {
|
|
117
|
+
let ret;
|
|
118
|
+
if (multiplier === '*') {
|
|
119
|
+
return {
|
|
120
|
+
type: "array",
|
|
121
|
+
items: modifiee
|
|
122
|
+
};
|
|
123
|
+
} else if (multiplier === '+') {
|
|
124
|
+
return {
|
|
125
|
+
type: "array",
|
|
126
|
+
items: modifiee,
|
|
127
|
+
minItems: 1
|
|
128
|
+
};
|
|
129
|
+
} else if (multiplier === '#') {
|
|
130
|
+
return {
|
|
131
|
+
type: "array",
|
|
132
|
+
items: modifiee,
|
|
133
|
+
separator: ","
|
|
134
|
+
};
|
|
135
|
+
} else if (multiplier.startsWith('{')) {
|
|
136
|
+
return {
|
|
137
|
+
type: "array",
|
|
138
|
+
items: modifiee,
|
|
139
|
+
...parseMultiplierRange(multiplier)
|
|
140
|
+
};
|
|
141
|
+
} else if (multiplier === '?') {
|
|
142
|
+
if (Array.isArray(modifiee)) {
|
|
143
|
+
return {type: "array", items: modifiee, maxItems: 1};
|
|
144
|
+
} else {
|
|
145
|
+
return {...modifiee, optional: true};
|
|
146
|
+
}
|
|
147
|
+
} else if (multiplier === '!') {
|
|
148
|
+
if (Array.isArray(modifiee)) {
|
|
149
|
+
return {type: "array", items: modifiee, minItems: 1};
|
|
150
|
+
} else {
|
|
151
|
+
throw new Error(`Multiplier "!" applied to non-group ${modifiee}`);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error(`Unrecognized multiplier ${multiplier}`);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const isMultiplier = s => typeof s === "string" && multipliersStarters.map(starter => s.startsWith(starter)).includes(true);
|
|
159
|
+
|
|
160
|
+
const primitiveMatch = (s, p) => s.match(new RegExp("<(" + p + ")( +\\\[[^\\\]]*\\\])?>"));
|
|
161
|
+
|
|
162
|
+
const parseBracketedRange = s => {
|
|
163
|
+
if (!s || !s.trim()) return undefined;
|
|
164
|
+
const range = s.trim().slice(1, s.length - 2).split(',').map(x => x.trim());
|
|
165
|
+
if (range.length != 2) throw new Error(`Unrecognized range descriptor ${s}`);
|
|
166
|
+
return range;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const parseTerminals = s => {
|
|
170
|
+
let m;
|
|
171
|
+
if ([...new Map(combinatorsMap).keys()].includes(s) || s === '[' || s.startsWith(']') || isMultiplier(s)) {
|
|
172
|
+
return s;
|
|
173
|
+
} else if (unquotedTokens.includes(s)) {
|
|
174
|
+
return {type: "string", content: s};
|
|
175
|
+
} else if ((m = s.match(/^\'([^\']*)\'$/))) {
|
|
176
|
+
return {type: "string", content: m[1]};
|
|
177
|
+
} else if ((m = s.match(/^<\'([-_a-zA-Z][^\'>]*)\'>$/))) {
|
|
178
|
+
return {type: "propertyref", name: m[1]};
|
|
179
|
+
} else if ((m = [...primitives.keys()].find(p => primitiveMatch(s, p)))) {
|
|
180
|
+
// TODO: parse bracketed range notation
|
|
181
|
+
const [, name, range] = primitiveMatch(s, m);
|
|
182
|
+
return Object.assign({type: "primitive", name}, range ? {range: parseBracketedRange(range)} : {});
|
|
183
|
+
} else if ((m = s.match(/^<[-_a-zA-Z]([^>]*)>$/))) {
|
|
184
|
+
return {type: "valuespace", name: s.slice(1, s.length -1)};
|
|
185
|
+
} else if ((m = s.match(/^[-_a-zA-Z][-_a-zA-Z0-9]*$/))) {
|
|
186
|
+
return {type: "keyword", name: s};
|
|
187
|
+
} else if ((m = s.match(/^[-_a-zA-Z][-_a-zA-Z0-9]*\($/))) {
|
|
188
|
+
return {type: "functionstart", name: s};
|
|
189
|
+
} else { // TODO: add support for functional notations https://drafts.csswg.org/css-values-4/#functional-notation even though they're not recognized as top-level items in the grammar
|
|
190
|
+
throw new Error(`Unrecognized token ${s}`);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const tokenize = (value) => {
|
|
195
|
+
let i = 0, currentToken='', tokens=[], state = 'new';
|
|
196
|
+
const delimiterStates = ['new', 'keyword', 'pipe'];
|
|
197
|
+
while(i < value.length) {
|
|
198
|
+
const c = value[i];
|
|
199
|
+
if (c.match(/\s/)) {
|
|
200
|
+
if (state === 'labracket') { // bracketed range notation
|
|
201
|
+
currentToken += c;
|
|
202
|
+
} else {
|
|
203
|
+
if (currentToken) tokens.push(currentToken);
|
|
204
|
+
currentToken = '';
|
|
205
|
+
state = 'new';
|
|
206
|
+
}
|
|
207
|
+
} else if (c === '<') {
|
|
208
|
+
if (delimiterStates.includes(state)) {
|
|
209
|
+
if (currentToken) tokens.push(currentToken);
|
|
210
|
+
currentToken = c;
|
|
211
|
+
state = 'labracket';
|
|
212
|
+
} else if (state === 'quote') {
|
|
213
|
+
currentToken += c;
|
|
214
|
+
} else {
|
|
215
|
+
throw new Error(`Unexpected < in ${currentToken} while parsing ${value} in state ${state}`);
|
|
216
|
+
}
|
|
217
|
+
} else if (c === ">") {
|
|
218
|
+
if (state === 'quote') {
|
|
219
|
+
currentToken += c;
|
|
220
|
+
} else if (state === 'rabracket' || state === 'labracket') {
|
|
221
|
+
currentToken += c;
|
|
222
|
+
tokens.push(currentToken);
|
|
223
|
+
currentToken = '';
|
|
224
|
+
state = 'new';
|
|
225
|
+
} else {
|
|
226
|
+
throw new Error(`Unexpected > in ${currentToken} while parsing ${value} in state ${state}`);
|
|
227
|
+
}
|
|
228
|
+
} else if (c === "'") {
|
|
229
|
+
if (state === 'quote') {
|
|
230
|
+
currentToken += c;
|
|
231
|
+
tokens.push(currentToken);
|
|
232
|
+
currentToken = '';
|
|
233
|
+
state = 'new';
|
|
234
|
+
} else if (state === 'labracket') {
|
|
235
|
+
currentToken += c;
|
|
236
|
+
state = 'labracketquote';
|
|
237
|
+
} else if (state === 'labracketquote') {
|
|
238
|
+
currentToken += c;
|
|
239
|
+
state = 'rabracket';
|
|
240
|
+
} else {
|
|
241
|
+
if (currentToken) tokens.push(currentToken);
|
|
242
|
+
currentToken = c;
|
|
243
|
+
state = 'quote';
|
|
244
|
+
}
|
|
245
|
+
} else if (c === "[" || c === "]" || c === "+" || c === "*" || c === "#" || c === "!" || c === '?' || c === '/') {
|
|
246
|
+
if (delimiterStates.includes(state)) {
|
|
247
|
+
if (currentToken) tokens.push(currentToken);
|
|
248
|
+
tokens.push(c);
|
|
249
|
+
currentToken='';
|
|
250
|
+
state = 'new';
|
|
251
|
+
} else if (state === 'quote') {
|
|
252
|
+
currentToken += c;
|
|
253
|
+
} else if (state === 'labracket' && c === '[') {
|
|
254
|
+
// bracketed range notation
|
|
255
|
+
state = 'bracketedrange';
|
|
256
|
+
currentToken += c;
|
|
257
|
+
} else if (state === 'bracketedrange' && c === ']') {
|
|
258
|
+
currentToken += c;
|
|
259
|
+
state = 'labracket';
|
|
260
|
+
} else {
|
|
261
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
262
|
+
}
|
|
263
|
+
} else if ( c === '{' ) {
|
|
264
|
+
if (state === 'quote') {
|
|
265
|
+
currentToken += c;
|
|
266
|
+
} else if (delimiterStates.includes(state)) {
|
|
267
|
+
if (currentToken) tokens.push(currentToken);
|
|
268
|
+
currentToken = c;
|
|
269
|
+
state = 'curlybracket';
|
|
270
|
+
} else {
|
|
271
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
272
|
+
}
|
|
273
|
+
} else if ( c === '}' ) {
|
|
274
|
+
if (state === 'quote') {
|
|
275
|
+
currentToken += c;
|
|
276
|
+
} else if (state === 'curlybracket') {
|
|
277
|
+
currentToken += c;
|
|
278
|
+
tokens.push(currentToken);
|
|
279
|
+
currentToken = '';
|
|
280
|
+
state = 'new';
|
|
281
|
+
} else {
|
|
282
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
283
|
+
}
|
|
284
|
+
} else if ( c === ',') {
|
|
285
|
+
if (delimiterStates.includes(state)) {
|
|
286
|
+
if (currentToken) tokens.push(currentToken);
|
|
287
|
+
tokens.push(c);
|
|
288
|
+
currentToken='';
|
|
289
|
+
state = 'new';
|
|
290
|
+
} else if (state === 'quote' || state === 'curlybracket' || state === 'bracketedrange') {
|
|
291
|
+
currentToken += c;
|
|
292
|
+
} else {
|
|
293
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
294
|
+
}
|
|
295
|
+
} else if ( c === '(') {
|
|
296
|
+
if (state === 'new' || state === 'pipe') {
|
|
297
|
+
if (currentToken) tokens.push(currentToken);
|
|
298
|
+
tokens.push(c);
|
|
299
|
+
currentToken='';
|
|
300
|
+
state = 'new';
|
|
301
|
+
} else if (state === 'quote' || state === 'labracket' || state === 'labracketquote') {
|
|
302
|
+
currentToken += c;
|
|
303
|
+
} else if (state === 'keyword') {
|
|
304
|
+
currentToken += c;
|
|
305
|
+
tokens.push(currentToken);
|
|
306
|
+
currentToken ='';
|
|
307
|
+
state = 'new';
|
|
308
|
+
} else {
|
|
309
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
310
|
+
}
|
|
311
|
+
} else if (c === ")") {
|
|
312
|
+
if (delimiterStates.includes(state)) {
|
|
313
|
+
if (currentToken) tokens.push(currentToken);
|
|
314
|
+
tokens.push(c);
|
|
315
|
+
currentToken='';
|
|
316
|
+
state = 'new';
|
|
317
|
+
} else if (state === 'quote' || state === 'labracket' || state === 'labracketquote') {
|
|
318
|
+
currentToken += c;
|
|
319
|
+
} else {
|
|
320
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
321
|
+
}
|
|
322
|
+
} else if ( c === '&') {
|
|
323
|
+
if (state === 'new' || state === 'keyword') { // 'pipe' can't appear just before ampersand
|
|
324
|
+
if (currentToken) tokens.push(currentToken);
|
|
325
|
+
currentToken=c;
|
|
326
|
+
state = 'ampersand';
|
|
327
|
+
} else if (state === 'quote') {
|
|
328
|
+
currentToken += c;
|
|
329
|
+
} else if (state === 'ampersand') {
|
|
330
|
+
currentToken += c;
|
|
331
|
+
tokens.push(currentToken);
|
|
332
|
+
currentToken='';
|
|
333
|
+
state = 'new';
|
|
334
|
+
} else {
|
|
335
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
336
|
+
}
|
|
337
|
+
} else if (c === '|') {
|
|
338
|
+
if (state === 'new' || state === 'keyword') {
|
|
339
|
+
if (currentToken) tokens.push(currentToken);
|
|
340
|
+
currentToken=c;
|
|
341
|
+
state = 'pipe';
|
|
342
|
+
} else if (state === 'quote') {
|
|
343
|
+
currentToken += c;
|
|
344
|
+
} else if (state === 'pipe') {
|
|
345
|
+
currentToken += c;
|
|
346
|
+
tokens.push(currentToken);
|
|
347
|
+
currentToken='';
|
|
348
|
+
state = 'new';
|
|
349
|
+
} else {
|
|
350
|
+
throw new Error(`Unexpected ${c} in ${currentToken} while parsing ${value} in state ${state}`);
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
if (state === 'pipe') {
|
|
354
|
+
tokens.push(currentToken);
|
|
355
|
+
currentToken = 'c';
|
|
356
|
+
state = 'keyword';
|
|
357
|
+
} else {
|
|
358
|
+
currentToken += c;
|
|
359
|
+
if (state === 'new') state = 'keyword';
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
i++;
|
|
363
|
+
}
|
|
364
|
+
if (state === 'new' || state === 'keyword') {
|
|
365
|
+
if (currentToken) tokens.push(currentToken);
|
|
366
|
+
} else {
|
|
367
|
+
throw new Error(`Unexpected EOF while parsing ${value} in state ${state}`);
|
|
368
|
+
}
|
|
369
|
+
return tokens;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const parsePropDefValue = (value) => {
|
|
373
|
+
value = value.trim();
|
|
374
|
+
const tokens = tokenize(value);
|
|
375
|
+
let parts = tokens.filter(x => x)
|
|
376
|
+
.map(parseTerminals);
|
|
377
|
+
|
|
378
|
+
// Applying multipliers on terminals
|
|
379
|
+
parts = parts.reduce((arr, item, i) => {
|
|
380
|
+
if (!isMultiplier(item)) {
|
|
381
|
+
arr.push(item);
|
|
382
|
+
return arr;
|
|
383
|
+
}
|
|
384
|
+
if (i === 0)
|
|
385
|
+
throw new Error(`Unexpected multiplier ${item} at the start of ${value}`);
|
|
386
|
+
const prevItem = arr.pop();
|
|
387
|
+
if (prevItem !== ']') {
|
|
388
|
+
arr.push(applyMultiplier(item, prevItem));
|
|
389
|
+
} else {
|
|
390
|
+
arr.push(prevItem);
|
|
391
|
+
arr.push(item);
|
|
392
|
+
}
|
|
393
|
+
return arr;
|
|
394
|
+
}, []);
|
|
395
|
+
|
|
396
|
+
// matching functional notations
|
|
397
|
+
while(parts.findIndex(p => p.type === 'functionstart') !== -1) {
|
|
398
|
+
const funcIdx = parts.findIndex(p => p.type === 'functionstart');
|
|
399
|
+
const matchingClosingFuncIdx = parts.findIndex((p, i) => p.content === ')' && i > funcIdx);
|
|
400
|
+
if (matchingClosingFuncIdx === -1) {
|
|
401
|
+
throw new Error(`Unterminated function notation in ${value}`);
|
|
402
|
+
}
|
|
403
|
+
const name = parts[funcIdx].name;
|
|
404
|
+
const func = { type: "function",
|
|
405
|
+
name: name.slice(0, name.length - 1),
|
|
406
|
+
arguments: parts.slice(funcIdx + 1, matchingClosingFuncIdx)
|
|
407
|
+
};
|
|
408
|
+
parts = parts.slice(0, funcIdx)
|
|
409
|
+
.concat([func])
|
|
410
|
+
.concat(parts.slice(matchingClosingFuncIdx + 1));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// matching potentially nested bracket-groups
|
|
414
|
+
while(parts.lastIndexOf('[') !== -1) {
|
|
415
|
+
const bracketIdx = parts.lastIndexOf('[');
|
|
416
|
+
|
|
417
|
+
// closing bracket may be more than just ']'
|
|
418
|
+
// since it can be accompanied with multipliers
|
|
419
|
+
const matchingBracketIdx = parts.findIndex((p, i) => p === ']' && i > bracketIdx);
|
|
420
|
+
|
|
421
|
+
if (matchingBracketIdx === -1) {
|
|
422
|
+
throw new Error(`Unterminated bracket-group in ${value}`);
|
|
423
|
+
}
|
|
424
|
+
let group = parts.slice(bracketIdx + 1, matchingBracketIdx);
|
|
425
|
+
let multiplier, i = 0, multiplied = false;
|
|
426
|
+
while ((multiplier = parts.slice(matchingBracketIdx + 1)[i]) && isMultiplier(multiplier)) {
|
|
427
|
+
group = applyMultiplier(multiplier, group);
|
|
428
|
+
multiplied = true;
|
|
429
|
+
i++;
|
|
430
|
+
}
|
|
431
|
+
const multipliedGroup = multiplied ? group : [ group ] ;
|
|
432
|
+
parts = parts.slice(0, bracketIdx)
|
|
433
|
+
.concat(multipliedGroup)
|
|
434
|
+
.concat(parts.slice(matchingBracketIdx + 1 + i));
|
|
435
|
+
}
|
|
436
|
+
const res = componentizeByCombinators(parts);
|
|
437
|
+
return res.length === 1 ? res[0] : res;
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
module.exports.parsePropDefValue = parsePropDefValue;
|