Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(91)

Side by Side Diff: pkg/csslib/lib/src/analyzer.dart

Issue 814113004: Pull args, intl, logging, shelf, and source_maps out of the SDK. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Also csslib. Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/csslib/lib/parser.dart ('k') | pkg/csslib/lib/src/css_printer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of csslib.parser;
6
7
8 // TODO(terry): Add optimizing phase to remove duplicated selectors in the same
9 // selector group (e.g., .btn, .btn { color: red; }). Also, look
10 // at simplifying selectors expressions too (much harder).
11 // TODO(terry): Detect invalid directive usage. All @imports must occur before
12 // all rules other than @charset directive. Any @import directive
13 // after any non @charset or @import directive are ignored. e.g.,
14 // @import "a.css";
15 // div { color: red; }
16 // @import "b.css";
17 // becomes:
18 // @import "a.css";
19 // div { color: red; }
20 // <http://www.w3.org/TR/css3-syntax/#at-rules>
21
22 /**
23 * Analysis phase will validate/fixup any new CSS feature or any SASS style
24 * feature.
25 */
26 class Analyzer {
27 final List<StyleSheet> _styleSheets;
28 final Messages _messages;
29
30 Analyzer(this._styleSheets, this._messages);
31
32 // TODO(terry): Currently each feature walks the AST each time. Once we have
33 // our complete feature set consider benchmarking the cost and
34 // possibly combine in one walk.
35 void run() {
36 // Expand top-level @include.
37 _styleSheets.forEach((styleSheet) =>
38 TopLevelIncludes.expand(_messages, _styleSheets));
39
40 // Expand @include in declarations.
41 _styleSheets.forEach((styleSheet) =>
42 DeclarationIncludes.expand(_messages, _styleSheets));
43
44 // Remove all @mixin and @include
45 _styleSheets.forEach((styleSheet) => MixinsAndIncludes.remove(styleSheet));
46
47 // Expand any nested selectors using selector desendant combinator to
48 // signal CSS inheritance notation.
49 _styleSheets.forEach((styleSheet) => new ExpandNestedSelectors()
50 ..visitStyleSheet(styleSheet)
51 ..flatten(styleSheet));
52
53 // Expand any @extend.
54 _styleSheets.forEach((styleSheet) {
55 var allExtends = new AllExtends()..visitStyleSheet(styleSheet);
56 new InheritExtends(_messages, allExtends)..visitStyleSheet(styleSheet);
57 });
58 }
59 }
60
61 /**
62 * Traverse all rulesets looking for nested ones. If a ruleset is in a
63 * declaration group (implies nested selector) then generate new ruleset(s) at
64 * level 0 of CSS using selector inheritance syntax (flattens the nesting).
65 *
66 * How the AST works for a rule [RuleSet] and nested rules. First of all a
67 * CSS rule [RuleSet] consist of a selector and a declaration e.g.,
68 *
69 * selector {
70 * declaration
71 * }
72 *
73 * AST structure of a [RuleSet] is:
74 *
75 * RuleSet
76 * SelectorGroup
77 * List<Selector>
78 * List<SimpleSelectorSequence>
79 * Combinator // +, >, ~, DESCENDENT, or NONE
80 * SimpleSelector // class, id, element, namespace, attribute
81 * DeclarationGroup
82 * List // Declaration or RuleSet
83 *
84 * For the simple rule:
85 *
86 * div + span { color: red; }
87 *
88 * the AST [RuleSet] is:
89 *
90 * RuleSet
91 * SelectorGroup
92 * List<Selector>
93 * [0]
94 * List<SimpleSelectorSequence>
95 * [0] Combinator = COMBINATOR_NONE
96 * ElementSelector (name = div)
97 * [1] Combinator = COMBINATOR_PLUS
98 * ElementSelector (name = span)
99 * DeclarationGroup
100 * List // Declarations or RuleSets
101 * [0]
102 * Declaration (property = color, expression = red)
103 *
104 * Usually a SelectorGroup contains 1 Selector. Consider the selectors:
105 *
106 * div { color: red; }
107 * a { color: red; }
108 *
109 * are equivalent to
110 *
111 * div, a { color : red; }
112 *
113 * In the above the RuleSet would have a SelectorGroup with 2 selectors e.g.,
114 *
115 * RuleSet
116 * SelectorGroup
117 * List<Selector>
118 * [0]
119 * List<SimpleSelectorSequence>
120 * [0] Combinator = COMBINATOR_NONE
121 * ElementSelector (name = div)
122 * [1]
123 * List<SimpleSelectorSequence>
124 * [0] Combinator = COMBINATOR_NONE
125 * ElementSelector (name = a)
126 * DeclarationGroup
127 * List // Declarations or RuleSets
128 * [0]
129 * Declaration (property = color, expression = red)
130 *
131 * For a nested rule e.g.,
132 *
133 * div {
134 * color : blue;
135 * a { color : red; }
136 * }
137 *
138 * Would map to the follow CSS rules:
139 *
140 * div { color: blue; }
141 * div a { color: red; }
142 *
143 * The AST for the former nested rule is:
144 *
145 * RuleSet
146 * SelectorGroup
147 * List<Selector>
148 * [0]
149 * List<SimpleSelectorSequence>
150 * [0] Combinator = COMBINATOR_NONE
151 * ElementSelector (name = div)
152 * DeclarationGroup
153 * List // Declarations or RuleSets
154 * [0]
155 * Declaration (property = color, expression = blue)
156 * [1]
157 * RuleSet
158 * SelectorGroup
159 * List<Selector>
160 * [0]
161 * List<SimpleSelectorSequence>
162 * [0] Combinator = COMBINATOR_NONE
163 * ElementSelector (name = a)
164 * DeclarationGroup
165 * List // Declarations or RuleSets
166 * [0]
167 * Declaration (property = color, expression = red)
168 *
169 * Nested rules is a terse mechanism to describe CSS inheritance. The analyzer
170 * will flatten and expand the nested rules to it's flatten strucure. Using the
171 * all parent [RuleSets] (selector expressions) and applying each nested
172 * [RuleSet] to the list of [Selectors] in a [SelectorGroup].
173 *
174 * Then result is a style sheet where all nested rules have been flatten and
175 * expanded.
176 */
177 class ExpandNestedSelectors extends Visitor {
178 /** Parent [RuleSet] if a nested rule otherwise [:null:]. */
179 RuleSet _parentRuleSet;
180
181 /** Top-most rule if nested rules. */
182 SelectorGroup _topLevelSelectorGroup;
183
184 /** SelectorGroup at each nesting level. */
185 SelectorGroup _nestedSelectorGroup;
186
187 /** Declaration (sans the nested selectors). */
188 DeclarationGroup _flatDeclarationGroup;
189
190 /** Each nested selector get's a flatten RuleSet. */
191 List<RuleSet> _expandedRuleSets = [];
192
193 /** Maping of a nested rule set to the fully expanded list of RuleSet(s). */
194 final Map<RuleSet, List<RuleSet>> _expansions = new Map();
195
196 void visitRuleSet(RuleSet node) {
197 final oldParent = _parentRuleSet;
198
199 var oldNestedSelectorGroups = _nestedSelectorGroup;
200
201 if (_nestedSelectorGroup == null) {
202 // Create top-level selector (may have nested rules).
203 final newSelectors = node.selectorGroup.selectors.toList();
204 _topLevelSelectorGroup = new SelectorGroup(newSelectors, node.span);
205 _nestedSelectorGroup = _topLevelSelectorGroup;
206 } else {
207 // Generate new selector groups from the nested rules.
208 _nestedSelectorGroup = _mergeToFlatten(node);
209 }
210
211 _parentRuleSet = node;
212
213 super.visitRuleSet(node);
214
215 _parentRuleSet = oldParent;
216
217 // Remove nested rules; they're all flatten and in the _expandedRuleSets.
218 node.declarationGroup.declarations.removeWhere((declaration) =>
219 declaration is RuleSet);
220
221 _nestedSelectorGroup = oldNestedSelectorGroups;
222
223 // If any expandedRuleSets and we're back at the top-level rule set then
224 // there were nested rule set(s).
225 if (_parentRuleSet == null) {
226 if (!_expandedRuleSets.isEmpty) {
227 // Remember ruleset to replace with these flattened rulesets.
228 _expansions[node] = _expandedRuleSets;
229 _expandedRuleSets = [];
230 }
231 assert(_flatDeclarationGroup == null);
232 assert(_nestedSelectorGroup == null);
233 }
234 }
235
236 /**
237 * Build up the list of all inherited sequences from the parent selector
238 * [node] is the current nested selector and it's parent is the last entry in
239 * the [_nestedSelectorGroup].
240 */
241 SelectorGroup _mergeToFlatten(RuleSet node) {
242 // Create a new SelectorGroup for this nesting level.
243 var nestedSelectors = _nestedSelectorGroup.selectors;
244 var selectors = node.selectorGroup.selectors;
245
246 // Create a merged set of previous parent selectors and current selectors.
247 var newSelectors = [];
248 for (Selector selector in selectors) {
249 for (Selector nestedSelector in nestedSelectors) {
250 var seq = _mergeNestedSelector(nestedSelector.simpleSelectorSequences,
251 selector.simpleSelectorSequences);
252 newSelectors.add(new Selector(seq, node.span));
253 }
254 }
255
256 return new SelectorGroup(newSelectors, node.span);
257 }
258
259 /**
260 * Merge the nested selector sequences [current] to the [parent] sequences or
261 * substitue any & with the parent selector.
262 */
263 List<SimpleSelectorSequence> _mergeNestedSelector(
264 List<SimpleSelectorSequence> parent,
265 List<SimpleSelectorSequence> current) {
266
267 // If any & operator then the parent selector will be substituted otherwise
268 // the parent selector is pre-pended to the current selector.
269 var hasThis = current.any((s) => s.simpleSelector.isThis);
270
271 var newSequence = [];
272
273 if (!hasThis) {
274 // If no & in the sector group then prefix with the parent selector.
275 newSequence.addAll(parent);
276 newSequence.addAll(_convertToDescendentSequence(current));
277 } else {
278 for (var sequence in current) {
279 if (sequence.simpleSelector.isThis) {
280 // Substitue the & with the parent selector and only use a combinator
281 // descendant if & is prefix by a sequence with an empty name e.g.,
282 // "... + &", "&", "... ~ &", etc.
283 var hasPrefix = !newSequence.isEmpty &&
284 !newSequence.last.simpleSelector.name.isEmpty;
285 newSequence.addAll(
286 hasPrefix ? _convertToDescendentSequence(parent) : parent);
287 } else {
288 newSequence.add(sequence);
289 }
290 }
291 }
292
293 return newSequence;
294 }
295
296 /**
297 * Return selector sequences with first sequence combinator being a
298 * descendant. Used for nested selectors when the parent selector needs to
299 * be prefixed to a nested selector or to substitute the this (&) with the
300 * parent selector.
301 */
302 List<SimpleSelectorSequence> _convertToDescendentSequence(
303 List<SimpleSelectorSequence> sequences) {
304 if (sequences.isEmpty) return sequences;
305
306 var newSequences = [];
307 var first = sequences.first;
308 newSequences.add(new SimpleSelectorSequence(first.simpleSelector,
309 first.span, TokenKind.COMBINATOR_DESCENDANT));
310 newSequences.addAll(sequences.skip(1));
311
312 return newSequences;
313 }
314
315 void visitDeclarationGroup(DeclarationGroup node) {
316 var span = node.span;
317
318 var currentGroup = new DeclarationGroup([], span);
319
320 var oldGroup = _flatDeclarationGroup;
321 _flatDeclarationGroup = currentGroup;
322
323 var expandedLength = _expandedRuleSets.length;
324
325 super.visitDeclarationGroup(node);
326
327 // We're done with the group.
328 _flatDeclarationGroup = oldGroup;
329
330 // No nested rule to process it's a top-level rule.
331 if (_nestedSelectorGroup == _topLevelSelectorGroup) return;
332
333 // If flatten selector's declaration is empty skip this selector, no need
334 // to emit an empty nested selector.
335 if (currentGroup.declarations.isEmpty) return;
336
337 var selectorGroup = _nestedSelectorGroup;
338
339 // Build new rule set from the nested selectors and declarations.
340 var newRuleSet = new RuleSet(selectorGroup, currentGroup, span);
341
342 // Place in order so outer-most rule is first.
343 if (expandedLength == _expandedRuleSets.length) {
344 _expandedRuleSets.add(newRuleSet);
345 } else {
346 _expandedRuleSets.insert(expandedLength, newRuleSet);
347 }
348 }
349
350 // Record all declarations in a nested selector (Declaration, VarDefinition
351 // and MarginGroup) but not the nested rule in the Declaration.
352
353 void visitDeclaration(Declaration node) {
354 if (_parentRuleSet != null) {
355 _flatDeclarationGroup.declarations.add(node);
356 }
357 super.visitDeclaration(node);
358 }
359
360 void visitVarDefinition(VarDefinition node) {
361 if (_parentRuleSet != null) {
362 _flatDeclarationGroup.declarations.add(node);
363 }
364 super.visitVarDefinition(node);
365 }
366
367 void visitExtendDeclaration(ExtendDeclaration node) {
368 if (_parentRuleSet != null) {
369 _flatDeclarationGroup.declarations.add(node);
370 }
371 super.visitExtendDeclaration(node);
372 }
373
374 void visitMarginGroup(MarginGroup node) {
375 if (_parentRuleSet != null) {
376 _flatDeclarationGroup.declarations.add(node);
377 }
378 super.visitMarginGroup(node);
379 }
380
381 /**
382 * Replace the rule set that contains nested rules with the flatten rule sets.
383 */
384 void flatten(StyleSheet styleSheet) {
385 // TODO(terry): Iterate over topLevels instead of _expansions it's already
386 // a map (this maybe quadratic).
387 _expansions.forEach((RuleSet ruleSet, List<RuleSet> newRules) {
388 var index = styleSheet.topLevels.indexOf(ruleSet);
389 if (index == -1) {
390 // Check any @media directives for nested rules and replace them.
391 var found = _MediaRulesReplacer.replace(styleSheet, ruleSet, newRules);
392 assert(found);
393 } else {
394 styleSheet.topLevels.insertAll(index + 1, newRules);
395 }
396 });
397 _expansions.clear();
398 }
399 }
400
401 class _MediaRulesReplacer extends Visitor {
402 RuleSet _ruleSet;
403 List<RuleSet> _newRules;
404 bool _foundAndReplaced = false;
405
406 /**
407 * Look for the [ruleSet] inside of an @media directive; if found then replace
408 * with the [newRules]. If [ruleSet] is found and replaced return true.
409 */
410 static bool replace(StyleSheet styleSheet, RuleSet ruleSet,
411 List<RuleSet>newRules) {
412 var visitor = new _MediaRulesReplacer(ruleSet, newRules);
413 visitor.visitStyleSheet(styleSheet);
414 return visitor._foundAndReplaced;
415 }
416
417 _MediaRulesReplacer(this._ruleSet, this._newRules);
418
419 visitMediaDirective(MediaDirective node) {
420 var index = node.rulesets.indexOf(_ruleSet);
421 if (index != -1) {
422 node.rulesets.insertAll(index + 1, _newRules);
423 _foundAndReplaced = true;
424 }
425 }
426 }
427
428 /**
429 * Expand all @include at the top-level the ruleset(s) associated with the
430 * mixin.
431 */
432 class TopLevelIncludes extends Visitor {
433 StyleSheet _styleSheet;
434 final Messages _messages;
435 /** Map of variable name key to it's definition. */
436 final Map<String, MixinDefinition> map = new Map<String, MixinDefinition>();
437 MixinDefinition currDef;
438
439 static void expand(Messages messages, List<StyleSheet> styleSheets) {
440 new TopLevelIncludes(messages, styleSheets);
441 }
442
443 bool _anyRulesets(MixinRulesetDirective def) =>
444 def.rulesets.any((rule) => rule is RuleSet);
445
446 TopLevelIncludes(this._messages, List<StyleSheet> styleSheets) {
447 for (var styleSheet in styleSheets) {
448 visitTree(styleSheet);
449 }
450 }
451
452 void visitStyleSheet(StyleSheet ss) {
453 _styleSheet = ss;
454 super.visitStyleSheet(ss);
455 _styleSheet = null;
456 }
457
458 void visitIncludeDirective(IncludeDirective node) {
459 if (map.containsKey(node.name)) {
460 var mixinDef = map[node.name];
461 if (mixinDef is MixinRulesetDirective) {
462 _TopLevelIncludeReplacer.replace(_messages, _styleSheet, node,
463 mixinDef.rulesets);
464 } else if (currDef is MixinRulesetDirective && _anyRulesets(currDef)) {
465 // currDef is MixinRulesetDirective
466 MixinRulesetDirective mixinRuleset = currDef;
467 int index = mixinRuleset.rulesets.indexOf(node as dynamic);
468 mixinRuleset.rulesets.replaceRange(index, index + 1, [new NoOp()]);
469 _messages.warning(
470 'Using declaration mixin ${node.name} as top-level mixin',
471 node.span);
472 }
473 } else {
474 if (currDef is MixinRulesetDirective) {
475 MixinRulesetDirective rulesetDirect = currDef as MixinRulesetDirective;
476 var index = 0;
477 rulesetDirect.rulesets.forEach((entry) {
478 if (entry == node) {
479 rulesetDirect.rulesets.replaceRange(index, index + 1, [new NoOp()]);
480 _messages.warning('Undefined mixin ${node.name}', node.span);
481 }
482 index++;
483 });
484 }
485 }
486 super.visitIncludeDirective(node);
487 }
488
489 void visitMixinRulesetDirective(MixinRulesetDirective node) {
490 currDef = node;
491
492 super.visitMixinRulesetDirective(node);
493
494 // Replace with latest top-level mixin definition.
495 map[node.name] = node;
496 currDef = null;
497 }
498
499 void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
500 currDef = node;
501
502 super.visitMixinDeclarationDirective(node);
503
504 // Replace with latest mixin definition.
505 map[node.name] = node;
506 currDef = null;
507 }
508 }
509
510 /** @include as a top-level with ruleset(s). */
511 class _TopLevelIncludeReplacer extends Visitor {
512 final Messages _messages;
513 final IncludeDirective _include;
514 final List<RuleSet> _newRules;
515 bool _foundAndReplaced = false;
516
517 /**
518 * Look for the [ruleSet] inside of an @media directive; if found then replace
519 * with the [newRules]. If [ruleSet] is found and replaced return true.
520 */
521 static bool replace(Messages messages, StyleSheet styleSheet,
522 IncludeDirective include, List<RuleSet>newRules) {
523 var visitor = new _TopLevelIncludeReplacer(messages, include, newRules);
524 visitor.visitStyleSheet(styleSheet);
525 return visitor._foundAndReplaced;
526 }
527
528 _TopLevelIncludeReplacer(this._messages, this._include, this._newRules);
529
530 visitStyleSheet(StyleSheet node) {
531 var index = node.topLevels.indexOf(_include);
532 if (index != -1) {
533 node.topLevels.insertAll(index + 1, _newRules);
534 node.topLevels.replaceRange(index, index + 1, [new NoOp()]);
535 _foundAndReplaced = true;
536 }
537 super.visitStyleSheet(node);
538 }
539
540 void visitMixinRulesetDirective(MixinRulesetDirective node) {
541 var index = node.rulesets.indexOf(_include as dynamic);
542 if (index != -1) {
543 node.rulesets.insertAll(index + 1, _newRules);
544 // Only the resolve the @include once.
545 node.rulesets.replaceRange(index, index + 1, [new NoOp()]);
546 _foundAndReplaced = true;
547 }
548 super.visitMixinRulesetDirective(node);
549 }
550 }
551
552 /**
553 * Utility function to match an include to a list of either Declarations or
554 * RuleSets, depending on type of mixin (ruleset or declaration). The include
555 * can be an include in a declaration or an include directive (top-level).
556 */
557 int _findInclude(List list, var node) {
558 IncludeDirective matchNode = (node is IncludeMixinAtDeclaration) ?
559 node.include : node;
560
561 var index = 0;
562 for (var item in list) {
563 var includeNode = (item is IncludeMixinAtDeclaration) ?
564 item.include : item;
565 if (includeNode == matchNode) return index;
566 index++;
567 }
568 return -1;
569 }
570
571 /**
572 * Stamp out a mixin with the defined args substituted with the user's
573 * parameters.
574 */
575 class CallMixin extends Visitor {
576 final MixinDefinition mixinDef;
577 List _definedArgs;
578 Expressions _currExpressions;
579 int _currIndex = -1;
580
581 final varUsages = new Map<String, Map<Expressions, Set<int>>>();
582
583 /** Only var defs with more than one expression (comma separated). */
584 final Map<String, VarDefinition> varDefs;
585
586 CallMixin(this.mixinDef, [this.varDefs]) {
587 if (mixinDef is MixinRulesetDirective) {
588 visitMixinRulesetDirective(mixinDef);
589 } else {
590 visitMixinDeclarationDirective(mixinDef);
591 }
592 }
593
594 /**
595 * Given a mixin's defined arguments return a cloned mixin defintion that has
596 * replaced all defined arguments with user's supplied VarUsages.
597 */
598 MixinDefinition transform(List<TreeNode> callArgs) {
599 // TODO(terry): Handle default arguments and varArgs.
600 // Transform mixin with callArgs.
601 var index = 0;
602 for (var index = 0; index < _definedArgs.length; index++) {
603 var definedArg = _definedArgs[index];
604 VarDefinition varDef;
605 if (definedArg is VarDefinition) {
606 varDef = definedArg;
607 } else if (definedArg is VarDefinitionDirective) {
608 VarDefinitionDirective varDirective = definedArg;
609 varDef = varDirective.def;
610 }
611 var callArg = callArgs[index];
612
613 // Is callArg a var definition with multi-args (expressions > 1).
614 var defArgs = _varDefsAsCallArgs(callArg);
615 if (defArgs.isNotEmpty) {
616 // Replace call args with the var def parameters.
617 callArgs.insertAll(index, defArgs);
618 callArgs.removeAt(index + defArgs.length);
619 callArg = callArgs[index];
620 }
621
622 var expressions = varUsages[varDef.definedName];
623 expressions.forEach((k, v) {
624 for (var usagesIndex in v) {
625 k.expressions.replaceRange(usagesIndex, usagesIndex + 1, callArg);
626 }
627 });
628 }
629
630 // Clone the mixin
631 return mixinDef.clone();
632 }
633
634 /** Rip apart var def with multiple parameters. */
635 List<List<TreeNode>> _varDefsAsCallArgs(var callArg) {
636 var defArgs = [];
637 if (callArg is List && callArg[0] is VarUsage) {
638 var varDef = varDefs[callArg[0].name];
639 var expressions = varDef.expression.expressions;
640 assert(expressions.length > 1);
641 for (var expr in expressions) {
642 if (expr is! OperatorComma) {
643 defArgs.add([expr]);
644 }
645 }
646 }
647 return defArgs;
648 }
649
650 void visitExpressions(Expressions node) {
651 var oldExpressions = _currExpressions;
652 var oldIndex = _currIndex;
653
654 _currExpressions = node;
655 for (_currIndex = 0; _currIndex < node.expressions.length; _currIndex++) {
656 node.expressions[_currIndex].visit(this);
657 }
658
659 _currIndex = oldIndex;
660 _currExpressions = oldExpressions;
661 }
662
663 void _addExpression(Map<Expressions, Set<int>> expressions) {
664 var indexSet = new Set<int>();
665 indexSet.add(_currIndex);
666 expressions[_currExpressions] = indexSet;
667 }
668
669 void visitVarUsage(VarUsage node) {
670 assert(_currIndex != -1);
671 assert(_currExpressions != null);
672 if (varUsages.containsKey(node.name)) {
673 Map<Expressions, Set<int>> expressions = varUsages[node.name];
674 Set<int> allIndexes = expressions[_currExpressions];
675 if (allIndexes == null) {
676 _addExpression(expressions);
677 } else {
678 allIndexes.add(_currIndex);
679 }
680 } else {
681 var newExpressions = new Map<Expressions, Set<int>>();
682 _addExpression(newExpressions);
683 varUsages[node.name] = newExpressions;
684 }
685 super.visitVarUsage(node);
686 }
687
688 void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
689 _definedArgs = node.definedArgs;
690 super.visitMixinDeclarationDirective(node);
691 }
692
693 void visitMixinRulesetDirective(MixinRulesetDirective node) {
694 _definedArgs = node.definedArgs;
695 super.visitMixinRulesetDirective(node);
696 }
697 }
698
699 /** Expand all @include inside of a declaration associated with a mixin. */
700 class DeclarationIncludes extends Visitor {
701 StyleSheet _styleSheet;
702 final Messages _messages;
703 /** Map of variable name key to it's definition. */
704 final Map<String, MixinDefinition> map = new Map<String, MixinDefinition>();
705 /** Cache of mixin called with parameters. */
706 final Map<String, CallMixin> callMap = new Map<String, CallMixin>();
707 MixinDefinition currDef;
708 DeclarationGroup currDeclGroup;
709
710 /** Var definitions with more than 1 expression. */
711 final Map<String, VarDefinition> varDefs = new Map<String, VarDefinition>();
712
713 static void expand(Messages messages, List<StyleSheet> styleSheets) {
714 new DeclarationIncludes(messages, styleSheets);
715 }
716
717 DeclarationIncludes(this._messages, List<StyleSheet> styleSheets) {
718 for (var styleSheet in styleSheets) {
719 visitTree(styleSheet);
720 }
721 }
722
723 bool _allIncludes(rulesets) =>
724 rulesets.every((rule) => rule is IncludeDirective || rule is NoOp);
725
726 CallMixin _createCallDeclMixin(MixinDefinition mixinDef) {
727 callMap.putIfAbsent(mixinDef.name, () =>
728 callMap[mixinDef.name] = new CallMixin(mixinDef, varDefs));
729 return callMap[mixinDef.name];
730 }
731
732 void visitStyleSheet(StyleSheet ss) {
733 _styleSheet = ss;
734 super.visitStyleSheet(ss);
735 _styleSheet = null;
736 }
737
738 void visitDeclarationGroup(DeclarationGroup node) {
739 currDeclGroup = node;
740 super.visitDeclarationGroup(node);
741 currDeclGroup = null;
742 }
743
744 void visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
745 if (map.containsKey(node.include.name)) {
746 var mixinDef = map[node.include.name];
747
748 // Fix up any mixin that is really a Declaration but has includes.
749 if (mixinDef is MixinRulesetDirective) {
750 if (!_allIncludes(mixinDef.rulesets) && currDeclGroup != null) {
751 var index = _findInclude(currDeclGroup.declarations, node);
752 if (index != -1) {
753 currDeclGroup.declarations.replaceRange(index, index + 1,
754 [new NoOp()]);
755 }
756 _messages.warning(
757 "Using top-level mixin ${node.include.name} as a declaration",
758 node.span);
759 } else {
760 // We're a list of @include(s) inside of a mixin ruleset - convert
761 // to a list of IncludeMixinAtDeclaration(s).
762 var origRulesets = mixinDef.rulesets;
763 var rulesets = [];
764 if (origRulesets.every((ruleset) => ruleset is IncludeDirective)) {
765 origRulesets.forEach((ruleset) {
766 rulesets.add(new IncludeMixinAtDeclaration(ruleset,
767 ruleset.span));
768 });
769 _IncludeReplacer.replace(_styleSheet, node, rulesets);
770 }
771 }
772 }
773
774 if ( mixinDef.definedArgs.length > 0 && node.include.args.length > 0) {
775 var callMixin = _createCallDeclMixin(mixinDef);
776 mixinDef = callMixin.transform(node.include.args);
777 }
778
779 if (mixinDef is MixinDeclarationDirective) {
780 _IncludeReplacer.replace(_styleSheet, node,
781 mixinDef.declarations.declarations);
782 }
783 } else {
784 _messages.warning("Undefined mixin ${node.include.name}", node.span);
785 }
786
787 super.visitIncludeMixinAtDeclaration(node);
788 }
789
790 void visitIncludeDirective(IncludeDirective node) {
791 if (map.containsKey(node.name)) {
792 var mixinDef = map[node.name];
793 if (currDef is MixinDeclarationDirective &&
794 mixinDef is MixinDeclarationDirective) {
795 _IncludeReplacer.replace(_styleSheet, node,
796 mixinDef.declarations.declarations);
797 } else if (currDef is MixinDeclarationDirective) {
798 var decls = (currDef as MixinDeclarationDirective)
799 .declarations.declarations;
800 var index = _findInclude(decls, node);
801 if (index != -1) {
802 decls.replaceRange(index, index + 1, [new NoOp()]);
803 }
804 }
805 }
806
807 super.visitIncludeDirective(node);
808 }
809
810 void visitMixinRulesetDirective(MixinRulesetDirective node) {
811 currDef = node;
812
813 super.visitMixinRulesetDirective(node);
814
815 // Replace with latest top-level mixin definition.
816 map[node.name] = node;
817 currDef = null;
818 }
819
820 void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
821 currDef = node;
822
823 super.visitMixinDeclarationDirective(node);
824
825 // Replace with latest mixin definition.
826 map[node.name] = node;
827 currDef = null;
828 }
829
830 void visitVarDefinition(VarDefinition node) {
831 // Only record var definitions that have multiple expressions (comma
832 // separated for mixin parameter substitution.
833 var exprs = (node.expression as Expressions).expressions;
834 if (exprs.length > 1) {
835 varDefs[node.definedName] = node;
836 }
837 super.visitVarDefinition(node);
838 }
839
840 void visitVarDefinitionDirective(VarDefinitionDirective node) {
841 visitVarDefinition(node.def);
842 }
843 }
844
845 /** @include as a top-level with ruleset(s). */
846 class _IncludeReplacer extends Visitor {
847 final _include;
848 final List<Declaration> _newDeclarations;
849 bool _foundAndReplaced = false;
850
851 /**
852 * Look for the [ruleSet] inside of a @media directive; if found then replace
853 * with the [newRules].
854 */
855 static void replace(StyleSheet ss, var include,
856 List<Declaration> newDeclarations) {
857 var visitor = new _IncludeReplacer(include, newDeclarations);
858 visitor.visitStyleSheet(ss);
859 }
860
861 _IncludeReplacer(this._include, this._newDeclarations);
862
863 void visitDeclarationGroup(DeclarationGroup node) {
864 var index = _findInclude(node.declarations, _include);
865 if (index != -1) {
866 node.declarations.insertAll(index + 1, _newDeclarations);
867 // Change @include to NoOp so it's processed only once.
868 node.declarations.replaceRange(index, index + 1, [new NoOp()]);
869 _foundAndReplaced = true;
870 }
871 super.visitDeclarationGroup(node);
872 }
873 }
874
875 /**
876 * Remove all @mixin and @include and any NoOp used as placeholder for @include.
877 */
878 class MixinsAndIncludes extends Visitor {
879 static void remove(StyleSheet styleSheet) {
880 new MixinsAndIncludes()..visitStyleSheet(styleSheet);
881 }
882
883 bool _nodesToRemove(node) =>
884 node is IncludeDirective || node is MixinDefinition || node is NoOp;
885
886 void visitStyleSheet(StyleSheet ss) {
887 var index = ss.topLevels.length;
888 while (--index >= 0) {
889 if (_nodesToRemove(ss.topLevels[index])) {
890 ss.topLevels.removeAt(index);
891 }
892 }
893 super.visitStyleSheet(ss);
894 }
895
896 void visitDeclarationGroup(DeclarationGroup node) {
897 var index = node.declarations.length;
898 while (--index >= 0) {
899 if (_nodesToRemove(node.declarations[index])) {
900 node.declarations.removeAt(index);
901 }
902 }
903 super.visitDeclarationGroup(node);
904 }
905 }
906
907 /** Find all @extend to create inheritance. */
908 class AllExtends extends Visitor {
909 final Map<String, List<SelectorGroup>> inherits =
910 new Map<String, List<SelectorGroup>>();
911
912 SelectorGroup _currSelectorGroup;
913 List _currDecls;
914 int _currDeclIndex;
915 List<int> _extendsToRemove = [];
916
917 void visitRuleSet(RuleSet node) {
918 var oldSelectorGroup = _currSelectorGroup;
919 _currSelectorGroup = node.selectorGroup;
920
921 super.visitRuleSet(node);
922
923 _currSelectorGroup = oldSelectorGroup;
924 }
925
926 void visitExtendDeclaration(ExtendDeclaration node) {
927 var inheritName = "";
928 for (var selector in node.selectors) {
929 inheritName += selector.toString();
930 }
931 if (inherits.containsKey(inheritName)) {
932 inherits[inheritName].add(_currSelectorGroup);
933 } else {
934 inherits[inheritName] = [_currSelectorGroup];
935 }
936
937 // Remove this @extend
938 _extendsToRemove.add(_currDeclIndex);
939
940 super.visitExtendDeclaration(node);
941 }
942
943 void visitDeclarationGroup(DeclarationGroup node) {
944 var oldDeclIndex = _currDeclIndex;
945
946 var decls = node.declarations;
947 for (_currDeclIndex = 0; _currDeclIndex < decls.length; _currDeclIndex++) {
948 decls[_currDeclIndex].visit(this);
949 }
950
951 if (_extendsToRemove.isNotEmpty) {
952 var removeTotal = _extendsToRemove.length - 1;
953 for (var index = removeTotal; index >= 0; index--) {
954 decls.removeAt(_extendsToRemove[index]);
955 }
956 _extendsToRemove.clear();
957 }
958
959 _currDeclIndex = oldDeclIndex;
960 }
961 }
962
963 // TODO(terry): Need to handle merging selector sequences
964 // TODO(terry): Need to handle @extend-Only selectors.
965 // TODO(terry): Need to handle !optional glag.
966 /**
967 * Changes any selector that matches @extend.
968 */
969 class InheritExtends extends Visitor {
970 final Messages _messages;
971 final AllExtends _allExtends;
972
973 InheritExtends(this._messages, this._allExtends);
974
975 void visitSelectorGroup(SelectorGroup node) {
976 for (var selectorsIndex = 0; selectorsIndex < node.selectors.length;
977 selectorsIndex++) {
978 var selectors = node.selectors[selectorsIndex];
979 var isLastNone = false;
980 var selectorName = "";
981 for (var index = 0; index < selectors.simpleSelectorSequences.length;
982 index++) {
983 var simpleSeq = selectors.simpleSelectorSequences[index];
984 var namePart = simpleSeq.simpleSelector.toString();
985 selectorName = (isLastNone) ? (selectorName + namePart) : namePart;
986 List<SelectorGroup> matches = _allExtends.inherits[selectorName];
987 if (matches != null) {
988 for (var match in matches) {
989 // Create a new group.
990 var newSelectors = selectors.clone();
991 var newSeq = match.selectors[0].clone();
992 if (isLastNone) {
993 // Add the inherited selector.
994 node.selectors.add(newSeq);
995 } else {
996 // Replace the selector sequence to the left of the pseudo class
997 // or pseudo element.
998
999 // Make new selector seq combinator the same as the original.
1000 var orgCombinator =
1001 newSelectors.simpleSelectorSequences[index].combinator;
1002 newSeq.simpleSelectorSequences[0].combinator = orgCombinator;
1003
1004 newSelectors.simpleSelectorSequences.replaceRange(index,
1005 index + 1, newSeq.simpleSelectorSequences);
1006 node.selectors.add(newSelectors);
1007 }
1008 isLastNone = false;
1009 }
1010 } else {
1011 isLastNone = simpleSeq.isCombinatorNone;
1012 }
1013 }
1014 }
1015 super.visitSelectorGroup(node);
1016 }
1017 }
OLDNEW
« no previous file with comments | « pkg/csslib/lib/parser.dart ('k') | pkg/csslib/lib/src/css_printer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698