vscode-css-languageservice 6.0.1 → 6.1.0
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 +3 -1
- package/SECURITY.md +41 -0
- package/lib/esm/beautify/beautify-css.js +11 -4
- package/lib/esm/cssLanguageService.d.ts +38 -37
- package/lib/esm/cssLanguageService.js +73 -72
- package/lib/esm/cssLanguageTypes.d.ts +238 -238
- package/lib/esm/cssLanguageTypes.js +42 -42
- package/lib/esm/data/webCustomData.js +22089 -21959
- package/lib/esm/languageFacts/builtinData.js +142 -142
- package/lib/esm/languageFacts/colors.js +469 -469
- package/lib/esm/languageFacts/dataManager.js +88 -88
- package/lib/esm/languageFacts/dataProvider.js +73 -73
- package/lib/esm/languageFacts/entry.js +137 -137
- package/lib/esm/languageFacts/facts.js +8 -8
- package/lib/esm/parser/cssErrors.js +48 -48
- package/lib/esm/parser/cssNodes.js +1511 -1502
- package/lib/esm/parser/cssParser.js +1606 -1534
- package/lib/esm/parser/cssScanner.js +592 -592
- package/lib/esm/parser/cssSymbolScope.js +311 -311
- package/lib/esm/parser/lessParser.js +715 -714
- package/lib/esm/parser/lessScanner.js +57 -57
- package/lib/esm/parser/scssErrors.js +18 -18
- package/lib/esm/parser/scssParser.js +806 -796
- package/lib/esm/parser/scssScanner.js +95 -95
- package/lib/esm/services/cssCodeActions.js +77 -77
- package/lib/esm/services/cssCompletion.js +1054 -1054
- package/lib/esm/services/cssFolding.js +190 -190
- package/lib/esm/services/cssFormatter.js +136 -136
- package/lib/esm/services/cssHover.js +148 -148
- package/lib/esm/services/cssNavigation.js +441 -378
- package/lib/esm/services/cssSelectionRange.js +47 -47
- package/lib/esm/services/cssValidation.js +41 -41
- package/lib/esm/services/lessCompletion.js +378 -378
- package/lib/esm/services/lint.js +518 -518
- package/lib/esm/services/lintRules.js +76 -76
- package/lib/esm/services/lintUtil.js +196 -196
- package/lib/esm/services/pathCompletion.js +157 -157
- package/lib/esm/services/scssCompletion.js +354 -354
- package/lib/esm/services/scssNavigation.js +82 -82
- package/lib/esm/services/selectorPrinting.js +492 -492
- package/lib/esm/utils/arrays.js +40 -40
- package/lib/esm/utils/objects.js +11 -11
- package/lib/esm/utils/resources.js +11 -11
- package/lib/esm/utils/strings.js +102 -102
- package/lib/umd/beautify/beautify-css.js +11 -4
- package/lib/umd/cssLanguageService.d.ts +38 -37
- package/lib/umd/cssLanguageService.js +104 -99
- package/lib/umd/cssLanguageTypes.d.ts +238 -238
- package/lib/umd/cssLanguageTypes.js +89 -89
- package/lib/umd/data/webCustomData.js +22102 -21972
- package/lib/umd/languageFacts/builtinData.js +154 -154
- package/lib/umd/languageFacts/colors.js +492 -492
- package/lib/umd/languageFacts/dataManager.js +101 -101
- package/lib/umd/languageFacts/dataProvider.js +86 -86
- package/lib/umd/languageFacts/entry.js +152 -152
- package/lib/umd/languageFacts/facts.js +33 -29
- package/lib/umd/parser/cssErrors.js +61 -61
- package/lib/umd/parser/cssNodes.js +1597 -1587
- package/lib/umd/parser/cssParser.js +1619 -1547
- package/lib/umd/parser/cssScanner.js +606 -606
- package/lib/umd/parser/cssSymbolScope.js +328 -328
- package/lib/umd/parser/lessParser.js +728 -727
- package/lib/umd/parser/lessScanner.js +70 -70
- package/lib/umd/parser/scssErrors.js +31 -31
- package/lib/umd/parser/scssParser.js +819 -809
- package/lib/umd/parser/scssScanner.js +108 -108
- package/lib/umd/services/cssCodeActions.js +90 -90
- package/lib/umd/services/cssCompletion.js +1067 -1067
- package/lib/umd/services/cssFolding.js +203 -203
- package/lib/umd/services/cssFormatter.js +150 -150
- package/lib/umd/services/cssHover.js +161 -161
- package/lib/umd/services/cssNavigation.js +454 -391
- package/lib/umd/services/cssSelectionRange.js +60 -60
- package/lib/umd/services/cssValidation.js +54 -54
- package/lib/umd/services/lessCompletion.js +391 -391
- package/lib/umd/services/lint.js +531 -531
- package/lib/umd/services/lintRules.js +91 -91
- package/lib/umd/services/lintUtil.js +210 -210
- package/lib/umd/services/pathCompletion.js +171 -171
- package/lib/umd/services/scssCompletion.js +367 -367
- package/lib/umd/services/scssNavigation.js +95 -95
- package/lib/umd/services/selectorPrinting.js +510 -510
- package/lib/umd/utils/arrays.js +55 -55
- package/lib/umd/utils/objects.js +25 -25
- package/lib/umd/utils/resources.js +26 -26
- package/lib/umd/utils/strings.js +120 -120
- package/package.json +13 -12
|
@@ -1,1534 +1,1606 @@
|
|
|
1
|
-
/*---------------------------------------------------------------------------------------------
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
-
*--------------------------------------------------------------------------------------------*/
|
|
5
|
-
'use strict';
|
|
6
|
-
import { TokenType, Scanner } from './cssScanner';
|
|
7
|
-
import * as nodes from './cssNodes';
|
|
8
|
-
import { ParseError } from './cssErrors';
|
|
9
|
-
import * as languageFacts from '../languageFacts/facts';
|
|
10
|
-
import { isDefined } from '../utils/objects';
|
|
11
|
-
/// <summary>
|
|
12
|
-
/// A parser for the css core specification. See for reference:
|
|
13
|
-
/// https://www.w3.org/TR/CSS21/grammar.html
|
|
14
|
-
/// http://www.w3.org/TR/CSS21/syndata.html#tokenization
|
|
15
|
-
/// </summary>
|
|
16
|
-
export class Parser {
|
|
17
|
-
constructor(scnr = new Scanner()) {
|
|
18
|
-
this.keyframeRegex = /^@(\-(webkit|ms|moz|o)\-)?keyframes$/i;
|
|
19
|
-
this.scanner = scnr;
|
|
20
|
-
this.token = { type: TokenType.EOF, offset: -1, len: 0, text: '' };
|
|
21
|
-
this.prevToken = undefined;
|
|
22
|
-
}
|
|
23
|
-
peekIdent(text) {
|
|
24
|
-
return TokenType.Ident === this.token.type && text.length === this.token.text.length && text === this.token.text.toLowerCase();
|
|
25
|
-
}
|
|
26
|
-
peekKeyword(text) {
|
|
27
|
-
return TokenType.AtKeyword === this.token.type && text.length === this.token.text.length && text === this.token.text.toLowerCase();
|
|
28
|
-
}
|
|
29
|
-
peekDelim(text) {
|
|
30
|
-
return TokenType.Delim === this.token.type && text === this.token.text;
|
|
31
|
-
}
|
|
32
|
-
peek(type) {
|
|
33
|
-
return type === this.token.type;
|
|
34
|
-
}
|
|
35
|
-
peekOne(...types) {
|
|
36
|
-
return types.indexOf(this.token.type) !== -1;
|
|
37
|
-
}
|
|
38
|
-
peekRegExp(type, regEx) {
|
|
39
|
-
if (type !== this.token.type) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
return regEx.test(this.token.text);
|
|
43
|
-
}
|
|
44
|
-
hasWhitespace() {
|
|
45
|
-
return !!this.prevToken && (this.prevToken.offset + this.prevToken.len !== this.token.offset);
|
|
46
|
-
}
|
|
47
|
-
consumeToken() {
|
|
48
|
-
this.prevToken = this.token;
|
|
49
|
-
this.token = this.scanner.scan();
|
|
50
|
-
}
|
|
51
|
-
acceptUnicodeRange() {
|
|
52
|
-
const token = this.scanner.tryScanUnicode();
|
|
53
|
-
if (token) {
|
|
54
|
-
this.prevToken = token;
|
|
55
|
-
this.token = this.scanner.scan();
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
mark() {
|
|
61
|
-
return {
|
|
62
|
-
prev: this.prevToken,
|
|
63
|
-
curr: this.token,
|
|
64
|
-
pos: this.scanner.pos()
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
restoreAtMark(mark) {
|
|
68
|
-
this.prevToken = mark.prev;
|
|
69
|
-
this.token = mark.curr;
|
|
70
|
-
this.scanner.goBackTo(mark.pos);
|
|
71
|
-
}
|
|
72
|
-
try(func) {
|
|
73
|
-
const pos = this.mark();
|
|
74
|
-
const node = func();
|
|
75
|
-
if (!node) {
|
|
76
|
-
this.restoreAtMark(pos);
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
return node;
|
|
80
|
-
}
|
|
81
|
-
acceptOneKeyword(keywords) {
|
|
82
|
-
if (TokenType.AtKeyword === this.token.type) {
|
|
83
|
-
for (const keyword of keywords) {
|
|
84
|
-
if (keyword.length === this.token.text.length && keyword === this.token.text.toLowerCase()) {
|
|
85
|
-
this.consumeToken();
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
accept(type) {
|
|
93
|
-
if (type === this.token.type) {
|
|
94
|
-
this.consumeToken();
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
acceptIdent(text) {
|
|
100
|
-
if (this.peekIdent(text)) {
|
|
101
|
-
this.consumeToken();
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
acceptKeyword(text) {
|
|
107
|
-
if (this.peekKeyword(text)) {
|
|
108
|
-
this.consumeToken();
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
acceptDelim(text) {
|
|
114
|
-
if (this.peekDelim(text)) {
|
|
115
|
-
this.consumeToken();
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
acceptRegexp(regEx) {
|
|
121
|
-
if (regEx.test(this.token.text)) {
|
|
122
|
-
this.consumeToken();
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
_parseRegexp(regEx) {
|
|
128
|
-
let node = this.createNode(nodes.NodeType.Identifier);
|
|
129
|
-
do { } while (this.acceptRegexp(regEx));
|
|
130
|
-
return this.finish(node);
|
|
131
|
-
}
|
|
132
|
-
acceptUnquotedString() {
|
|
133
|
-
const pos = this.scanner.pos();
|
|
134
|
-
this.scanner.goBackTo(this.token.offset);
|
|
135
|
-
const unquoted = this.scanner.scanUnquotedString();
|
|
136
|
-
if (unquoted) {
|
|
137
|
-
this.token = unquoted;
|
|
138
|
-
this.consumeToken();
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
this.scanner.goBackTo(pos);
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
resync(resyncTokens, resyncStopTokens) {
|
|
145
|
-
while (true) {
|
|
146
|
-
if (resyncTokens && resyncTokens.indexOf(this.token.type) !== -1) {
|
|
147
|
-
this.consumeToken();
|
|
148
|
-
return true;
|
|
149
|
-
}
|
|
150
|
-
else if (resyncStopTokens && resyncStopTokens.indexOf(this.token.type) !== -1) {
|
|
151
|
-
return true;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
if (this.token.type === TokenType.EOF) {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
this.token = this.scanner.scan();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
createNode(nodeType) {
|
|
162
|
-
return new nodes.Node(this.token.offset, this.token.len, nodeType);
|
|
163
|
-
}
|
|
164
|
-
create(ctor) {
|
|
165
|
-
return new ctor(this.token.offset, this.token.len);
|
|
166
|
-
}
|
|
167
|
-
finish(node, error, resyncTokens, resyncStopTokens) {
|
|
168
|
-
// parseNumeric misuses error for boolean flagging (however the real error mustn't be a false)
|
|
169
|
-
// + nodelist offsets mustn't be modified, because there is a offset hack in rulesets for smartselection
|
|
170
|
-
if (!(node instanceof nodes.Nodelist)) {
|
|
171
|
-
if (error) {
|
|
172
|
-
this.markError(node, error, resyncTokens, resyncStopTokens);
|
|
173
|
-
}
|
|
174
|
-
// set the node end position
|
|
175
|
-
if (this.prevToken) {
|
|
176
|
-
// length with more elements belonging together
|
|
177
|
-
const prevEnd = this.prevToken.offset + this.prevToken.len;
|
|
178
|
-
node.length = prevEnd > node.offset ? prevEnd - node.offset : 0; // offset is taken from current token, end from previous: Use 0 for empty nodes
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return node;
|
|
182
|
-
}
|
|
183
|
-
markError(node, error, resyncTokens, resyncStopTokens) {
|
|
184
|
-
if (this.token !== this.lastErrorToken) { // do not report twice on the same token
|
|
185
|
-
node.addIssue(new nodes.Marker(node, error, nodes.Level.Error, undefined, this.token.offset, this.token.len));
|
|
186
|
-
this.lastErrorToken = this.token;
|
|
187
|
-
}
|
|
188
|
-
if (resyncTokens || resyncStopTokens) {
|
|
189
|
-
this.resync(resyncTokens, resyncStopTokens);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
parseStylesheet(textDocument) {
|
|
193
|
-
const versionId = textDocument.version;
|
|
194
|
-
const text = textDocument.getText();
|
|
195
|
-
const textProvider = (offset, length) => {
|
|
196
|
-
if (textDocument.version !== versionId) {
|
|
197
|
-
throw new Error('Underlying model has changed, AST is no longer valid');
|
|
198
|
-
}
|
|
199
|
-
return text.substr(offset, length);
|
|
200
|
-
};
|
|
201
|
-
return this.internalParse(text, this._parseStylesheet, textProvider);
|
|
202
|
-
}
|
|
203
|
-
internalParse(input, parseFunc, textProvider) {
|
|
204
|
-
this.scanner.setSource(input);
|
|
205
|
-
this.token = this.scanner.scan();
|
|
206
|
-
const node = parseFunc.bind(this)();
|
|
207
|
-
if (node) {
|
|
208
|
-
if (textProvider) {
|
|
209
|
-
node.textProvider = textProvider;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
node.textProvider = (offset, length) => { return input.substr(offset, length); };
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return node;
|
|
216
|
-
}
|
|
217
|
-
_parseStylesheet() {
|
|
218
|
-
const node = this.create(nodes.Stylesheet);
|
|
219
|
-
while (node.addChild(this._parseStylesheetStart())) {
|
|
220
|
-
// Parse statements only valid at the beginning of stylesheets.
|
|
221
|
-
}
|
|
222
|
-
let inRecovery = false;
|
|
223
|
-
do {
|
|
224
|
-
let hasMatch = false;
|
|
225
|
-
do {
|
|
226
|
-
hasMatch = false;
|
|
227
|
-
const statement = this._parseStylesheetStatement();
|
|
228
|
-
if (statement) {
|
|
229
|
-
node.addChild(statement);
|
|
230
|
-
hasMatch = true;
|
|
231
|
-
inRecovery = false;
|
|
232
|
-
if (!this.peek(TokenType.EOF) && this._needsSemicolonAfter(statement) && !this.accept(TokenType.SemiColon)) {
|
|
233
|
-
this.markError(node, ParseError.SemiColonExpected);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
while (this.accept(TokenType.SemiColon) || this.accept(TokenType.CDO) || this.accept(TokenType.CDC)) {
|
|
237
|
-
// accept empty statements
|
|
238
|
-
hasMatch = true;
|
|
239
|
-
inRecovery = false;
|
|
240
|
-
}
|
|
241
|
-
} while (hasMatch);
|
|
242
|
-
if (this.peek(TokenType.EOF)) {
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
if (!inRecovery) {
|
|
246
|
-
if (this.peek(TokenType.AtKeyword)) {
|
|
247
|
-
this.markError(node, ParseError.UnknownAtRule);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
this.markError(node, ParseError.RuleOrSelectorExpected);
|
|
251
|
-
}
|
|
252
|
-
inRecovery = true;
|
|
253
|
-
}
|
|
254
|
-
this.consumeToken();
|
|
255
|
-
} while (!this.peek(TokenType.EOF));
|
|
256
|
-
return this.finish(node);
|
|
257
|
-
}
|
|
258
|
-
_parseStylesheetStart() {
|
|
259
|
-
return this._parseCharset();
|
|
260
|
-
}
|
|
261
|
-
_parseStylesheetStatement(isNested = false) {
|
|
262
|
-
if (this.peek(TokenType.AtKeyword)) {
|
|
263
|
-
return this._parseStylesheetAtStatement(isNested);
|
|
264
|
-
}
|
|
265
|
-
return this._parseRuleset(isNested);
|
|
266
|
-
}
|
|
267
|
-
_parseStylesheetAtStatement(isNested = false) {
|
|
268
|
-
return this._parseImport()
|
|
269
|
-
|| this._parseMedia(isNested)
|
|
270
|
-
|| this._parsePage()
|
|
271
|
-
|| this._parseFontFace()
|
|
272
|
-
|| this._parseKeyframe()
|
|
273
|
-
|| this._parseSupports(isNested)
|
|
274
|
-
|| this.
|
|
275
|
-
|| this.
|
|
276
|
-
|| this.
|
|
277
|
-
|| this.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
case nodes.NodeType.
|
|
320
|
-
case nodes.NodeType.
|
|
321
|
-
case nodes.NodeType.
|
|
322
|
-
case nodes.NodeType.
|
|
323
|
-
case nodes.NodeType.
|
|
324
|
-
case nodes.NodeType.
|
|
325
|
-
case nodes.NodeType.
|
|
326
|
-
case nodes.NodeType.
|
|
327
|
-
case nodes.NodeType.
|
|
328
|
-
case nodes.NodeType.
|
|
329
|
-
case nodes.NodeType.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
case nodes.NodeType.
|
|
333
|
-
case nodes.NodeType.
|
|
334
|
-
case nodes.NodeType.
|
|
335
|
-
case nodes.NodeType.
|
|
336
|
-
case nodes.NodeType.
|
|
337
|
-
case nodes.NodeType.
|
|
338
|
-
case nodes.NodeType.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
node.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
node.addChild(this.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
let
|
|
486
|
-
let
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
if
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
//
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
this.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (!this.
|
|
649
|
-
return
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const node = this.create(nodes.
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
node.
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|| this.
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
//
|
|
790
|
-
//
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
this.
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
else {
|
|
830
|
-
this.
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
//
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
node.addChild(this.
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
return this.
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
this.
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
if (
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
this.
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
this.
|
|
1238
|
-
|
|
1239
|
-
node.
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
return this.
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
return this.finish(node);
|
|
1277
|
-
}
|
|
1278
|
-
return
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
this.
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
if (
|
|
1313
|
-
return this.finish(node);
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
if (!
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
return
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
if (
|
|
1366
|
-
return this.finish(node);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
return
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
this.
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
this.
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
return this.
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
this.
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
if (!
|
|
1478
|
-
return
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
return this.finish(node
|
|
1496
|
-
}
|
|
1497
|
-
return
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
if (!this.peek(TokenType.
|
|
1501
|
-
return null;
|
|
1502
|
-
}
|
|
1503
|
-
const node = this.
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
this.
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
'use strict';
|
|
6
|
+
import { TokenType, Scanner } from './cssScanner';
|
|
7
|
+
import * as nodes from './cssNodes';
|
|
8
|
+
import { ParseError } from './cssErrors';
|
|
9
|
+
import * as languageFacts from '../languageFacts/facts';
|
|
10
|
+
import { isDefined } from '../utils/objects';
|
|
11
|
+
/// <summary>
|
|
12
|
+
/// A parser for the css core specification. See for reference:
|
|
13
|
+
/// https://www.w3.org/TR/CSS21/grammar.html
|
|
14
|
+
/// http://www.w3.org/TR/CSS21/syndata.html#tokenization
|
|
15
|
+
/// </summary>
|
|
16
|
+
export class Parser {
|
|
17
|
+
constructor(scnr = new Scanner()) {
|
|
18
|
+
this.keyframeRegex = /^@(\-(webkit|ms|moz|o)\-)?keyframes$/i;
|
|
19
|
+
this.scanner = scnr;
|
|
20
|
+
this.token = { type: TokenType.EOF, offset: -1, len: 0, text: '' };
|
|
21
|
+
this.prevToken = undefined;
|
|
22
|
+
}
|
|
23
|
+
peekIdent(text) {
|
|
24
|
+
return TokenType.Ident === this.token.type && text.length === this.token.text.length && text === this.token.text.toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
peekKeyword(text) {
|
|
27
|
+
return TokenType.AtKeyword === this.token.type && text.length === this.token.text.length && text === this.token.text.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
peekDelim(text) {
|
|
30
|
+
return TokenType.Delim === this.token.type && text === this.token.text;
|
|
31
|
+
}
|
|
32
|
+
peek(type) {
|
|
33
|
+
return type === this.token.type;
|
|
34
|
+
}
|
|
35
|
+
peekOne(...types) {
|
|
36
|
+
return types.indexOf(this.token.type) !== -1;
|
|
37
|
+
}
|
|
38
|
+
peekRegExp(type, regEx) {
|
|
39
|
+
if (type !== this.token.type) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return regEx.test(this.token.text);
|
|
43
|
+
}
|
|
44
|
+
hasWhitespace() {
|
|
45
|
+
return !!this.prevToken && (this.prevToken.offset + this.prevToken.len !== this.token.offset);
|
|
46
|
+
}
|
|
47
|
+
consumeToken() {
|
|
48
|
+
this.prevToken = this.token;
|
|
49
|
+
this.token = this.scanner.scan();
|
|
50
|
+
}
|
|
51
|
+
acceptUnicodeRange() {
|
|
52
|
+
const token = this.scanner.tryScanUnicode();
|
|
53
|
+
if (token) {
|
|
54
|
+
this.prevToken = token;
|
|
55
|
+
this.token = this.scanner.scan();
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
mark() {
|
|
61
|
+
return {
|
|
62
|
+
prev: this.prevToken,
|
|
63
|
+
curr: this.token,
|
|
64
|
+
pos: this.scanner.pos()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
restoreAtMark(mark) {
|
|
68
|
+
this.prevToken = mark.prev;
|
|
69
|
+
this.token = mark.curr;
|
|
70
|
+
this.scanner.goBackTo(mark.pos);
|
|
71
|
+
}
|
|
72
|
+
try(func) {
|
|
73
|
+
const pos = this.mark();
|
|
74
|
+
const node = func();
|
|
75
|
+
if (!node) {
|
|
76
|
+
this.restoreAtMark(pos);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return node;
|
|
80
|
+
}
|
|
81
|
+
acceptOneKeyword(keywords) {
|
|
82
|
+
if (TokenType.AtKeyword === this.token.type) {
|
|
83
|
+
for (const keyword of keywords) {
|
|
84
|
+
if (keyword.length === this.token.text.length && keyword === this.token.text.toLowerCase()) {
|
|
85
|
+
this.consumeToken();
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
accept(type) {
|
|
93
|
+
if (type === this.token.type) {
|
|
94
|
+
this.consumeToken();
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
acceptIdent(text) {
|
|
100
|
+
if (this.peekIdent(text)) {
|
|
101
|
+
this.consumeToken();
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
acceptKeyword(text) {
|
|
107
|
+
if (this.peekKeyword(text)) {
|
|
108
|
+
this.consumeToken();
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
acceptDelim(text) {
|
|
114
|
+
if (this.peekDelim(text)) {
|
|
115
|
+
this.consumeToken();
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
acceptRegexp(regEx) {
|
|
121
|
+
if (regEx.test(this.token.text)) {
|
|
122
|
+
this.consumeToken();
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
_parseRegexp(regEx) {
|
|
128
|
+
let node = this.createNode(nodes.NodeType.Identifier);
|
|
129
|
+
do { } while (this.acceptRegexp(regEx));
|
|
130
|
+
return this.finish(node);
|
|
131
|
+
}
|
|
132
|
+
acceptUnquotedString() {
|
|
133
|
+
const pos = this.scanner.pos();
|
|
134
|
+
this.scanner.goBackTo(this.token.offset);
|
|
135
|
+
const unquoted = this.scanner.scanUnquotedString();
|
|
136
|
+
if (unquoted) {
|
|
137
|
+
this.token = unquoted;
|
|
138
|
+
this.consumeToken();
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
this.scanner.goBackTo(pos);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
resync(resyncTokens, resyncStopTokens) {
|
|
145
|
+
while (true) {
|
|
146
|
+
if (resyncTokens && resyncTokens.indexOf(this.token.type) !== -1) {
|
|
147
|
+
this.consumeToken();
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
else if (resyncStopTokens && resyncStopTokens.indexOf(this.token.type) !== -1) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
if (this.token.type === TokenType.EOF) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
this.token = this.scanner.scan();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
createNode(nodeType) {
|
|
162
|
+
return new nodes.Node(this.token.offset, this.token.len, nodeType);
|
|
163
|
+
}
|
|
164
|
+
create(ctor) {
|
|
165
|
+
return new ctor(this.token.offset, this.token.len);
|
|
166
|
+
}
|
|
167
|
+
finish(node, error, resyncTokens, resyncStopTokens) {
|
|
168
|
+
// parseNumeric misuses error for boolean flagging (however the real error mustn't be a false)
|
|
169
|
+
// + nodelist offsets mustn't be modified, because there is a offset hack in rulesets for smartselection
|
|
170
|
+
if (!(node instanceof nodes.Nodelist)) {
|
|
171
|
+
if (error) {
|
|
172
|
+
this.markError(node, error, resyncTokens, resyncStopTokens);
|
|
173
|
+
}
|
|
174
|
+
// set the node end position
|
|
175
|
+
if (this.prevToken) {
|
|
176
|
+
// length with more elements belonging together
|
|
177
|
+
const prevEnd = this.prevToken.offset + this.prevToken.len;
|
|
178
|
+
node.length = prevEnd > node.offset ? prevEnd - node.offset : 0; // offset is taken from current token, end from previous: Use 0 for empty nodes
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return node;
|
|
182
|
+
}
|
|
183
|
+
markError(node, error, resyncTokens, resyncStopTokens) {
|
|
184
|
+
if (this.token !== this.lastErrorToken) { // do not report twice on the same token
|
|
185
|
+
node.addIssue(new nodes.Marker(node, error, nodes.Level.Error, undefined, this.token.offset, this.token.len));
|
|
186
|
+
this.lastErrorToken = this.token;
|
|
187
|
+
}
|
|
188
|
+
if (resyncTokens || resyncStopTokens) {
|
|
189
|
+
this.resync(resyncTokens, resyncStopTokens);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
parseStylesheet(textDocument) {
|
|
193
|
+
const versionId = textDocument.version;
|
|
194
|
+
const text = textDocument.getText();
|
|
195
|
+
const textProvider = (offset, length) => {
|
|
196
|
+
if (textDocument.version !== versionId) {
|
|
197
|
+
throw new Error('Underlying model has changed, AST is no longer valid');
|
|
198
|
+
}
|
|
199
|
+
return text.substr(offset, length);
|
|
200
|
+
};
|
|
201
|
+
return this.internalParse(text, this._parseStylesheet, textProvider);
|
|
202
|
+
}
|
|
203
|
+
internalParse(input, parseFunc, textProvider) {
|
|
204
|
+
this.scanner.setSource(input);
|
|
205
|
+
this.token = this.scanner.scan();
|
|
206
|
+
const node = parseFunc.bind(this)();
|
|
207
|
+
if (node) {
|
|
208
|
+
if (textProvider) {
|
|
209
|
+
node.textProvider = textProvider;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
node.textProvider = (offset, length) => { return input.substr(offset, length); };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return node;
|
|
216
|
+
}
|
|
217
|
+
_parseStylesheet() {
|
|
218
|
+
const node = this.create(nodes.Stylesheet);
|
|
219
|
+
while (node.addChild(this._parseStylesheetStart())) {
|
|
220
|
+
// Parse statements only valid at the beginning of stylesheets.
|
|
221
|
+
}
|
|
222
|
+
let inRecovery = false;
|
|
223
|
+
do {
|
|
224
|
+
let hasMatch = false;
|
|
225
|
+
do {
|
|
226
|
+
hasMatch = false;
|
|
227
|
+
const statement = this._parseStylesheetStatement();
|
|
228
|
+
if (statement) {
|
|
229
|
+
node.addChild(statement);
|
|
230
|
+
hasMatch = true;
|
|
231
|
+
inRecovery = false;
|
|
232
|
+
if (!this.peek(TokenType.EOF) && this._needsSemicolonAfter(statement) && !this.accept(TokenType.SemiColon)) {
|
|
233
|
+
this.markError(node, ParseError.SemiColonExpected);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
while (this.accept(TokenType.SemiColon) || this.accept(TokenType.CDO) || this.accept(TokenType.CDC)) {
|
|
237
|
+
// accept empty statements
|
|
238
|
+
hasMatch = true;
|
|
239
|
+
inRecovery = false;
|
|
240
|
+
}
|
|
241
|
+
} while (hasMatch);
|
|
242
|
+
if (this.peek(TokenType.EOF)) {
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
if (!inRecovery) {
|
|
246
|
+
if (this.peek(TokenType.AtKeyword)) {
|
|
247
|
+
this.markError(node, ParseError.UnknownAtRule);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
this.markError(node, ParseError.RuleOrSelectorExpected);
|
|
251
|
+
}
|
|
252
|
+
inRecovery = true;
|
|
253
|
+
}
|
|
254
|
+
this.consumeToken();
|
|
255
|
+
} while (!this.peek(TokenType.EOF));
|
|
256
|
+
return this.finish(node);
|
|
257
|
+
}
|
|
258
|
+
_parseStylesheetStart() {
|
|
259
|
+
return this._parseCharset();
|
|
260
|
+
}
|
|
261
|
+
_parseStylesheetStatement(isNested = false) {
|
|
262
|
+
if (this.peek(TokenType.AtKeyword)) {
|
|
263
|
+
return this._parseStylesheetAtStatement(isNested);
|
|
264
|
+
}
|
|
265
|
+
return this._parseRuleset(isNested);
|
|
266
|
+
}
|
|
267
|
+
_parseStylesheetAtStatement(isNested = false) {
|
|
268
|
+
return this._parseImport()
|
|
269
|
+
|| this._parseMedia(isNested)
|
|
270
|
+
|| this._parsePage()
|
|
271
|
+
|| this._parseFontFace()
|
|
272
|
+
|| this._parseKeyframe()
|
|
273
|
+
|| this._parseSupports(isNested)
|
|
274
|
+
|| this._parseLayer()
|
|
275
|
+
|| this._parseViewPort()
|
|
276
|
+
|| this._parseNamespace()
|
|
277
|
+
|| this._parseDocument()
|
|
278
|
+
|| this._parseUnknownAtRule();
|
|
279
|
+
}
|
|
280
|
+
_tryParseRuleset(isNested) {
|
|
281
|
+
const mark = this.mark();
|
|
282
|
+
if (this._parseSelector(isNested)) {
|
|
283
|
+
while (this.accept(TokenType.Comma) && this._parseSelector(isNested)) {
|
|
284
|
+
// loop
|
|
285
|
+
}
|
|
286
|
+
if (this.accept(TokenType.CurlyL)) {
|
|
287
|
+
this.restoreAtMark(mark);
|
|
288
|
+
return this._parseRuleset(isNested);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
this.restoreAtMark(mark);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
_parseRuleset(isNested = false) {
|
|
295
|
+
const node = this.create(nodes.RuleSet);
|
|
296
|
+
const selectors = node.getSelectors();
|
|
297
|
+
if (!selectors.addChild(this._parseSelector(isNested))) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
while (this.accept(TokenType.Comma)) {
|
|
301
|
+
if (!selectors.addChild(this._parseSelector(isNested))) {
|
|
302
|
+
return this.finish(node, ParseError.SelectorExpected);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
|
306
|
+
}
|
|
307
|
+
_parseRuleSetDeclarationAtStatement() {
|
|
308
|
+
return this._parseUnknownAtRule();
|
|
309
|
+
}
|
|
310
|
+
_parseRuleSetDeclaration() {
|
|
311
|
+
// https://www.w3.org/TR/css-syntax-3/#consume-a-list-of-declarations
|
|
312
|
+
if (this.peek(TokenType.AtKeyword)) {
|
|
313
|
+
return this._parseRuleSetDeclarationAtStatement();
|
|
314
|
+
}
|
|
315
|
+
return this._parseDeclaration();
|
|
316
|
+
}
|
|
317
|
+
_needsSemicolonAfter(node) {
|
|
318
|
+
switch (node.type) {
|
|
319
|
+
case nodes.NodeType.Keyframe:
|
|
320
|
+
case nodes.NodeType.ViewPort:
|
|
321
|
+
case nodes.NodeType.Media:
|
|
322
|
+
case nodes.NodeType.Ruleset:
|
|
323
|
+
case nodes.NodeType.Namespace:
|
|
324
|
+
case nodes.NodeType.If:
|
|
325
|
+
case nodes.NodeType.For:
|
|
326
|
+
case nodes.NodeType.Each:
|
|
327
|
+
case nodes.NodeType.While:
|
|
328
|
+
case nodes.NodeType.MixinDeclaration:
|
|
329
|
+
case nodes.NodeType.FunctionDeclaration:
|
|
330
|
+
case nodes.NodeType.MixinContentDeclaration:
|
|
331
|
+
return false;
|
|
332
|
+
case nodes.NodeType.ExtendsReference:
|
|
333
|
+
case nodes.NodeType.MixinContentReference:
|
|
334
|
+
case nodes.NodeType.ReturnStatement:
|
|
335
|
+
case nodes.NodeType.MediaQuery:
|
|
336
|
+
case nodes.NodeType.Debug:
|
|
337
|
+
case nodes.NodeType.Import:
|
|
338
|
+
case nodes.NodeType.AtApplyRule:
|
|
339
|
+
case nodes.NodeType.CustomPropertyDeclaration:
|
|
340
|
+
return true;
|
|
341
|
+
case nodes.NodeType.VariableDeclaration:
|
|
342
|
+
return node.needsSemicolon;
|
|
343
|
+
case nodes.NodeType.MixinReference:
|
|
344
|
+
return !node.getContent();
|
|
345
|
+
case nodes.NodeType.Declaration:
|
|
346
|
+
return !node.getNestedProperties();
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
_parseDeclarations(parseDeclaration) {
|
|
351
|
+
const node = this.create(nodes.Declarations);
|
|
352
|
+
if (!this.accept(TokenType.CurlyL)) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
let decl = parseDeclaration();
|
|
356
|
+
while (node.addChild(decl)) {
|
|
357
|
+
if (this.peek(TokenType.CurlyR)) {
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) {
|
|
361
|
+
return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]);
|
|
362
|
+
}
|
|
363
|
+
// We accepted semicolon token. Link it to declaration.
|
|
364
|
+
if (decl && this.prevToken && this.prevToken.type === TokenType.SemiColon) {
|
|
365
|
+
decl.semicolonPosition = this.prevToken.offset;
|
|
366
|
+
}
|
|
367
|
+
while (this.accept(TokenType.SemiColon)) {
|
|
368
|
+
// accept empty statements
|
|
369
|
+
}
|
|
370
|
+
decl = parseDeclaration();
|
|
371
|
+
}
|
|
372
|
+
if (!this.accept(TokenType.CurlyR)) {
|
|
373
|
+
return this.finish(node, ParseError.RightCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]);
|
|
374
|
+
}
|
|
375
|
+
return this.finish(node);
|
|
376
|
+
}
|
|
377
|
+
_parseBody(node, parseDeclaration) {
|
|
378
|
+
if (!node.setDeclarations(this._parseDeclarations(parseDeclaration))) {
|
|
379
|
+
return this.finish(node, ParseError.LeftCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]);
|
|
380
|
+
}
|
|
381
|
+
return this.finish(node);
|
|
382
|
+
}
|
|
383
|
+
_parseSelector(isNested) {
|
|
384
|
+
const node = this.create(nodes.Selector);
|
|
385
|
+
let hasContent = false;
|
|
386
|
+
if (isNested) {
|
|
387
|
+
// nested selectors can start with a combinator
|
|
388
|
+
hasContent = node.addChild(this._parseCombinator());
|
|
389
|
+
}
|
|
390
|
+
while (node.addChild(this._parseSimpleSelector())) {
|
|
391
|
+
hasContent = true;
|
|
392
|
+
node.addChild(this._parseCombinator()); // optional
|
|
393
|
+
}
|
|
394
|
+
return hasContent ? this.finish(node) : null;
|
|
395
|
+
}
|
|
396
|
+
_parseDeclaration(stopTokens) {
|
|
397
|
+
const custonProperty = this._tryParseCustomPropertyDeclaration(stopTokens);
|
|
398
|
+
if (custonProperty) {
|
|
399
|
+
return custonProperty;
|
|
400
|
+
}
|
|
401
|
+
const node = this.create(nodes.Declaration);
|
|
402
|
+
if (!node.setProperty(this._parseProperty())) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
if (!this.accept(TokenType.Colon)) {
|
|
406
|
+
return this.finish(node, ParseError.ColonExpected, [TokenType.Colon], stopTokens || [TokenType.SemiColon]);
|
|
407
|
+
}
|
|
408
|
+
if (this.prevToken) {
|
|
409
|
+
node.colonPosition = this.prevToken.offset;
|
|
410
|
+
}
|
|
411
|
+
if (!node.setValue(this._parseExpr())) {
|
|
412
|
+
return this.finish(node, ParseError.PropertyValueExpected);
|
|
413
|
+
}
|
|
414
|
+
node.addChild(this._parsePrio());
|
|
415
|
+
if (this.peek(TokenType.SemiColon)) {
|
|
416
|
+
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
|
|
417
|
+
}
|
|
418
|
+
return this.finish(node);
|
|
419
|
+
}
|
|
420
|
+
_tryParseCustomPropertyDeclaration(stopTokens) {
|
|
421
|
+
if (!this.peekRegExp(TokenType.Ident, /^--/)) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
const node = this.create(nodes.CustomPropertyDeclaration);
|
|
425
|
+
if (!node.setProperty(this._parseProperty())) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
if (!this.accept(TokenType.Colon)) {
|
|
429
|
+
return this.finish(node, ParseError.ColonExpected, [TokenType.Colon]);
|
|
430
|
+
}
|
|
431
|
+
if (this.prevToken) {
|
|
432
|
+
node.colonPosition = this.prevToken.offset;
|
|
433
|
+
}
|
|
434
|
+
const mark = this.mark();
|
|
435
|
+
if (this.peek(TokenType.CurlyL)) {
|
|
436
|
+
// try to parse it as nested declaration
|
|
437
|
+
const propertySet = this.create(nodes.CustomPropertySet);
|
|
438
|
+
const declarations = this._parseDeclarations(this._parseRuleSetDeclaration.bind(this));
|
|
439
|
+
if (propertySet.setDeclarations(declarations) && !declarations.isErroneous(true)) {
|
|
440
|
+
propertySet.addChild(this._parsePrio());
|
|
441
|
+
if (this.peek(TokenType.SemiColon)) {
|
|
442
|
+
this.finish(propertySet);
|
|
443
|
+
node.setPropertySet(propertySet);
|
|
444
|
+
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
|
|
445
|
+
return this.finish(node);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
this.restoreAtMark(mark);
|
|
449
|
+
}
|
|
450
|
+
// try to parse as expression
|
|
451
|
+
const expression = this._parseExpr();
|
|
452
|
+
if (expression && !expression.isErroneous(true)) {
|
|
453
|
+
this._parsePrio();
|
|
454
|
+
if (this.peekOne(...(stopTokens || []), TokenType.SemiColon, TokenType.EOF)) {
|
|
455
|
+
node.setValue(expression);
|
|
456
|
+
if (this.peek(TokenType.SemiColon)) {
|
|
457
|
+
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
|
|
458
|
+
}
|
|
459
|
+
return this.finish(node);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
this.restoreAtMark(mark);
|
|
463
|
+
node.addChild(this._parseCustomPropertyValue(stopTokens));
|
|
464
|
+
node.addChild(this._parsePrio());
|
|
465
|
+
if (isDefined(node.colonPosition) && this.token.offset === node.colonPosition + 1) {
|
|
466
|
+
return this.finish(node, ParseError.PropertyValueExpected);
|
|
467
|
+
}
|
|
468
|
+
return this.finish(node);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Parse custom property values.
|
|
472
|
+
*
|
|
473
|
+
* Based on https://www.w3.org/TR/css-variables/#syntax
|
|
474
|
+
*
|
|
475
|
+
* This code is somewhat unusual, as the allowed syntax is incredibly broad,
|
|
476
|
+
* parsing almost any sequence of tokens, save for a small set of exceptions.
|
|
477
|
+
* Unbalanced delimitors, invalid tokens, and declaration
|
|
478
|
+
* terminators like semicolons and !important directives (when not inside
|
|
479
|
+
* of delimitors).
|
|
480
|
+
*/
|
|
481
|
+
_parseCustomPropertyValue(stopTokens = [TokenType.CurlyR]) {
|
|
482
|
+
const node = this.create(nodes.Node);
|
|
483
|
+
const isTopLevel = () => curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0;
|
|
484
|
+
const onStopToken = () => stopTokens.indexOf(this.token.type) !== -1;
|
|
485
|
+
let curlyDepth = 0;
|
|
486
|
+
let parensDepth = 0;
|
|
487
|
+
let bracketsDepth = 0;
|
|
488
|
+
done: while (true) {
|
|
489
|
+
switch (this.token.type) {
|
|
490
|
+
case TokenType.SemiColon:
|
|
491
|
+
// A semicolon only ends things if we're not inside a delimitor.
|
|
492
|
+
if (isTopLevel()) {
|
|
493
|
+
break done;
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
case TokenType.Exclamation:
|
|
497
|
+
// An exclamation ends the value if we're not inside delims.
|
|
498
|
+
if (isTopLevel()) {
|
|
499
|
+
break done;
|
|
500
|
+
}
|
|
501
|
+
break;
|
|
502
|
+
case TokenType.CurlyL:
|
|
503
|
+
curlyDepth++;
|
|
504
|
+
break;
|
|
505
|
+
case TokenType.CurlyR:
|
|
506
|
+
curlyDepth--;
|
|
507
|
+
if (curlyDepth < 0) {
|
|
508
|
+
// The property value has been terminated without a semicolon, and
|
|
509
|
+
// this is the last declaration in the ruleset.
|
|
510
|
+
if (onStopToken() && parensDepth === 0 && bracketsDepth === 0) {
|
|
511
|
+
break done;
|
|
512
|
+
}
|
|
513
|
+
return this.finish(node, ParseError.LeftCurlyExpected);
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
case TokenType.ParenthesisL:
|
|
517
|
+
parensDepth++;
|
|
518
|
+
break;
|
|
519
|
+
case TokenType.ParenthesisR:
|
|
520
|
+
parensDepth--;
|
|
521
|
+
if (parensDepth < 0) {
|
|
522
|
+
if (onStopToken() && bracketsDepth === 0 && curlyDepth === 0) {
|
|
523
|
+
break done;
|
|
524
|
+
}
|
|
525
|
+
return this.finish(node, ParseError.LeftParenthesisExpected);
|
|
526
|
+
}
|
|
527
|
+
break;
|
|
528
|
+
case TokenType.BracketL:
|
|
529
|
+
bracketsDepth++;
|
|
530
|
+
break;
|
|
531
|
+
case TokenType.BracketR:
|
|
532
|
+
bracketsDepth--;
|
|
533
|
+
if (bracketsDepth < 0) {
|
|
534
|
+
return this.finish(node, ParseError.LeftSquareBracketExpected);
|
|
535
|
+
}
|
|
536
|
+
break;
|
|
537
|
+
case TokenType.BadString: // fall through
|
|
538
|
+
break done;
|
|
539
|
+
case TokenType.EOF:
|
|
540
|
+
// We shouldn't have reached the end of input, something is
|
|
541
|
+
// unterminated.
|
|
542
|
+
let error = ParseError.RightCurlyExpected;
|
|
543
|
+
if (bracketsDepth > 0) {
|
|
544
|
+
error = ParseError.RightSquareBracketExpected;
|
|
545
|
+
}
|
|
546
|
+
else if (parensDepth > 0) {
|
|
547
|
+
error = ParseError.RightParenthesisExpected;
|
|
548
|
+
}
|
|
549
|
+
return this.finish(node, error);
|
|
550
|
+
}
|
|
551
|
+
this.consumeToken();
|
|
552
|
+
}
|
|
553
|
+
return this.finish(node);
|
|
554
|
+
}
|
|
555
|
+
_tryToParseDeclaration(stopTokens) {
|
|
556
|
+
const mark = this.mark();
|
|
557
|
+
if (this._parseProperty() && this.accept(TokenType.Colon)) {
|
|
558
|
+
// looks like a declaration, go ahead
|
|
559
|
+
this.restoreAtMark(mark);
|
|
560
|
+
return this._parseDeclaration(stopTokens);
|
|
561
|
+
}
|
|
562
|
+
this.restoreAtMark(mark);
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
_parseProperty() {
|
|
566
|
+
const node = this.create(nodes.Property);
|
|
567
|
+
const mark = this.mark();
|
|
568
|
+
if (this.acceptDelim('*') || this.acceptDelim('_')) {
|
|
569
|
+
// support for IE 5.x, 6 and 7 star hack: see http://en.wikipedia.org/wiki/CSS_filter#Star_hack
|
|
570
|
+
if (this.hasWhitespace()) {
|
|
571
|
+
this.restoreAtMark(mark);
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (node.setIdentifier(this._parsePropertyIdentifier())) {
|
|
576
|
+
return this.finish(node);
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
_parsePropertyIdentifier() {
|
|
581
|
+
return this._parseIdent();
|
|
582
|
+
}
|
|
583
|
+
_parseCharset() {
|
|
584
|
+
if (!this.peek(TokenType.Charset)) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const node = this.create(nodes.Node);
|
|
588
|
+
this.consumeToken(); // charset
|
|
589
|
+
if (!this.accept(TokenType.String)) {
|
|
590
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
591
|
+
}
|
|
592
|
+
if (!this.accept(TokenType.SemiColon)) {
|
|
593
|
+
return this.finish(node, ParseError.SemiColonExpected);
|
|
594
|
+
}
|
|
595
|
+
return this.finish(node);
|
|
596
|
+
}
|
|
597
|
+
_parseImport() {
|
|
598
|
+
// @import [ <url> | <string> ]
|
|
599
|
+
// [ layer | layer(<layer-name>) ]?
|
|
600
|
+
// <import-condition> ;
|
|
601
|
+
// <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]?
|
|
602
|
+
// <media-query-list>?
|
|
603
|
+
if (!this.peekKeyword('@import')) {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
const node = this.create(nodes.Import);
|
|
607
|
+
this.consumeToken(); // @import
|
|
608
|
+
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
|
|
609
|
+
return this.finish(node, ParseError.URIOrStringExpected);
|
|
610
|
+
}
|
|
611
|
+
if (this.acceptIdent('layer')) {
|
|
612
|
+
if (this.accept(TokenType.ParenthesisL)) {
|
|
613
|
+
if (!node.addChild(this._parseLayerName())) {
|
|
614
|
+
return this.finish(node, ParseError.IdentifierExpected, [TokenType.SemiColon]);
|
|
615
|
+
}
|
|
616
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
617
|
+
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (this.acceptIdent('supports')) {
|
|
622
|
+
if (this.accept(TokenType.ParenthesisL)) {
|
|
623
|
+
node.addChild(this._tryToParseDeclaration() || this._parseSupportsCondition());
|
|
624
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
625
|
+
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) {
|
|
630
|
+
node.setMedialist(this._parseMediaQueryList());
|
|
631
|
+
}
|
|
632
|
+
return this.finish(node);
|
|
633
|
+
}
|
|
634
|
+
_parseNamespace() {
|
|
635
|
+
// http://www.w3.org/TR/css3-namespace/
|
|
636
|
+
// namespace : NAMESPACE_SYM S* [IDENT S*]? [STRING|URI] S* ';' S*
|
|
637
|
+
if (!this.peekKeyword('@namespace')) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
const node = this.create(nodes.Namespace);
|
|
641
|
+
this.consumeToken(); // @namespace
|
|
642
|
+
if (!node.addChild(this._parseURILiteral())) { // url literal also starts with ident
|
|
643
|
+
node.addChild(this._parseIdent()); // optional prefix
|
|
644
|
+
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
|
|
645
|
+
return this.finish(node, ParseError.URIExpected, [TokenType.SemiColon]);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (!this.accept(TokenType.SemiColon)) {
|
|
649
|
+
return this.finish(node, ParseError.SemiColonExpected);
|
|
650
|
+
}
|
|
651
|
+
return this.finish(node);
|
|
652
|
+
}
|
|
653
|
+
_parseFontFace() {
|
|
654
|
+
if (!this.peekKeyword('@font-face')) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
const node = this.create(nodes.FontFace);
|
|
658
|
+
this.consumeToken(); // @font-face
|
|
659
|
+
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
|
660
|
+
}
|
|
661
|
+
_parseViewPort() {
|
|
662
|
+
if (!this.peekKeyword('@-ms-viewport') &&
|
|
663
|
+
!this.peekKeyword('@-o-viewport') &&
|
|
664
|
+
!this.peekKeyword('@viewport')) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
const node = this.create(nodes.ViewPort);
|
|
668
|
+
this.consumeToken(); // @-ms-viewport
|
|
669
|
+
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
|
670
|
+
}
|
|
671
|
+
_parseKeyframe() {
|
|
672
|
+
if (!this.peekRegExp(TokenType.AtKeyword, this.keyframeRegex)) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
const node = this.create(nodes.Keyframe);
|
|
676
|
+
const atNode = this.create(nodes.Node);
|
|
677
|
+
this.consumeToken(); // atkeyword
|
|
678
|
+
node.setKeyword(this.finish(atNode));
|
|
679
|
+
if (atNode.matches('@-ms-keyframes')) { // -ms-keyframes never existed
|
|
680
|
+
this.markError(atNode, ParseError.UnknownKeyword);
|
|
681
|
+
}
|
|
682
|
+
if (!node.setIdentifier(this._parseKeyframeIdent())) {
|
|
683
|
+
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
|
|
684
|
+
}
|
|
685
|
+
return this._parseBody(node, this._parseKeyframeSelector.bind(this));
|
|
686
|
+
}
|
|
687
|
+
_parseKeyframeIdent() {
|
|
688
|
+
return this._parseIdent([nodes.ReferenceType.Keyframe]);
|
|
689
|
+
}
|
|
690
|
+
_parseKeyframeSelector() {
|
|
691
|
+
const node = this.create(nodes.KeyframeSelector);
|
|
692
|
+
if (!node.addChild(this._parseIdent()) && !this.accept(TokenType.Percentage)) {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
while (this.accept(TokenType.Comma)) {
|
|
696
|
+
if (!node.addChild(this._parseIdent()) && !this.accept(TokenType.Percentage)) {
|
|
697
|
+
return this.finish(node, ParseError.PercentageExpected);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
|
701
|
+
}
|
|
702
|
+
_tryParseKeyframeSelector() {
|
|
703
|
+
const node = this.create(nodes.KeyframeSelector);
|
|
704
|
+
const pos = this.mark();
|
|
705
|
+
if (!node.addChild(this._parseIdent()) && !this.accept(TokenType.Percentage)) {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
while (this.accept(TokenType.Comma)) {
|
|
709
|
+
if (!node.addChild(this._parseIdent()) && !this.accept(TokenType.Percentage)) {
|
|
710
|
+
this.restoreAtMark(pos);
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (!this.peek(TokenType.CurlyL)) {
|
|
715
|
+
this.restoreAtMark(pos);
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
|
719
|
+
}
|
|
720
|
+
_parseLayer() {
|
|
721
|
+
// @layer layer-name {rules}
|
|
722
|
+
// @layer layer-name;
|
|
723
|
+
// @layer layer-name, layer-name, layer-name;
|
|
724
|
+
// @layer {rules}
|
|
725
|
+
if (!this.peekKeyword('@layer')) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
const node = this.create(nodes.Layer);
|
|
729
|
+
this.consumeToken(); // @layer
|
|
730
|
+
const names = this._parseLayerNameList();
|
|
731
|
+
if (names) {
|
|
732
|
+
node.setNames(names);
|
|
733
|
+
}
|
|
734
|
+
if ((!names || names.getChildren().length === 1) && this.peek(TokenType.CurlyL)) {
|
|
735
|
+
return this._parseBody(node, this._parseStylesheetStatement.bind(this));
|
|
736
|
+
}
|
|
737
|
+
if (!this.accept(TokenType.SemiColon)) {
|
|
738
|
+
return this.finish(node, ParseError.SemiColonExpected);
|
|
739
|
+
}
|
|
740
|
+
return this.finish(node);
|
|
741
|
+
}
|
|
742
|
+
_parseLayerNameList() {
|
|
743
|
+
const node = this.createNode(nodes.NodeType.LayerNameList);
|
|
744
|
+
if (!node.addChild(this._parseLayerName())) {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
while (this.accept(TokenType.Comma)) {
|
|
748
|
+
if (!node.addChild(this._parseLayerName())) {
|
|
749
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return this.finish(node);
|
|
753
|
+
}
|
|
754
|
+
_parseLayerName() {
|
|
755
|
+
// <layer-name> = <ident> [ '.' <ident> ]*
|
|
756
|
+
if (!this.peek(TokenType.Ident)) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
const node = this.createNode(nodes.NodeType.LayerName);
|
|
760
|
+
node.addChild(this._parseIdent());
|
|
761
|
+
while (!this.hasWhitespace() && this.acceptDelim('.')) {
|
|
762
|
+
if (this.hasWhitespace() || !node.addChild(this._parseIdent())) {
|
|
763
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return this.finish(node);
|
|
767
|
+
}
|
|
768
|
+
_parseSupports(isNested = false) {
|
|
769
|
+
// SUPPORTS_SYM S* supports_condition '{' S* ruleset* '}' S*
|
|
770
|
+
if (!this.peekKeyword('@supports')) {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
const node = this.create(nodes.Supports);
|
|
774
|
+
this.consumeToken(); // @supports
|
|
775
|
+
node.addChild(this._parseSupportsCondition());
|
|
776
|
+
return this._parseBody(node, this._parseSupportsDeclaration.bind(this, isNested));
|
|
777
|
+
}
|
|
778
|
+
_parseSupportsDeclaration(isNested = false) {
|
|
779
|
+
if (isNested) {
|
|
780
|
+
// if nested, the body can contain rulesets, but also declarations
|
|
781
|
+
return this._tryParseRuleset(true)
|
|
782
|
+
|| this._tryToParseDeclaration()
|
|
783
|
+
|| this._parseStylesheetStatement(true);
|
|
784
|
+
}
|
|
785
|
+
return this._parseStylesheetStatement(false);
|
|
786
|
+
}
|
|
787
|
+
_parseSupportsCondition() {
|
|
788
|
+
// supports_condition : supports_negation | supports_conjunction | supports_disjunction | supports_condition_in_parens ;
|
|
789
|
+
// supports_condition_in_parens: ( '(' S* supports_condition S* ')' ) | supports_declaration_condition | general_enclosed ;
|
|
790
|
+
// supports_negation: NOT S+ supports_condition_in_parens ;
|
|
791
|
+
// supports_conjunction: supports_condition_in_parens ( S+ AND S+ supports_condition_in_parens )+;
|
|
792
|
+
// supports_disjunction: supports_condition_in_parens ( S+ OR S+ supports_condition_in_parens )+;
|
|
793
|
+
// supports_declaration_condition: '(' S* declaration ')';
|
|
794
|
+
// general_enclosed: ( FUNCTION | '(' ) ( any | unused )* ')' ;
|
|
795
|
+
const node = this.create(nodes.SupportsCondition);
|
|
796
|
+
if (this.acceptIdent('not')) {
|
|
797
|
+
node.addChild(this._parseSupportsConditionInParens());
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
node.addChild(this._parseSupportsConditionInParens());
|
|
801
|
+
if (this.peekRegExp(TokenType.Ident, /^(and|or)$/i)) {
|
|
802
|
+
const text = this.token.text.toLowerCase();
|
|
803
|
+
while (this.acceptIdent(text)) {
|
|
804
|
+
node.addChild(this._parseSupportsConditionInParens());
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return this.finish(node);
|
|
809
|
+
}
|
|
810
|
+
_parseSupportsConditionInParens() {
|
|
811
|
+
const node = this.create(nodes.SupportsCondition);
|
|
812
|
+
if (this.accept(TokenType.ParenthesisL)) {
|
|
813
|
+
if (this.prevToken) {
|
|
814
|
+
node.lParent = this.prevToken.offset;
|
|
815
|
+
}
|
|
816
|
+
if (!node.addChild(this._tryToParseDeclaration([TokenType.ParenthesisR]))) {
|
|
817
|
+
if (!this._parseSupportsCondition()) {
|
|
818
|
+
return this.finish(node, ParseError.ConditionExpected);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
822
|
+
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []);
|
|
823
|
+
}
|
|
824
|
+
if (this.prevToken) {
|
|
825
|
+
node.rParent = this.prevToken.offset;
|
|
826
|
+
}
|
|
827
|
+
return this.finish(node);
|
|
828
|
+
}
|
|
829
|
+
else if (this.peek(TokenType.Ident)) {
|
|
830
|
+
const pos = this.mark();
|
|
831
|
+
this.consumeToken();
|
|
832
|
+
if (!this.hasWhitespace() && this.accept(TokenType.ParenthesisL)) {
|
|
833
|
+
let openParentCount = 1;
|
|
834
|
+
while (this.token.type !== TokenType.EOF && openParentCount !== 0) {
|
|
835
|
+
if (this.token.type === TokenType.ParenthesisL) {
|
|
836
|
+
openParentCount++;
|
|
837
|
+
}
|
|
838
|
+
else if (this.token.type === TokenType.ParenthesisR) {
|
|
839
|
+
openParentCount--;
|
|
840
|
+
}
|
|
841
|
+
this.consumeToken();
|
|
842
|
+
}
|
|
843
|
+
return this.finish(node);
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
this.restoreAtMark(pos);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.ParenthesisL]);
|
|
850
|
+
}
|
|
851
|
+
_parseMediaDeclaration(isNested = false) {
|
|
852
|
+
if (isNested) {
|
|
853
|
+
// if nested, the body can contain rulesets, but also declarations
|
|
854
|
+
return this._tryParseRuleset(true)
|
|
855
|
+
|| this._tryToParseDeclaration()
|
|
856
|
+
|| this._parseStylesheetStatement(true);
|
|
857
|
+
}
|
|
858
|
+
return this._parseStylesheetStatement(false);
|
|
859
|
+
}
|
|
860
|
+
_parseMedia(isNested = false) {
|
|
861
|
+
// MEDIA_SYM S* media_query_list '{' S* ruleset* '}' S*
|
|
862
|
+
// media_query_list : S* [media_query [ ',' S* media_query ]* ]?
|
|
863
|
+
if (!this.peekKeyword('@media')) {
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
const node = this.create(nodes.Media);
|
|
867
|
+
this.consumeToken(); // @media
|
|
868
|
+
if (!node.addChild(this._parseMediaQueryList())) {
|
|
869
|
+
return this.finish(node, ParseError.MediaQueryExpected);
|
|
870
|
+
}
|
|
871
|
+
return this._parseBody(node, this._parseMediaDeclaration.bind(this, isNested));
|
|
872
|
+
}
|
|
873
|
+
_parseMediaQueryList() {
|
|
874
|
+
const node = this.create(nodes.Medialist);
|
|
875
|
+
if (!node.addChild(this._parseMediaQuery())) {
|
|
876
|
+
return this.finish(node, ParseError.MediaQueryExpected);
|
|
877
|
+
}
|
|
878
|
+
while (this.accept(TokenType.Comma)) {
|
|
879
|
+
if (!node.addChild(this._parseMediaQuery())) {
|
|
880
|
+
return this.finish(node, ParseError.MediaQueryExpected);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return this.finish(node);
|
|
884
|
+
}
|
|
885
|
+
_parseMediaQuery() {
|
|
886
|
+
// <media-query> = <media-condition> | [ not | only ]? <media-type> [ and <media-condition-without-or> ]?
|
|
887
|
+
const node = this.create(nodes.MediaQuery);
|
|
888
|
+
const pos = this.mark();
|
|
889
|
+
this.acceptIdent('not');
|
|
890
|
+
if (!this.peek(TokenType.ParenthesisL)) {
|
|
891
|
+
if (this.acceptIdent('only')) {
|
|
892
|
+
// optional
|
|
893
|
+
}
|
|
894
|
+
if (!node.addChild(this._parseIdent())) {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
if (this.acceptIdent('and')) {
|
|
898
|
+
node.addChild(this._parseMediaCondition());
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
this.restoreAtMark(pos); // 'not' is part of the MediaCondition
|
|
903
|
+
node.addChild(this._parseMediaCondition());
|
|
904
|
+
}
|
|
905
|
+
return this.finish(node);
|
|
906
|
+
}
|
|
907
|
+
_parseRatio() {
|
|
908
|
+
const pos = this.mark();
|
|
909
|
+
const node = this.create(nodes.RatioValue);
|
|
910
|
+
if (!this._parseNumeric()) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
if (!this.acceptDelim('/')) {
|
|
914
|
+
this.restoreAtMark(pos);
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
if (!this._parseNumeric()) {
|
|
918
|
+
return this.finish(node, ParseError.NumberExpected);
|
|
919
|
+
}
|
|
920
|
+
return this.finish(node);
|
|
921
|
+
}
|
|
922
|
+
_parseMediaCondition() {
|
|
923
|
+
// <media-condition> = <media-not> | <media-and> | <media-or> | <media-in-parens>
|
|
924
|
+
// <media-not> = not <media-in-parens>
|
|
925
|
+
// <media-and> = <media-in-parens> [ and <media-in-parens> ]+
|
|
926
|
+
// <media-or> = <media-in-parens> [ or <media-in-parens> ]+
|
|
927
|
+
// <media-in-parens> = ( <media-condition> ) | <media-feature> | <general-enclosed>
|
|
928
|
+
const node = this.create(nodes.MediaCondition);
|
|
929
|
+
this.acceptIdent('not');
|
|
930
|
+
let parseExpression = true;
|
|
931
|
+
while (parseExpression) {
|
|
932
|
+
if (!this.accept(TokenType.ParenthesisL)) {
|
|
933
|
+
return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]);
|
|
934
|
+
}
|
|
935
|
+
if (this.peek(TokenType.ParenthesisL) || this.peekIdent('not')) {
|
|
936
|
+
// <media-condition>
|
|
937
|
+
node.addChild(this._parseMediaCondition());
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
node.addChild(this._parseMediaFeature());
|
|
941
|
+
}
|
|
942
|
+
// not yet implemented: general enclosed
|
|
943
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
944
|
+
return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]);
|
|
945
|
+
}
|
|
946
|
+
parseExpression = this.acceptIdent('and') || this.acceptIdent('or');
|
|
947
|
+
}
|
|
948
|
+
return this.finish(node);
|
|
949
|
+
}
|
|
950
|
+
_parseMediaFeature() {
|
|
951
|
+
const resyncStopToken = [TokenType.ParenthesisR];
|
|
952
|
+
const node = this.create(nodes.MediaFeature);
|
|
953
|
+
// <media-feature> = ( [ <mf-plain> | <mf-boolean> | <mf-range> ] )
|
|
954
|
+
// <mf-plain> = <mf-name> : <mf-value>
|
|
955
|
+
// <mf-boolean> = <mf-name>
|
|
956
|
+
// <mf-range> = <mf-name> [ '<' | '>' ]? '='? <mf-value> | <mf-value> [ '<' | '>' ]? '='? <mf-name> | <mf-value> '<' '='? <mf-name> '<' '='? <mf-value> | <mf-value> '>' '='? <mf-name> '>' '='? <mf-value>
|
|
957
|
+
const parseRangeOperator = () => {
|
|
958
|
+
if (this.acceptDelim('<') || this.acceptDelim('>')) {
|
|
959
|
+
if (!this.hasWhitespace()) {
|
|
960
|
+
this.acceptDelim('=');
|
|
961
|
+
}
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
else if (this.acceptDelim('=')) {
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
};
|
|
969
|
+
if (node.addChild(this._parseMediaFeatureName())) {
|
|
970
|
+
if (this.accept(TokenType.Colon)) {
|
|
971
|
+
if (!node.addChild(this._parseMediaFeatureValue())) {
|
|
972
|
+
return this.finish(node, ParseError.TermExpected, [], resyncStopToken);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
else if (parseRangeOperator()) {
|
|
976
|
+
if (!node.addChild(this._parseMediaFeatureValue())) {
|
|
977
|
+
return this.finish(node, ParseError.TermExpected, [], resyncStopToken);
|
|
978
|
+
}
|
|
979
|
+
if (parseRangeOperator()) {
|
|
980
|
+
if (!node.addChild(this._parseMediaFeatureValue())) {
|
|
981
|
+
return this.finish(node, ParseError.TermExpected, [], resyncStopToken);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
// <mf-boolean> = <mf-name>
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
else if (node.addChild(this._parseMediaFeatureValue())) {
|
|
990
|
+
if (!parseRangeOperator()) {
|
|
991
|
+
return this.finish(node, ParseError.OperatorExpected, [], resyncStopToken);
|
|
992
|
+
}
|
|
993
|
+
if (!node.addChild(this._parseMediaFeatureName())) {
|
|
994
|
+
return this.finish(node, ParseError.IdentifierExpected, [], resyncStopToken);
|
|
995
|
+
}
|
|
996
|
+
if (parseRangeOperator()) {
|
|
997
|
+
if (!node.addChild(this._parseMediaFeatureValue())) {
|
|
998
|
+
return this.finish(node, ParseError.TermExpected, [], resyncStopToken);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
return this.finish(node, ParseError.IdentifierExpected, [], resyncStopToken);
|
|
1004
|
+
}
|
|
1005
|
+
return this.finish(node);
|
|
1006
|
+
}
|
|
1007
|
+
_parseMediaFeatureName() {
|
|
1008
|
+
return this._parseIdent();
|
|
1009
|
+
}
|
|
1010
|
+
_parseMediaFeatureValue() {
|
|
1011
|
+
return this._parseRatio() || this._parseTermExpression();
|
|
1012
|
+
}
|
|
1013
|
+
_parseMedium() {
|
|
1014
|
+
const node = this.create(nodes.Node);
|
|
1015
|
+
if (node.addChild(this._parseIdent())) {
|
|
1016
|
+
return this.finish(node);
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
_parsePageDeclaration() {
|
|
1023
|
+
return this._parsePageMarginBox() || this._parseRuleSetDeclaration();
|
|
1024
|
+
}
|
|
1025
|
+
_parsePage() {
|
|
1026
|
+
// http://www.w3.org/TR/css3-page/
|
|
1027
|
+
// page_rule : PAGE_SYM S* page_selector_list '{' S* page_body '}' S*
|
|
1028
|
+
// page_body : /* Can be empty */ declaration? [ ';' S* page_body ]? | page_margin_box page_body
|
|
1029
|
+
if (!this.peekKeyword('@page')) {
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
const node = this.create(nodes.Page);
|
|
1033
|
+
this.consumeToken();
|
|
1034
|
+
if (node.addChild(this._parsePageSelector())) {
|
|
1035
|
+
while (this.accept(TokenType.Comma)) {
|
|
1036
|
+
if (!node.addChild(this._parsePageSelector())) {
|
|
1037
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return this._parseBody(node, this._parsePageDeclaration.bind(this));
|
|
1042
|
+
}
|
|
1043
|
+
_parsePageMarginBox() {
|
|
1044
|
+
// page_margin_box : margin_sym S* '{' S* declaration? [ ';' S* declaration? ]* '}' S*
|
|
1045
|
+
if (!this.peek(TokenType.AtKeyword)) {
|
|
1046
|
+
return null;
|
|
1047
|
+
}
|
|
1048
|
+
const node = this.create(nodes.PageBoxMarginBox);
|
|
1049
|
+
if (!this.acceptOneKeyword(languageFacts.pageBoxDirectives)) {
|
|
1050
|
+
this.markError(node, ParseError.UnknownAtRule, [], [TokenType.CurlyL]);
|
|
1051
|
+
}
|
|
1052
|
+
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
|
|
1053
|
+
}
|
|
1054
|
+
_parsePageSelector() {
|
|
1055
|
+
// page_selector : pseudo_page+ | IDENT pseudo_page*
|
|
1056
|
+
// pseudo_page : ':' [ "left" | "right" | "first" | "blank" ];
|
|
1057
|
+
if (!this.peek(TokenType.Ident) && !this.peek(TokenType.Colon)) {
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
const node = this.create(nodes.Node);
|
|
1061
|
+
node.addChild(this._parseIdent()); // optional ident
|
|
1062
|
+
if (this.accept(TokenType.Colon)) {
|
|
1063
|
+
if (!node.addChild(this._parseIdent())) { // optional ident
|
|
1064
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return this.finish(node);
|
|
1068
|
+
}
|
|
1069
|
+
_parseDocument() {
|
|
1070
|
+
// -moz-document is experimental but has been pushed to css4
|
|
1071
|
+
if (!this.peekKeyword('@-moz-document')) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
const node = this.create(nodes.Document);
|
|
1075
|
+
this.consumeToken(); // @-moz-document
|
|
1076
|
+
this.resync([], [TokenType.CurlyL]); // ignore all the rules
|
|
1077
|
+
return this._parseBody(node, this._parseStylesheetStatement.bind(this));
|
|
1078
|
+
}
|
|
1079
|
+
// https://www.w3.org/TR/css-syntax-3/#consume-an-at-rule
|
|
1080
|
+
_parseUnknownAtRule() {
|
|
1081
|
+
if (!this.peek(TokenType.AtKeyword)) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const node = this.create(nodes.UnknownAtRule);
|
|
1085
|
+
node.addChild(this._parseUnknownAtRuleName());
|
|
1086
|
+
const isTopLevel = () => curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0;
|
|
1087
|
+
let curlyLCount = 0;
|
|
1088
|
+
let curlyDepth = 0;
|
|
1089
|
+
let parensDepth = 0;
|
|
1090
|
+
let bracketsDepth = 0;
|
|
1091
|
+
done: while (true) {
|
|
1092
|
+
switch (this.token.type) {
|
|
1093
|
+
case TokenType.SemiColon:
|
|
1094
|
+
if (isTopLevel()) {
|
|
1095
|
+
break done;
|
|
1096
|
+
}
|
|
1097
|
+
break;
|
|
1098
|
+
case TokenType.EOF:
|
|
1099
|
+
if (curlyDepth > 0) {
|
|
1100
|
+
return this.finish(node, ParseError.RightCurlyExpected);
|
|
1101
|
+
}
|
|
1102
|
+
else if (bracketsDepth > 0) {
|
|
1103
|
+
return this.finish(node, ParseError.RightSquareBracketExpected);
|
|
1104
|
+
}
|
|
1105
|
+
else if (parensDepth > 0) {
|
|
1106
|
+
return this.finish(node, ParseError.RightParenthesisExpected);
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
return this.finish(node);
|
|
1110
|
+
}
|
|
1111
|
+
case TokenType.CurlyL:
|
|
1112
|
+
curlyLCount++;
|
|
1113
|
+
curlyDepth++;
|
|
1114
|
+
break;
|
|
1115
|
+
case TokenType.CurlyR:
|
|
1116
|
+
curlyDepth--;
|
|
1117
|
+
// End of at-rule, consume CurlyR and return node
|
|
1118
|
+
if (curlyLCount > 0 && curlyDepth === 0) {
|
|
1119
|
+
this.consumeToken();
|
|
1120
|
+
if (bracketsDepth > 0) {
|
|
1121
|
+
return this.finish(node, ParseError.RightSquareBracketExpected);
|
|
1122
|
+
}
|
|
1123
|
+
else if (parensDepth > 0) {
|
|
1124
|
+
return this.finish(node, ParseError.RightParenthesisExpected);
|
|
1125
|
+
}
|
|
1126
|
+
break done;
|
|
1127
|
+
}
|
|
1128
|
+
if (curlyDepth < 0) {
|
|
1129
|
+
// The property value has been terminated without a semicolon, and
|
|
1130
|
+
// this is the last declaration in the ruleset.
|
|
1131
|
+
if (parensDepth === 0 && bracketsDepth === 0) {
|
|
1132
|
+
break done;
|
|
1133
|
+
}
|
|
1134
|
+
return this.finish(node, ParseError.LeftCurlyExpected);
|
|
1135
|
+
}
|
|
1136
|
+
break;
|
|
1137
|
+
case TokenType.ParenthesisL:
|
|
1138
|
+
parensDepth++;
|
|
1139
|
+
break;
|
|
1140
|
+
case TokenType.ParenthesisR:
|
|
1141
|
+
parensDepth--;
|
|
1142
|
+
if (parensDepth < 0) {
|
|
1143
|
+
return this.finish(node, ParseError.LeftParenthesisExpected);
|
|
1144
|
+
}
|
|
1145
|
+
break;
|
|
1146
|
+
case TokenType.BracketL:
|
|
1147
|
+
bracketsDepth++;
|
|
1148
|
+
break;
|
|
1149
|
+
case TokenType.BracketR:
|
|
1150
|
+
bracketsDepth--;
|
|
1151
|
+
if (bracketsDepth < 0) {
|
|
1152
|
+
return this.finish(node, ParseError.LeftSquareBracketExpected);
|
|
1153
|
+
}
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
this.consumeToken();
|
|
1157
|
+
}
|
|
1158
|
+
return node;
|
|
1159
|
+
}
|
|
1160
|
+
_parseUnknownAtRuleName() {
|
|
1161
|
+
const node = this.create(nodes.Node);
|
|
1162
|
+
if (this.accept(TokenType.AtKeyword)) {
|
|
1163
|
+
return this.finish(node);
|
|
1164
|
+
}
|
|
1165
|
+
return node;
|
|
1166
|
+
}
|
|
1167
|
+
_parseOperator() {
|
|
1168
|
+
// these are operators for binary expressions
|
|
1169
|
+
if (this.peekDelim('/') ||
|
|
1170
|
+
this.peekDelim('*') ||
|
|
1171
|
+
this.peekDelim('+') ||
|
|
1172
|
+
this.peekDelim('-') ||
|
|
1173
|
+
this.peek(TokenType.Dashmatch) ||
|
|
1174
|
+
this.peek(TokenType.Includes) ||
|
|
1175
|
+
this.peek(TokenType.SubstringOperator) ||
|
|
1176
|
+
this.peek(TokenType.PrefixOperator) ||
|
|
1177
|
+
this.peek(TokenType.SuffixOperator) ||
|
|
1178
|
+
this.peekDelim('=')) { // doesn't stick to the standard here
|
|
1179
|
+
const node = this.createNode(nodes.NodeType.Operator);
|
|
1180
|
+
this.consumeToken();
|
|
1181
|
+
return this.finish(node);
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
_parseUnaryOperator() {
|
|
1188
|
+
if (!this.peekDelim('+') && !this.peekDelim('-')) {
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
const node = this.create(nodes.Node);
|
|
1192
|
+
this.consumeToken();
|
|
1193
|
+
return this.finish(node);
|
|
1194
|
+
}
|
|
1195
|
+
_parseCombinator() {
|
|
1196
|
+
if (this.peekDelim('>')) {
|
|
1197
|
+
const node = this.create(nodes.Node);
|
|
1198
|
+
this.consumeToken();
|
|
1199
|
+
const mark = this.mark();
|
|
1200
|
+
if (!this.hasWhitespace() && this.acceptDelim('>')) {
|
|
1201
|
+
if (!this.hasWhitespace() && this.acceptDelim('>')) {
|
|
1202
|
+
node.type = nodes.NodeType.SelectorCombinatorShadowPiercingDescendant;
|
|
1203
|
+
return this.finish(node);
|
|
1204
|
+
}
|
|
1205
|
+
this.restoreAtMark(mark);
|
|
1206
|
+
}
|
|
1207
|
+
node.type = nodes.NodeType.SelectorCombinatorParent;
|
|
1208
|
+
return this.finish(node);
|
|
1209
|
+
}
|
|
1210
|
+
else if (this.peekDelim('+')) {
|
|
1211
|
+
const node = this.create(nodes.Node);
|
|
1212
|
+
this.consumeToken();
|
|
1213
|
+
node.type = nodes.NodeType.SelectorCombinatorSibling;
|
|
1214
|
+
return this.finish(node);
|
|
1215
|
+
}
|
|
1216
|
+
else if (this.peekDelim('~')) {
|
|
1217
|
+
const node = this.create(nodes.Node);
|
|
1218
|
+
this.consumeToken();
|
|
1219
|
+
node.type = nodes.NodeType.SelectorCombinatorAllSiblings;
|
|
1220
|
+
return this.finish(node);
|
|
1221
|
+
}
|
|
1222
|
+
else if (this.peekDelim('/')) {
|
|
1223
|
+
const node = this.create(nodes.Node);
|
|
1224
|
+
this.consumeToken();
|
|
1225
|
+
const mark = this.mark();
|
|
1226
|
+
if (!this.hasWhitespace() && this.acceptIdent('deep') && !this.hasWhitespace() && this.acceptDelim('/')) {
|
|
1227
|
+
node.type = nodes.NodeType.SelectorCombinatorShadowPiercingDescendant;
|
|
1228
|
+
return this.finish(node);
|
|
1229
|
+
}
|
|
1230
|
+
this.restoreAtMark(mark);
|
|
1231
|
+
}
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
_parseSimpleSelector() {
|
|
1235
|
+
// simple_selector
|
|
1236
|
+
// : element_name [ HASH | class | attrib | pseudo ]* | [ HASH | class | attrib | pseudo ]+ ;
|
|
1237
|
+
const node = this.create(nodes.SimpleSelector);
|
|
1238
|
+
let c = 0;
|
|
1239
|
+
if (node.addChild(this._parseElementName())) {
|
|
1240
|
+
c++;
|
|
1241
|
+
}
|
|
1242
|
+
while ((c === 0 || !this.hasWhitespace()) && node.addChild(this._parseSimpleSelectorBody())) {
|
|
1243
|
+
c++;
|
|
1244
|
+
}
|
|
1245
|
+
return c > 0 ? this.finish(node) : null;
|
|
1246
|
+
}
|
|
1247
|
+
_parseSimpleSelectorBody() {
|
|
1248
|
+
return this._parsePseudo() || this._parseHash() || this._parseClass() || this._parseAttrib();
|
|
1249
|
+
}
|
|
1250
|
+
_parseSelectorIdent() {
|
|
1251
|
+
return this._parseIdent();
|
|
1252
|
+
}
|
|
1253
|
+
_parseHash() {
|
|
1254
|
+
if (!this.peek(TokenType.Hash) && !this.peekDelim('#')) {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
const node = this.createNode(nodes.NodeType.IdentifierSelector);
|
|
1258
|
+
if (this.acceptDelim('#')) {
|
|
1259
|
+
if (this.hasWhitespace() || !node.addChild(this._parseSelectorIdent())) {
|
|
1260
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
else {
|
|
1264
|
+
this.consumeToken(); // TokenType.Hash
|
|
1265
|
+
}
|
|
1266
|
+
return this.finish(node);
|
|
1267
|
+
}
|
|
1268
|
+
_parseClass() {
|
|
1269
|
+
// class: '.' IDENT ;
|
|
1270
|
+
if (!this.peekDelim('.')) {
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
const node = this.createNode(nodes.NodeType.ClassSelector);
|
|
1274
|
+
this.consumeToken(); // '.'
|
|
1275
|
+
if (this.hasWhitespace() || !node.addChild(this._parseSelectorIdent())) {
|
|
1276
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
1277
|
+
}
|
|
1278
|
+
return this.finish(node);
|
|
1279
|
+
}
|
|
1280
|
+
_parseElementName() {
|
|
1281
|
+
// element_name: (ns? '|')? IDENT | '*';
|
|
1282
|
+
const pos = this.mark();
|
|
1283
|
+
const node = this.createNode(nodes.NodeType.ElementNameSelector);
|
|
1284
|
+
node.addChild(this._parseNamespacePrefix());
|
|
1285
|
+
if (!node.addChild(this._parseSelectorIdent()) && !this.acceptDelim('*')) {
|
|
1286
|
+
this.restoreAtMark(pos);
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
return this.finish(node);
|
|
1290
|
+
}
|
|
1291
|
+
_parseNamespacePrefix() {
|
|
1292
|
+
const pos = this.mark();
|
|
1293
|
+
const node = this.createNode(nodes.NodeType.NamespacePrefix);
|
|
1294
|
+
if (!node.addChild(this._parseIdent()) && !this.acceptDelim('*')) {
|
|
1295
|
+
// ns is optional
|
|
1296
|
+
}
|
|
1297
|
+
if (!this.acceptDelim('|')) {
|
|
1298
|
+
this.restoreAtMark(pos);
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
return this.finish(node);
|
|
1302
|
+
}
|
|
1303
|
+
_parseAttrib() {
|
|
1304
|
+
// attrib : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* [ IDENT | STRING ] S* ]? ']'
|
|
1305
|
+
if (!this.peek(TokenType.BracketL)) {
|
|
1306
|
+
return null;
|
|
1307
|
+
}
|
|
1308
|
+
const node = this.create(nodes.AttributeSelector);
|
|
1309
|
+
this.consumeToken(); // BracketL
|
|
1310
|
+
// Optional attrib namespace
|
|
1311
|
+
node.setNamespacePrefix(this._parseNamespacePrefix());
|
|
1312
|
+
if (!node.setIdentifier(this._parseIdent())) {
|
|
1313
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
1314
|
+
}
|
|
1315
|
+
if (node.setOperator(this._parseOperator())) {
|
|
1316
|
+
node.setValue(this._parseBinaryExpr());
|
|
1317
|
+
this.acceptIdent('i'); // case insensitive matching
|
|
1318
|
+
this.acceptIdent('s'); // case sensitive matching
|
|
1319
|
+
}
|
|
1320
|
+
if (!this.accept(TokenType.BracketR)) {
|
|
1321
|
+
return this.finish(node, ParseError.RightSquareBracketExpected);
|
|
1322
|
+
}
|
|
1323
|
+
return this.finish(node);
|
|
1324
|
+
}
|
|
1325
|
+
_parsePseudo() {
|
|
1326
|
+
// pseudo: ':' [ IDENT | FUNCTION S* [IDENT S*]? ')' ]
|
|
1327
|
+
const node = this._tryParsePseudoIdentifier();
|
|
1328
|
+
if (node) {
|
|
1329
|
+
if (!this.hasWhitespace() && this.accept(TokenType.ParenthesisL)) {
|
|
1330
|
+
const tryAsSelector = () => {
|
|
1331
|
+
const selectors = this.create(nodes.Node);
|
|
1332
|
+
if (!selectors.addChild(this._parseSelector(true))) {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
while (this.accept(TokenType.Comma) && selectors.addChild(this._parseSelector(true))) {
|
|
1336
|
+
// loop
|
|
1337
|
+
}
|
|
1338
|
+
if (this.peek(TokenType.ParenthesisR)) {
|
|
1339
|
+
return this.finish(selectors);
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
};
|
|
1343
|
+
node.addChild(this.try(tryAsSelector) || this._parseBinaryExpr());
|
|
1344
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
1345
|
+
return this.finish(node, ParseError.RightParenthesisExpected);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return this.finish(node);
|
|
1349
|
+
}
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
_tryParsePseudoIdentifier() {
|
|
1353
|
+
if (!this.peek(TokenType.Colon)) {
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
const pos = this.mark();
|
|
1357
|
+
const node = this.createNode(nodes.NodeType.PseudoSelector);
|
|
1358
|
+
this.consumeToken(); // Colon
|
|
1359
|
+
if (this.hasWhitespace()) {
|
|
1360
|
+
this.restoreAtMark(pos);
|
|
1361
|
+
return null;
|
|
1362
|
+
}
|
|
1363
|
+
// optional, support ::
|
|
1364
|
+
this.accept(TokenType.Colon);
|
|
1365
|
+
if (this.hasWhitespace() || !node.addChild(this._parseIdent())) {
|
|
1366
|
+
return this.finish(node, ParseError.IdentifierExpected);
|
|
1367
|
+
}
|
|
1368
|
+
return this.finish(node);
|
|
1369
|
+
}
|
|
1370
|
+
_tryParsePrio() {
|
|
1371
|
+
const mark = this.mark();
|
|
1372
|
+
const prio = this._parsePrio();
|
|
1373
|
+
if (prio) {
|
|
1374
|
+
return prio;
|
|
1375
|
+
}
|
|
1376
|
+
this.restoreAtMark(mark);
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
_parsePrio() {
|
|
1380
|
+
if (!this.peek(TokenType.Exclamation)) {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
const node = this.createNode(nodes.NodeType.Prio);
|
|
1384
|
+
if (this.accept(TokenType.Exclamation) && this.acceptIdent('important')) {
|
|
1385
|
+
return this.finish(node);
|
|
1386
|
+
}
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
_parseExpr(stopOnComma = false) {
|
|
1390
|
+
const node = this.create(nodes.Expression);
|
|
1391
|
+
if (!node.addChild(this._parseBinaryExpr())) {
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
while (true) {
|
|
1395
|
+
if (this.peek(TokenType.Comma)) { // optional
|
|
1396
|
+
if (stopOnComma) {
|
|
1397
|
+
return this.finish(node);
|
|
1398
|
+
}
|
|
1399
|
+
this.consumeToken();
|
|
1400
|
+
}
|
|
1401
|
+
if (!node.addChild(this._parseBinaryExpr())) {
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return this.finish(node);
|
|
1406
|
+
}
|
|
1407
|
+
_parseUnicodeRange() {
|
|
1408
|
+
if (!this.peekIdent('u')) {
|
|
1409
|
+
return null;
|
|
1410
|
+
}
|
|
1411
|
+
const node = this.create(nodes.UnicodeRange);
|
|
1412
|
+
if (!this.acceptUnicodeRange()) {
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1415
|
+
return this.finish(node);
|
|
1416
|
+
}
|
|
1417
|
+
_parseNamedLine() {
|
|
1418
|
+
// https://www.w3.org/TR/css-grid-1/#named-lines
|
|
1419
|
+
if (!this.peek(TokenType.BracketL)) {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
const node = this.createNode(nodes.NodeType.GridLine);
|
|
1423
|
+
this.consumeToken();
|
|
1424
|
+
while (node.addChild(this._parseIdent())) {
|
|
1425
|
+
// repeat
|
|
1426
|
+
}
|
|
1427
|
+
if (!this.accept(TokenType.BracketR)) {
|
|
1428
|
+
return this.finish(node, ParseError.RightSquareBracketExpected);
|
|
1429
|
+
}
|
|
1430
|
+
return this.finish(node);
|
|
1431
|
+
}
|
|
1432
|
+
_parseBinaryExpr(preparsedLeft, preparsedOper) {
|
|
1433
|
+
let node = this.create(nodes.BinaryExpression);
|
|
1434
|
+
if (!node.setLeft((preparsedLeft || this._parseTerm()))) {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
if (!node.setOperator(preparsedOper || this._parseOperator())) {
|
|
1438
|
+
return this.finish(node);
|
|
1439
|
+
}
|
|
1440
|
+
if (!node.setRight(this._parseTerm())) {
|
|
1441
|
+
return this.finish(node, ParseError.TermExpected);
|
|
1442
|
+
}
|
|
1443
|
+
// things needed for multiple binary expressions
|
|
1444
|
+
node = this.finish(node);
|
|
1445
|
+
const operator = this._parseOperator();
|
|
1446
|
+
if (operator) {
|
|
1447
|
+
node = this._parseBinaryExpr(node, operator);
|
|
1448
|
+
}
|
|
1449
|
+
return this.finish(node);
|
|
1450
|
+
}
|
|
1451
|
+
_parseTerm() {
|
|
1452
|
+
let node = this.create(nodes.Term);
|
|
1453
|
+
node.setOperator(this._parseUnaryOperator()); // optional
|
|
1454
|
+
if (node.setExpression(this._parseTermExpression())) {
|
|
1455
|
+
return this.finish(node);
|
|
1456
|
+
}
|
|
1457
|
+
return null;
|
|
1458
|
+
}
|
|
1459
|
+
_parseTermExpression() {
|
|
1460
|
+
return this._parseURILiteral() || // url before function
|
|
1461
|
+
this._parseUnicodeRange() ||
|
|
1462
|
+
this._parseFunction() || // function before ident
|
|
1463
|
+
this._parseIdent() ||
|
|
1464
|
+
this._parseStringLiteral() ||
|
|
1465
|
+
this._parseNumeric() ||
|
|
1466
|
+
this._parseHexColor() ||
|
|
1467
|
+
this._parseOperation() ||
|
|
1468
|
+
this._parseNamedLine();
|
|
1469
|
+
}
|
|
1470
|
+
_parseOperation() {
|
|
1471
|
+
if (!this.peek(TokenType.ParenthesisL)) {
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
const node = this.create(nodes.Node);
|
|
1475
|
+
this.consumeToken(); // ParenthesisL
|
|
1476
|
+
node.addChild(this._parseExpr());
|
|
1477
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
1478
|
+
return this.finish(node, ParseError.RightParenthesisExpected);
|
|
1479
|
+
}
|
|
1480
|
+
return this.finish(node);
|
|
1481
|
+
}
|
|
1482
|
+
_parseNumeric() {
|
|
1483
|
+
if (this.peek(TokenType.Num) ||
|
|
1484
|
+
this.peek(TokenType.Percentage) ||
|
|
1485
|
+
this.peek(TokenType.Resolution) ||
|
|
1486
|
+
this.peek(TokenType.Length) ||
|
|
1487
|
+
this.peek(TokenType.EMS) ||
|
|
1488
|
+
this.peek(TokenType.EXS) ||
|
|
1489
|
+
this.peek(TokenType.Angle) ||
|
|
1490
|
+
this.peek(TokenType.Time) ||
|
|
1491
|
+
this.peek(TokenType.Dimension) ||
|
|
1492
|
+
this.peek(TokenType.Freq)) {
|
|
1493
|
+
const node = this.create(nodes.NumericValue);
|
|
1494
|
+
this.consumeToken();
|
|
1495
|
+
return this.finish(node);
|
|
1496
|
+
}
|
|
1497
|
+
return null;
|
|
1498
|
+
}
|
|
1499
|
+
_parseStringLiteral() {
|
|
1500
|
+
if (!this.peek(TokenType.String) && !this.peek(TokenType.BadString)) {
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
const node = this.createNode(nodes.NodeType.StringLiteral);
|
|
1504
|
+
this.consumeToken();
|
|
1505
|
+
return this.finish(node);
|
|
1506
|
+
}
|
|
1507
|
+
_parseURILiteral() {
|
|
1508
|
+
if (!this.peekRegExp(TokenType.Ident, /^url(-prefix)?$/i)) {
|
|
1509
|
+
return null;
|
|
1510
|
+
}
|
|
1511
|
+
const pos = this.mark();
|
|
1512
|
+
const node = this.createNode(nodes.NodeType.URILiteral);
|
|
1513
|
+
this.accept(TokenType.Ident);
|
|
1514
|
+
if (this.hasWhitespace() || !this.peek(TokenType.ParenthesisL)) {
|
|
1515
|
+
this.restoreAtMark(pos);
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1518
|
+
this.scanner.inURL = true;
|
|
1519
|
+
this.consumeToken(); // consume ()
|
|
1520
|
+
node.addChild(this._parseURLArgument()); // argument is optional
|
|
1521
|
+
this.scanner.inURL = false;
|
|
1522
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
1523
|
+
return this.finish(node, ParseError.RightParenthesisExpected);
|
|
1524
|
+
}
|
|
1525
|
+
return this.finish(node);
|
|
1526
|
+
}
|
|
1527
|
+
_parseURLArgument() {
|
|
1528
|
+
const node = this.create(nodes.Node);
|
|
1529
|
+
if (!this.accept(TokenType.String) && !this.accept(TokenType.BadString) && !this.acceptUnquotedString()) {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
return this.finish(node);
|
|
1533
|
+
}
|
|
1534
|
+
_parseIdent(referenceTypes) {
|
|
1535
|
+
if (!this.peek(TokenType.Ident)) {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
const node = this.create(nodes.Identifier);
|
|
1539
|
+
if (referenceTypes) {
|
|
1540
|
+
node.referenceTypes = referenceTypes;
|
|
1541
|
+
}
|
|
1542
|
+
node.isCustomProperty = this.peekRegExp(TokenType.Ident, /^--/);
|
|
1543
|
+
this.consumeToken();
|
|
1544
|
+
return this.finish(node);
|
|
1545
|
+
}
|
|
1546
|
+
_parseFunction() {
|
|
1547
|
+
const pos = this.mark();
|
|
1548
|
+
const node = this.create(nodes.Function);
|
|
1549
|
+
if (!node.setIdentifier(this._parseFunctionIdentifier())) {
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) {
|
|
1553
|
+
this.restoreAtMark(pos);
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
if (node.getArguments().addChild(this._parseFunctionArgument())) {
|
|
1557
|
+
while (this.accept(TokenType.Comma)) {
|
|
1558
|
+
if (this.peek(TokenType.ParenthesisR)) {
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
if (!node.getArguments().addChild(this._parseFunctionArgument())) {
|
|
1562
|
+
this.markError(node, ParseError.ExpressionExpected);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
if (!this.accept(TokenType.ParenthesisR)) {
|
|
1567
|
+
return this.finish(node, ParseError.RightParenthesisExpected);
|
|
1568
|
+
}
|
|
1569
|
+
return this.finish(node);
|
|
1570
|
+
}
|
|
1571
|
+
_parseFunctionIdentifier() {
|
|
1572
|
+
if (!this.peek(TokenType.Ident)) {
|
|
1573
|
+
return null;
|
|
1574
|
+
}
|
|
1575
|
+
const node = this.create(nodes.Identifier);
|
|
1576
|
+
node.referenceTypes = [nodes.ReferenceType.Function];
|
|
1577
|
+
if (this.acceptIdent('progid')) {
|
|
1578
|
+
// support for IE7 specific filters: 'progid:DXImageTransform.Microsoft.MotionBlur(strength=13, direction=310)'
|
|
1579
|
+
if (this.accept(TokenType.Colon)) {
|
|
1580
|
+
while (this.accept(TokenType.Ident) && this.acceptDelim('.')) {
|
|
1581
|
+
// loop
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return this.finish(node);
|
|
1585
|
+
}
|
|
1586
|
+
this.consumeToken();
|
|
1587
|
+
return this.finish(node);
|
|
1588
|
+
}
|
|
1589
|
+
_parseFunctionArgument() {
|
|
1590
|
+
const node = this.create(nodes.FunctionArgument);
|
|
1591
|
+
if (node.setValue(this._parseExpr(true))) {
|
|
1592
|
+
return this.finish(node);
|
|
1593
|
+
}
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
_parseHexColor() {
|
|
1597
|
+
if (this.peekRegExp(TokenType.Hash, /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/g)) {
|
|
1598
|
+
const node = this.create(nodes.HexColorValue);
|
|
1599
|
+
this.consumeToken();
|
|
1600
|
+
return this.finish(node);
|
|
1601
|
+
}
|
|
1602
|
+
else {
|
|
1603
|
+
return null;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|