1 | var Marker = require('../../tokenizer/marker');
|
---|
2 | var split = require('../../utils/split');
|
---|
3 |
|
---|
4 | var DEEP_SELECTOR_PATTERN = /\/deep\//;
|
---|
5 | var DOUBLE_COLON_PATTERN = /^::/;
|
---|
6 | var VENDOR_PREFIXED_PATTERN = /:(-moz-|-ms-|-o-|-webkit-)/;
|
---|
7 |
|
---|
8 | var NOT_PSEUDO = ':not';
|
---|
9 | var PSEUDO_CLASSES_WITH_ARGUMENTS = [
|
---|
10 | ':dir',
|
---|
11 | ':lang',
|
---|
12 | ':not',
|
---|
13 | ':nth-child',
|
---|
14 | ':nth-last-child',
|
---|
15 | ':nth-last-of-type',
|
---|
16 | ':nth-of-type'
|
---|
17 | ];
|
---|
18 | var RELATION_PATTERN = /[>+~]/;
|
---|
19 | var UNMIXABLE_PSEUDO_CLASSES = [
|
---|
20 | ':after',
|
---|
21 | ':before',
|
---|
22 | ':first-letter',
|
---|
23 | ':first-line',
|
---|
24 | ':lang'
|
---|
25 | ];
|
---|
26 | var UNMIXABLE_PSEUDO_ELEMENTS = [
|
---|
27 | '::after',
|
---|
28 | '::before',
|
---|
29 | '::first-letter',
|
---|
30 | '::first-line'
|
---|
31 | ];
|
---|
32 |
|
---|
33 | var Level = {
|
---|
34 | DOUBLE_QUOTE: 'double-quote',
|
---|
35 | SINGLE_QUOTE: 'single-quote',
|
---|
36 | ROOT: 'root'
|
---|
37 | };
|
---|
38 |
|
---|
39 | function isMergeable(selector, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) {
|
---|
40 | var singleSelectors = split(selector, Marker.COMMA);
|
---|
41 | var singleSelector;
|
---|
42 | var i, l;
|
---|
43 |
|
---|
44 | for (i = 0, l = singleSelectors.length; i < l; i++) {
|
---|
45 | singleSelector = singleSelectors[i];
|
---|
46 |
|
---|
47 | if (singleSelector.length === 0
|
---|
48 | || isDeepSelector(singleSelector)
|
---|
49 | || isVendorPrefixed(singleSelector)
|
---|
50 | || (singleSelector.indexOf(Marker.COLON) > -1
|
---|
51 | && !areMergeable(
|
---|
52 | singleSelector,
|
---|
53 | extractPseudoFrom(singleSelector),
|
---|
54 | mergeablePseudoClasses,
|
---|
55 | mergeablePseudoElements,
|
---|
56 | multiplePseudoMerging
|
---|
57 | ))) {
|
---|
58 | return false;
|
---|
59 | }
|
---|
60 | }
|
---|
61 |
|
---|
62 | return true;
|
---|
63 | }
|
---|
64 |
|
---|
65 | function isDeepSelector(selector) {
|
---|
66 | return DEEP_SELECTOR_PATTERN.test(selector);
|
---|
67 | }
|
---|
68 |
|
---|
69 | function isVendorPrefixed(selector) {
|
---|
70 | return VENDOR_PREFIXED_PATTERN.test(selector);
|
---|
71 | }
|
---|
72 |
|
---|
73 | function extractPseudoFrom(selector) {
|
---|
74 | var list = [];
|
---|
75 | var character;
|
---|
76 | var buffer = [];
|
---|
77 | var level = Level.ROOT;
|
---|
78 | var roundBracketLevel = 0;
|
---|
79 | var isQuoted;
|
---|
80 | var isEscaped;
|
---|
81 | var isPseudo = false;
|
---|
82 | var isRelation;
|
---|
83 | var wasColon = false;
|
---|
84 | var index;
|
---|
85 | var len;
|
---|
86 |
|
---|
87 | for (index = 0, len = selector.length; index < len; index++) {
|
---|
88 | character = selector[index];
|
---|
89 |
|
---|
90 | isRelation = !isEscaped && RELATION_PATTERN.test(character);
|
---|
91 | isQuoted = level == Level.DOUBLE_QUOTE || level == Level.SINGLE_QUOTE;
|
---|
92 |
|
---|
93 | if (isEscaped) {
|
---|
94 | buffer.push(character);
|
---|
95 | } else if (character == Marker.DOUBLE_QUOTE && level == Level.ROOT) {
|
---|
96 | buffer.push(character);
|
---|
97 | level = Level.DOUBLE_QUOTE;
|
---|
98 | } else if (character == Marker.DOUBLE_QUOTE && level == Level.DOUBLE_QUOTE) {
|
---|
99 | buffer.push(character);
|
---|
100 | level = Level.ROOT;
|
---|
101 | } else if (character == Marker.SINGLE_QUOTE && level == Level.ROOT) {
|
---|
102 | buffer.push(character);
|
---|
103 | level = Level.SINGLE_QUOTE;
|
---|
104 | } else if (character == Marker.SINGLE_QUOTE && level == Level.SINGLE_QUOTE) {
|
---|
105 | buffer.push(character);
|
---|
106 | level = Level.ROOT;
|
---|
107 | } else if (isQuoted) {
|
---|
108 | buffer.push(character);
|
---|
109 | } else if (character == Marker.OPEN_ROUND_BRACKET) {
|
---|
110 | buffer.push(character);
|
---|
111 | roundBracketLevel++;
|
---|
112 | } else if (character == Marker.CLOSE_ROUND_BRACKET && roundBracketLevel == 1 && isPseudo) {
|
---|
113 | buffer.push(character);
|
---|
114 | list.push(buffer.join(''));
|
---|
115 | roundBracketLevel--;
|
---|
116 | buffer = [];
|
---|
117 | isPseudo = false;
|
---|
118 | } else if (character == Marker.CLOSE_ROUND_BRACKET) {
|
---|
119 | buffer.push(character);
|
---|
120 | roundBracketLevel--;
|
---|
121 | } else if (character == Marker.COLON && roundBracketLevel === 0 && isPseudo && !wasColon) {
|
---|
122 | list.push(buffer.join(''));
|
---|
123 | buffer = [];
|
---|
124 | buffer.push(character);
|
---|
125 | } else if (character == Marker.COLON && roundBracketLevel === 0 && !wasColon) {
|
---|
126 | buffer = [];
|
---|
127 | buffer.push(character);
|
---|
128 | isPseudo = true;
|
---|
129 | } else if (character == Marker.SPACE && roundBracketLevel === 0 && isPseudo) {
|
---|
130 | list.push(buffer.join(''));
|
---|
131 | buffer = [];
|
---|
132 | isPseudo = false;
|
---|
133 | } else if (isRelation && roundBracketLevel === 0 && isPseudo) {
|
---|
134 | list.push(buffer.join(''));
|
---|
135 | buffer = [];
|
---|
136 | isPseudo = false;
|
---|
137 | } else {
|
---|
138 | buffer.push(character);
|
---|
139 | }
|
---|
140 |
|
---|
141 | isEscaped = character == Marker.BACK_SLASH;
|
---|
142 | wasColon = character == Marker.COLON;
|
---|
143 | }
|
---|
144 |
|
---|
145 | if (buffer.length > 0 && isPseudo) {
|
---|
146 | list.push(buffer.join(''));
|
---|
147 | }
|
---|
148 |
|
---|
149 | return list;
|
---|
150 | }
|
---|
151 |
|
---|
152 | function areMergeable(selector, matches, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) {
|
---|
153 | return areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements)
|
---|
154 | && needArguments(matches)
|
---|
155 | && (matches.length < 2 || !someIncorrectlyChained(selector, matches))
|
---|
156 | && (matches.length < 2 || multiplePseudoMerging && allMixable(matches));
|
---|
157 | }
|
---|
158 |
|
---|
159 | function areAllowed(matches, mergeablePseudoClasses, mergeablePseudoElements) {
|
---|
160 | var match;
|
---|
161 | var name;
|
---|
162 | var i, l;
|
---|
163 |
|
---|
164 | for (i = 0, l = matches.length; i < l; i++) {
|
---|
165 | match = matches[i];
|
---|
166 | name = match.indexOf(Marker.OPEN_ROUND_BRACKET) > -1
|
---|
167 | ? match.substring(0, match.indexOf(Marker.OPEN_ROUND_BRACKET))
|
---|
168 | : match;
|
---|
169 |
|
---|
170 | if (mergeablePseudoClasses.indexOf(name) === -1 && mergeablePseudoElements.indexOf(name) === -1) {
|
---|
171 | return false;
|
---|
172 | }
|
---|
173 | }
|
---|
174 |
|
---|
175 | return true;
|
---|
176 | }
|
---|
177 |
|
---|
178 | function needArguments(matches) {
|
---|
179 | var match;
|
---|
180 | var name;
|
---|
181 | var bracketOpensAt;
|
---|
182 | var hasArguments;
|
---|
183 | var i, l;
|
---|
184 |
|
---|
185 | for (i = 0, l = matches.length; i < l; i++) {
|
---|
186 | match = matches[i];
|
---|
187 |
|
---|
188 | bracketOpensAt = match.indexOf(Marker.OPEN_ROUND_BRACKET);
|
---|
189 | hasArguments = bracketOpensAt > -1;
|
---|
190 | name = hasArguments
|
---|
191 | ? match.substring(0, bracketOpensAt)
|
---|
192 | : match;
|
---|
193 |
|
---|
194 | if (hasArguments && PSEUDO_CLASSES_WITH_ARGUMENTS.indexOf(name) == -1) {
|
---|
195 | return false;
|
---|
196 | }
|
---|
197 |
|
---|
198 | if (!hasArguments && PSEUDO_CLASSES_WITH_ARGUMENTS.indexOf(name) > -1) {
|
---|
199 | return false;
|
---|
200 | }
|
---|
201 | }
|
---|
202 |
|
---|
203 | return true;
|
---|
204 | }
|
---|
205 |
|
---|
206 | function someIncorrectlyChained(selector, matches) {
|
---|
207 | var positionInSelector = 0;
|
---|
208 | var match;
|
---|
209 | var matchAt;
|
---|
210 | var nextMatch;
|
---|
211 | var nextMatchAt;
|
---|
212 | var name;
|
---|
213 | var nextName;
|
---|
214 | var areChained;
|
---|
215 | var i, l;
|
---|
216 |
|
---|
217 | for (i = 0, l = matches.length; i < l; i++) {
|
---|
218 | match = matches[i];
|
---|
219 | nextMatch = matches[i + 1];
|
---|
220 |
|
---|
221 | if (!nextMatch) {
|
---|
222 | break;
|
---|
223 | }
|
---|
224 |
|
---|
225 | matchAt = selector.indexOf(match, positionInSelector);
|
---|
226 | nextMatchAt = selector.indexOf(match, matchAt + 1);
|
---|
227 | positionInSelector = nextMatchAt;
|
---|
228 | areChained = matchAt + match.length == nextMatchAt;
|
---|
229 |
|
---|
230 | if (areChained) {
|
---|
231 | name = match.indexOf(Marker.OPEN_ROUND_BRACKET) > -1
|
---|
232 | ? match.substring(0, match.indexOf(Marker.OPEN_ROUND_BRACKET))
|
---|
233 | : match;
|
---|
234 | nextName = nextMatch.indexOf(Marker.OPEN_ROUND_BRACKET) > -1
|
---|
235 | ? nextMatch.substring(0, nextMatch.indexOf(Marker.OPEN_ROUND_BRACKET))
|
---|
236 | : nextMatch;
|
---|
237 |
|
---|
238 | if (name != NOT_PSEUDO || nextName != NOT_PSEUDO) {
|
---|
239 | return true;
|
---|
240 | }
|
---|
241 | }
|
---|
242 | }
|
---|
243 |
|
---|
244 | return false;
|
---|
245 | }
|
---|
246 |
|
---|
247 | function allMixable(matches) {
|
---|
248 | var unmixableMatches = 0;
|
---|
249 | var match;
|
---|
250 | var i, l;
|
---|
251 |
|
---|
252 | for (i = 0, l = matches.length; i < l; i++) {
|
---|
253 | match = matches[i];
|
---|
254 |
|
---|
255 | if (isPseudoElement(match)) {
|
---|
256 | unmixableMatches += UNMIXABLE_PSEUDO_ELEMENTS.indexOf(match) > -1 ? 1 : 0;
|
---|
257 | } else {
|
---|
258 | unmixableMatches += UNMIXABLE_PSEUDO_CLASSES.indexOf(match) > -1 ? 1 : 0;
|
---|
259 | }
|
---|
260 |
|
---|
261 | if (unmixableMatches > 1) {
|
---|
262 | return false;
|
---|
263 | }
|
---|
264 | }
|
---|
265 |
|
---|
266 | return true;
|
---|
267 | }
|
---|
268 |
|
---|
269 | function isPseudoElement(pseudo) {
|
---|
270 | return DOUBLE_COLON_PATTERN.test(pseudo);
|
---|
271 | }
|
---|
272 |
|
---|
273 | module.exports = isMergeable;
|
---|