| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of csslib.parser; |
| |
| /** |
| * CSS polyfill emits CSS to be understood by older parsers that which do not |
| * understand (var, calc, etc.). |
| */ |
| class PolyFill { |
| final Messages _messages; |
| final bool _warningsAsErrors; |
| Map<String, VarDefinition> _allVarDefinitions = |
| new Map<String, VarDefinition>(); |
| |
| Set<StyleSheet> allStyleSheets = new Set<StyleSheet>(); |
| |
| /** |
| * [_pseudoElements] list of known pseudo attributes found in HTML, any |
| * CSS pseudo-elements 'name::custom-element' is mapped to the manged name |
| * associated with the pseudo-element key. |
| */ |
| PolyFill(this._messages, this._warningsAsErrors); |
| |
| /** |
| * Run the analyzer on every file that is a style sheet or any component that |
| * has a style tag. |
| */ |
| void process(StyleSheet styleSheet, {List<StyleSheet> includes: null}) { |
| if (includes != null) { |
| processVarDefinitions(includes); |
| } |
| processVars(styleSheet); |
| |
| // Remove all var definitions for this style sheet. |
| new _RemoveVarDefinitions().visitTree(styleSheet); |
| } |
| |
| /** Process all includes looking for var definitions. */ |
| void processVarDefinitions(List<StyleSheet> includes) { |
| for (var include in includes) { |
| _allVarDefinitions = (new _VarDefinitionsIncludes(_allVarDefinitions) |
| ..visitTree(include)).varDefs; |
| } |
| } |
| |
| void processVars(StyleSheet styleSheet) { |
| // Build list of all var definitions. |
| var mainStyleSheetVarDefs = |
| (new _VarDefAndUsage(this._messages, _allVarDefinitions) |
| ..visitTree(styleSheet)).varDefs; |
| |
| // Resolve all definitions to a non-VarUsage (terminal expression). |
| mainStyleSheetVarDefs.forEach((key, value) { |
| for (Expression expr in (value.expression as Expressions).expressions) { |
| mainStyleSheetVarDefs[key] = |
| _findTerminalVarDefinition(_allVarDefinitions, value); |
| } |
| }); |
| } |
| } |
| |
| /** Build list of all var definitions in all includes. */ |
| class _VarDefinitionsIncludes extends Visitor { |
| final Map<String, VarDefinition> varDefs; |
| |
| _VarDefinitionsIncludes(this.varDefs); |
| |
| void visitTree(StyleSheet tree) { |
| visitStyleSheet(tree); |
| } |
| |
| visitVarDefinition(VarDefinition node) { |
| // Replace with latest variable definition. |
| varDefs[node.definedName] = node; |
| super.visitVarDefinition(node); |
| } |
| |
| void visitVarDefinitionDirective(VarDefinitionDirective node) { |
| visitVarDefinition(node.def); |
| } |
| } |
| |
| /** |
| * Find var- definitions in a style sheet. |
| * [found] list of known definitions. |
| */ |
| class _VarDefAndUsage extends Visitor { |
| final Messages _messages; |
| final Map<String, VarDefinition> _knownVarDefs; |
| final Map<String, VarDefinition> varDefs = new Map<String, VarDefinition>(); |
| |
| VarDefinition currVarDefinition; |
| List<Expression> currentExpressions; |
| |
| _VarDefAndUsage(this._messages, this._knownVarDefs); |
| |
| void visitTree(StyleSheet tree) { |
| visitStyleSheet(tree); |
| } |
| |
| visitVarDefinition(VarDefinition node) { |
| // Replace with latest variable definition. |
| currVarDefinition = node; |
| |
| _knownVarDefs[node.definedName] = node; |
| varDefs[node.definedName] = node; |
| |
| super.visitVarDefinition(node); |
| |
| currVarDefinition = null; |
| } |
| |
| void visitVarDefinitionDirective(VarDefinitionDirective node) { |
| visitVarDefinition(node.def); |
| } |
| |
| void visitExpressions(Expressions node) { |
| currentExpressions = node.expressions; |
| super.visitExpressions(node); |
| currentExpressions = null; |
| } |
| |
| void visitVarUsage(VarUsage node) { |
| if (currVarDefinition != null && currVarDefinition.badUsage) return; |
| |
| // Don't process other var() inside of a varUsage. That implies that the |
| // default is a var() too. Also, don't process any var() inside of a |
| // varDefinition (they're just place holders until we've resolved all real |
| // usages. |
| var expressions = currentExpressions; |
| var index = expressions.indexOf(node); |
| assert(index >= 0); |
| var def = _knownVarDefs[node.name]; |
| if (def != null) { |
| if (def.badUsage) { |
| // Remove any expressions pointing to a bad var definition. |
| expressions.removeAt(index); |
| return; |
| } |
| _resolveVarUsage(currentExpressions, index, |
| _findTerminalVarDefinition(_knownVarDefs, def)); |
| } else if (node.defaultValues.any((e) => e is VarUsage)) { |
| // Don't have a VarDefinition need to use default values resolve all |
| // default values. |
| var terminalDefaults = []; |
| for (var defaultValue in node.defaultValues) { |
| terminalDefaults.addAll(resolveUsageTerminal(defaultValue)); |
| } |
| expressions.replaceRange(index, index + 1, terminalDefaults); |
| } else if (node.defaultValues.isNotEmpty){ |
| // No VarDefinition but default value is a terminal expression; use it. |
| expressions.replaceRange(index, index + 1, node.defaultValues); |
| } else { |
| if (currVarDefinition != null) { |
| currVarDefinition.badUsage = true; |
| var mainStyleSheetDef = varDefs[node.name]; |
| if (mainStyleSheetDef != null) { |
| varDefs.remove(currVarDefinition.property); |
| } |
| } |
| // Remove var usage that points at an undefined definition. |
| expressions.removeAt(index); |
| _messages.warning("Variable is not defined.", node.span); |
| } |
| |
| var oldExpressions = currentExpressions; |
| currentExpressions = node.defaultValues; |
| super.visitVarUsage(node); |
| currentExpressions = oldExpressions; |
| } |
| |
| List<Expression> resolveUsageTerminal(VarUsage usage) { |
| var result = []; |
| |
| var varDef = _knownVarDefs[usage.name]; |
| var expressions; |
| if (varDef == null) { |
| // VarDefinition not found try the defaultValues. |
| expressions = usage.defaultValues; |
| } else { |
| // Use the VarDefinition found. |
| expressions = (varDef.expression as Expressions).expressions; |
| } |
| |
| for (var expr in expressions) { |
| if (expr is VarUsage) { |
| // Get terminal value. |
| result.addAll(resolveUsageTerminal(expr)); |
| } |
| } |
| |
| // We're at a terminal just return the VarDefinition expression. |
| if (result.isEmpty && varDef != null) { |
| result = (varDef.expression as Expressions).expressions; |
| } |
| |
| return result; |
| } |
| |
| _resolveVarUsage(List<Expressions> expressions, int index, |
| VarDefinition def) { |
| var defExpressions = (def.expression as Expressions).expressions; |
| expressions.replaceRange(index, index + 1, defExpressions); |
| } |
| } |
| |
| /** Remove all var definitions. */ |
| class _RemoveVarDefinitions extends Visitor { |
| void visitTree(StyleSheet tree) { |
| visitStyleSheet(tree); |
| } |
| |
| void visitStyleSheet(StyleSheet ss) { |
| ss.topLevels.removeWhere((e) => e is VarDefinitionDirective); |
| super.visitStyleSheet(ss); |
| } |
| |
| void visitDeclarationGroup(DeclarationGroup node) { |
| node.declarations.removeWhere((e) => e is VarDefinition); |
| super.visitDeclarationGroup(node); |
| } |
| } |
| |
| /** Find terminal definition (non VarUsage implies real CSS value). */ |
| VarDefinition _findTerminalVarDefinition(Map<String, VarDefinition> varDefs, |
| VarDefinition varDef) { |
| var expressions = varDef.expression as Expressions; |
| for (var expr in expressions.expressions) { |
| if (expr is VarUsage) { |
| var usageName = (expr as VarUsage).name; |
| var foundDef = varDefs[usageName]; |
| |
| // If foundDef is unknown check if defaultValues; if it exist then resolve |
| // to terminal value. |
| if (foundDef == null) { |
| // We're either a VarUsage or terminal definition if in varDefs; |
| // either way replace VarUsage with it's default value because the |
| // VarDefinition isn't found. |
| var defaultValues = (expr as VarUsage).defaultValues; |
| var replaceExprs = expressions.expressions; |
| assert(replaceExprs.length == 1); |
| replaceExprs.replaceRange(0, 1, defaultValues); |
| return varDef; |
| } |
| if (foundDef is VarDefinition) { |
| return _findTerminalVarDefinition(varDefs, foundDef); |
| } |
| } else { |
| // Return real CSS property. |
| return varDef; |
| } |
| } |
| |
| // Didn't point to a var definition that existed. |
| return varDef; |
| } |