Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1163)

Unified Diff: pkg/polymer/lib/src/css_analyzer.dart

Issue 23224003: move polymer.dart into dart svn (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: add --deploy to todomvc sample Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/polymer/lib/src/compiler_options.dart ('k') | pkg/polymer/lib/src/css_emitters.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/polymer/lib/src/css_analyzer.dart
diff --git a/pkg/polymer/lib/src/css_analyzer.dart b/pkg/polymer/lib/src/css_analyzer.dart
new file mode 100644
index 0000000000000000000000000000000000000000..270af5d5ba0c6a4485298097a3e67c948cd70c94
--- /dev/null
+++ b/pkg/polymer/lib/src/css_analyzer.dart
@@ -0,0 +1,507 @@
+// 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.
+
+/** Portion of the analyzer dealing with CSS sources. */
+library polymer.src.css_analyzer;
+
+import 'package:csslib/parser.dart' as css;
+import 'package:csslib/visitor.dart';
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/dom_parsing.dart';
+
+import 'info.dart';
+import 'files.dart' show SourceFile;
+import 'messages.dart';
+import 'compiler_options.dart';
+
+void analyzeCss(String packageRoot, List<SourceFile> files,
+ Map<String, FileInfo> info, Map<String, String> pseudoElements,
+ Messages messages, {warningsAsErrors: false}) {
+ var analyzer = new _AnalyzerCss(packageRoot, info, pseudoElements, messages,
+ warningsAsErrors);
+ for (var file in files) analyzer.process(file);
+ analyzer.normalize();
+}
+
+class _AnalyzerCss {
+ final String packageRoot;
+ final Map<String, FileInfo> info;
+ final Map<String, String> _pseudoElements;
+ final Messages _messages;
+ final bool _warningsAsErrors;
+
+ 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.
+ */
+ _AnalyzerCss(this.packageRoot, this.info, this._pseudoElements,
+ 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(SourceFile file) {
+ var fileInfo = info[file.path];
+ if (file.isStyleSheet || fileInfo.styleSheets.length > 0) {
+ var styleSheets = processVars(fileInfo);
+
+ // Add to list of all style sheets analyzed.
+ allStyleSheets.addAll(styleSheets);
+ }
+
+ // Process any components.
+ for (var component in fileInfo.declaredComponents) {
+ var all = processVars(component);
+
+ // Add to list of all style sheets analyzed.
+ allStyleSheets.addAll(all);
+ }
+
+ processCustomPseudoElements();
+ }
+
+ void normalize() {
+ // Remove all var definitions for all style sheets analyzed.
+ for (var tree in allStyleSheets) new _RemoveVarDefinitions().visitTree(tree);
+ }
+
+ List<StyleSheet> processVars(var libraryInfo) {
+ // Get list of all stylesheet(s) dependencies referenced from this file.
+ var styleSheets = _dependencies(libraryInfo).toList();
+
+ var errors = [];
+ css.analyze(styleSheets, errors: errors, options:
+ [_warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
+
+ // Print errors as warnings.
+ for (var e in errors) {
+ _messages.warning(e.message, e.span);
+ }
+
+ // Build list of all var definitions.
+ Map varDefs = new Map();
+ for (var tree in styleSheets) {
+ var allDefs = (new _VarDefinitions()..visitTree(tree)).found;
+ allDefs.forEach((key, value) {
+ varDefs[key] = value;
+ });
+ }
+
+ // Resolve all definitions to a non-VarUsage (terminal expression).
+ varDefs.forEach((key, value) {
+ for (var expr in (value.expression as Expressions).expressions) {
+ var def = _findTerminalVarDefinition(varDefs, value);
+ varDefs[key] = def;
+ }
+ });
+
+ // Resolve all var usages.
+ for (var tree in styleSheets) new _ResolveVarUsages(varDefs).visitTree(tree);
+
+ return styleSheets;
+ }
+
+ processCustomPseudoElements() {
+ var polyFiller = new _PseudoElementExpander(_pseudoElements);
+ for (var tree in allStyleSheets) {
+ polyFiller.visitTree(tree);
+ }
+ }
+
+ /**
+ * Given a component or file check if any stylesheets referenced. If so then
+ * return a list of all referenced stylesheet dependencies (@imports or <link
+ * rel="stylesheet" ..>).
+ */
+ Set<StyleSheet> _dependencies(var libraryInfo, {Set<StyleSheet> seen}) {
+ if (seen == null) seen = new Set();
+
+ // Used to resolve all pathing information.
+ var inputUrl = libraryInfo is FileInfo
+ ? libraryInfo.inputUrl
+ : (libraryInfo as ComponentInfo).declaringFile.inputUrl;
+
+ for (var styleSheet in libraryInfo.styleSheets) {
+ if (!seen.contains(styleSheet)) {
+ // TODO(terry): VM uses expandos to implement hashes. Currently, it's a
+ // linear (not constant) time cost (see dartbug.com/5746).
+ // If this bug isn't fixed and performance show's this a
+ // a problem we'll need to implement our own hashCode or
+ // use a different key for better perf.
+ // Add the stylesheet.
+ seen.add(styleSheet);
+
+ // Any other imports in this stylesheet?
+ var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot,
+ inputUrl, _messages);
+
+ // Process other imports in this stylesheets.
+ for (var importSS in urlInfos) {
+ var importInfo = info[importSS.resolvedPath];
+ if (importInfo != null) {
+ // Add all known stylesheets processed.
+ seen.addAll(importInfo.styleSheets);
+ // Find dependencies for stylesheet referenced with a
+ // @import
+ for (var ss in importInfo.styleSheets) {
+ var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl,
+ _messages);
+ for (var url in urls) {
+ _dependencies(info[url.resolvedPath], seen: seen);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return seen;
+ }
+}
+
+/**
+ * Find var- definitions in a style sheet.
+ * [found] list of known definitions.
+ */
+class _VarDefinitions extends Visitor {
+ final Map<String, VarDefinition> found = new Map();
+
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ visitVarDefinition(VarDefinition node) {
+ // Replace with latest variable definition.
+ found[node.definedName] = node;
+ super.visitVarDefinition(node);
+ }
+
+ void visitVarDefinitionDirective(VarDefinitionDirective node) {
+ visitVarDefinition(node.def);
+ }
+}
+
+/**
+ * Resolve any CSS expression which contains a var() usage to the ultimate real
+ * CSS expression value e.g.,
+ *
+ * var-one: var(two);
+ * var-two: #ff00ff;
+ *
+ * .test {
+ * color: var(one);
+ * }
+ *
+ * then .test's color would be #ff00ff
+ */
+class _ResolveVarUsages extends Visitor {
+ final Map<String, VarDefinition> varDefs;
+ bool inVarDefinition = false;
+ bool inUsage = false;
+ Expressions currentExpressions;
+
+ _ResolveVarUsages(this.varDefs);
+
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ void visitVarDefinition(VarDefinition varDef) {
+ inVarDefinition = true;
+ super.visitVarDefinition(varDef);
+ inVarDefinition = false;
+ }
+
+ void visitExpressions(Expressions node) {
+ currentExpressions = node;
+ super.visitExpressions(node);
+ currentExpressions = null;
+ }
+
+ void visitVarUsage(VarUsage node) {
+ // 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.
+ if (!inUsage && !inVarDefinition && currentExpressions != null) {
+ var expressions = currentExpressions.expressions;
+ var index = expressions.indexOf(node);
+ assert(index >= 0);
+ var def = varDefs[node.name];
+ if (def != null) {
+ // Found a VarDefinition use it.
+ _resolveVarUsage(currentExpressions.expressions, index, 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 {
+ // No VarDefinition but default value is a terminal expression; use it.
+ expressions.replaceRange(index, index + 1, node.defaultValues);
+ }
+ }
+
+ inUsage = true;
+ super.visitVarUsage(node);
+ inUsage = false;
+ }
+
+ List<Expression> resolveUsageTerminal(VarUsage usage) {
+ var result = [];
+
+ var varDef = varDefs[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);
+ }
+}
+
+/**
+ * Process all selectors looking for a pseudo-element in a selector. If the
+ * name is found in our list of known pseudo-elements. Known pseudo-elements
+ * are built when parsing a component looking for an attribute named "pseudo".
+ * The value of the pseudo attribute is the name of the custom pseudo-element.
+ * The name is mangled so Dart/JS can't directly access the pseudo-element only
+ * CSS can access a custom pseudo-element (and see issue #510, querying needs
+ * access to custom pseudo-elements).
+ *
+ * Change the custom pseudo-element to be a child of the pseudo attribute's
+ * mangled custom pseudo element name. e.g,
+ *
+ * .test::x-box
+ *
+ * would become:
+ *
+ * .test > *[pseudo="x-box_2"]
+ */
+class _PseudoElementExpander extends Visitor {
+ final Map<String, String> _pseudoElements;
+
+ _PseudoElementExpander(this._pseudoElements);
+
+ void visitTree(StyleSheet tree) => visitStyleSheet(tree);
+
+ visitSelector(Selector node) {
+ var selectors = node.simpleSelectorSequences;
+ for (var index = 0; index < selectors.length; index++) {
+ var selector = selectors[index].simpleSelector;
+ if (selector is PseudoElementSelector) {
+ if (_pseudoElements.containsKey(selector.name)) {
+ // Pseudo Element is a custom element.
+ var mangledName = _pseudoElements[selector.name];
+
+ var span = selectors[index].span;
+
+ var attrSelector = new AttributeSelector(
+ new Identifier('pseudo', span), css.TokenKind.EQUALS,
+ mangledName, span);
+ // The wildcard * namespace selector.
+ var wildCard = new ElementSelector(new Wildcard(span), span);
+ selectors[index] = new SimpleSelectorSequence(wildCard, span,
+ css.TokenKind.COMBINATOR_GREATER);
+ selectors.insert(++index,
+ new SimpleSelectorSequence(attrSelector, span));
+ }
+ }
+ }
+ }
+}
+
+List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet,
+ String packageRoot, UrlInfo inputUrl, Messages messages) {
+ var visitor = new _CssImports(packageRoot, inputUrl, messages);
+ visitor.visitTree(styleSheet);
+ return visitor.urlInfos;
+}
+
+/**
+ * Find any imports in the style sheet; normalize the style sheet href and
+ * return a list of all fully qualified CSS files.
+ */
+class _CssImports extends Visitor {
+ final String packageRoot;
+
+ /** Input url of the css file, used to normalize relative import urls. */
+ final UrlInfo inputUrl;
+
+ /** List of all imported style sheets. */
+ final List<UrlInfo> urlInfos = [];
+
+ final Messages _messages;
+
+ _CssImports(this.packageRoot, this.inputUrl, this._messages);
+
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ void visitImportDirective(ImportDirective node) {
+ var urlInfo = UrlInfo.resolve(node.import, inputUrl,
+ node.span, packageRoot, _messages, ignoreAbsolute: true);
+ if (urlInfo == null) return;
+ urlInfos.add(urlInfo);
+ }
+}
+
+StyleSheet parseCss(String content, Messages messages,
+ CompilerOptions options) {
+ if (content.trim().isEmpty) return null;
+
+ var errors = [];
+
+ // TODO(terry): Add --checked when fully implemented and error handling.
+ var stylesheet = css.parse(content, errors: errors, options:
+ [options.warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
+
+ // Note: errors aren't fatal in HTML (unless strict mode is on).
+ // So just print them as warnings.
+ for (var e in errors) {
+ messages.warning(e.message, e.span);
+ }
+
+ return stylesheet;
+}
+
+/** 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;
+}
+
+/**
+ * Find urls imported inside style tags under [info]. If [info] is a FileInfo
+ * then process only style tags in the body (don't process any style tags in a
+ * component). If [info] is a ComponentInfo only process style tags inside of
+ * the element are processed. For an [info] of type FileInfo [node] is the
+ * file's document and for an [info] of type ComponentInfo then [node] is the
+ * component's element tag.
+ */
+List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl,
+ String packageRoot, Node node, Messages messages, CompilerOptions options) {
+ // Process any @imports inside of the <style> tag.
+ var styleProcessor =
+ new _CssStyleTag(packageRoot, info, inputUrl, messages, options);
+ styleProcessor.visit(node);
+ return styleProcessor.imports;
+}
+
+/* Process CSS inside of a style tag. */
+class _CssStyleTag extends TreeVisitor {
+ final String _packageRoot;
+
+ /** Either a FileInfo or ComponentInfo. */
+ final LibraryInfo _info;
+ final Messages _messages;
+ final CompilerOptions _options;
+
+ /**
+ * Path of the declaring file, for a [_info] of type FileInfo it's the file's
+ * path for a type ComponentInfo it's the declaring file path.
+ */
+ final UrlInfo _inputUrl;
+
+ /** List of @imports found. */
+ List<UrlInfo> imports = [];
+
+ _CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages,
+ this._options);
+
+ void visitElement(Element node) {
+ // Don't process any style tags inside of element if we're processing a
+ // FileInfo. The style tags inside of a component defintion will be
+ // processed when _info is a ComponentInfo.
+ if (node.tagName == 'polymer-element' && _info is FileInfo) return;
+ if (node.tagName == 'style') {
+ // Parse the contents of the scoped style tag.
+ var styleSheet = parseCss(node.nodes.single.value, _messages, _options);
+ if (styleSheet != null) {
+ _info.styleSheets.add(styleSheet);
+
+ // Find all imports return list of @imports in this style tag.
+ var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot,
+ _inputUrl, _messages);
+ imports.addAll(urlInfos);
+ }
+ }
+ super.visitElement(node);
+ }
+}
« no previous file with comments | « pkg/polymer/lib/src/compiler_options.dart ('k') | pkg/polymer/lib/src/css_emitters.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698