1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var Visitor = require('./')
|
13 | , nodes = require('../nodes')
|
14 | , utils = require('../utils');
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | var Normalizer = module.exports = function Normalizer(root, options) {
|
31 | options = options || {};
|
32 | Visitor.call(this, root);
|
33 | this.hoist = options['hoist atrules'];
|
34 | this.stack = [];
|
35 | this.map = {};
|
36 | this.imports = [];
|
37 | };
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | Normalizer.prototype.__proto__ = Visitor.prototype;
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | Normalizer.prototype.normalize = function(){
|
53 | var ret = this.visit(this.root);
|
54 |
|
55 | if (this.hoist) {
|
56 |
|
57 | if (this.imports.length) ret.nodes = this.imports.concat(ret.nodes);
|
58 |
|
59 |
|
60 | if (this.charset) ret.nodes = [this.charset].concat(ret.nodes);
|
61 | }
|
62 |
|
63 | return ret;
|
64 | };
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 | Normalizer.prototype.bubble = function(node){
|
74 | var props = []
|
75 | , other = []
|
76 | , self = this;
|
77 |
|
78 | function filterProps(block) {
|
79 | block.nodes.forEach(function(node) {
|
80 | node = self.visit(node);
|
81 |
|
82 | switch (node.nodeName) {
|
83 | case 'property':
|
84 | props.push(node);
|
85 | break;
|
86 | case 'block':
|
87 | filterProps(node);
|
88 | break;
|
89 | default:
|
90 | other.push(node);
|
91 | }
|
92 | });
|
93 | }
|
94 |
|
95 | filterProps(node.block);
|
96 |
|
97 | if (props.length) {
|
98 | var selector = new nodes.Selector([new nodes.Literal('&')]);
|
99 | selector.lineno = node.lineno;
|
100 | selector.column = node.column;
|
101 | selector.filename = node.filename;
|
102 | selector.val = '&';
|
103 |
|
104 | var group = new nodes.Group;
|
105 | group.lineno = node.lineno;
|
106 | group.column = node.column;
|
107 | group.filename = node.filename;
|
108 |
|
109 | var block = new nodes.Block(node.block, group);
|
110 | block.lineno = node.lineno;
|
111 | block.column = node.column;
|
112 | block.filename = node.filename;
|
113 |
|
114 | props.forEach(function(prop){
|
115 | block.push(prop);
|
116 | });
|
117 |
|
118 | group.push(selector);
|
119 | group.block = block;
|
120 |
|
121 | node.block.nodes = [];
|
122 | node.block.push(group);
|
123 | other.forEach(function(n){
|
124 | node.block.push(n);
|
125 | });
|
126 |
|
127 | var group = this.closestGroup(node.block);
|
128 | if (group) node.group = group.clone();
|
129 |
|
130 | node.bubbled = true;
|
131 | }
|
132 | };
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 | Normalizer.prototype.closestGroup = function(block){
|
143 | var parent = block.parent
|
144 | , node;
|
145 | while (parent && (node = parent.node)) {
|
146 | if ('group' == node.nodeName) return node;
|
147 | parent = node.block && node.block.parent;
|
148 | }
|
149 | };
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | Normalizer.prototype.visitRoot = function(block){
|
156 | var ret = new nodes.Root
|
157 | , node;
|
158 |
|
159 | for (var i = 0; i < block.nodes.length; ++i) {
|
160 | node = block.nodes[i];
|
161 | switch (node.nodeName) {
|
162 | case 'null':
|
163 | case 'expression':
|
164 | case 'function':
|
165 | case 'unit':
|
166 | case 'atblock':
|
167 | continue;
|
168 | default:
|
169 | this.rootIndex = i;
|
170 | ret.push(this.visit(node));
|
171 | }
|
172 | }
|
173 |
|
174 | return ret;
|
175 | };
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 | Normalizer.prototype.visitProperty = function(prop){
|
182 | this.visit(prop.expr);
|
183 | return prop;
|
184 | };
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 | Normalizer.prototype.visitExpression = function(expr){
|
191 | expr.nodes = expr.nodes.map(function(node){
|
192 |
|
193 |
|
194 | if ('block' == node.nodeName) {
|
195 | var literal = new nodes.Literal('block');
|
196 | literal.lineno = expr.lineno;
|
197 | literal.column = expr.column;
|
198 | return literal;
|
199 | }
|
200 | return node;
|
201 | });
|
202 | return expr;
|
203 | };
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | Normalizer.prototype.visitBlock = function(block){
|
210 | var node;
|
211 |
|
212 | if (block.hasProperties) {
|
213 | for (var i = 0, len = block.nodes.length; i < len; ++i) {
|
214 | node = block.nodes[i];
|
215 | switch (node.nodeName) {
|
216 | case 'null':
|
217 | case 'expression':
|
218 | case 'function':
|
219 | case 'group':
|
220 | case 'unit':
|
221 | case 'atblock':
|
222 | continue;
|
223 | default:
|
224 | block.nodes[i] = this.visit(node);
|
225 | }
|
226 | }
|
227 | }
|
228 |
|
229 |
|
230 | for (var i = 0, len = block.nodes.length; i < len; ++i) {
|
231 | node = block.nodes[i];
|
232 | block.nodes[i] = this.visit(node);
|
233 | }
|
234 |
|
235 | return block;
|
236 | };
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | Normalizer.prototype.visitGroup = function(group){
|
243 | var stack = this.stack
|
244 | , map = this.map
|
245 | , parts;
|
246 |
|
247 |
|
248 | group.nodes.forEach(function(selector, i){
|
249 | if (!~selector.val.indexOf(',')) return;
|
250 | if (~selector.val.indexOf('\\,')) {
|
251 | selector.val = selector.val.replace(/\\,/g, ',');
|
252 | return;
|
253 | }
|
254 | parts = selector.val.split(',');
|
255 | var root = '/' == selector.val.charAt(0)
|
256 | , part, s;
|
257 | for (var k = 0, len = parts.length; k < len; ++k){
|
258 | part = parts[k].trim();
|
259 | if (root && k > 0 && !~part.indexOf('&')) {
|
260 | part = '/' + part;
|
261 | }
|
262 | s = new nodes.Selector([new nodes.Literal(part)]);
|
263 | s.val = part;
|
264 | s.block = group.block;
|
265 | group.nodes[i++] = s;
|
266 | }
|
267 | });
|
268 | stack.push(group.nodes);
|
269 |
|
270 | var selectors = utils.compileSelectors(stack, true);
|
271 |
|
272 |
|
273 | selectors.forEach(function(selector){
|
274 | map[selector] = map[selector] || [];
|
275 | map[selector].push(group);
|
276 | });
|
277 |
|
278 |
|
279 | this.extend(group, selectors);
|
280 |
|
281 | stack.pop();
|
282 | return group;
|
283 | };
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | Normalizer.prototype.visitFunction = function(){
|
290 | return nodes.null;
|
291 | };
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 | Normalizer.prototype.visitMedia = function(media){
|
298 | var medias = []
|
299 | , group = this.closestGroup(media.block)
|
300 | , parent;
|
301 |
|
302 | function mergeQueries(block) {
|
303 | block.nodes.forEach(function(node, i){
|
304 | switch (node.nodeName) {
|
305 | case 'media':
|
306 | node.val = media.val.merge(node.val);
|
307 | medias.push(node);
|
308 | block.nodes[i] = nodes.null;
|
309 | break;
|
310 | case 'block':
|
311 | mergeQueries(node);
|
312 | break;
|
313 | default:
|
314 | if (node.block && node.block.nodes)
|
315 | mergeQueries(node.block);
|
316 | }
|
317 | });
|
318 | }
|
319 |
|
320 | mergeQueries(media.block);
|
321 | this.bubble(media);
|
322 |
|
323 | if (medias.length) {
|
324 | medias.forEach(function(node){
|
325 | if (group) {
|
326 | group.block.push(node);
|
327 | } else {
|
328 | this.root.nodes.splice(++this.rootIndex, 0, node);
|
329 | }
|
330 | node = this.visit(node);
|
331 | parent = node.block.parent;
|
332 | if (node.bubbled && (!group || 'group' == parent.node.nodeName)) {
|
333 | node.group.block = node.block.nodes[0].block;
|
334 | node.block.nodes[0] = node.group;
|
335 | }
|
336 | }, this);
|
337 | }
|
338 | return media;
|
339 | };
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 | Normalizer.prototype.visitSupports = function(node){
|
346 | this.bubble(node);
|
347 | return node;
|
348 | };
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 | Normalizer.prototype.visitAtrule = function(node){
|
355 | if (node.block) node.block = this.visit(node.block);
|
356 | return node;
|
357 | };
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 | Normalizer.prototype.visitKeyframes = function(node){
|
364 | var frames = node.block.nodes.filter(function(frame){
|
365 | return frame.block && frame.block.hasProperties;
|
366 | });
|
367 | node.frames = frames.length;
|
368 | return node;
|
369 | };
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | Normalizer.prototype.visitImport = function(node){
|
376 | this.imports.push(node);
|
377 | return this.hoist ? nodes.null : node;
|
378 | };
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 | Normalizer.prototype.visitCharset = function(node){
|
385 | this.charset = node;
|
386 | return this.hoist ? nodes.null : node;
|
387 | };
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 | Normalizer.prototype.extend = function(group, selectors){
|
398 | var map = this.map
|
399 | , self = this
|
400 | , parent = this.closestGroup(group.block);
|
401 |
|
402 | group.extends.forEach(function(extend){
|
403 | var groups = map[extend.selector];
|
404 | if (!groups) {
|
405 | if (extend.optional) return;
|
406 | var err = new Error('Failed to @extend "' + extend.selector + '"');
|
407 | err.lineno = extend.lineno;
|
408 | err.column = extend.column;
|
409 | throw err;
|
410 | }
|
411 | selectors.forEach(function(selector){
|
412 | var node = new nodes.Selector;
|
413 | node.val = selector;
|
414 | node.inherits = false;
|
415 | groups.forEach(function(group){
|
416 |
|
417 | if (!parent || (parent != group)) self.extend(group, selectors);
|
418 | group.push(node);
|
419 | });
|
420 | });
|
421 | });
|
422 |
|
423 | group.block = this.visit(group.block);
|
424 | };
|