source: trip-planner-front/node_modules/postcss-values-parser/lib/parser.js

Last change on this file was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 13.4 KB
Line 
1'use strict';
2
3const Root = require('./root');
4const Value = require('./value');
5
6const AtWord = require('./atword');
7const Colon = require('./colon');
8const Comma = require('./comma');
9const Comment = require('./comment');
10const Func = require('./function');
11const Numbr = require('./number');
12const Operator = require('./operator');
13const Paren = require('./paren');
14const Str = require('./string');
15const Word = require('./word');
16const UnicodeRange = require('./unicode-range');
17
18const tokenize = require('./tokenize');
19
20const flatten = require('flatten');
21const indexesOf = require('indexes-of');
22const uniq = require('uniq');
23const ParserError = require('./errors/ParserError');
24
25function sortAscending (list) {
26 return list.sort((a, b) => a - b);
27}
28
29module.exports = class Parser {
30 constructor (input, options) {
31 const defaults = { loose: false };
32
33 // cache needs to be an array for values with more than 1 level of function nesting
34 this.cache = [];
35 this.input = input;
36 this.options = Object.assign({}, defaults, options);
37 this.position = 0;
38 // we'll use this to keep track of the paren balance
39 this.unbalanced = 0;
40 this.root = new Root();
41
42 let value = new Value();
43
44 this.root.append(value);
45
46 this.current = value;
47 this.tokens = tokenize(input, this.options);
48 }
49
50 parse () {
51 return this.loop();
52 }
53
54 colon () {
55 let token = this.currToken;
56
57 this.newNode(new Colon({
58 value: token[1],
59 source: {
60 start: {
61 line: token[2],
62 column: token[3]
63 },
64 end: {
65 line: token[4],
66 column: token[5]
67 }
68 },
69 sourceIndex: token[6]
70 }));
71
72 this.position ++;
73 }
74
75 comma () {
76 let token = this.currToken;
77
78 this.newNode(new Comma({
79 value: token[1],
80 source: {
81 start: {
82 line: token[2],
83 column: token[3]
84 },
85 end: {
86 line: token[4],
87 column: token[5]
88 }
89 },
90 sourceIndex: token[6]
91 }));
92
93 this.position ++;
94 }
95
96 comment () {
97 let inline = false,
98 value = this.currToken[1].replace(/\/\*|\*\//g, ''),
99 node;
100
101 if (this.options.loose && value.startsWith("//")) {
102 value = value.substring(2);
103 inline = true;
104 }
105
106 node = new Comment({
107 value: value,
108 inline: inline,
109 source: {
110 start: {
111 line: this.currToken[2],
112 column: this.currToken[3]
113 },
114 end: {
115 line: this.currToken[4],
116 column: this.currToken[5]
117 }
118 },
119 sourceIndex: this.currToken[6]
120 });
121
122 this.newNode(node);
123 this.position++;
124 }
125
126 error (message, token) {
127 throw new ParserError(message + ` at line: ${token[2]}, column ${token[3]}`);
128 }
129
130 loop () {
131 while (this.position < this.tokens.length) {
132 this.parseTokens();
133 }
134
135 if (!this.current.last && this.spaces) {
136 this.current.raws.before += this.spaces;
137 }
138 else if (this.spaces) {
139 this.current.last.raws.after += this.spaces;
140 }
141
142 this.spaces = '';
143
144 return this.root;
145 }
146
147 operator () {
148
149 // if a +|- operator is followed by a non-word character (. is allowed) and
150 // is preceded by a non-word character. (5+5)
151 let char = this.currToken[1],
152 node;
153
154 if (char === '+' || char === '-') {
155 // only inspect if the operator is not the first token, and we're only
156 // within a calc() function: the only spec-valid place for math expressions
157 if (!this.options.loose) {
158 if (this.position > 0) {
159 if (this.current.type === 'func' && this.current.value === 'calc') {
160 // allow operators to be proceeded by spaces and opening parens
161 if (this.prevToken[0] !== 'space' && this.prevToken[0] !== '(') {
162 this.error('Syntax Error', this.currToken);
163 }
164 // valid: calc(1 - +2)
165 // invalid: calc(1 -+2)
166 else if (this.nextToken[0] !== 'space' && this.nextToken[0] !== 'word') {
167 this.error('Syntax Error', this.currToken);
168 }
169 // valid: calc(1 - +2)
170 // valid: calc(-0.5 + 2)
171 // invalid: calc(1 -2)
172 else if (this.nextToken[0] === 'word' && this.current.last.type !== 'operator' &&
173 this.current.last.value !== '(') {
174 this.error('Syntax Error', this.currToken);
175 }
176 }
177 // if we're not in a function and someone has doubled up on operators,
178 // or they're trying to perform a calc outside of a calc
179 // eg. +-4px or 5+ 5, throw an error
180 else if (this.nextToken[0] === 'space'
181 || this.nextToken[0] === 'operator'
182 || this.prevToken[0] === 'operator') {
183 this.error('Syntax Error', this.currToken);
184 }
185 }
186 }
187
188 if (!this.options.loose) {
189 if (this.nextToken[0] === 'word') {
190 return this.word();
191 }
192 }
193 else {
194 if ((!this.current.nodes.length || (this.current.last && this.current.last.type === 'operator')) && this.nextToken[0] === 'word') {
195 return this.word();
196 }
197 }
198 }
199
200 node = new Operator({
201 value: this.currToken[1],
202 source: {
203 start: {
204 line: this.currToken[2],
205 column: this.currToken[3]
206 },
207 end: {
208 line: this.currToken[2],
209 column: this.currToken[3]
210 }
211 },
212 sourceIndex: this.currToken[4]
213 });
214
215 this.position ++;
216
217 return this.newNode(node);
218 }
219
220 parseTokens () {
221 switch (this.currToken[0]) {
222 case 'space':
223 this.space();
224 break;
225 case 'colon':
226 this.colon();
227 break;
228 case 'comma':
229 this.comma();
230 break;
231 case 'comment':
232 this.comment();
233 break;
234 case '(':
235 this.parenOpen();
236 break;
237 case ')':
238 this.parenClose();
239 break;
240 case 'atword':
241 case 'word':
242 this.word();
243 break;
244 case 'operator':
245 this.operator();
246 break;
247 case 'string':
248 this.string();
249 break;
250 case 'unicoderange':
251 this.unicodeRange();
252 break;
253 default:
254 this.word();
255 break;
256 }
257 }
258
259 parenOpen () {
260 let unbalanced = 1,
261 pos = this.position + 1,
262 token = this.currToken,
263 last;
264
265 // check for balanced parens
266 while (pos < this.tokens.length && unbalanced) {
267 let tkn = this.tokens[pos];
268
269 if (tkn[0] === '(') {
270 unbalanced++;
271 }
272 if (tkn[0] === ')') {
273 unbalanced--;
274 }
275 pos ++;
276 }
277
278 if (unbalanced) {
279 this.error('Expected closing parenthesis', token);
280 }
281
282 // ok, all parens are balanced. continue on
283
284 last = this.current.last;
285
286 if (last && last.type === 'func' && last.unbalanced < 0) {
287 last.unbalanced = 0; // ok we're ready to add parens now
288 this.current = last;
289 }
290
291 this.current.unbalanced ++;
292
293 this.newNode(new Paren({
294 value: token[1],
295 source: {
296 start: {
297 line: token[2],
298 column: token[3]
299 },
300 end: {
301 line: token[4],
302 column: token[5]
303 }
304 },
305 sourceIndex: token[6]
306 }));
307
308 this.position ++;
309
310 // url functions get special treatment, and anything between the function
311 // parens get treated as one word, if the contents aren't not a string.
312 if (this.current.type === 'func' && this.current.unbalanced &&
313 this.current.value === 'url' && this.currToken[0] !== 'string' &&
314 this.currToken[0] !== ')' && !this.options.loose) {
315
316 let nextToken = this.nextToken,
317 value = this.currToken[1],
318 start = {
319 line: this.currToken[2],
320 column: this.currToken[3]
321 };
322
323 while (nextToken && nextToken[0] !== ')' && this.current.unbalanced) {
324 this.position ++;
325 value += this.currToken[1];
326 nextToken = this.nextToken;
327 }
328
329 if (this.position !== this.tokens.length - 1) {
330 // skip the following word definition, or it'll be a duplicate
331 this.position ++;
332
333 this.newNode(new Word({
334 value,
335 source: {
336 start,
337 end: {
338 line: this.currToken[4],
339 column: this.currToken[5]
340 }
341 },
342 sourceIndex: this.currToken[6]
343 }));
344 }
345 }
346 }
347
348 parenClose () {
349 let token = this.currToken;
350
351 this.newNode(new Paren({
352 value: token[1],
353 source: {
354 start: {
355 line: token[2],
356 column: token[3]
357 },
358 end: {
359 line: token[4],
360 column: token[5]
361 }
362 },
363 sourceIndex: token[6]
364 }));
365
366 this.position ++;
367
368 if (this.position >= this.tokens.length - 1 && !this.current.unbalanced) {
369 return;
370 }
371
372 this.current.unbalanced --;
373
374 if (this.current.unbalanced < 0) {
375 this.error('Expected opening parenthesis', token);
376 }
377
378 if (!this.current.unbalanced && this.cache.length) {
379 this.current = this.cache.pop();
380 }
381 }
382
383 space () {
384 let token = this.currToken;
385 // Handle space before and after the selector
386 if (this.position === (this.tokens.length - 1) || this.nextToken[0] === ',' || this.nextToken[0] === ')') {
387 this.current.last.raws.after += token[1];
388 this.position ++;
389 }
390 else {
391 this.spaces = token[1];
392 this.position ++;
393 }
394 }
395
396 unicodeRange () {
397 let token = this.currToken;
398
399 this.newNode(new UnicodeRange({
400 value: token[1],
401 source: {
402 start: {
403 line: token[2],
404 column: token[3]
405 },
406 end: {
407 line: token[4],
408 column: token[5]
409 }
410 },
411 sourceIndex: token[6]
412 }));
413
414 this.position ++;
415 }
416
417 splitWord () {
418 let nextToken = this.nextToken,
419 word = this.currToken[1],
420 rNumber = /^[\+\-]?((\d+(\.\d*)?)|(\.\d+))([eE][\+\-]?\d+)?/,
421
422 // treat css-like groupings differently so they can be inspected,
423 // but don't address them as anything but a word, but allow hex values
424 // to pass through.
425 rNoFollow = /^(?!\#([a-z0-9]+))[\#\{\}]/gi,
426
427 hasAt, indices;
428
429 if (!rNoFollow.test(word)) {
430 while (nextToken && nextToken[0] === 'word') {
431 this.position ++;
432
433 let current = this.currToken[1];
434 word += current;
435
436 nextToken = this.nextToken;
437 }
438 }
439
440 hasAt = indexesOf(word, '@');
441 indices = sortAscending(uniq(flatten([[0], hasAt])));
442
443 indices.forEach((ind, i) => {
444 let index = indices[i + 1] || word.length,
445 value = word.slice(ind, index),
446 node;
447
448 if (~hasAt.indexOf(ind)) {
449 node = new AtWord({
450 value: value.slice(1),
451 source: {
452 start: {
453 line: this.currToken[2],
454 column: this.currToken[3] + ind
455 },
456 end: {
457 line: this.currToken[4],
458 column: this.currToken[3] + (index - 1)
459 }
460 },
461 sourceIndex: this.currToken[6] + indices[i]
462 });
463 }
464 else if (rNumber.test(this.currToken[1])) {
465 let unit = value.replace(rNumber, '');
466
467 node = new Numbr({
468 value: value.replace(unit, ''),
469 source: {
470 start: {
471 line: this.currToken[2],
472 column: this.currToken[3] + ind
473 },
474 end: {
475 line: this.currToken[4],
476 column: this.currToken[3] + (index - 1)
477 }
478 },
479 sourceIndex: this.currToken[6] + indices[i],
480 unit
481 });
482 }
483 else {
484 node = new (nextToken && nextToken[0] === '(' ? Func : Word)({
485 value,
486 source: {
487 start: {
488 line: this.currToken[2],
489 column: this.currToken[3] + ind
490 },
491 end: {
492 line: this.currToken[4],
493 column: this.currToken[3] + (index - 1)
494 }
495 },
496 sourceIndex: this.currToken[6] + indices[i]
497 });
498
499 if (node.constructor.name === 'Word') {
500 node.isHex = /^#(.+)/.test(value);
501 node.isColor = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value);
502 }
503 else {
504 this.cache.push(this.current);
505 }
506 }
507
508 this.newNode(node);
509
510 });
511
512 this.position ++;
513 }
514
515 string () {
516 let token = this.currToken,
517 value = this.currToken[1],
518 rQuote = /^(\"|\')/,
519 quoted = rQuote.test(value),
520 quote = '',
521 node;
522
523 if (quoted) {
524 quote = value.match(rQuote)[0];
525 // set value to the string within the quotes
526 // quotes are stored in raws
527 value = value.slice(1, value.length - 1);
528 }
529
530 node = new Str({
531 value,
532 source: {
533 start: {
534 line: token[2],
535 column: token[3]
536 },
537 end: {
538 line: token[4],
539 column: token[5]
540 }
541 },
542 sourceIndex: token[6],
543 quoted
544 });
545
546 node.raws.quote = quote;
547
548 this.newNode(node);
549 this.position++;
550 }
551
552 word () {
553 return this.splitWord();
554 }
555
556 newNode (node) {
557 if (this.spaces) {
558 node.raws.before += this.spaces;
559 this.spaces = '';
560 }
561
562 return this.current.append(node);
563 }
564
565 get currToken () {
566 return this.tokens[this.position];
567 }
568
569 get nextToken () {
570 return this.tokens[this.position + 1];
571 }
572
573 get prevToken () {
574 return this.tokens[this.position - 1];
575 }
576};
Note: See TracBrowser for help on using the repository browser.