1 | var Spaces = require('../../options/format').Spaces;
|
---|
2 | var Marker = require('../../tokenizer/marker');
|
---|
3 | var formatPosition = require('../../utils/format-position');
|
---|
4 |
|
---|
5 | var CASE_ATTRIBUTE_PATTERN = /[\s"'][iI]\s*\]/;
|
---|
6 | var CASE_RESTORE_PATTERN = /([\d\w])([iI])\]/g;
|
---|
7 | var DOUBLE_QUOTE_CASE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"([iI])/g;
|
---|
8 | var DOUBLE_QUOTE_PATTERN = /="([a-zA-Z][a-zA-Z\d\-_]+)"(\s|\])/g;
|
---|
9 | var HTML_COMMENT_PATTERN = /^(?:(?:<!--|-->)\s*)+/;
|
---|
10 | var SINGLE_QUOTE_CASE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'([iI])/g;
|
---|
11 | var SINGLE_QUOTE_PATTERN = /='([a-zA-Z][a-zA-Z\d\-_]+)'(\s|\])/g;
|
---|
12 | var RELATION_PATTERN = /[>+~]/;
|
---|
13 | var WHITESPACE_PATTERN = /\s/;
|
---|
14 |
|
---|
15 | var ASTERISK_PLUS_HTML_HACK = '*+html ';
|
---|
16 | var ASTERISK_FIRST_CHILD_PLUS_HTML_HACK = '*:first-child+html ';
|
---|
17 | var LESS_THAN = '<';
|
---|
18 |
|
---|
19 | var PSEUDO_CLASSES_WITH_SELECTORS = [
|
---|
20 | ':current',
|
---|
21 | ':future',
|
---|
22 | ':has',
|
---|
23 | ':host',
|
---|
24 | ':host-context',
|
---|
25 | ':is',
|
---|
26 | ':not',
|
---|
27 | ':past',
|
---|
28 | ':where'
|
---|
29 | ];
|
---|
30 |
|
---|
31 | function hasInvalidCharacters(value) {
|
---|
32 | var isEscaped;
|
---|
33 | var isInvalid = false;
|
---|
34 | var character;
|
---|
35 | var isQuote = false;
|
---|
36 | var i, l;
|
---|
37 |
|
---|
38 | for (i = 0, l = value.length; i < l; i++) {
|
---|
39 | character = value[i];
|
---|
40 |
|
---|
41 | if (isEscaped) {
|
---|
42 | // continue as always
|
---|
43 | } else if (character == Marker.SINGLE_QUOTE || character == Marker.DOUBLE_QUOTE) {
|
---|
44 | isQuote = !isQuote;
|
---|
45 | } else if (!isQuote
|
---|
46 | && (character == Marker.CLOSE_CURLY_BRACKET
|
---|
47 | || character == Marker.EXCLAMATION
|
---|
48 | || character == LESS_THAN
|
---|
49 | || character == Marker.SEMICOLON)
|
---|
50 | ) {
|
---|
51 | isInvalid = true;
|
---|
52 | break;
|
---|
53 | } else if (!isQuote && i === 0 && RELATION_PATTERN.test(character)) {
|
---|
54 | isInvalid = true;
|
---|
55 | break;
|
---|
56 | }
|
---|
57 |
|
---|
58 | isEscaped = character == Marker.BACK_SLASH;
|
---|
59 | }
|
---|
60 |
|
---|
61 | return isInvalid;
|
---|
62 | }
|
---|
63 |
|
---|
64 | function removeWhitespace(value, format) {
|
---|
65 | var stripped = [];
|
---|
66 | var character;
|
---|
67 | var isNewLineNix;
|
---|
68 | var isNewLineWin;
|
---|
69 | var isEscaped;
|
---|
70 | var wasEscaped;
|
---|
71 | var isQuoted;
|
---|
72 | var isSingleQuoted;
|
---|
73 | var isDoubleQuoted;
|
---|
74 | var isAttribute;
|
---|
75 | var isRelation;
|
---|
76 | var isWhitespace;
|
---|
77 | var isSpaceAwarePseudoClass;
|
---|
78 | var roundBracketLevel = 0;
|
---|
79 | var wasComma = false;
|
---|
80 | var wasRelation = false;
|
---|
81 | var wasWhitespace = false;
|
---|
82 | var withCaseAttribute = CASE_ATTRIBUTE_PATTERN.test(value);
|
---|
83 | var spaceAroundRelation = format && format.spaces[Spaces.AroundSelectorRelation];
|
---|
84 | var i, l;
|
---|
85 |
|
---|
86 | for (i = 0, l = value.length; i < l; i++) {
|
---|
87 | character = value[i];
|
---|
88 |
|
---|
89 | isNewLineNix = character == Marker.NEW_LINE_NIX;
|
---|
90 | isNewLineWin = character == Marker.NEW_LINE_NIX && value[i - 1] == Marker.CARRIAGE_RETURN;
|
---|
91 | isQuoted = isSingleQuoted || isDoubleQuoted;
|
---|
92 | isRelation = !isAttribute && !isEscaped && roundBracketLevel === 0 && RELATION_PATTERN.test(character);
|
---|
93 | isWhitespace = WHITESPACE_PATTERN.test(character);
|
---|
94 | isSpaceAwarePseudoClass = roundBracketLevel == 1 && character == Marker.CLOSE_ROUND_BRACKET
|
---|
95 | ? false
|
---|
96 | : isSpaceAwarePseudoClass
|
---|
97 | || (roundBracketLevel === 0 && character == Marker.COLON && isPseudoClassWithSelectors(value, i));
|
---|
98 |
|
---|
99 | if (wasEscaped && isQuoted && isNewLineWin) {
|
---|
100 | // swallow escaped new windows lines in comments
|
---|
101 | stripped.pop();
|
---|
102 | stripped.pop();
|
---|
103 | } else if (isEscaped && isQuoted && isNewLineNix) {
|
---|
104 | // swallow escaped new *nix lines in comments
|
---|
105 | stripped.pop();
|
---|
106 | } else if (isEscaped) {
|
---|
107 | stripped.push(character);
|
---|
108 | } else if (character == Marker.OPEN_SQUARE_BRACKET && !isQuoted) {
|
---|
109 | stripped.push(character);
|
---|
110 | isAttribute = true;
|
---|
111 | } else if (character == Marker.CLOSE_SQUARE_BRACKET && !isQuoted) {
|
---|
112 | stripped.push(character);
|
---|
113 | isAttribute = false;
|
---|
114 | } else if (character == Marker.OPEN_ROUND_BRACKET && !isQuoted) {
|
---|
115 | stripped.push(character);
|
---|
116 | roundBracketLevel++;
|
---|
117 | } else if (character == Marker.CLOSE_ROUND_BRACKET && !isQuoted) {
|
---|
118 | stripped.push(character);
|
---|
119 | roundBracketLevel--;
|
---|
120 | } else if (character == Marker.SINGLE_QUOTE && !isQuoted) {
|
---|
121 | stripped.push(character);
|
---|
122 | isSingleQuoted = true;
|
---|
123 | } else if (character == Marker.DOUBLE_QUOTE && !isQuoted) {
|
---|
124 | stripped.push(character);
|
---|
125 | isDoubleQuoted = true;
|
---|
126 | } else if (character == Marker.SINGLE_QUOTE && isQuoted) {
|
---|
127 | stripped.push(character);
|
---|
128 | isSingleQuoted = false;
|
---|
129 | } else if (character == Marker.DOUBLE_QUOTE && isQuoted) {
|
---|
130 | stripped.push(character);
|
---|
131 | isDoubleQuoted = false;
|
---|
132 | } else if (isWhitespace && wasRelation && !spaceAroundRelation) {
|
---|
133 | continue;
|
---|
134 | } else if (!isWhitespace && wasRelation && spaceAroundRelation) {
|
---|
135 | stripped.push(Marker.SPACE);
|
---|
136 | stripped.push(character);
|
---|
137 | } else if (isWhitespace && !wasWhitespace && wasComma && roundBracketLevel > 0 && isSpaceAwarePseudoClass) {
|
---|
138 | // skip space
|
---|
139 | } else if (isWhitespace && !wasWhitespace && roundBracketLevel > 0 && isSpaceAwarePseudoClass) {
|
---|
140 | stripped.push(character);
|
---|
141 | } else if (isWhitespace && (isAttribute || roundBracketLevel > 0) && !isQuoted) {
|
---|
142 | // skip space
|
---|
143 | } else if (isWhitespace && wasWhitespace && !isQuoted) {
|
---|
144 | // skip extra space
|
---|
145 | } else if ((isNewLineWin || isNewLineNix) && (isAttribute || roundBracketLevel > 0) && isQuoted) {
|
---|
146 | // skip newline
|
---|
147 | } else if (isRelation && wasWhitespace && !spaceAroundRelation) {
|
---|
148 | stripped.pop();
|
---|
149 | stripped.push(character);
|
---|
150 | } else if (isRelation && !wasWhitespace && spaceAroundRelation) {
|
---|
151 | stripped.push(Marker.SPACE);
|
---|
152 | stripped.push(character);
|
---|
153 | } else if (isWhitespace) {
|
---|
154 | stripped.push(Marker.SPACE);
|
---|
155 | } else {
|
---|
156 | stripped.push(character);
|
---|
157 | }
|
---|
158 |
|
---|
159 | wasEscaped = isEscaped;
|
---|
160 | isEscaped = character == Marker.BACK_SLASH;
|
---|
161 | wasRelation = isRelation;
|
---|
162 | wasWhitespace = isWhitespace;
|
---|
163 | wasComma = character == Marker.COMMA;
|
---|
164 | }
|
---|
165 |
|
---|
166 | return withCaseAttribute
|
---|
167 | ? stripped.join('').replace(CASE_RESTORE_PATTERN, '$1 $2]')
|
---|
168 | : stripped.join('');
|
---|
169 | }
|
---|
170 |
|
---|
171 | function isPseudoClassWithSelectors(value, colonPosition) {
|
---|
172 | var pseudoClass = value.substring(colonPosition, value.indexOf(Marker.OPEN_ROUND_BRACKET, colonPosition));
|
---|
173 |
|
---|
174 | return PSEUDO_CLASSES_WITH_SELECTORS.indexOf(pseudoClass) > -1;
|
---|
175 | }
|
---|
176 |
|
---|
177 | function removeQuotes(value) {
|
---|
178 | if (value.indexOf('\'') == -1 && value.indexOf('"') == -1) {
|
---|
179 | return value;
|
---|
180 | }
|
---|
181 |
|
---|
182 | return value
|
---|
183 | .replace(SINGLE_QUOTE_CASE_PATTERN, '=$1 $2')
|
---|
184 | .replace(SINGLE_QUOTE_PATTERN, '=$1$2')
|
---|
185 | .replace(DOUBLE_QUOTE_CASE_PATTERN, '=$1 $2')
|
---|
186 | .replace(DOUBLE_QUOTE_PATTERN, '=$1$2');
|
---|
187 | }
|
---|
188 |
|
---|
189 | function replacePseudoClasses(value) {
|
---|
190 | return value
|
---|
191 | .replace('nth-child(1)', 'first-child')
|
---|
192 | .replace('nth-of-type(1)', 'first-of-type')
|
---|
193 | .replace('nth-of-type(even)', 'nth-of-type(2n)')
|
---|
194 | .replace('nth-child(even)', 'nth-child(2n)')
|
---|
195 | .replace('nth-of-type(2n+1)', 'nth-of-type(odd)')
|
---|
196 | .replace('nth-child(2n+1)', 'nth-child(odd)')
|
---|
197 | .replace('nth-last-child(1)', 'last-child')
|
---|
198 | .replace('nth-last-of-type(1)', 'last-of-type')
|
---|
199 | .replace('nth-last-of-type(even)', 'nth-last-of-type(2n)')
|
---|
200 | .replace('nth-last-child(even)', 'nth-last-child(2n)')
|
---|
201 | .replace('nth-last-of-type(2n+1)', 'nth-last-of-type(odd)')
|
---|
202 | .replace('nth-last-child(2n+1)', 'nth-last-child(odd)');
|
---|
203 | }
|
---|
204 |
|
---|
205 | function tidyRules(rules, removeUnsupported, adjacentSpace, format, warnings) {
|
---|
206 | var list = [];
|
---|
207 | var repeated = [];
|
---|
208 |
|
---|
209 | function removeHTMLComment(rule, match) {
|
---|
210 | warnings.push('HTML comment \'' + match + '\' at ' + formatPosition(rule[2][0]) + '. Removing.');
|
---|
211 | return '';
|
---|
212 | }
|
---|
213 |
|
---|
214 | for (var i = 0, l = rules.length; i < l; i++) {
|
---|
215 | var rule = rules[i];
|
---|
216 | var reduced = rule[1];
|
---|
217 |
|
---|
218 | reduced = reduced.replace(HTML_COMMENT_PATTERN, removeHTMLComment.bind(null, rule));
|
---|
219 |
|
---|
220 | if (hasInvalidCharacters(reduced)) {
|
---|
221 | warnings.push('Invalid selector \'' + rule[1] + '\' at ' + formatPosition(rule[2][0]) + '. Ignoring.');
|
---|
222 | continue;
|
---|
223 | }
|
---|
224 |
|
---|
225 | reduced = removeWhitespace(reduced, format);
|
---|
226 | reduced = removeQuotes(reduced);
|
---|
227 |
|
---|
228 | if (adjacentSpace && reduced.indexOf('nav') > 0) {
|
---|
229 | reduced = reduced.replace(/\+nav(\S|$)/, '+ nav$1');
|
---|
230 | }
|
---|
231 |
|
---|
232 | if (removeUnsupported && reduced.indexOf(ASTERISK_PLUS_HTML_HACK) > -1) {
|
---|
233 | continue;
|
---|
234 | }
|
---|
235 |
|
---|
236 | if (removeUnsupported && reduced.indexOf(ASTERISK_FIRST_CHILD_PLUS_HTML_HACK) > -1) {
|
---|
237 | continue;
|
---|
238 | }
|
---|
239 |
|
---|
240 | if (reduced.indexOf('*') > -1) {
|
---|
241 | reduced = reduced
|
---|
242 | .replace(/\*([:#.[])/g, '$1')
|
---|
243 | .replace(/^(:first-child)?\+html/, '*$1+html');
|
---|
244 | }
|
---|
245 |
|
---|
246 | if (repeated.indexOf(reduced) > -1) {
|
---|
247 | continue;
|
---|
248 | }
|
---|
249 |
|
---|
250 | reduced = replacePseudoClasses(reduced);
|
---|
251 |
|
---|
252 | rule[1] = reduced;
|
---|
253 | repeated.push(reduced);
|
---|
254 | list.push(rule);
|
---|
255 | }
|
---|
256 |
|
---|
257 | if (list.length == 1 && list[0][1].length === 0) {
|
---|
258 | warnings.push('Empty selector \'' + list[0][1] + '\' at ' + formatPosition(list[0][2][0]) + '. Ignoring.');
|
---|
259 | list = [];
|
---|
260 | }
|
---|
261 |
|
---|
262 | return list;
|
---|
263 | }
|
---|
264 |
|
---|
265 | module.exports = tidyRules;
|
---|