// 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 '../parser.dart'; /// CSS polyfill emits CSS to be understood by older parsers that which do not /// understand (var, calc, etc.). class PolyFill { final Messages _messages; Map _allVarDefinitions = {}; Set allStyleSheets = {}; /// [_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); /// Run the analyzer on every file that is a style sheet or any component that /// has a style tag. void process(StyleSheet styleSheet, {List? includes}) { if (includes != null) { processVarDefinitions(includes); } processVars(styleSheet); // Remove all var definitions for this style sheet. _RemoveVarDefinitions().visitTree(styleSheet); } /// Process all includes looking for var definitions. void processVarDefinitions(List includes) { for (var include in includes) { _allVarDefinitions = (_VarDefinitionsIncludes(_allVarDefinitions) ..visitTree(include)) .varDefs; } } void processVars(StyleSheet styleSheet) { // Build list of all var definitions. var mainStyleSheetVarDefs = (_VarDefAndUsage(_messages, _allVarDefinitions) ..visitTree(styleSheet)) .varDefs; // Resolve all definitions to a non-VarUsage (terminal expression). mainStyleSheetVarDefs.forEach((key, value) { for (var _ 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 varDefs; _VarDefinitionsIncludes(this.varDefs); @override void visitTree(StyleSheet tree) { visitStyleSheet(tree); } @override void visitVarDefinition(VarDefinition node) { // Replace with latest variable definition. varDefs[node.definedName] = node; super.visitVarDefinition(node); } @override 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 _knownVarDefs; final varDefs = {}; VarDefinition? currVarDefinition; List? currentExpressions; _VarDefAndUsage(this._messages, this._knownVarDefs); @override void visitTree(StyleSheet tree) { visitStyleSheet(tree); } @override void visitVarDefinition(VarDefinition node) { // Replace with latest variable definition. currVarDefinition = node; _knownVarDefs[node.definedName] = node; varDefs[node.definedName] = node; super.visitVarDefinition(node); currVarDefinition = null; } @override void visitVarDefinitionDirective(VarDefinitionDirective node) { visitVarDefinition(node.def); } @override void visitExpressions(Expressions node) { currentExpressions = node.expressions; super.visitExpressions(node); currentExpressions = null; } @override 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 as VarUsage)); } 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 resolveUsageTerminal(VarUsage usage) { var result = []; var varDef = _knownVarDefs[usage.name]; List 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; } void _resolveVarUsage( List 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 { @override void visitTree(StyleSheet tree) { visitStyleSheet(tree); } @override void visitStyleSheet(StyleSheet ss) { ss.topLevels.removeWhere((e) => e is VarDefinitionDirective); super.visitStyleSheet(ss); } @override 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 varDefs, VarDefinition varDef) { var expressions = varDef.expression as Expressions; for (var expr in expressions.expressions) { if (expr is VarUsage) { var usageName = expr.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.defaultValues; var replaceExprs = expressions.expressions; assert(replaceExprs.length == 1); replaceExprs.replaceRange(0, 1, defaultValues); return varDef; } return _findTerminalVarDefinition(varDefs, foundDef); } else { // Return real CSS property. return varDef; } } // Didn't point to a var definition that existed. return varDef; }