UNPKG

5.79 kBJavaScriptView Raw
1/*
2 Copyright © 2018 Andrew Powell
3
4 This Source Code Form is subject to the terms of the Mozilla Public
5 License, v. 2.0. If a copy of the MPL was not distributed with this
6 file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8 The above copyright notice and this permission notice shall be
9 included in all copies or substantial portions of this Source Code Form.
10*/
11const Parser = require('postcss/lib/parser');
12
13const AtWord = require('./nodes/AtWord');
14const Comment = require('./nodes/Comment');
15const Func = require('./nodes/Func');
16const Interpolation = require('./nodes/Interpolation');
17const Numeric = require('./nodes/Numeric');
18const Operator = require('./nodes/Operator');
19const Punctuation = require('./nodes/Punctuation');
20const Quoted = require('./nodes/Quoted');
21const UnicodeRange = require('./nodes/UnicodeRange');
22const Word = require('./nodes/Word');
23
24const defaults = {
25 ignoreUnknownWords: false,
26 // interpolation: { prefix: '@' }
27 interpolation: false,
28 parentNode: null,
29 variables: {
30 prefixes: ['--']
31 }
32};
33
34module.exports = class ValuesParser extends Parser {
35 constructor(input, opts = {}) {
36 super(input);
37
38 this.lastNode = null;
39 this.options = Object.assign({}, defaults, opts);
40 this.parentNode = this.options.parentNode;
41 }
42
43 back(tokens) {
44 for (const token of tokens.reverse()) {
45 this.tokenizer.back(token);
46 }
47 }
48
49 comment(token) {
50 super.comment(token);
51
52 const inline = Comment.testInline(token);
53 const node = this.lastNode;
54 node.inline = inline;
55 Object.setPrototypeOf(node, Comment.prototype);
56 }
57
58 fromFirst(tokens, Constructor) {
59 const [first] = tokens;
60 const [, value, startLine, startChar] = first;
61 const node = new Constructor({ value });
62
63 this.init(node, startLine, startChar);
64 this.current = node;
65 this.end(first);
66 this.back(tokens.slice(1));
67 }
68
69 init(node, line, column) {
70 super.init(node, line, column);
71
72 // base methods like comment() don't set this.current, so we need some way of tracking the last
73 // node for manipulation
74 this.lastNode = node;
75 }
76
77 other(start) {
78 // console.log('other', start);
79
80 const brackets = [];
81 const tokens = [];
82 let token = start;
83 let type = null;
84 let bracket = null;
85
86 while (token) {
87 [type] = token;
88 tokens.push(token);
89
90 if (type === '(' || type === '[') {
91 if (!bracket) {
92 bracket = token;
93 }
94
95 brackets.push(type === '(' ? ')' : ']');
96 } else if (type === brackets[brackets.length - 1]) {
97 brackets.pop();
98 if (brackets.length === 0) {
99 bracket = null;
100 }
101 }
102
103 token = this.tokenizer.nextToken();
104 }
105
106 if (brackets.length > 0) {
107 this.unclosedBracket(bracket);
108 }
109
110 this.unknownWord(tokens);
111 }
112
113 // overriden to remove certain node types we don't need
114 parse() {
115 let token;
116 while (!this.tokenizer.endOfFile()) {
117 token = this.tokenizer.nextToken();
118
119 switch (token[0]) {
120 case 'space':
121 this.spaces += token[1];
122 break;
123
124 case 'comment':
125 this.comment(token);
126 break;
127
128 case 'at-word':
129 this.atrule(token);
130 Object.setPrototypeOf(this.lastNode, AtWord.prototype);
131 this.lastNode.type = 'atword';
132 break;
133
134 default:
135 this.other(token);
136 break;
137 }
138 }
139 this.endFile();
140 }
141
142 unknownWord(tokens) {
143 // NOTE: keep commented for examining unknown structures
144 // console.log('unknown', tokens);
145
146 const [first] = tokens;
147 const [type, value] = first;
148
149 if (Punctuation.chars.includes(type)) {
150 Punctuation.fromTokens(tokens, this);
151 } else if (Func.test(tokens)) {
152 Func.fromTokens(tokens, this);
153 } else if (this.options.interpolation && Interpolation.test(tokens, this)) {
154 Interpolation.fromTokens(tokens, this);
155 } else if (type === 'brackets') {
156 Punctuation.tokenizeBrackets(tokens, this);
157 } else if (type === 'comma') {
158 Punctuation.fromTokens(tokens, this);
159 } else if (type === 'operator') {
160 Operator.fromTokens(tokens, this);
161 } else if (type === 'string') {
162 Quoted.fromTokens(tokens, this);
163 } else if (type === 'word') {
164 if (value === ',') {
165 Punctuation.fromTokens(tokens, this);
166 } else if (value === '//') {
167 Comment.tokenizeNext(tokens, this);
168 } else if (Comment.testInline(first)) {
169 // catch protocol-relative urls in a url() function
170 // https://github.com/shellscape/postcss-values-parser/issues/65
171 const { parentNode } = this;
172 if (parentNode && parentNode.type === 'func' && parentNode.name === 'url') {
173 Word.fromTokens(tokens, this);
174 } else {
175 Comment.tokenizeInline(tokens, this);
176 }
177 } else if (value.includes(',')) {
178 Punctuation.tokenizeCommas(tokens, this);
179 } else if (Word.testWord(tokens, this)) {
180 // we need to catch variables before the numeric and operator tests
181 Word.fromTokens(tokens, this);
182 } else if (Numeric.test(value)) {
183 Numeric.fromTokens(tokens, this);
184 } else if (UnicodeRange.test(value)) {
185 UnicodeRange.fromTokens(tokens, this);
186 } else if (Operator.chars.includes(value)) {
187 Operator.fromTokens(tokens, this);
188 } else if (/^[\w-]+$/.test(value)) {
189 Word.fromTokens(tokens, this);
190 } else if (Operator.regex.test(value)) {
191 Operator.tokenize(tokens, this);
192 } else if (this.options.ignoreUnknownWords) {
193 Word.fromTokens(tokens, this);
194 } else {
195 super.unknownWord(tokens);
196 }
197 } else {
198 /* istanbul ignore next */
199 super.unknownWord(tokens);
200 }
201 }
202};