OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, 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 * CSS polyfill emits CSS to be understood by older parsers that which do not |
| 9 * understand (var, calc, etc.). |
| 10 */ |
| 11 class PolyFill { |
| 12 final Messages _messages; |
| 13 final bool _warningsAsErrors; |
| 14 Map<String, VarDefinition> _allVarDefinitions = |
| 15 new Map<String, VarDefinition>(); |
| 16 |
| 17 Set<StyleSheet> allStyleSheets = new Set<StyleSheet>(); |
| 18 |
| 19 /** |
| 20 * [_pseudoElements] list of known pseudo attributes found in HTML, any |
| 21 * CSS pseudo-elements 'name::custom-element' is mapped to the manged name |
| 22 * associated with the pseudo-element key. |
| 23 */ |
| 24 PolyFill(this._messages, this._warningsAsErrors); |
| 25 |
| 26 /** |
| 27 * Run the analyzer on every file that is a style sheet or any component that |
| 28 * has a style tag. |
| 29 */ |
| 30 void process(StyleSheet styleSheet, {List<StyleSheet> includes: null}) { |
| 31 if (includes != null) { |
| 32 processVarDefinitions(includes); |
| 33 } |
| 34 processVars(styleSheet); |
| 35 |
| 36 // Remove all var definitions for this style sheet. |
| 37 new _RemoveVarDefinitions().visitTree(styleSheet); |
| 38 } |
| 39 |
| 40 /** Process all includes looking for var definitions. */ |
| 41 void processVarDefinitions(List<StyleSheet> includes) { |
| 42 for (var include in includes) { |
| 43 _allVarDefinitions = (new _VarDefinitionsIncludes(_allVarDefinitions) |
| 44 ..visitTree(include)).varDefs; |
| 45 } |
| 46 } |
| 47 |
| 48 void processVars(StyleSheet styleSheet) { |
| 49 // Build list of all var definitions. |
| 50 var mainStyleSheetVarDefs = (new _VarDefAndUsage( |
| 51 this._messages, _allVarDefinitions)..visitTree(styleSheet)).varDefs; |
| 52 |
| 53 // Resolve all definitions to a non-VarUsage (terminal expression). |
| 54 mainStyleSheetVarDefs.forEach((key, value) { |
| 55 for (var _ in (value.expression as Expressions).expressions) { |
| 56 mainStyleSheetVarDefs[key] = |
| 57 _findTerminalVarDefinition(_allVarDefinitions, value); |
| 58 } |
| 59 }); |
| 60 } |
| 61 } |
| 62 |
| 63 /** Build list of all var definitions in all includes. */ |
| 64 class _VarDefinitionsIncludes extends Visitor { |
| 65 final Map<String, VarDefinition> varDefs; |
| 66 |
| 67 _VarDefinitionsIncludes(this.varDefs); |
| 68 |
| 69 void visitTree(StyleSheet tree) { |
| 70 visitStyleSheet(tree); |
| 71 } |
| 72 |
| 73 visitVarDefinition(VarDefinition node) { |
| 74 // Replace with latest variable definition. |
| 75 varDefs[node.definedName] = node; |
| 76 super.visitVarDefinition(node); |
| 77 } |
| 78 |
| 79 void visitVarDefinitionDirective(VarDefinitionDirective node) { |
| 80 visitVarDefinition(node.def); |
| 81 } |
| 82 } |
| 83 |
| 84 /** |
| 85 * Find var- definitions in a style sheet. |
| 86 * [found] list of known definitions. |
| 87 */ |
| 88 class _VarDefAndUsage extends Visitor { |
| 89 final Messages _messages; |
| 90 final Map<String, VarDefinition> _knownVarDefs; |
| 91 final Map<String, VarDefinition> varDefs = new Map<String, VarDefinition>(); |
| 92 |
| 93 VarDefinition currVarDefinition; |
| 94 List<Expression> currentExpressions; |
| 95 |
| 96 _VarDefAndUsage(this._messages, this._knownVarDefs); |
| 97 |
| 98 void visitTree(StyleSheet tree) { |
| 99 visitStyleSheet(tree); |
| 100 } |
| 101 |
| 102 visitVarDefinition(VarDefinition node) { |
| 103 // Replace with latest variable definition. |
| 104 currVarDefinition = node; |
| 105 |
| 106 _knownVarDefs[node.definedName] = node; |
| 107 varDefs[node.definedName] = node; |
| 108 |
| 109 super.visitVarDefinition(node); |
| 110 |
| 111 currVarDefinition = null; |
| 112 } |
| 113 |
| 114 void visitVarDefinitionDirective(VarDefinitionDirective node) { |
| 115 visitVarDefinition(node.def); |
| 116 } |
| 117 |
| 118 void visitExpressions(Expressions node) { |
| 119 currentExpressions = node.expressions; |
| 120 super.visitExpressions(node); |
| 121 currentExpressions = null; |
| 122 } |
| 123 |
| 124 void visitVarUsage(VarUsage node) { |
| 125 if (currVarDefinition != null && currVarDefinition.badUsage) return; |
| 126 |
| 127 // Don't process other var() inside of a varUsage. That implies that the |
| 128 // default is a var() too. Also, don't process any var() inside of a |
| 129 // varDefinition (they're just place holders until we've resolved all real |
| 130 // usages. |
| 131 var expressions = currentExpressions; |
| 132 var index = expressions.indexOf(node); |
| 133 assert(index >= 0); |
| 134 var def = _knownVarDefs[node.name]; |
| 135 if (def != null) { |
| 136 if (def.badUsage) { |
| 137 // Remove any expressions pointing to a bad var definition. |
| 138 expressions.removeAt(index); |
| 139 return; |
| 140 } |
| 141 _resolveVarUsage(currentExpressions, index, |
| 142 _findTerminalVarDefinition(_knownVarDefs, def)); |
| 143 } else if (node.defaultValues.any((e) => e is VarUsage)) { |
| 144 // Don't have a VarDefinition need to use default values resolve all |
| 145 // default values. |
| 146 var terminalDefaults = <Expression>[]; |
| 147 for (var defaultValue in node.defaultValues) { |
| 148 terminalDefaults.addAll(resolveUsageTerminal(defaultValue)); |
| 149 } |
| 150 expressions.replaceRange(index, index + 1, terminalDefaults); |
| 151 } else if (node.defaultValues.isNotEmpty) { |
| 152 // No VarDefinition but default value is a terminal expression; use it. |
| 153 expressions.replaceRange(index, index + 1, node.defaultValues); |
| 154 } else { |
| 155 if (currVarDefinition != null) { |
| 156 currVarDefinition.badUsage = true; |
| 157 var mainStyleSheetDef = varDefs[node.name]; |
| 158 if (mainStyleSheetDef != null) { |
| 159 varDefs.remove(currVarDefinition.property); |
| 160 } |
| 161 } |
| 162 // Remove var usage that points at an undefined definition. |
| 163 expressions.removeAt(index); |
| 164 _messages.warning("Variable is not defined.", node.span); |
| 165 } |
| 166 |
| 167 var oldExpressions = currentExpressions; |
| 168 currentExpressions = node.defaultValues; |
| 169 super.visitVarUsage(node); |
| 170 currentExpressions = oldExpressions; |
| 171 } |
| 172 |
| 173 List<Expression> resolveUsageTerminal(VarUsage usage) { |
| 174 var result = []; |
| 175 |
| 176 var varDef = _knownVarDefs[usage.name]; |
| 177 var expressions; |
| 178 if (varDef == null) { |
| 179 // VarDefinition not found try the defaultValues. |
| 180 expressions = usage.defaultValues; |
| 181 } else { |
| 182 // Use the VarDefinition found. |
| 183 expressions = (varDef.expression as Expressions).expressions; |
| 184 } |
| 185 |
| 186 for (var expr in expressions) { |
| 187 if (expr is VarUsage) { |
| 188 // Get terminal value. |
| 189 result.addAll(resolveUsageTerminal(expr)); |
| 190 } |
| 191 } |
| 192 |
| 193 // We're at a terminal just return the VarDefinition expression. |
| 194 if (result.isEmpty && varDef != null) { |
| 195 result = (varDef.expression as Expressions).expressions; |
| 196 } |
| 197 |
| 198 return result; |
| 199 } |
| 200 |
| 201 _resolveVarUsage(List<Expression> expressions, int index, VarDefinition def) { |
| 202 var defExpressions = (def.expression as Expressions).expressions; |
| 203 expressions.replaceRange(index, index + 1, defExpressions); |
| 204 } |
| 205 } |
| 206 |
| 207 /** Remove all var definitions. */ |
| 208 class _RemoveVarDefinitions extends Visitor { |
| 209 void visitTree(StyleSheet tree) { |
| 210 visitStyleSheet(tree); |
| 211 } |
| 212 |
| 213 void visitStyleSheet(StyleSheet ss) { |
| 214 ss.topLevels.removeWhere((e) => e is VarDefinitionDirective); |
| 215 super.visitStyleSheet(ss); |
| 216 } |
| 217 |
| 218 void visitDeclarationGroup(DeclarationGroup node) { |
| 219 node.declarations.removeWhere((e) => e is VarDefinition); |
| 220 super.visitDeclarationGroup(node); |
| 221 } |
| 222 } |
| 223 |
| 224 /** Find terminal definition (non VarUsage implies real CSS value). */ |
| 225 VarDefinition _findTerminalVarDefinition( |
| 226 Map<String, VarDefinition> varDefs, VarDefinition varDef) { |
| 227 var expressions = varDef.expression as Expressions; |
| 228 for (var expr in expressions.expressions) { |
| 229 if (expr is VarUsage) { |
| 230 var usageName = (expr as VarUsage).name; |
| 231 var foundDef = varDefs[usageName]; |
| 232 |
| 233 // If foundDef is unknown check if defaultValues; if it exist then resolve |
| 234 // to terminal value. |
| 235 if (foundDef == null) { |
| 236 // We're either a VarUsage or terminal definition if in varDefs; |
| 237 // either way replace VarUsage with it's default value because the |
| 238 // VarDefinition isn't found. |
| 239 var defaultValues = (expr as VarUsage).defaultValues; |
| 240 var replaceExprs = expressions.expressions; |
| 241 assert(replaceExprs.length == 1); |
| 242 replaceExprs.replaceRange(0, 1, defaultValues); |
| 243 return varDef; |
| 244 } |
| 245 if (foundDef is VarDefinition) { |
| 246 return _findTerminalVarDefinition(varDefs, foundDef); |
| 247 } |
| 248 } else { |
| 249 // Return real CSS property. |
| 250 return varDef; |
| 251 } |
| 252 } |
| 253 |
| 254 // Didn't point to a var definition that existed. |
| 255 return varDef; |
| 256 } |
OLD | NEW |