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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
« 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 /** Portion of the analyzer dealing with CSS sources. */
6 library polymer.src.css_analyzer;
7
8 import 'package:csslib/parser.dart' as css;
9 import 'package:csslib/visitor.dart';
10 import 'package:html5lib/dom.dart';
11 import 'package:html5lib/dom_parsing.dart';
12
13 import 'info.dart';
14 import 'files.dart' show SourceFile;
15 import 'messages.dart';
16 import 'compiler_options.dart';
17
18 void analyzeCss(String packageRoot, List<SourceFile> files,
19 Map<String, FileInfo> info, Map<String, String> pseudoElements,
20 Messages messages, {warningsAsErrors: false}) {
21 var analyzer = new _AnalyzerCss(packageRoot, info, pseudoElements, messages,
22 warningsAsErrors);
23 for (var file in files) analyzer.process(file);
24 analyzer.normalize();
25 }
26
27 class _AnalyzerCss {
28 final String packageRoot;
29 final Map<String, FileInfo> info;
30 final Map<String, String> _pseudoElements;
31 final Messages _messages;
32 final bool _warningsAsErrors;
33
34 Set<StyleSheet> allStyleSheets = new Set<StyleSheet>();
35
36 /**
37 * [_pseudoElements] list of known pseudo attributes found in HTML, any
38 * CSS pseudo-elements 'name::custom-element' is mapped to the manged name
39 * associated with the pseudo-element key.
40 */
41 _AnalyzerCss(this.packageRoot, this.info, this._pseudoElements,
42 this._messages, this._warningsAsErrors);
43
44 /**
45 * Run the analyzer on every file that is a style sheet or any component that
46 * has a style tag.
47 */
48 void process(SourceFile file) {
49 var fileInfo = info[file.path];
50 if (file.isStyleSheet || fileInfo.styleSheets.length > 0) {
51 var styleSheets = processVars(fileInfo);
52
53 // Add to list of all style sheets analyzed.
54 allStyleSheets.addAll(styleSheets);
55 }
56
57 // Process any components.
58 for (var component in fileInfo.declaredComponents) {
59 var all = processVars(component);
60
61 // Add to list of all style sheets analyzed.
62 allStyleSheets.addAll(all);
63 }
64
65 processCustomPseudoElements();
66 }
67
68 void normalize() {
69 // Remove all var definitions for all style sheets analyzed.
70 for (var tree in allStyleSheets) new _RemoveVarDefinitions().visitTree(tree) ;
71 }
72
73 List<StyleSheet> processVars(var libraryInfo) {
74 // Get list of all stylesheet(s) dependencies referenced from this file.
75 var styleSheets = _dependencies(libraryInfo).toList();
76
77 var errors = [];
78 css.analyze(styleSheets, errors: errors, options:
79 [_warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
80
81 // Print errors as warnings.
82 for (var e in errors) {
83 _messages.warning(e.message, e.span);
84 }
85
86 // Build list of all var definitions.
87 Map varDefs = new Map();
88 for (var tree in styleSheets) {
89 var allDefs = (new _VarDefinitions()..visitTree(tree)).found;
90 allDefs.forEach((key, value) {
91 varDefs[key] = value;
92 });
93 }
94
95 // Resolve all definitions to a non-VarUsage (terminal expression).
96 varDefs.forEach((key, value) {
97 for (var expr in (value.expression as Expressions).expressions) {
98 var def = _findTerminalVarDefinition(varDefs, value);
99 varDefs[key] = def;
100 }
101 });
102
103 // Resolve all var usages.
104 for (var tree in styleSheets) new _ResolveVarUsages(varDefs).visitTree(tree) ;
105
106 return styleSheets;
107 }
108
109 processCustomPseudoElements() {
110 var polyFiller = new _PseudoElementExpander(_pseudoElements);
111 for (var tree in allStyleSheets) {
112 polyFiller.visitTree(tree);
113 }
114 }
115
116 /**
117 * Given a component or file check if any stylesheets referenced. If so then
118 * return a list of all referenced stylesheet dependencies (@imports or <link
119 * rel="stylesheet" ..>).
120 */
121 Set<StyleSheet> _dependencies(var libraryInfo, {Set<StyleSheet> seen}) {
122 if (seen == null) seen = new Set();
123
124 // Used to resolve all pathing information.
125 var inputUrl = libraryInfo is FileInfo
126 ? libraryInfo.inputUrl
127 : (libraryInfo as ComponentInfo).declaringFile.inputUrl;
128
129 for (var styleSheet in libraryInfo.styleSheets) {
130 if (!seen.contains(styleSheet)) {
131 // TODO(terry): VM uses expandos to implement hashes. Currently, it's a
132 // linear (not constant) time cost (see dartbug.com/5746).
133 // If this bug isn't fixed and performance show's this a
134 // a problem we'll need to implement our own hashCode or
135 // use a different key for better perf.
136 // Add the stylesheet.
137 seen.add(styleSheet);
138
139 // Any other imports in this stylesheet?
140 var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot,
141 inputUrl, _messages);
142
143 // Process other imports in this stylesheets.
144 for (var importSS in urlInfos) {
145 var importInfo = info[importSS.resolvedPath];
146 if (importInfo != null) {
147 // Add all known stylesheets processed.
148 seen.addAll(importInfo.styleSheets);
149 // Find dependencies for stylesheet referenced with a
150 // @import
151 for (var ss in importInfo.styleSheets) {
152 var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl,
153 _messages);
154 for (var url in urls) {
155 _dependencies(info[url.resolvedPath], seen: seen);
156 }
157 }
158 }
159 }
160 }
161 }
162
163 return seen;
164 }
165 }
166
167 /**
168 * Find var- definitions in a style sheet.
169 * [found] list of known definitions.
170 */
171 class _VarDefinitions extends Visitor {
172 final Map<String, VarDefinition> found = new Map();
173
174 void visitTree(StyleSheet tree) {
175 visitStyleSheet(tree);
176 }
177
178 visitVarDefinition(VarDefinition node) {
179 // Replace with latest variable definition.
180 found[node.definedName] = node;
181 super.visitVarDefinition(node);
182 }
183
184 void visitVarDefinitionDirective(VarDefinitionDirective node) {
185 visitVarDefinition(node.def);
186 }
187 }
188
189 /**
190 * Resolve any CSS expression which contains a var() usage to the ultimate real
191 * CSS expression value e.g.,
192 *
193 * var-one: var(two);
194 * var-two: #ff00ff;
195 *
196 * .test {
197 * color: var(one);
198 * }
199 *
200 * then .test's color would be #ff00ff
201 */
202 class _ResolveVarUsages extends Visitor {
203 final Map<String, VarDefinition> varDefs;
204 bool inVarDefinition = false;
205 bool inUsage = false;
206 Expressions currentExpressions;
207
208 _ResolveVarUsages(this.varDefs);
209
210 void visitTree(StyleSheet tree) {
211 visitStyleSheet(tree);
212 }
213
214 void visitVarDefinition(VarDefinition varDef) {
215 inVarDefinition = true;
216 super.visitVarDefinition(varDef);
217 inVarDefinition = false;
218 }
219
220 void visitExpressions(Expressions node) {
221 currentExpressions = node;
222 super.visitExpressions(node);
223 currentExpressions = null;
224 }
225
226 void visitVarUsage(VarUsage node) {
227 // Don't process other var() inside of a varUsage. That implies that the
228 // default is a var() too. Also, don't process any var() inside of a
229 // varDefinition (they're just place holders until we've resolved all real
230 // usages.
231 if (!inUsage && !inVarDefinition && currentExpressions != null) {
232 var expressions = currentExpressions.expressions;
233 var index = expressions.indexOf(node);
234 assert(index >= 0);
235 var def = varDefs[node.name];
236 if (def != null) {
237 // Found a VarDefinition use it.
238 _resolveVarUsage(currentExpressions.expressions, index, def);
239 } else if (node.defaultValues.any((e) => e is VarUsage)) {
240 // Don't have a VarDefinition need to use default values resolve all
241 // default values.
242 var terminalDefaults = [];
243 for (var defaultValue in node.defaultValues) {
244 terminalDefaults.addAll(resolveUsageTerminal(defaultValue));
245 }
246 expressions.replaceRange(index, index + 1, terminalDefaults);
247 } else {
248 // No VarDefinition but default value is a terminal expression; use it.
249 expressions.replaceRange(index, index + 1, node.defaultValues);
250 }
251 }
252
253 inUsage = true;
254 super.visitVarUsage(node);
255 inUsage = false;
256 }
257
258 List<Expression> resolveUsageTerminal(VarUsage usage) {
259 var result = [];
260
261 var varDef = varDefs[usage.name];
262 var expressions;
263 if (varDef == null) {
264 // VarDefinition not found try the defaultValues.
265 expressions = usage.defaultValues;
266 } else {
267 // Use the VarDefinition found.
268 expressions = (varDef.expression as Expressions).expressions;
269 }
270
271 for (var expr in expressions) {
272 if (expr is VarUsage) {
273 // Get terminal value.
274 result.addAll(resolveUsageTerminal(expr));
275 }
276 }
277
278 // We're at a terminal just return the VarDefinition expression.
279 if (result.isEmpty && varDef != null) {
280 result = (varDef.expression as Expressions).expressions;
281 }
282
283 return result;
284 }
285
286 _resolveVarUsage(List<Expressions> expressions, int index,
287 VarDefinition def) {
288 var defExpressions = (def.expression as Expressions).expressions;
289 expressions.replaceRange(index, index + 1, defExpressions);
290 }
291 }
292
293 /** Remove all var definitions. */
294 class _RemoveVarDefinitions extends Visitor {
295 void visitTree(StyleSheet tree) {
296 visitStyleSheet(tree);
297 }
298
299 void visitStyleSheet(StyleSheet ss) {
300 ss.topLevels.removeWhere((e) => e is VarDefinitionDirective);
301 super.visitStyleSheet(ss);
302 }
303
304 void visitDeclarationGroup(DeclarationGroup node) {
305 node.declarations.removeWhere((e) => e is VarDefinition);
306 super.visitDeclarationGroup(node);
307 }
308 }
309
310 /**
311 * Process all selectors looking for a pseudo-element in a selector. If the
312 * name is found in our list of known pseudo-elements. Known pseudo-elements
313 * are built when parsing a component looking for an attribute named "pseudo".
314 * The value of the pseudo attribute is the name of the custom pseudo-element.
315 * The name is mangled so Dart/JS can't directly access the pseudo-element only
316 * CSS can access a custom pseudo-element (and see issue #510, querying needs
317 * access to custom pseudo-elements).
318 *
319 * Change the custom pseudo-element to be a child of the pseudo attribute's
320 * mangled custom pseudo element name. e.g,
321 *
322 * .test::x-box
323 *
324 * would become:
325 *
326 * .test > *[pseudo="x-box_2"]
327 */
328 class _PseudoElementExpander extends Visitor {
329 final Map<String, String> _pseudoElements;
330
331 _PseudoElementExpander(this._pseudoElements);
332
333 void visitTree(StyleSheet tree) => visitStyleSheet(tree);
334
335 visitSelector(Selector node) {
336 var selectors = node.simpleSelectorSequences;
337 for (var index = 0; index < selectors.length; index++) {
338 var selector = selectors[index].simpleSelector;
339 if (selector is PseudoElementSelector) {
340 if (_pseudoElements.containsKey(selector.name)) {
341 // Pseudo Element is a custom element.
342 var mangledName = _pseudoElements[selector.name];
343
344 var span = selectors[index].span;
345
346 var attrSelector = new AttributeSelector(
347 new Identifier('pseudo', span), css.TokenKind.EQUALS,
348 mangledName, span);
349 // The wildcard * namespace selector.
350 var wildCard = new ElementSelector(new Wildcard(span), span);
351 selectors[index] = new SimpleSelectorSequence(wildCard, span,
352 css.TokenKind.COMBINATOR_GREATER);
353 selectors.insert(++index,
354 new SimpleSelectorSequence(attrSelector, span));
355 }
356 }
357 }
358 }
359 }
360
361 List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet,
362 String packageRoot, UrlInfo inputUrl, Messages messages) {
363 var visitor = new _CssImports(packageRoot, inputUrl, messages);
364 visitor.visitTree(styleSheet);
365 return visitor.urlInfos;
366 }
367
368 /**
369 * Find any imports in the style sheet; normalize the style sheet href and
370 * return a list of all fully qualified CSS files.
371 */
372 class _CssImports extends Visitor {
373 final String packageRoot;
374
375 /** Input url of the css file, used to normalize relative import urls. */
376 final UrlInfo inputUrl;
377
378 /** List of all imported style sheets. */
379 final List<UrlInfo> urlInfos = [];
380
381 final Messages _messages;
382
383 _CssImports(this.packageRoot, this.inputUrl, this._messages);
384
385 void visitTree(StyleSheet tree) {
386 visitStyleSheet(tree);
387 }
388
389 void visitImportDirective(ImportDirective node) {
390 var urlInfo = UrlInfo.resolve(node.import, inputUrl,
391 node.span, packageRoot, _messages, ignoreAbsolute: true);
392 if (urlInfo == null) return;
393 urlInfos.add(urlInfo);
394 }
395 }
396
397 StyleSheet parseCss(String content, Messages messages,
398 CompilerOptions options) {
399 if (content.trim().isEmpty) return null;
400
401 var errors = [];
402
403 // TODO(terry): Add --checked when fully implemented and error handling.
404 var stylesheet = css.parse(content, errors: errors, options:
405 [options.warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
406
407 // Note: errors aren't fatal in HTML (unless strict mode is on).
408 // So just print them as warnings.
409 for (var e in errors) {
410 messages.warning(e.message, e.span);
411 }
412
413 return stylesheet;
414 }
415
416 /** Find terminal definition (non VarUsage implies real CSS value). */
417 VarDefinition _findTerminalVarDefinition(Map<String, VarDefinition> varDefs,
418 VarDefinition varDef) {
419 var expressions = varDef.expression as Expressions;
420 for (var expr in expressions.expressions) {
421 if (expr is VarUsage) {
422 var usageName = (expr as VarUsage).name;
423 var foundDef = varDefs[usageName];
424
425 // If foundDef is unknown check if defaultValues; if it exist then resolve
426 // to terminal value.
427 if (foundDef == null) {
428 // We're either a VarUsage or terminal definition if in varDefs;
429 // either way replace VarUsage with it's default value because the
430 // VarDefinition isn't found.
431 var defaultValues = (expr as VarUsage).defaultValues;
432 var replaceExprs = expressions.expressions;
433 assert(replaceExprs.length == 1);
434 replaceExprs.replaceRange(0, 1, defaultValues);
435 return varDef;
436 }
437 if (foundDef is VarDefinition) {
438 return _findTerminalVarDefinition(varDefs, foundDef);
439 }
440 } else {
441 // Return real CSS property.
442 return varDef;
443 }
444 }
445
446 // Didn't point to a var definition that existed.
447 return varDef;
448 }
449
450 /**
451 * Find urls imported inside style tags under [info]. If [info] is a FileInfo
452 * then process only style tags in the body (don't process any style tags in a
453 * component). If [info] is a ComponentInfo only process style tags inside of
454 * the element are processed. For an [info] of type FileInfo [node] is the
455 * file's document and for an [info] of type ComponentInfo then [node] is the
456 * component's element tag.
457 */
458 List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl,
459 String packageRoot, Node node, Messages messages, CompilerOptions options) {
460 // Process any @imports inside of the <style> tag.
461 var styleProcessor =
462 new _CssStyleTag(packageRoot, info, inputUrl, messages, options);
463 styleProcessor.visit(node);
464 return styleProcessor.imports;
465 }
466
467 /* Process CSS inside of a style tag. */
468 class _CssStyleTag extends TreeVisitor {
469 final String _packageRoot;
470
471 /** Either a FileInfo or ComponentInfo. */
472 final LibraryInfo _info;
473 final Messages _messages;
474 final CompilerOptions _options;
475
476 /**
477 * Path of the declaring file, for a [_info] of type FileInfo it's the file's
478 * path for a type ComponentInfo it's the declaring file path.
479 */
480 final UrlInfo _inputUrl;
481
482 /** List of @imports found. */
483 List<UrlInfo> imports = [];
484
485 _CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages,
486 this._options);
487
488 void visitElement(Element node) {
489 // Don't process any style tags inside of element if we're processing a
490 // FileInfo. The style tags inside of a component defintion will be
491 // processed when _info is a ComponentInfo.
492 if (node.tagName == 'polymer-element' && _info is FileInfo) return;
493 if (node.tagName == 'style') {
494 // Parse the contents of the scoped style tag.
495 var styleSheet = parseCss(node.nodes.single.value, _messages, _options);
496 if (styleSheet != null) {
497 _info.styleSheets.add(styleSheet);
498
499 // Find all imports return list of @imports in this style tag.
500 var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot,
501 _inputUrl, _messages);
502 imports.addAll(urlInfos);
503 }
504 }
505 super.visitElement(node);
506 }
507 }
OLDNEW
« 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