| 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 |