blob: 927d18aa5828c749b04b90c8f97a9bdc766927a9 [file] [log] [blame]
Mathias Bynens79e2cf02020-05-29 16:46:17 +02001"use strict";
2
3exports.__esModule = true;
4exports.default = void 0;
5
6var _indexesOf = _interopRequireDefault(require("indexes-of"));
7
8var _uniq = _interopRequireDefault(require("uniq"));
9
10var _root = _interopRequireDefault(require("./selectors/root"));
11
12var _selector = _interopRequireDefault(require("./selectors/selector"));
13
14var _className = _interopRequireDefault(require("./selectors/className"));
15
16var _comment = _interopRequireDefault(require("./selectors/comment"));
17
18var _id = _interopRequireDefault(require("./selectors/id"));
19
20var _tag = _interopRequireDefault(require("./selectors/tag"));
21
22var _string = _interopRequireDefault(require("./selectors/string"));
23
24var _pseudo = _interopRequireDefault(require("./selectors/pseudo"));
25
26var _attribute = _interopRequireWildcard(require("./selectors/attribute"));
27
28var _universal = _interopRequireDefault(require("./selectors/universal"));
29
30var _combinator = _interopRequireDefault(require("./selectors/combinator"));
31
32var _nesting = _interopRequireDefault(require("./selectors/nesting"));
33
34var _sortAscending = _interopRequireDefault(require("./sortAscending"));
35
36var _tokenize = _interopRequireWildcard(require("./tokenize"));
37
38var tokens = _interopRequireWildcard(require("./tokenTypes"));
39
40var types = _interopRequireWildcard(require("./selectors/types"));
41
42var _util = require("./util");
43
44var _WHITESPACE_TOKENS, _Object$assign;
45
46function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
47
48function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
49
50function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
51
52function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
53
54var WHITESPACE_TOKENS = (_WHITESPACE_TOKENS = {}, _WHITESPACE_TOKENS[tokens.space] = true, _WHITESPACE_TOKENS[tokens.cr] = true, _WHITESPACE_TOKENS[tokens.feed] = true, _WHITESPACE_TOKENS[tokens.newline] = true, _WHITESPACE_TOKENS[tokens.tab] = true, _WHITESPACE_TOKENS);
55var WHITESPACE_EQUIV_TOKENS = Object.assign({}, WHITESPACE_TOKENS, (_Object$assign = {}, _Object$assign[tokens.comment] = true, _Object$assign));
56
57function tokenStart(token) {
58 return {
59 line: token[_tokenize.FIELDS.START_LINE],
60 column: token[_tokenize.FIELDS.START_COL]
61 };
62}
63
64function tokenEnd(token) {
65 return {
66 line: token[_tokenize.FIELDS.END_LINE],
67 column: token[_tokenize.FIELDS.END_COL]
68 };
69}
70
71function getSource(startLine, startColumn, endLine, endColumn) {
72 return {
73 start: {
74 line: startLine,
75 column: startColumn
76 },
77 end: {
78 line: endLine,
79 column: endColumn
80 }
81 };
82}
83
84function getTokenSource(token) {
85 return getSource(token[_tokenize.FIELDS.START_LINE], token[_tokenize.FIELDS.START_COL], token[_tokenize.FIELDS.END_LINE], token[_tokenize.FIELDS.END_COL]);
86}
87
88function getTokenSourceSpan(startToken, endToken) {
89 if (!startToken) {
90 return undefined;
91 }
92
93 return getSource(startToken[_tokenize.FIELDS.START_LINE], startToken[_tokenize.FIELDS.START_COL], endToken[_tokenize.FIELDS.END_LINE], endToken[_tokenize.FIELDS.END_COL]);
94}
95
96function unescapeProp(node, prop) {
97 var value = node[prop];
98
99 if (typeof value !== "string") {
100 return;
101 }
102
103 if (value.indexOf("\\") !== -1) {
104 (0, _util.ensureObject)(node, 'raws');
105 node[prop] = (0, _util.unesc)(value);
106
107 if (node.raws[prop] === undefined) {
108 node.raws[prop] = value;
109 }
110 }
111
112 return node;
113}
114
115var Parser =
116/*#__PURE__*/
117function () {
118 function Parser(rule, options) {
119 if (options === void 0) {
120 options = {};
121 }
122
123 this.rule = rule;
124 this.options = Object.assign({
125 lossy: false,
126 safe: false
127 }, options);
128 this.position = 0;
129 this.css = typeof this.rule === 'string' ? this.rule : this.rule.selector;
130 this.tokens = (0, _tokenize.default)({
131 css: this.css,
132 error: this._errorGenerator(),
133 safe: this.options.safe
134 });
135 var rootSource = getTokenSourceSpan(this.tokens[0], this.tokens[this.tokens.length - 1]);
136 this.root = new _root.default({
137 source: rootSource
138 });
139 this.root.errorGenerator = this._errorGenerator();
140 var selector = new _selector.default({
141 source: {
142 start: {
143 line: 1,
144 column: 1
145 }
146 }
147 });
148 this.root.append(selector);
149 this.current = selector;
150 this.loop();
151 }
152
153 var _proto = Parser.prototype;
154
155 _proto._errorGenerator = function _errorGenerator() {
156 var _this = this;
157
158 return function (message, errorOptions) {
159 if (typeof _this.rule === 'string') {
160 return new Error(message);
161 }
162
163 return _this.rule.error(message, errorOptions);
164 };
165 };
166
167 _proto.attribute = function attribute() {
168 var attr = [];
169 var startingToken = this.currToken;
170 this.position++;
171
172 while (this.position < this.tokens.length && this.currToken[_tokenize.FIELDS.TYPE] !== tokens.closeSquare) {
173 attr.push(this.currToken);
174 this.position++;
175 }
176
177 if (this.currToken[_tokenize.FIELDS.TYPE] !== tokens.closeSquare) {
178 return this.expected('closing square bracket', this.currToken[_tokenize.FIELDS.START_POS]);
179 }
180
181 var len = attr.length;
182 var node = {
183 source: getSource(startingToken[1], startingToken[2], this.currToken[3], this.currToken[4]),
184 sourceIndex: startingToken[_tokenize.FIELDS.START_POS]
185 };
186
187 if (len === 1 && !~[tokens.word].indexOf(attr[0][_tokenize.FIELDS.TYPE])) {
188 return this.expected('attribute', attr[0][_tokenize.FIELDS.START_POS]);
189 }
190
191 var pos = 0;
192 var spaceBefore = '';
193 var commentBefore = '';
194 var lastAdded = null;
195 var spaceAfterMeaningfulToken = false;
196
197 while (pos < len) {
198 var token = attr[pos];
199 var content = this.content(token);
200 var next = attr[pos + 1];
201
202 switch (token[_tokenize.FIELDS.TYPE]) {
203 case tokens.space:
204 // if (
205 // len === 1 ||
206 // pos === 0 && this.content(next) === '|'
207 // ) {
208 // return this.expected('attribute', token[TOKEN.START_POS], content);
209 // }
210 spaceAfterMeaningfulToken = true;
211
212 if (this.options.lossy) {
213 break;
214 }
215
216 if (lastAdded) {
217 (0, _util.ensureObject)(node, 'spaces', lastAdded);
218 var prevContent = node.spaces[lastAdded].after || '';
219 node.spaces[lastAdded].after = prevContent + content;
220 var existingComment = (0, _util.getProp)(node, 'raws', 'spaces', lastAdded, 'after') || null;
221
222 if (existingComment) {
223 node.raws.spaces[lastAdded].after = existingComment + content;
224 }
225 } else {
226 spaceBefore = spaceBefore + content;
227 commentBefore = commentBefore + content;
228 }
229
230 break;
231
232 case tokens.asterisk:
233 if (next[_tokenize.FIELDS.TYPE] === tokens.equals) {
234 node.operator = content;
235 lastAdded = 'operator';
236 } else if ((!node.namespace || lastAdded === "namespace" && !spaceAfterMeaningfulToken) && next) {
237 if (spaceBefore) {
238 (0, _util.ensureObject)(node, 'spaces', 'attribute');
239 node.spaces.attribute.before = spaceBefore;
240 spaceBefore = '';
241 }
242
243 if (commentBefore) {
244 (0, _util.ensureObject)(node, 'raws', 'spaces', 'attribute');
245 node.raws.spaces.attribute.before = spaceBefore;
246 commentBefore = '';
247 }
248
249 node.namespace = (node.namespace || "") + content;
250 var rawValue = (0, _util.getProp)(node, 'raws', 'namespace') || null;
251
252 if (rawValue) {
253 node.raws.namespace += content;
254 }
255
256 lastAdded = 'namespace';
257 }
258
259 spaceAfterMeaningfulToken = false;
260 break;
261
262 case tokens.dollar:
263 if (lastAdded === "value") {
264 var oldRawValue = (0, _util.getProp)(node, 'raws', 'value');
265 node.value += "$";
266
267 if (oldRawValue) {
268 node.raws.value = oldRawValue + "$";
269 }
270
271 break;
272 }
273
274 // Falls through
275
276 case tokens.caret:
277 if (next[_tokenize.FIELDS.TYPE] === tokens.equals) {
278 node.operator = content;
279 lastAdded = 'operator';
280 }
281
282 spaceAfterMeaningfulToken = false;
283 break;
284
285 case tokens.combinator:
286 if (content === '~' && next[_tokenize.FIELDS.TYPE] === tokens.equals) {
287 node.operator = content;
288 lastAdded = 'operator';
289 }
290
291 if (content !== '|') {
292 spaceAfterMeaningfulToken = false;
293 break;
294 }
295
296 if (next[_tokenize.FIELDS.TYPE] === tokens.equals) {
297 node.operator = content;
298 lastAdded = 'operator';
299 } else if (!node.namespace && !node.attribute) {
300 node.namespace = true;
301 }
302
303 spaceAfterMeaningfulToken = false;
304 break;
305
306 case tokens.word:
307 if (next && this.content(next) === '|' && attr[pos + 2] && attr[pos + 2][_tokenize.FIELDS.TYPE] !== tokens.equals && // this look-ahead probably fails with comment nodes involved.
308 !node.operator && !node.namespace) {
309 node.namespace = content;
310 lastAdded = 'namespace';
311 } else if (!node.attribute || lastAdded === "attribute" && !spaceAfterMeaningfulToken) {
312 if (spaceBefore) {
313 (0, _util.ensureObject)(node, 'spaces', 'attribute');
314 node.spaces.attribute.before = spaceBefore;
315 spaceBefore = '';
316 }
317
318 if (commentBefore) {
319 (0, _util.ensureObject)(node, 'raws', 'spaces', 'attribute');
320 node.raws.spaces.attribute.before = commentBefore;
321 commentBefore = '';
322 }
323
324 node.attribute = (node.attribute || "") + content;
325
326 var _rawValue = (0, _util.getProp)(node, 'raws', 'attribute') || null;
327
328 if (_rawValue) {
329 node.raws.attribute += content;
330 }
331
332 lastAdded = 'attribute';
333 } else if (!node.value && node.value !== "" || lastAdded === "value" && !spaceAfterMeaningfulToken) {
334 var _unescaped = (0, _util.unesc)(content);
335
336 var _oldRawValue = (0, _util.getProp)(node, 'raws', 'value') || '';
337
338 var oldValue = node.value || '';
339 node.value = oldValue + _unescaped;
340 node.quoteMark = null;
341
342 if (_unescaped !== content || _oldRawValue) {
343 (0, _util.ensureObject)(node, 'raws');
344 node.raws.value = (_oldRawValue || oldValue) + content;
345 }
346
347 lastAdded = 'value';
348 } else {
349 var insensitive = content === 'i' || content === "I";
350
351 if ((node.value || node.value === '') && (node.quoteMark || spaceAfterMeaningfulToken)) {
352 node.insensitive = insensitive;
353
354 if (!insensitive || content === "I") {
355 (0, _util.ensureObject)(node, 'raws');
356 node.raws.insensitiveFlag = content;
357 }
358
359 lastAdded = 'insensitive';
360
361 if (spaceBefore) {
362 (0, _util.ensureObject)(node, 'spaces', 'insensitive');
363 node.spaces.insensitive.before = spaceBefore;
364 spaceBefore = '';
365 }
366
367 if (commentBefore) {
368 (0, _util.ensureObject)(node, 'raws', 'spaces', 'insensitive');
369 node.raws.spaces.insensitive.before = commentBefore;
370 commentBefore = '';
371 }
372 } else if (node.value || node.value === '') {
373 lastAdded = 'value';
374 node.value += content;
375
376 if (node.raws.value) {
377 node.raws.value += content;
378 }
379 }
380 }
381
382 spaceAfterMeaningfulToken = false;
383 break;
384
385 case tokens.str:
386 if (!node.attribute || !node.operator) {
387 return this.error("Expected an attribute followed by an operator preceding the string.", {
388 index: token[_tokenize.FIELDS.START_POS]
389 });
390 }
391
392 var _unescapeValue = (0, _attribute.unescapeValue)(content),
393 unescaped = _unescapeValue.unescaped,
394 quoteMark = _unescapeValue.quoteMark;
395
396 node.value = unescaped;
397 node.quoteMark = quoteMark;
398 lastAdded = 'value';
399 (0, _util.ensureObject)(node, 'raws');
400 node.raws.value = content;
401 spaceAfterMeaningfulToken = false;
402 break;
403
404 case tokens.equals:
405 if (!node.attribute) {
406 return this.expected('attribute', token[_tokenize.FIELDS.START_POS], content);
407 }
408
409 if (node.value) {
410 return this.error('Unexpected "=" found; an operator was already defined.', {
411 index: token[_tokenize.FIELDS.START_POS]
412 });
413 }
414
415 node.operator = node.operator ? node.operator + content : content;
416 lastAdded = 'operator';
417 spaceAfterMeaningfulToken = false;
418 break;
419
420 case tokens.comment:
421 if (lastAdded) {
422 if (spaceAfterMeaningfulToken || next && next[_tokenize.FIELDS.TYPE] === tokens.space || lastAdded === 'insensitive') {
423 var lastComment = (0, _util.getProp)(node, 'spaces', lastAdded, 'after') || '';
424 var rawLastComment = (0, _util.getProp)(node, 'raws', 'spaces', lastAdded, 'after') || lastComment;
425 (0, _util.ensureObject)(node, 'raws', 'spaces', lastAdded);
426 node.raws.spaces[lastAdded].after = rawLastComment + content;
427 } else {
428 var lastValue = node[lastAdded] || '';
429 var rawLastValue = (0, _util.getProp)(node, 'raws', lastAdded) || lastValue;
430 (0, _util.ensureObject)(node, 'raws');
431 node.raws[lastAdded] = rawLastValue + content;
432 }
433 } else {
434 commentBefore = commentBefore + content;
435 }
436
437 break;
438
439 default:
440 return this.error("Unexpected \"" + content + "\" found.", {
441 index: token[_tokenize.FIELDS.START_POS]
442 });
443 }
444
445 pos++;
446 }
447
448 unescapeProp(node, "attribute");
449 unescapeProp(node, "namespace");
450 this.newNode(new _attribute.default(node));
451 this.position++;
452 }
453 /**
454 * return a node containing meaningless garbage up to (but not including) the specified token position.
455 * if the token position is negative, all remaining tokens are consumed.
456 *
457 * This returns an array containing a single string node if all whitespace,
458 * otherwise an array of comment nodes with space before and after.
459 *
460 * These tokens are not added to the current selector, the caller can add them or use them to amend
461 * a previous node's space metadata.
462 *
463 * In lossy mode, this returns only comments.
464 */
465 ;
466
467 _proto.parseWhitespaceEquivalentTokens = function parseWhitespaceEquivalentTokens(stopPosition) {
468 if (stopPosition < 0) {
469 stopPosition = this.tokens.length;
470 }
471
472 var startPosition = this.position;
473 var nodes = [];
474 var space = "";
475 var lastComment = undefined;
476
477 do {
478 if (WHITESPACE_TOKENS[this.currToken[_tokenize.FIELDS.TYPE]]) {
479 if (!this.options.lossy) {
480 space += this.content();
481 }
482 } else if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.comment) {
483 var spaces = {};
484
485 if (space) {
486 spaces.before = space;
487 space = "";
488 }
489
490 lastComment = new _comment.default({
491 value: this.content(),
492 source: getTokenSource(this.currToken),
493 sourceIndex: this.currToken[_tokenize.FIELDS.START_POS],
494 spaces: spaces
495 });
496 nodes.push(lastComment);
497 }
498 } while (++this.position < stopPosition);
499
500 if (space) {
501 if (lastComment) {
502 lastComment.spaces.after = space;
503 } else if (!this.options.lossy) {
504 var firstToken = this.tokens[startPosition];
505 var lastToken = this.tokens[this.position - 1];
506 nodes.push(new _string.default({
507 value: '',
508 source: getSource(firstToken[_tokenize.FIELDS.START_LINE], firstToken[_tokenize.FIELDS.START_COL], lastToken[_tokenize.FIELDS.END_LINE], lastToken[_tokenize.FIELDS.END_COL]),
509 sourceIndex: firstToken[_tokenize.FIELDS.START_POS],
510 spaces: {
511 before: space,
512 after: ''
513 }
514 }));
515 }
516 }
517
518 return nodes;
519 }
520 /**
521 *
522 * @param {*} nodes
523 */
524 ;
525
526 _proto.convertWhitespaceNodesToSpace = function convertWhitespaceNodesToSpace(nodes, requiredSpace) {
527 var _this2 = this;
528
529 if (requiredSpace === void 0) {
530 requiredSpace = false;
531 }
532
533 var space = "";
534 var rawSpace = "";
535 nodes.forEach(function (n) {
536 var spaceBefore = _this2.lossySpace(n.spaces.before, requiredSpace);
537
538 var rawSpaceBefore = _this2.lossySpace(n.rawSpaceBefore, requiredSpace);
539
540 space += spaceBefore + _this2.lossySpace(n.spaces.after, requiredSpace && spaceBefore.length === 0);
541 rawSpace += spaceBefore + n.value + _this2.lossySpace(n.rawSpaceAfter, requiredSpace && rawSpaceBefore.length === 0);
542 });
543
544 if (rawSpace === space) {
545 rawSpace = undefined;
546 }
547
548 var result = {
549 space: space,
550 rawSpace: rawSpace
551 };
552 return result;
553 };
554
555 _proto.isNamedCombinator = function isNamedCombinator(position) {
556 if (position === void 0) {
557 position = this.position;
558 }
559
560 return this.tokens[position + 0] && this.tokens[position + 0][_tokenize.FIELDS.TYPE] === tokens.slash && this.tokens[position + 1] && this.tokens[position + 1][_tokenize.FIELDS.TYPE] === tokens.word && this.tokens[position + 2] && this.tokens[position + 2][_tokenize.FIELDS.TYPE] === tokens.slash;
561 };
562
563 _proto.namedCombinator = function namedCombinator() {
564 if (this.isNamedCombinator()) {
565 var nameRaw = this.content(this.tokens[this.position + 1]);
566 var name = (0, _util.unesc)(nameRaw).toLowerCase();
567 var raws = {};
568
569 if (name !== nameRaw) {
570 raws.value = "/" + nameRaw + "/";
571 }
572
573 var node = new _combinator.default({
574 value: "/" + name + "/",
575 source: getSource(this.currToken[_tokenize.FIELDS.START_LINE], this.currToken[_tokenize.FIELDS.START_COL], this.tokens[this.position + 2][_tokenize.FIELDS.END_LINE], this.tokens[this.position + 2][_tokenize.FIELDS.END_COL]),
576 sourceIndex: this.currToken[_tokenize.FIELDS.START_POS],
577 raws: raws
578 });
579 this.position = this.position + 3;
580 return node;
581 } else {
582 this.unexpected();
583 }
584 };
585
586 _proto.combinator = function combinator() {
587 var _this3 = this;
588
589 if (this.content() === '|') {
590 return this.namespace();
591 } // We need to decide between a space that's a descendant combinator and meaningless whitespace at the end of a selector.
592
593
594 var nextSigTokenPos = this.locateNextMeaningfulToken(this.position);
595
596 if (nextSigTokenPos < 0 || this.tokens[nextSigTokenPos][_tokenize.FIELDS.TYPE] === tokens.comma) {
597 var nodes = this.parseWhitespaceEquivalentTokens(nextSigTokenPos);
598
599 if (nodes.length > 0) {
600 var last = this.current.last;
601
602 if (last) {
603 var _this$convertWhitespa = this.convertWhitespaceNodesToSpace(nodes),
604 space = _this$convertWhitespa.space,
605 rawSpace = _this$convertWhitespa.rawSpace;
606
607 if (rawSpace !== undefined) {
608 last.rawSpaceAfter += rawSpace;
609 }
610
611 last.spaces.after += space;
612 } else {
613 nodes.forEach(function (n) {
614 return _this3.newNode(n);
615 });
616 }
617 }
618
619 return;
620 }
621
622 var firstToken = this.currToken;
623 var spaceOrDescendantSelectorNodes = undefined;
624
625 if (nextSigTokenPos > this.position) {
626 spaceOrDescendantSelectorNodes = this.parseWhitespaceEquivalentTokens(nextSigTokenPos);
627 }
628
629 var node;
630
631 if (this.isNamedCombinator()) {
632 node = this.namedCombinator();
633 } else if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.combinator) {
634 node = new _combinator.default({
635 value: this.content(),
636 source: getTokenSource(this.currToken),
637 sourceIndex: this.currToken[_tokenize.FIELDS.START_POS]
638 });
639 this.position++;
640 } else if (WHITESPACE_TOKENS[this.currToken[_tokenize.FIELDS.TYPE]]) {// pass
641 } else if (!spaceOrDescendantSelectorNodes) {
642 this.unexpected();
643 }
644
645 if (node) {
646 if (spaceOrDescendantSelectorNodes) {
647 var _this$convertWhitespa2 = this.convertWhitespaceNodesToSpace(spaceOrDescendantSelectorNodes),
648 _space = _this$convertWhitespa2.space,
649 _rawSpace = _this$convertWhitespa2.rawSpace;
650
651 node.spaces.before = _space;
652 node.rawSpaceBefore = _rawSpace;
653 }
654 } else {
655 // descendant combinator
656 var _this$convertWhitespa3 = this.convertWhitespaceNodesToSpace(spaceOrDescendantSelectorNodes, true),
657 _space2 = _this$convertWhitespa3.space,
658 _rawSpace2 = _this$convertWhitespa3.rawSpace;
659
660 if (!_rawSpace2) {
661 _rawSpace2 = _space2;
662 }
663
664 var spaces = {};
665 var raws = {
666 spaces: {}
667 };
668
669 if (_space2.endsWith(' ') && _rawSpace2.endsWith(' ')) {
670 spaces.before = _space2.slice(0, _space2.length - 1);
671 raws.spaces.before = _rawSpace2.slice(0, _rawSpace2.length - 1);
672 } else if (_space2.startsWith(' ') && _rawSpace2.startsWith(' ')) {
673 spaces.after = _space2.slice(1);
674 raws.spaces.after = _rawSpace2.slice(1);
675 } else {
676 raws.value = _rawSpace2;
677 }
678
679 node = new _combinator.default({
680 value: ' ',
681 source: getTokenSourceSpan(firstToken, this.tokens[this.position - 1]),
682 sourceIndex: firstToken[_tokenize.FIELDS.START_POS],
683 spaces: spaces,
684 raws: raws
685 });
686 }
687
688 if (this.currToken && this.currToken[_tokenize.FIELDS.TYPE] === tokens.space) {
689 node.spaces.after = this.optionalSpace(this.content());
690 this.position++;
691 }
692
693 return this.newNode(node);
694 };
695
696 _proto.comma = function comma() {
697 if (this.position === this.tokens.length - 1) {
698 this.root.trailingComma = true;
699 this.position++;
700 return;
701 }
702
703 this.current._inferEndPosition();
704
705 var selector = new _selector.default({
706 source: {
707 start: tokenStart(this.tokens[this.position + 1])
708 }
709 });
710 this.current.parent.append(selector);
711 this.current = selector;
712 this.position++;
713 };
714
715 _proto.comment = function comment() {
716 var current = this.currToken;
717 this.newNode(new _comment.default({
718 value: this.content(),
719 source: getTokenSource(current),
720 sourceIndex: current[_tokenize.FIELDS.START_POS]
721 }));
722 this.position++;
723 };
724
725 _proto.error = function error(message, opts) {
726 throw this.root.error(message, opts);
727 };
728
729 _proto.missingBackslash = function missingBackslash() {
730 return this.error('Expected a backslash preceding the semicolon.', {
731 index: this.currToken[_tokenize.FIELDS.START_POS]
732 });
733 };
734
735 _proto.missingParenthesis = function missingParenthesis() {
736 return this.expected('opening parenthesis', this.currToken[_tokenize.FIELDS.START_POS]);
737 };
738
739 _proto.missingSquareBracket = function missingSquareBracket() {
740 return this.expected('opening square bracket', this.currToken[_tokenize.FIELDS.START_POS]);
741 };
742
743 _proto.unexpected = function unexpected() {
744 return this.error("Unexpected '" + this.content() + "'. Escaping special characters with \\ may help.", this.currToken[_tokenize.FIELDS.START_POS]);
745 };
746
747 _proto.namespace = function namespace() {
748 var before = this.prevToken && this.content(this.prevToken) || true;
749
750 if (this.nextToken[_tokenize.FIELDS.TYPE] === tokens.word) {
751 this.position++;
752 return this.word(before);
753 } else if (this.nextToken[_tokenize.FIELDS.TYPE] === tokens.asterisk) {
754 this.position++;
755 return this.universal(before);
756 }
757 };
758
759 _proto.nesting = function nesting() {
760 if (this.nextToken) {
761 var nextContent = this.content(this.nextToken);
762
763 if (nextContent === "|") {
764 this.position++;
765 return;
766 }
767 }
768
769 var current = this.currToken;
770 this.newNode(new _nesting.default({
771 value: this.content(),
772 source: getTokenSource(current),
773 sourceIndex: current[_tokenize.FIELDS.START_POS]
774 }));
775 this.position++;
776 };
777
778 _proto.parentheses = function parentheses() {
779 var last = this.current.last;
780 var unbalanced = 1;
781 this.position++;
782
783 if (last && last.type === types.PSEUDO) {
784 var selector = new _selector.default({
785 source: {
786 start: tokenStart(this.tokens[this.position - 1])
787 }
788 });
789 var cache = this.current;
790 last.append(selector);
791 this.current = selector;
792
793 while (this.position < this.tokens.length && unbalanced) {
794 if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis) {
795 unbalanced++;
796 }
797
798 if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.closeParenthesis) {
799 unbalanced--;
800 }
801
802 if (unbalanced) {
803 this.parse();
804 } else {
805 this.current.source.end = tokenEnd(this.currToken);
806 this.current.parent.source.end = tokenEnd(this.currToken);
807 this.position++;
808 }
809 }
810
811 this.current = cache;
812 } else {
813 // I think this case should be an error. It's used to implement a basic parse of media queries
814 // but I don't think it's a good idea.
815 var parenStart = this.currToken;
816 var parenValue = "(";
817 var parenEnd;
818
819 while (this.position < this.tokens.length && unbalanced) {
820 if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis) {
821 unbalanced++;
822 }
823
824 if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.closeParenthesis) {
825 unbalanced--;
826 }
827
828 parenEnd = this.currToken;
829 parenValue += this.parseParenthesisToken(this.currToken);
830 this.position++;
831 }
832
833 if (last) {
834 last.appendToPropertyAndEscape("value", parenValue, parenValue);
835 } else {
836 this.newNode(new _string.default({
837 value: parenValue,
838 source: getSource(parenStart[_tokenize.FIELDS.START_LINE], parenStart[_tokenize.FIELDS.START_COL], parenEnd[_tokenize.FIELDS.END_LINE], parenEnd[_tokenize.FIELDS.END_COL]),
839 sourceIndex: parenStart[_tokenize.FIELDS.START_POS]
840 }));
841 }
842 }
843
844 if (unbalanced) {
845 return this.expected('closing parenthesis', this.currToken[_tokenize.FIELDS.START_POS]);
846 }
847 };
848
849 _proto.pseudo = function pseudo() {
850 var _this4 = this;
851
852 var pseudoStr = '';
853 var startingToken = this.currToken;
854
855 while (this.currToken && this.currToken[_tokenize.FIELDS.TYPE] === tokens.colon) {
856 pseudoStr += this.content();
857 this.position++;
858 }
859
860 if (!this.currToken) {
861 return this.expected(['pseudo-class', 'pseudo-element'], this.position - 1);
862 }
863
864 if (this.currToken[_tokenize.FIELDS.TYPE] === tokens.word) {
865 this.splitWord(false, function (first, length) {
866 pseudoStr += first;
867
868 _this4.newNode(new _pseudo.default({
869 value: pseudoStr,
870 source: getTokenSourceSpan(startingToken, _this4.currToken),
871 sourceIndex: startingToken[_tokenize.FIELDS.START_POS]
872 }));
873
874 if (length > 1 && _this4.nextToken && _this4.nextToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis) {
875 _this4.error('Misplaced parenthesis.', {
876 index: _this4.nextToken[_tokenize.FIELDS.START_POS]
877 });
878 }
879 });
880 } else {
881 return this.expected(['pseudo-class', 'pseudo-element'], this.currToken[_tokenize.FIELDS.START_POS]);
882 }
883 };
884
885 _proto.space = function space() {
886 var content = this.content(); // Handle space before and after the selector
887
888 if (this.position === 0 || this.prevToken[_tokenize.FIELDS.TYPE] === tokens.comma || this.prevToken[_tokenize.FIELDS.TYPE] === tokens.openParenthesis) {
889 this.spaces = this.optionalSpace(content);
890 this.position++;
891 } else if (this.position === this.tokens.length - 1 || this.nextToken[_tokenize.FIELDS.TYPE] === tokens.comma || this.nextToken[_tokenize.FIELDS.TYPE] === tokens.closeParenthesis) {
892 this.current.last.spaces.after = this.optionalSpace(content);
893 this.position++;
894 } else {
895 this.combinator();
896 }
897 };
898
899 _proto.string = function string() {
900 var current = this.currToken;
901 this.newNode(new _string.default({
902 value: this.content(),
903 source: getTokenSource(current),
904 sourceIndex: current[_tokenize.FIELDS.START_POS]
905 }));
906 this.position++;
907 };
908
909 _proto.universal = function universal(namespace) {
910 var nextToken = this.nextToken;
911
912 if (nextToken && this.content(nextToken) === '|') {
913 this.position++;
914 return this.namespace();
915 }
916
917 var current = this.currToken;
918 this.newNode(new _universal.default({
919 value: this.content(),
920 source: getTokenSource(current),
921 sourceIndex: current[_tokenize.FIELDS.START_POS]
922 }), namespace);
923 this.position++;
924 };
925
926 _proto.splitWord = function splitWord(namespace, firstCallback) {
927 var _this5 = this;
928
929 var nextToken = this.nextToken;
930 var word = this.content();
931
932 while (nextToken && ~[tokens.dollar, tokens.caret, tokens.equals, tokens.word].indexOf(nextToken[_tokenize.FIELDS.TYPE])) {
933 this.position++;
934 var current = this.content();
935 word += current;
936
937 if (current.lastIndexOf('\\') === current.length - 1) {
938 var next = this.nextToken;
939
940 if (next && next[_tokenize.FIELDS.TYPE] === tokens.space) {
941 word += this.requiredSpace(this.content(next));
942 this.position++;
943 }
944 }
945
946 nextToken = this.nextToken;
947 }
948
949 var hasClass = (0, _indexesOf.default)(word, '.').filter(function (i) {
950 return word[i - 1] !== '\\';
951 });
952 var hasId = (0, _indexesOf.default)(word, '#').filter(function (i) {
953 return word[i - 1] !== '\\';
954 }); // Eliminate Sass interpolations from the list of id indexes
955
956 var interpolations = (0, _indexesOf.default)(word, '#{');
957
958 if (interpolations.length) {
959 hasId = hasId.filter(function (hashIndex) {
960 return !~interpolations.indexOf(hashIndex);
961 });
962 }
963
964 var indices = (0, _sortAscending.default)((0, _uniq.default)([0].concat(hasClass, hasId)));
965 indices.forEach(function (ind, i) {
966 var index = indices[i + 1] || word.length;
967 var value = word.slice(ind, index);
968
969 if (i === 0 && firstCallback) {
970 return firstCallback.call(_this5, value, indices.length);
971 }
972
973 var node;
974 var current = _this5.currToken;
975 var sourceIndex = current[_tokenize.FIELDS.START_POS] + indices[i];
976 var source = getSource(current[1], current[2] + ind, current[3], current[2] + (index - 1));
977
978 if (~hasClass.indexOf(ind)) {
979 var classNameOpts = {
980 value: value.slice(1),
981 source: source,
982 sourceIndex: sourceIndex
983 };
984 node = new _className.default(unescapeProp(classNameOpts, "value"));
985 } else if (~hasId.indexOf(ind)) {
986 var idOpts = {
987 value: value.slice(1),
988 source: source,
989 sourceIndex: sourceIndex
990 };
991 node = new _id.default(unescapeProp(idOpts, "value"));
992 } else {
993 var tagOpts = {
994 value: value,
995 source: source,
996 sourceIndex: sourceIndex
997 };
998 unescapeProp(tagOpts, "value");
999 node = new _tag.default(tagOpts);
1000 }
1001
1002 _this5.newNode(node, namespace); // Ensure that the namespace is used only once
1003
1004
1005 namespace = null;
1006 });
1007 this.position++;
1008 };
1009
1010 _proto.word = function word(namespace) {
1011 var nextToken = this.nextToken;
1012
1013 if (nextToken && this.content(nextToken) === '|') {
1014 this.position++;
1015 return this.namespace();
1016 }
1017
1018 return this.splitWord(namespace);
1019 };
1020
1021 _proto.loop = function loop() {
1022 while (this.position < this.tokens.length) {
1023 this.parse(true);
1024 }
1025
1026 this.current._inferEndPosition();
1027
1028 return this.root;
1029 };
1030
1031 _proto.parse = function parse(throwOnParenthesis) {
1032 switch (this.currToken[_tokenize.FIELDS.TYPE]) {
1033 case tokens.space:
1034 this.space();
1035 break;
1036
1037 case tokens.comment:
1038 this.comment();
1039 break;
1040
1041 case tokens.openParenthesis:
1042 this.parentheses();
1043 break;
1044
1045 case tokens.closeParenthesis:
1046 if (throwOnParenthesis) {
1047 this.missingParenthesis();
1048 }
1049
1050 break;
1051
1052 case tokens.openSquare:
1053 this.attribute();
1054 break;
1055
1056 case tokens.dollar:
1057 case tokens.caret:
1058 case tokens.equals:
1059 case tokens.word:
1060 this.word();
1061 break;
1062
1063 case tokens.colon:
1064 this.pseudo();
1065 break;
1066
1067 case tokens.comma:
1068 this.comma();
1069 break;
1070
1071 case tokens.asterisk:
1072 this.universal();
1073 break;
1074
1075 case tokens.ampersand:
1076 this.nesting();
1077 break;
1078
1079 case tokens.slash:
1080 case tokens.combinator:
1081 this.combinator();
1082 break;
1083
1084 case tokens.str:
1085 this.string();
1086 break;
1087 // These cases throw; no break needed.
1088
1089 case tokens.closeSquare:
1090 this.missingSquareBracket();
1091
1092 case tokens.semicolon:
1093 this.missingBackslash();
1094
1095 default:
1096 this.unexpected();
1097 }
1098 }
1099 /**
1100 * Helpers
1101 */
1102 ;
1103
1104 _proto.expected = function expected(description, index, found) {
1105 if (Array.isArray(description)) {
1106 var last = description.pop();
1107 description = description.join(', ') + " or " + last;
1108 }
1109
1110 var an = /^[aeiou]/.test(description[0]) ? 'an' : 'a';
1111
1112 if (!found) {
1113 return this.error("Expected " + an + " " + description + ".", {
1114 index: index
1115 });
1116 }
1117
1118 return this.error("Expected " + an + " " + description + ", found \"" + found + "\" instead.", {
1119 index: index
1120 });
1121 };
1122
1123 _proto.requiredSpace = function requiredSpace(space) {
1124 return this.options.lossy ? ' ' : space;
1125 };
1126
1127 _proto.optionalSpace = function optionalSpace(space) {
1128 return this.options.lossy ? '' : space;
1129 };
1130
1131 _proto.lossySpace = function lossySpace(space, required) {
1132 if (this.options.lossy) {
1133 return required ? ' ' : '';
1134 } else {
1135 return space;
1136 }
1137 };
1138
1139 _proto.parseParenthesisToken = function parseParenthesisToken(token) {
1140 var content = this.content(token);
1141
1142 if (token[_tokenize.FIELDS.TYPE] === tokens.space) {
1143 return this.requiredSpace(content);
1144 } else {
1145 return content;
1146 }
1147 };
1148
1149 _proto.newNode = function newNode(node, namespace) {
1150 if (namespace) {
1151 if (/^ +$/.test(namespace)) {
1152 if (!this.options.lossy) {
1153 this.spaces = (this.spaces || '') + namespace;
1154 }
1155
1156 namespace = true;
1157 }
1158
1159 node.namespace = namespace;
1160 unescapeProp(node, "namespace");
1161 }
1162
1163 if (this.spaces) {
1164 node.spaces.before = this.spaces;
1165 this.spaces = '';
1166 }
1167
1168 return this.current.append(node);
1169 };
1170
1171 _proto.content = function content(token) {
1172 if (token === void 0) {
1173 token = this.currToken;
1174 }
1175
1176 return this.css.slice(token[_tokenize.FIELDS.START_POS], token[_tokenize.FIELDS.END_POS]);
1177 };
1178
1179 /**
1180 * returns the index of the next non-whitespace, non-comment token.
1181 * returns -1 if no meaningful token is found.
1182 */
1183 _proto.locateNextMeaningfulToken = function locateNextMeaningfulToken(startPosition) {
1184 if (startPosition === void 0) {
1185 startPosition = this.position + 1;
1186 }
1187
1188 var searchPosition = startPosition;
1189
1190 while (searchPosition < this.tokens.length) {
1191 if (WHITESPACE_EQUIV_TOKENS[this.tokens[searchPosition][_tokenize.FIELDS.TYPE]]) {
1192 searchPosition++;
1193 continue;
1194 } else {
1195 return searchPosition;
1196 }
1197 }
1198
1199 return -1;
1200 };
1201
1202 _createClass(Parser, [{
1203 key: "currToken",
1204 get: function get() {
1205 return this.tokens[this.position];
1206 }
1207 }, {
1208 key: "nextToken",
1209 get: function get() {
1210 return this.tokens[this.position + 1];
1211 }
1212 }, {
1213 key: "prevToken",
1214 get: function get() {
1215 return this.tokens[this.position - 1];
1216 }
1217 }]);
1218
1219 return Parser;
1220}();
1221
1222exports.default = Parser;
1223module.exports = exports.default;