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