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

Side by Side Diff: pkg/polymer/lib/src/css_analyzer.dart

Issue 23898009: Switch polymer's build.dart to use the new linter. This CL does the following (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: addressing john comments (part 2) - renamed files Created 7 years, 3 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.inputUrl, 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(fileInfo.inputUrl, 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(inputUrl, libraryInfo) {
74 // Get list of all stylesheet(s) dependencies referenced from this file.
75 var styleSheets = _dependencies(inputUrl, 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(inputUrl, libraryInfo, {Set<StyleSheet> seen}) {
122 if (seen == null) seen = new Set();
123
124 for (var styleSheet in libraryInfo.styleSheets) {
125 if (!seen.contains(styleSheet)) {
126 // TODO(terry): VM uses expandos to implement hashes. Currently, it's a
127 // linear (not constant) time cost (see dartbug.com/5746).
128 // If this bug isn't fixed and performance show's this a
129 // a problem we'll need to implement our own hashCode or
130 // use a different key for better perf.
131 // Add the stylesheet.
132 seen.add(styleSheet);
133
134 // Any other imports in this stylesheet?
135 var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot,
136 inputUrl, _messages);
137
138 // Process other imports in this stylesheets.
139 for (var importSS in urlInfos) {
140 var importInfo = info[importSS.resolvedPath];
141 if (importInfo != null) {
142 // Add all known stylesheets processed.
143 seen.addAll(importInfo.styleSheets);
144 // Find dependencies for stylesheet referenced with a
145 // @import
146 for (var ss in importInfo.styleSheets) {
147 var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl,
148 _messages);
149 for (var url in urls) {
150 var fileInfo = info[url.resolvedPath];
151 _dependencies(fileInfo.inputUrl, fileInfo, seen: seen);
152 }
153 }
154 }
155 }
156 }
157 }
158
159 return seen;
160 }
161 }
162
163 /**
164 * Find var- definitions in a style sheet.
165 * [found] list of known definitions.
166 */
167 class _VarDefinitions extends Visitor {
168 final Map<String, VarDefinition> found = new Map();
169
170 void visitTree(StyleSheet tree) {
171 visitStyleSheet(tree);
172 }
173
174 visitVarDefinition(VarDefinition node) {
175 // Replace with latest variable definition.
176 found[node.definedName] = node;
177 super.visitVarDefinition(node);
178 }
179
180 void visitVarDefinitionDirective(VarDefinitionDirective node) {
181 visitVarDefinition(node.def);
182 }
183 }
184
185 /**
186 * Resolve any CSS expression which contains a var() usage to the ultimate real
187 * CSS expression value e.g.,
188 *
189 * var-one: var(two);
190 * var-two: #ff00ff;
191 *
192 * .test {
193 * color: var(one);
194 * }
195 *
196 * then .test's color would be #ff00ff
197 */
198 class _ResolveVarUsages extends Visitor {
199 final Map<String, VarDefinition> varDefs;
200 bool inVarDefinition = false;
201 bool inUsage = false;
202 Expressions currentExpressions;
203
204 _ResolveVarUsages(this.varDefs);
205
206 void visitTree(StyleSheet tree) {
207 visitStyleSheet(tree);
208 }
209
210 void visitVarDefinition(VarDefinition varDef) {
211 inVarDefinition = true;
212 super.visitVarDefinition(varDef);
213 inVarDefinition = false;
214 }
215
216 void visitExpressions(Expressions node) {
217 currentExpressions = node;
218 super.visitExpressions(node);
219 currentExpressions = null;
220 }
221
222 void visitVarUsage(VarUsage node) {
223 // Don't process other var() inside of a varUsage. That implies that the
224 // default is a var() too. Also, don't process any var() inside of a
225 // varDefinition (they're just place holders until we've resolved all real
226 // usages.
227 if (!inUsage && !inVarDefinition && currentExpressions != null) {
228 var expressions = currentExpressions.expressions;
229 var index = expressions.indexOf(node);
230 assert(index >= 0);
231 var def = varDefs[node.name];
232 if (def != null) {
233 // Found a VarDefinition use it.
234 _resolveVarUsage(currentExpressions.expressions, index, def);
235 } else if (node.defaultValues.any((e) => e is VarUsage)) {
236 // Don't have a VarDefinition need to use default values resolve all
237 // default values.
238 var terminalDefaults = [];
239 for (var defaultValue in node.defaultValues) {
240 terminalDefaults.addAll(resolveUsageTerminal(defaultValue));
241 }
242 expressions.replaceRange(index, index + 1, terminalDefaults);
243 } else {
244 // No VarDefinition but default value is a terminal expression; use it.
245 expressions.replaceRange(index, index + 1, node.defaultValues);
246 }
247 }
248
249 inUsage = true;
250 super.visitVarUsage(node);
251 inUsage = false;
252 }
253
254 List<Expression> resolveUsageTerminal(VarUsage usage) {
255 var result = [];
256
257 var varDef = varDefs[usage.name];
258 var expressions;
259 if (varDef == null) {
260 // VarDefinition not found try the defaultValues.
261 expressions = usage.defaultValues;
262 } else {
263 // Use the VarDefinition found.
264 expressions = (varDef.expression as Expressions).expressions;
265 }
266
267 for (var expr in expressions) {
268 if (expr is VarUsage) {
269 // Get terminal value.
270 result.addAll(resolveUsageTerminal(expr));
271 }
272 }
273
274 // We're at a terminal just return the VarDefinition expression.
275 if (result.isEmpty && varDef != null) {
276 result = (varDef.expression as Expressions).expressions;
277 }
278
279 return result;
280 }
281
282 _resolveVarUsage(List<Expressions> expressions, int index,
283 VarDefinition def) {
284 var defExpressions = (def.expression as Expressions).expressions;
285 expressions.replaceRange(index, index + 1, defExpressions);
286 }
287 }
288
289 /** Remove all var definitions. */
290 class _RemoveVarDefinitions extends Visitor {
291 void visitTree(StyleSheet tree) {
292 visitStyleSheet(tree);
293 }
294
295 void visitStyleSheet(StyleSheet ss) {
296 ss.topLevels.removeWhere((e) => e is VarDefinitionDirective);
297 super.visitStyleSheet(ss);
298 }
299
300 void visitDeclarationGroup(DeclarationGroup node) {
301 node.declarations.removeWhere((e) => e is VarDefinition);
302 super.visitDeclarationGroup(node);
303 }
304 }
305
306 /**
307 * Process all selectors looking for a pseudo-element in a selector. If the
308 * name is found in our list of known pseudo-elements. Known pseudo-elements
309 * are built when parsing a component looking for an attribute named "pseudo".
310 * The value of the pseudo attribute is the name of the custom pseudo-element.
311 * The name is mangled so Dart/JS can't directly access the pseudo-element only
312 * CSS can access a custom pseudo-element (and see issue #510, querying needs
313 * access to custom pseudo-elements).
314 *
315 * Change the custom pseudo-element to be a child of the pseudo attribute's
316 * mangled custom pseudo element name. e.g,
317 *
318 * .test::x-box
319 *
320 * would become:
321 *
322 * .test > *[pseudo="x-box_2"]
323 */
324 class _PseudoElementExpander extends Visitor {
325 final Map<String, String> _pseudoElements;
326
327 _PseudoElementExpander(this._pseudoElements);
328
329 void visitTree(StyleSheet tree) => visitStyleSheet(tree);
330
331 visitSelector(Selector node) {
332 var selectors = node.simpleSelectorSequences;
333 for (var index = 0; index < selectors.length; index++) {
334 var selector = selectors[index].simpleSelector;
335 if (selector is PseudoElementSelector) {
336 if (_pseudoElements.containsKey(selector.name)) {
337 // Pseudo Element is a custom element.
338 var mangledName = _pseudoElements[selector.name];
339
340 var span = selectors[index].span;
341
342 var attrSelector = new AttributeSelector(
343 new Identifier('pseudo', span), css.TokenKind.EQUALS,
344 mangledName, span);
345 // The wildcard * namespace selector.
346 var wildCard = new ElementSelector(new Wildcard(span), span);
347 selectors[index] = new SimpleSelectorSequence(wildCard, span,
348 css.TokenKind.COMBINATOR_GREATER);
349 selectors.insert(++index,
350 new SimpleSelectorSequence(attrSelector, span));
351 }
352 }
353 }
354 }
355 }
356
357 List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet,
358 String packageRoot, UrlInfo inputUrl, Messages messages) {
359 var visitor = new _CssImports(packageRoot, inputUrl, messages);
360 visitor.visitTree(styleSheet);
361 return visitor.urlInfos;
362 }
363
364 /**
365 * Find any imports in the style sheet; normalize the style sheet href and
366 * return a list of all fully qualified CSS files.
367 */
368 class _CssImports extends Visitor {
369 final String packageRoot;
370
371 /** Input url of the css file, used to normalize relative import urls. */
372 final UrlInfo inputUrl;
373
374 /** List of all imported style sheets. */
375 final List<UrlInfo> urlInfos = [];
376
377 final Messages _messages;
378
379 _CssImports(this.packageRoot, this.inputUrl, this._messages);
380
381 void visitTree(StyleSheet tree) {
382 visitStyleSheet(tree);
383 }
384
385 void visitImportDirective(ImportDirective node) {
386 var urlInfo = UrlInfo.resolve(node.import, inputUrl,
387 node.span, packageRoot, _messages, ignoreAbsolute: true);
388 if (urlInfo == null) return;
389 urlInfos.add(urlInfo);
390 }
391 }
392
393 StyleSheet parseCss(String content, Messages messages,
394 CompilerOptions options) {
395 if (content.trim().isEmpty) return null;
396
397 var errors = [];
398
399 // TODO(terry): Add --checked when fully implemented and error handling.
400 var stylesheet = css.parse(content, errors: errors, options:
401 [options.warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
402
403 // Note: errors aren't fatal in HTML (unless strict mode is on).
404 // So just print them as warnings.
405 for (var e in errors) {
406 messages.warning(e.message, e.span);
407 }
408
409 return stylesheet;
410 }
411
412 /** Find terminal definition (non VarUsage implies real CSS value). */
413 VarDefinition _findTerminalVarDefinition(Map<String, VarDefinition> varDefs,
414 VarDefinition varDef) {
415 var expressions = varDef.expression as Expressions;
416 for (var expr in expressions.expressions) {
417 if (expr is VarUsage) {
418 var usageName = (expr as VarUsage).name;
419 var foundDef = varDefs[usageName];
420
421 // If foundDef is unknown check if defaultValues; if it exist then resolve
422 // to terminal value.
423 if (foundDef == null) {
424 // We're either a VarUsage or terminal definition if in varDefs;
425 // either way replace VarUsage with it's default value because the
426 // VarDefinition isn't found.
427 var defaultValues = (expr as VarUsage).defaultValues;
428 var replaceExprs = expressions.expressions;
429 assert(replaceExprs.length == 1);
430 replaceExprs.replaceRange(0, 1, defaultValues);
431 return varDef;
432 }
433 if (foundDef is VarDefinition) {
434 return _findTerminalVarDefinition(varDefs, foundDef);
435 }
436 } else {
437 // Return real CSS property.
438 return varDef;
439 }
440 }
441
442 // Didn't point to a var definition that existed.
443 return varDef;
444 }
445
446 /**
447 * Find urls imported inside style tags under [info]. If [info] is a FileInfo
448 * then process only style tags in the body (don't process any style tags in a
449 * component). If [info] is a ComponentInfo only process style tags inside of
450 * the element are processed. For an [info] of type FileInfo [node] is the
451 * file's document and for an [info] of type ComponentInfo then [node] is the
452 * component's element tag.
453 */
454 List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl,
455 String packageRoot, Node node, Messages messages, CompilerOptions options) {
456 // Process any @imports inside of the <style> tag.
457 var styleProcessor =
458 new _CssStyleTag(packageRoot, info, inputUrl, messages, options);
459 styleProcessor.visit(node);
460 return styleProcessor.imports;
461 }
462
463 /* Process CSS inside of a style tag. */
464 class _CssStyleTag extends TreeVisitor {
465 final String _packageRoot;
466
467 /** Either a FileInfo or ComponentInfo. */
468 final LibraryInfo _info;
469 final Messages _messages;
470 final CompilerOptions _options;
471
472 /**
473 * Path of the declaring file, for a [_info] of type FileInfo it's the file's
474 * path for a type ComponentInfo it's the declaring file path.
475 */
476 final UrlInfo _inputUrl;
477
478 /** List of @imports found. */
479 List<UrlInfo> imports = [];
480
481 _CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages,
482 this._options);
483
484 void visitElement(Element node) {
485 // Don't process any style tags inside of element if we're processing a
486 // FileInfo. The style tags inside of a component defintion will be
487 // processed when _info is a ComponentInfo.
488 if (node.tagName == 'polymer-element' && _info is FileInfo) return;
489 if (node.tagName == 'style') {
490 // Parse the contents of the scoped style tag.
491 var styleSheet = parseCss(node.nodes.single.value, _messages, _options);
492 if (styleSheet != null) {
493 _info.styleSheets.add(styleSheet);
494
495 // Find all imports return list of @imports in this style tag.
496 var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot,
497 _inputUrl, _messages);
498 imports.addAll(urlInfos);
499 }
500 }
501 super.visitElement(node);
502 }
503 }
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