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

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

Issue 23819036: Support for @mixin, @include and @extend (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: All changes ready to commit Created 7 years, 2 months 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
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of csslib.parser; 5 part of csslib.parser;
6 6
7 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).
8 // TODO(terry): Detect invalid directive usage. All @imports must occur before 11 // TODO(terry): Detect invalid directive usage. All @imports must occur before
9 // all rules other than @charset directive. Any @import directive 12 // all rules other than @charset directive. Any @import directive
10 // after any non @charset or @import directive are ignored. e.g., 13 // after any non @charset or @import directive are ignored. e.g.,
11 // @import "a.css"; 14 // @import "a.css";
12 // div { color: red; } 15 // div { color: red; }
13 // @import "b.css"; 16 // @import "b.css";
14 // becomes: 17 // becomes:
15 // @import "a.css"; 18 // @import "a.css";
16 // div { color: red; } 19 // div { color: red; }
17 // <http://www.w3.org/TR/css3-syntax/#at-rules> 20 // <http://www.w3.org/TR/css3-syntax/#at-rules>
18 21
19 /** 22 /**
20 * Analysis phase will validate/fixup any new CSS feature or any SASS style 23 * Analysis phase will validate/fixup any new CSS feature or any SASS style
21 * feature. 24 * feature.
22 */ 25 */
23 class Analyzer { 26 class Analyzer {
24 final List<StyleSheet> _styleSheets; 27 final List<StyleSheet> _styleSheets;
25 final Messages _messages; 28 final Messages _messages;
26 VarDefinitions varDefs;
27 29
28 Analyzer(this._styleSheets, this._messages); 30 Analyzer(this._styleSheets, this._messages);
29 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.
30 void run() { 35 void run() {
31 varDefs = new VarDefinitions(_styleSheets); 36 // Expand top-level @include.
37 _styleSheets.forEach((styleSheet) =>
38 TopLevelIncludes.expand(_messages, _styleSheets));
32 39
33 // Any cycles? 40 // Expand @include in declarations.
34 var cycles = findAllCycles(); 41 _styleSheets.forEach((styleSheet) =>
35 for (var cycle in cycles) { 42 DeclarationIncludes.expand(_messages, _styleSheets));
36 _messages.warning("var cycle detected var-${cycle.definedName}",
37 cycle.span);
38 // TODO(terry): What if no var definition for a var usage an error?
39 // TODO(terry): Ensure a var definition imported from a different style
40 // sheet works.
41 }
42 43
43 // Remove any var definition from the stylesheet that has a cycle. 44 // Remove all @mixin and @include
44 _styleSheets.forEach((styleSheet) => 45 _styleSheets.forEach((styleSheet) => MixinsAndIncludes.remove(styleSheet));
45 new RemoveVarDefinitions(cycles).visitStyleSheet(styleSheet));
46 46
47 // Expand any nested selectors using selector desendant combinator to 47 // Expand any nested selectors using selector desendant combinator to
48 // signal CSS inheritance notation. 48 // signal CSS inheritance notation.
49 _styleSheets.forEach((styleSheet) => new ExpandNestedSelectors() 49 _styleSheets.forEach((styleSheet) => new ExpandNestedSelectors()
50 ..visitStyleSheet(styleSheet) 50 ..visitStyleSheet(styleSheet)
51 ..flatten(styleSheet)); 51 ..flatten(styleSheet));
52 }
53 52
54 List<VarDefinition> findAllCycles() { 53 // Expand any @extend.
55 var cycles = []; 54 _styleSheets.forEach((styleSheet) {
56 55 var allExtends = new AllExtends()..visitStyleSheet(styleSheet);
57 varDefs.map.values.forEach((value) { 56 new InheritExtends(_messages, allExtends)..visitStyleSheet(styleSheet);
58 if (hasCycle(value.property)) cycles.add(value); 57 });
59 });
60
61 // Update our local list of known varDefs remove any varDefs with a cycle.
62 // So the same varDef cycle isn't reported for each style sheet processed.
63 for (var cycle in cycles) {
64 varDefs.map.remove(cycle.property);
65 }
66
67 return cycles;
68 }
69
70 Iterable<VarUsage> variablesOf(Expressions exprs) =>
71 exprs.expressions.where((e) => e is VarUsage);
72
73 bool hasCycle(String varName, {Set<String> visiting, Set<String> visited}) {
74 if (visiting == null) visiting = new Set();
75 if (visited == null) visited = new Set();
76 if (visiting.contains(varName)) return true;
77 if (visited.contains(varName)) return false;
78 visiting.add(varName);
79 visited.add(varName);
80 bool cycleDetected = false;
81 if (varDefs.map[varName] != null) {
82 for (var usage in variablesOf(varDefs.map[varName].expression)) {
83 if (hasCycle(usage.name, visiting: visiting, visited: visited)) {
84 cycleDetected = true;
85 break;
86 }
87 }
88 }
89 visiting.remove(varName);
90 return cycleDetected;
91 }
92
93 // TODO(terry): Need to start supporting @host, custom pseudo elements,
94 // composition, intrinsics, etc.
95 }
96
97
98 /** Find all var definitions from a list of stylesheets. */
99 class VarDefinitions extends Visitor {
100 /** Map of variable name key to it's definition. */
101 final Map<String, VarDefinition> map = new Map<String, VarDefinition>();
102
103 VarDefinitions(List<StyleSheet> styleSheets) {
104 for (var styleSheet in styleSheets) {
105 visitTree(styleSheet);
106 }
107 }
108
109 void visitVarDefinition(VarDefinition node) {
110 // Replace with latest variable definition.
111 map[node.definedName] = node;
112 super.visitVarDefinition(node);
113 }
114
115 void visitVarDefinitionDirective(VarDefinitionDirective node) {
116 visitVarDefinition(node.def);
117 } 58 }
118 } 59 }
119 60
120 /**
121 * Remove the var definition from the stylesheet where it is defined; if it is
122 * a definition from the list to delete.
123 */
124 class RemoveVarDefinitions extends Visitor {
125 final List<VarDefinition> _varDefsToRemove;
126
127 RemoveVarDefinitions(this._varDefsToRemove);
128
129 void visitStyleSheet(StyleSheet ss) {
130 var idx = ss.topLevels.length;
131 while(--idx >= 0) {
132 var topLevel = ss.topLevels[idx];
133 if (topLevel is VarDefinitionDirective &&
134 _varDefsToRemove.contains(topLevel.def)) {
135 ss.topLevels.removeAt(idx);
136 }
137 }
138
139 super.visitStyleSheet(ss);
140 }
141
142 void visitDeclarationGroup(DeclarationGroup node) {
143 var idx = node.declarations.length;
144 while (--idx >= 0) {
145 var decl = node.declarations[idx];
146 if (decl is VarDefinition && _varDefsToRemove.contains(decl)) {
147 node.declarations.removeAt(idx);
148 }
149 }
150
151 super.visitDeclarationGroup(node);
152 }
153 }
154
155 /** 61 /**
156 * Traverse all rulesets looking for nested ones. If a ruleset is in a 62 * Traverse all rulesets looking for nested ones. If a ruleset is in a
157 * declaration group (implies nested selector) then generate new ruleset(s) at 63 * declaration group (implies nested selector) then generate new ruleset(s) at
158 * level 0 of CSS using selector inheritance syntax (flattens the nesting). 64 * level 0 of CSS using selector inheritance syntax (flattens the nesting).
159 * 65 *
160 * How the AST works for a rule [RuleSet] and nested rules. First of all a 66 * How the AST works for a rule [RuleSet] and nested rules. First of all a
161 * CSS rule [RuleSet] consist of a selector and a declaration e.g., 67 * CSS rule [RuleSet] consist of a selector and a declaration e.g.,
162 * 68 *
163 * selector { 69 * selector {
164 * declaration 70 * declaration
(...skipping 286 matching lines...) Expand 10 before | Expand all | Expand 10 after
451 super.visitDeclaration(node); 357 super.visitDeclaration(node);
452 } 358 }
453 359
454 void visitVarDefinition(VarDefinition node) { 360 void visitVarDefinition(VarDefinition node) {
455 if (_parentRuleSet != null) { 361 if (_parentRuleSet != null) {
456 _flatDeclarationGroup.declarations.add(node); 362 _flatDeclarationGroup.declarations.add(node);
457 } 363 }
458 super.visitVarDefinition(node); 364 super.visitVarDefinition(node);
459 } 365 }
460 366
367 void visitExtendDeclaration(ExtendDeclaration node) {
368 if (_parentRuleSet != null) {
369 _flatDeclarationGroup.declarations.add(node);
370 }
371 super.visitExtendDeclaration(node);
372 }
373
461 void visitMarginGroup(MarginGroup node) { 374 void visitMarginGroup(MarginGroup node) {
462 if (_parentRuleSet != null) { 375 if (_parentRuleSet != null) {
463 _flatDeclarationGroup.declarations.add(node); 376 _flatDeclarationGroup.declarations.add(node);
464 } 377 }
465 super.visitMarginGroup(node); 378 super.visitMarginGroup(node);
466 } 379 }
467 380
468 /** 381 /**
469 * Replace the rule set that contains nested rules with the flatten rule sets. 382 * Replace the rule set that contains nested rules with the flatten rule sets.
470 */ 383 */
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
504 _MediaRulesReplacer(this._ruleSet, this._newRules); 417 _MediaRulesReplacer(this._ruleSet, this._newRules);
505 418
506 visitMediaDirective(MediaDirective node) { 419 visitMediaDirective(MediaDirective node) {
507 var index = node.rulesets.indexOf(_ruleSet); 420 var index = node.rulesets.indexOf(_ruleSet);
508 if (index != -1) { 421 if (index != -1) {
509 node.rulesets.insertAll(index + 1, _newRules); 422 node.rulesets.insertAll(index + 1, _newRules);
510 _foundAndReplaced = true; 423 _foundAndReplaced = true;
511 } 424 }
512 } 425 }
513 } 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 var 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 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 var expressionsLength = expressions.length;
624 expressions.forEach((k, v) {
625 for (var usagesIndex in v) {
626 k.expressions.replaceRange(usagesIndex, usagesIndex + 1, callArg);
627 }
628 });
629 }
630
631 // Clone the mixin
632 return mixinDef.clone();
633 }
634
635 /** Rip apart var def with multiple parameters. */
636 List<List<TreeNode>> _varDefsAsCallArgs(var callArg) {
637 var defArgs = [];
638 if (callArg is List && callArg[0] is VarUsage) {
639 var varDef = varDefs[callArg[0].name];
640 var expressions = varDef.expression.expressions;
641 assert(expressions.length > 1);
642 for (var expr in expressions) {
643 if (expr is! OperatorComma) {
644 defArgs.add([expr]);
645 }
646 }
647 }
648 return defArgs;
649 }
650
651 void visitExpressions(Expressions node) {
652 var oldExpressions = _currExpressions;
653 var oldIndex = _currIndex;
654
655 _currExpressions = node;
656 for (_currIndex = 0; _currIndex < node.expressions.length; _currIndex++) {
657 node.expressions[_currIndex].visit(this);
658 }
659
660 _currIndex = oldIndex;
661 _currExpressions = oldExpressions;
662 }
663
664 void _addExpression(Map<Expressions, Set<int>> expressions) {
665 var indexSet = new Set<int>();
666 indexSet.add(_currIndex);
667 expressions[_currExpressions] = indexSet;
668 }
669
670 void visitVarUsage(VarUsage node) {
671 assert(_currIndex != -1);
672 assert(_currExpressions != null);
673 if (varUsages.containsKey(node.name)) {
674 Map<Expressions, Set<int>> expressions = varUsages[node.name];
675 Set<int> allIndexes = expressions[_currExpressions];
676 if (allIndexes == null) {
677 _addExpression(expressions);
678 } else {
679 allIndexes.add(_currIndex);
680 }
681 } else {
682 var newExpressions = new Map<Expressions, Set<int>>();
683 _addExpression(newExpressions);
684 varUsages[node.name] = newExpressions;
685 }
686 super.visitVarUsage(node);
687 }
688
689 void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
690 _definedArgs = node.definedArgs;
691 super.visitMixinDeclarationDirective(node);
692 }
693
694 void visitMixinRulesetDirective(MixinRulesetDirective node) {
695 _definedArgs = node.definedArgs;
696 super.visitMixinRulesetDirective(node);
697 }
698 }
699
700 /** Expand all @include inside of a declaration associated with a mixin. */
701 class DeclarationIncludes extends Visitor {
702 StyleSheet _styleSheet;
703 final Messages _messages;
704 /** Map of variable name key to it's definition. */
705 final Map<String, MixinDefinition> map = new Map<String, MixinDefinition>();
706 /** Cache of mixin called with parameters. */
707 final Map<String, CallMixin> callMap = new Map<String, CallMixin>();
708 MixinDefinition currDef;
709 DeclarationGroup currDeclGroup;
710
711 /** Var definitions with more than 1 expression. */
712 final Map<String, VarDefinition> varDefs = new Map<String, VarDefinition>();
713
714 static void expand(Messages messages, List<StyleSheet> styleSheets) {
715 new DeclarationIncludes(messages, styleSheets);
716 }
717
718 DeclarationIncludes(this._messages, List<StyleSheet> styleSheets) {
719 for (var styleSheet in styleSheets) {
720 visitTree(styleSheet);
721 }
722 }
723
724 bool _allIncludes(rulesets) =>
725 rulesets.every((rule) => rule is IncludeDirective || rule is NoOp);
726
727 CallMixin _createCallDeclMixin(mixinDef) {
728 callMap.putIfAbsent(mixinDef.name, () =>
729 callMap[mixinDef.name] = new CallMixin(mixinDef, varDefs));
730 return callMap[mixinDef.name];
731 }
732
733 void visitStyleSheet(StyleSheet ss) {
734 _styleSheet = ss;
735 super.visitStyleSheet(ss);
736 _styleSheet = null;
737 }
738
739 void visitDeclarationGroup(DeclarationGroup node) {
740 currDeclGroup = node;
741 super.visitDeclarationGroup(node);
742 currDeclGroup = null;
743 }
744
745 void visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
746 if (map.containsKey(node.include.name)) {
747 var mixinDef = map[node.include.name];
748
749 // Fix up any mixin that is really a Declaration but has includes.
750 if (mixinDef is MixinRulesetDirective) {
751 if (!_allIncludes(mixinDef.rulesets) && currDeclGroup != null) {
752 var index = _findInclude(currDeclGroup.declarations, node);
753 if (index != -1) {
754 currDeclGroup.declarations.replaceRange(index, index + 1,
755 [new NoOp()]);
756 }
757 _messages.warning(
758 "Using top-level mixin ${node.include.name} as a declaration",
759 node.span);
760 } else {
761 // We're a list of @include(s) inside of a mixin ruleset - convert
762 // to a list of IncludeMixinAtDeclaration(s).
763 var origRulesets = mixinDef.rulesets;
764 var rulesets = [];
765 if (origRulesets.every((ruleset) => ruleset is IncludeDirective)) {
766 origRulesets.forEach((ruleset) {
767 rulesets.add(new IncludeMixinAtDeclaration(ruleset,
768 ruleset.span));
769 });
770 _IncludeReplacer.replace(_styleSheet, node, rulesets);
771 }
772 }
773 }
774
775 if ( mixinDef.definedArgs.length > 0 && node.include.args.length > 0) {
776 var callMixin = _createCallDeclMixin(mixinDef);
777 mixinDef = callMixin.transform(node.include.args);
778 }
779
780 if (mixinDef is MixinDeclarationDirective) {
781 _IncludeReplacer.replace(_styleSheet, node,
782 mixinDef.declarations.declarations);
783 }
784 } else {
785 _messages.warning("Undefined mixin ${node.include.name}", node.span);
786 }
787
788 super.visitIncludeMixinAtDeclaration(node);
789 }
790
791 void visitIncludeDirective(IncludeDirective node) {
792 if (map.containsKey(node.name)) {
793 var mixinDef = map[node.name];
794 if (currDef is MixinDeclarationDirective &&
795 mixinDef is MixinDeclarationDirective) {
796 _IncludeReplacer.replace(_styleSheet, node,
797 mixinDef.declarations.declarations);
798 } else if (currDef is MixinDeclarationDirective) {
799 var decls = (currDef as MixinDeclarationDirective)
800 .declarations.declarations;
801 var index = _findInclude(decls, node);
802 if (index != -1) {
803 decls.replaceRange(index, index + 1, [new NoOp()]);
804 }
805 }
806 }
807
808 super.visitIncludeDirective(node);
809 }
810
811 void visitMixinRulesetDirective(MixinRulesetDirective node) {
812 currDef = node;
813
814 super.visitMixinRulesetDirective(node);
815
816 // Replace with latest top-level mixin definition.
817 map[node.name] = node;
818 currDef = null;
819 }
820
821 void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
822 currDef = node;
823
824 super.visitMixinDeclarationDirective(node);
825
826 // Replace with latest mixin definition.
827 map[node.name] = node;
828 currDef = null;
829 }
830
831 void visitVarDefinition(VarDefinition node) {
832 // Only record var definitions that have multiple expressions (comma
833 // separated for mixin parameter substitution.
834 var exprs = (node.expression as Expressions).expressions;
835 if (exprs.length > 1) {
836 varDefs[node.definedName] = node;
837 }
838 super.visitVarDefinition(node);
839 }
840
841 void visitVarDefinitionDirective(VarDefinitionDirective node) {
842 visitVarDefinition(node.def);
843 }
844 }
845
846 /** @include as a top-level with ruleset(s). */
847 class _IncludeReplacer extends Visitor {
848 final _include;
849 final List<Declaration> _newDeclarations;
850 bool _foundAndReplaced = false;
851
852 /**
853 * Look for the [ruleSet] inside of a @media directive; if found then replace
854 * with the [newRules].
855 */
856 static void replace(StyleSheet ss, var include,
857 List<Declaration> newDeclarations) {
858 var visitor = new _IncludeReplacer(include, newDeclarations);
859 visitor.visitStyleSheet(ss);
860 }
861
862 _IncludeReplacer(this._include, this._newDeclarations);
863
864 void visitDeclarationGroup(DeclarationGroup node) {
865 var index = _findInclude(node.declarations, _include);
866 if (index != -1) {
867 node.declarations.insertAll(index + 1, _newDeclarations);
868 // Change @include to NoOp so it's processed only once.
869 node.declarations.replaceRange(index, index + 1, [new NoOp()]);
870 _foundAndReplaced = true;
871 }
872 super.visitDeclarationGroup(node);
873 }
874 }
875
876 /**
877 * Remove all @mixin and @include and any NoOp used as placeholder for @include.
878 */
879 class MixinsAndIncludes extends Visitor {
880 static void remove(StyleSheet styleSheet) {
881 new MixinsAndIncludes()..visitStyleSheet(styleSheet);
882 }
883
884 bool _nodesToRemove(node) =>
885 node is IncludeDirective || node is MixinDefinition || node is NoOp;
886
887 void visitStyleSheet(StyleSheet ss) {
888 var index = ss.topLevels.length;
889 while (--index >= 0) {
890 if (_nodesToRemove(ss.topLevels[index])) {
891 ss.topLevels.removeAt(index);
892 }
893 }
894 super.visitStyleSheet(ss);
895 }
896
897 void visitDeclarationGroup(DeclarationGroup node) {
898 var index = node.declarations.length;
899 while (--index >= 0) {
900 if (_nodesToRemove(node.declarations[index])) {
901 node.declarations.removeAt(index);
902 }
903 }
904 super.visitDeclarationGroup(node);
905 }
906 }
907
908 /** Find all @extend to create inheritance. */
909 class AllExtends extends Visitor {
910 final Map<String, List<SelectorGroup>> inherits =
911 new Map<String, List<SelectorGroup>>();
912
913 SelectorGroup _currSelectorGroup;
914 List _currDecls;
915 int _currDeclIndex;
916 List<int> _extendsToRemove = [];
917
918 void visitRuleSet(RuleSet node) {
919 var oldSelectorGroup = _currSelectorGroup;
920 _currSelectorGroup = node.selectorGroup;
921
922 super.visitRuleSet(node);
923
924 _currSelectorGroup = oldSelectorGroup;
925 }
926
927 void visitExtendDeclaration(ExtendDeclaration node) {
928 var inheritName = "";
929 for (var selector in node.selectors) {
930 inheritName += selector.toString();
931 }
932 if (inherits.containsKey(inheritName)) {
933 inherits[inheritName].add(_currSelectorGroup);
934 } else {
935 inherits[inheritName] = [_currSelectorGroup];
936 }
937
938 // Remove this @extend
939 _extendsToRemove.add(_currDeclIndex);
940
941 super.visitExtendDeclaration(node);
942 }
943
944 void visitDeclarationGroup(DeclarationGroup node) {
945 var oldDeclIndex = _currDeclIndex;
946
947 var decls = node.declarations;
948 for (_currDeclIndex = 0; _currDeclIndex < decls.length; _currDeclIndex++) {
949 decls[_currDeclIndex].visit(this);
950 }
951
952 if (_extendsToRemove.isNotEmpty) {
953 var removeTotal = _extendsToRemove.length - 1;
954 for (var index = removeTotal; index >= 0; index--) {
955 decls.removeAt(_extendsToRemove[index]);
956 }
957 _extendsToRemove.clear();
958 }
959
960 _currDeclIndex = oldDeclIndex;
961 }
962 }
963
964 // TODO(terry): Need to handle merging selector sequences
965 // TODO(terry): Need to handle @extend-Only selectors.
966 // TODO(terry): Need to handle !optional glag.
967 /**
968 * Changes any selector that matches @extend.
969 */
970 class InheritExtends extends Visitor {
971 Messages _messages;
972 AllExtends _allExtends;
973
974 InheritExtends(this._messages, this._allExtends);
975
976 void visitSelectorGroup(SelectorGroup node) {
977 for (var selectorsIndex = 0; selectorsIndex < node.selectors.length;
978 selectorsIndex++) {
979 var selectors = node.selectors[selectorsIndex];
980 var isLastNone = false;
981 var selectorName = "";
982 for (var index = 0; index < selectors.simpleSelectorSequences.length;
983 index++) {
984 var simpleSeq = selectors.simpleSelectorSequences[index];
985 var namePart = simpleSeq.simpleSelector.toString();
986 selectorName = (isLastNone) ? (selectorName + namePart) : namePart;
987 List<SelectorGroup> matches = _allExtends.inherits[selectorName];
988 if (matches != null) {
989 for (var match in matches) {
990 // Create a new group.
991 var newSelectors = selectors.clone();
992 var newSeq = match.selectors[0].clone();
993 if (isLastNone) {
994 // Add the inherited selector.
995 node.selectors.add(newSeq);
996 } else {
997 // Replace the selector sequence to the left of the pseudo class
998 // or pseudo element.
999
1000 // Make new selector seq combinator the same as the original.
1001 var orgCombinator =
1002 newSelectors.simpleSelectorSequences[index].combinator;
1003 newSeq.simpleSelectorSequences[0].combinator = orgCombinator;
1004
1005 newSelectors.simpleSelectorSequences.replaceRange(index,
1006 index + 1, newSeq.simpleSelectorSequences);
1007 node.selectors.add(newSelectors);
1008 }
1009 isLastNone = false;
1010 }
1011 } else {
1012 isLastNone = simpleSeq.isCombinatorNone;
1013 }
1014 }
1015 }
1016 super.visitSelectorGroup(node);
1017 }
1018 }
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