OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Part of the template compilation that concerns with extracting information | 6 * Part of the template compilation that concerns with extracting information |
7 * from the HTML parse tree. | 7 * from the HTML parse tree. |
8 */ | 8 */ |
9 library analyzer; | 9 library analyzer; |
10 | 10 |
11 import 'package:csslib/parser.dart' as css; | |
12 import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString, Visitor
, Expressions, VarDefinition; | |
13 import 'package:html5lib/dom.dart'; | 11 import 'package:html5lib/dom.dart'; |
14 import 'package:html5lib/dom_parsing.dart'; | 12 import 'package:html5lib/dom_parsing.dart'; |
15 import 'package:source_maps/span.dart' hide SourceFile; | 13 import 'package:source_maps/span.dart' hide SourceFile; |
16 | 14 |
17 import 'custom_tag_name.dart'; | 15 import 'custom_tag_name.dart'; |
18 import 'dart_parser.dart'; | 16 import 'dart_parser.dart' show parseDartCode; |
19 import 'files.dart'; | 17 import 'files.dart'; |
20 import 'html_css_fixup.dart'; | |
21 import 'info.dart'; | 18 import 'info.dart'; |
22 import 'messages.dart'; | 19 import 'messages.dart'; |
23 import 'summary.dart'; | 20 import 'summary.dart'; |
24 import 'utils.dart'; | |
25 | 21 |
26 /** | 22 /** |
27 * Finds custom elements in this file and the list of referenced files with | 23 * Finds custom elements in this file and the list of referenced files with |
28 * component declarations. This is the first pass of analysis on a file. | 24 * component declarations. This is the first pass of analysis on a file. |
29 * | 25 * |
30 * Adds emitted error/warning messages to [messages], if [messages] is | 26 * Adds emitted error/warning messages to [messages], if [messages] is |
31 * supplied. | 27 * supplied. |
32 */ | 28 */ |
33 FileInfo analyzeDefinitions(GlobalInfo global, UrlInfo inputUrl, | 29 FileInfo analyzeDefinitions(GlobalInfo global, UrlInfo inputUrl, |
34 Document document, String packageRoot, | 30 Document document, String packageRoot, |
35 Messages messages, {bool isEntryPoint: false}) { | 31 Messages messages, {bool isEntryPoint: false}) { |
36 var result = new FileInfo(inputUrl, isEntryPoint); | 32 var result = new FileInfo(inputUrl, isEntryPoint); |
37 var loader = new _ElementLoader(global, result, packageRoot, messages); | 33 var loader = new _ElementLoader(global, result, packageRoot, messages); |
38 loader.visit(document); | 34 loader.visit(document); |
39 return result; | 35 return result; |
40 } | 36 } |
41 | 37 |
42 /** | 38 /** |
43 * Extract relevant information from [source] and it's children. | |
44 * Used for testing. | |
45 * | |
46 * Adds emitted error/warning messages to [messages], if [messages] is | |
47 * supplied. | |
48 */ | |
49 FileInfo analyzeNodeForTesting(Node source, Messages messages, | |
50 {String filepath: 'mock_testing_file.html'}) { | |
51 var result = new FileInfo(new UrlInfo(filepath, filepath, null)); | |
52 new _Analyzer(result, new IntIterator(), new GlobalInfo(), messages) | |
53 .visit(source); | |
54 return result; | |
55 } | |
56 | |
57 /** | |
58 * Extract relevant information from all files found from the root document. | 39 * Extract relevant information from all files found from the root document. |
59 * | 40 * |
60 * Adds emitted error/warning messages to [messages], if [messages] is | 41 * Adds emitted error/warning messages to [messages], if [messages] is |
61 * supplied. | 42 * supplied. |
62 */ | 43 */ |
63 void analyzeFile(SourceFile file, Map<String, FileInfo> info, | 44 void analyzeFile(SourceFile file, Map<String, FileInfo> info, |
64 Iterator<int> uniqueIds, GlobalInfo global, | 45 Iterator<int> uniqueIds, GlobalInfo global, |
65 Messages messages) { | 46 Messages messages, emulateScopedCss) { |
66 var fileInfo = info[file.path]; | 47 var fileInfo = info[file.path]; |
67 var analyzer = new _Analyzer(fileInfo, uniqueIds, global, messages); | 48 var analyzer = new _Analyzer(fileInfo, uniqueIds, global, messages, |
| 49 emulateScopedCss); |
68 analyzer._normalize(fileInfo, info); | 50 analyzer._normalize(fileInfo, info); |
69 analyzer.visit(file.document); | 51 analyzer.visit(file.document); |
70 } | 52 } |
71 | 53 |
72 | 54 |
73 /** A visitor that walks the HTML to extract all the relevant information. */ | 55 /** A visitor that walks the HTML to extract all the relevant information. */ |
74 class _Analyzer extends TreeVisitor { | 56 class _Analyzer extends TreeVisitor { |
75 final FileInfo _fileInfo; | 57 final FileInfo _fileInfo; |
76 LibraryInfo _currentInfo; | 58 LibraryInfo _currentInfo; |
77 Iterator<int> _uniqueIds; | 59 Iterator<int> _uniqueIds; |
78 GlobalInfo _global; | 60 GlobalInfo _global; |
79 Messages _messages; | 61 Messages _messages; |
80 | 62 |
81 int _generatedClassNumber = 0; | 63 int _generatedClassNumber = 0; |
82 | 64 |
83 /** | 65 /** |
84 * Whether to keep indentation spaces. Break lines and indentation spaces | 66 * Whether to keep indentation spaces. Break lines and indentation spaces |
85 * within templates are preserved in HTML. When users specify the attribute | 67 * within templates are preserved in HTML. When users specify the attribute |
86 * 'indentation="remove"' on a template tag, we'll trim those indentation | 68 * 'indentation="remove"' on a template tag, we'll trim those indentation |
87 * spaces that occur within that tag and its decendants. If any decendant | 69 * spaces that occur within that tag and its decendants. If any decendant |
88 * specifies 'indentation="preserve"', then we'll switch back to the normal | 70 * specifies 'indentation="preserve"', then we'll switch back to the normal |
89 * behavior. | 71 * behavior. |
90 */ | 72 */ |
91 bool _keepIndentationSpaces = true; | 73 bool _keepIndentationSpaces = true; |
92 | 74 |
93 _Analyzer(this._fileInfo, this._uniqueIds, this._global, this._messages) { | 75 final bool _emulateScopedCss; |
| 76 |
| 77 _Analyzer(this._fileInfo, this._uniqueIds, this._global, this._messages, |
| 78 this._emulateScopedCss) { |
94 _currentInfo = _fileInfo; | 79 _currentInfo = _fileInfo; |
95 } | 80 } |
96 | 81 |
97 void visitElement(Element node) { | 82 void visitElement(Element node) { |
98 if (node.tagName == 'script') { | 83 if (node.tagName == 'script') { |
99 // We already extracted script tags in previous phase. | 84 // We already extracted script tags in previous phase. |
100 return; | 85 return; |
101 } | 86 } |
102 | 87 |
103 if (node.tagName == 'style') { | 88 if (node.tagName == 'style') { |
104 // We've already parsed the CSS. | 89 // We've already parsed the CSS. |
105 // If this is a component remove the style node. | 90 // If this is a component remove the style node. |
106 if (_currentInfo is ComponentInfo) node.remove(); | 91 if (_currentInfo is ComponentInfo && _emulateScopedCss) node.remove(); |
107 return; | 92 return; |
108 } | 93 } |
109 | 94 |
110 node = _bindAndReplaceElement(node); | 95 node = _bindAndReplaceElement(node); |
111 | 96 |
112 var lastInfo = _currentInfo; | 97 var lastInfo = _currentInfo; |
113 if (node.tagName == 'polymer-element') { | 98 if (node.tagName == 'polymer-element') { |
114 // If element is invalid _ElementLoader already reported an error, but | 99 // If element is invalid _ElementLoader already reported an error, but |
115 // we skip the body of the element here. | 100 // we skip the body of the element here. |
116 var name = node.attributes['name']; | 101 var name = node.attributes['name']; |
(...skipping 314 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
431 // inside a Shadow DOM should be scoped to that <template> tag, and not | 416 // inside a Shadow DOM should be scoped to that <template> tag, and not |
432 // visible from the outside. | 417 // visible from the outside. |
433 if (_currentInfo is ComponentInfo) { | 418 if (_currentInfo is ComponentInfo) { |
434 _messages.error('Nested component definitions are not yet supported.', | 419 _messages.error('Nested component definitions are not yet supported.', |
435 node.sourceSpan); | 420 node.sourceSpan); |
436 return; | 421 return; |
437 } | 422 } |
438 | 423 |
439 var tagName = node.attributes['name']; | 424 var tagName = node.attributes['name']; |
440 var extendsTag = node.attributes['extends']; | 425 var extendsTag = node.attributes['extends']; |
441 var templateNodes = node.nodes.where((n) => n.tagName == 'template'); | |
442 | 426 |
443 if (tagName == null) { | 427 if (tagName == null) { |
444 _messages.error('Missing tag name of the component. Please include an ' | 428 _messages.error('Missing tag name of the component. Please include an ' |
445 'attribute like \'name="your-tag-name"\'.', | 429 'attribute like \'name="your-tag-name"\'.', |
446 node.sourceSpan); | 430 node.sourceSpan); |
447 return; | 431 return; |
448 } | 432 } |
449 | 433 |
450 if (extendsTag == null) { | 434 if (extendsTag == null) { |
451 // From the spec: | 435 // From the spec: |
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
556 Text text = node.nodes[0]; | 540 Text text = node.nodes[0]; |
557 | 541 |
558 if (_currentInfo.codeAttached) { | 542 if (_currentInfo.codeAttached) { |
559 _tooManyScriptsError(node); | 543 _tooManyScriptsError(node); |
560 } else if (_currentInfo == _fileInfo && !_fileInfo.isEntryPoint) { | 544 } else if (_currentInfo == _fileInfo && !_fileInfo.isEntryPoint) { |
561 _messages.warning('top-level dart code is ignored on ' | 545 _messages.warning('top-level dart code is ignored on ' |
562 ' HTML pages that define components, but are not the entry HTML ' | 546 ' HTML pages that define components, but are not the entry HTML ' |
563 'file.', node.sourceSpan); | 547 'file.', node.sourceSpan); |
564 } else { | 548 } else { |
565 _currentInfo.inlinedCode = parseDartCode( | 549 _currentInfo.inlinedCode = parseDartCode( |
566 _currentInfo.dartCodeUrl.resolvedPath, text.value, _messages, | 550 _currentInfo.dartCodeUrl.resolvedPath, text.value, |
567 text.sourceSpan.start); | 551 text.sourceSpan.start); |
568 if (_currentInfo.userCode.partOf != null) { | 552 if (_currentInfo.userCode.partOf != null) { |
569 _messages.error('expected a library, not a part.', | 553 _messages.error('expected a library, not a part.', |
570 node.sourceSpan); | 554 node.sourceSpan); |
571 } | 555 } |
572 } | 556 } |
573 } | 557 } |
574 | 558 |
575 void _tooManyScriptsError(Node node) { | 559 void _tooManyScriptsError(Node node) { |
576 var location = _currentInfo is ComponentInfo ? | 560 var location = _currentInfo is ComponentInfo ? |
577 'a custom element declaration' : 'the top-level HTML page'; | 561 'a custom element declaration' : 'the top-level HTML page'; |
578 | 562 |
579 _messages.error('there should be only one dart script tag in $location.', | 563 _messages.error('there should be only one dart script tag in $location.', |
580 node.sourceSpan); | 564 node.sourceSpan); |
581 } | 565 } |
582 } | 566 } |
583 | |
584 | |
585 void analyzeCss(String packageRoot, List<SourceFile> files, | |
586 Map<String, FileInfo> info, Map<String, String> pseudoElements, | |
587 Messages messages, {warningsAsErrors: false}) { | |
588 var analyzer = new _AnalyzerCss(packageRoot, info, pseudoElements, messages, | |
589 warningsAsErrors); | |
590 for (var file in files) analyzer.process(file); | |
591 analyzer.normalize(); | |
592 } | |
593 | |
594 class _AnalyzerCss { | |
595 final String packageRoot; | |
596 final Map<String, FileInfo> info; | |
597 final Map<String, String> _pseudoElements; | |
598 final Messages _messages; | |
599 final bool _warningsAsErrors; | |
600 | |
601 Set<StyleSheet> allStyleSheets = new Set<StyleSheet>(); | |
602 | |
603 /** | |
604 * [_pseudoElements] list of known pseudo attributes found in HTML, any | |
605 * CSS pseudo-elements 'name::custom-element' is mapped to the manged name | |
606 * associated with the pseudo-element key. | |
607 */ | |
608 _AnalyzerCss(this.packageRoot, this.info, this._pseudoElements, | |
609 this._messages, this._warningsAsErrors); | |
610 | |
611 /** | |
612 * Run the analyzer on every file that is a style sheet or any component that | |
613 * has a style tag. | |
614 */ | |
615 void process(SourceFile file) { | |
616 var fileInfo = info[file.path]; | |
617 if (file.isStyleSheet || fileInfo.styleSheets.length > 0) { | |
618 var styleSheets = processVars(fileInfo); | |
619 | |
620 // Add to list of all style sheets analyzed. | |
621 allStyleSheets.addAll(styleSheets); | |
622 } | |
623 | |
624 // Process any components. | |
625 for (var component in fileInfo.declaredComponents) { | |
626 var all = processVars(component); | |
627 | |
628 // Add to list of all style sheets analyzed. | |
629 allStyleSheets.addAll(all); | |
630 } | |
631 | |
632 processCustomPseudoElements(); | |
633 } | |
634 | |
635 void normalize() { | |
636 // Remove all var definitions for all style sheets analyzed. | |
637 for (var tree in allStyleSheets) new RemoveVarDefinitions().visitTree(tree); | |
638 } | |
639 | |
640 List<StyleSheet> processVars(var libraryInfo) { | |
641 // Get list of all stylesheet(s) dependencies referenced from this file. | |
642 var styleSheets = _dependencies(libraryInfo).toList(); | |
643 | |
644 var errors = []; | |
645 css.analyze(styleSheets, errors: errors, options: | |
646 [_warningsAsErrors ? '--warnings_as_errors' : '', 'memory']); | |
647 | |
648 // Print errors as warnings. | |
649 for (var e in errors) { | |
650 _messages.warning(e.message, e.span); | |
651 } | |
652 | |
653 // Build list of all var definitions. | |
654 Map varDefs = new Map(); | |
655 for (var tree in styleSheets) { | |
656 var allDefs = (new VarDefinitions()..visitTree(tree)).found; | |
657 allDefs.forEach((key, value) { | |
658 varDefs[key] = value; | |
659 }); | |
660 } | |
661 | |
662 // Resolve all definitions to a non-VarUsage (terminal expression). | |
663 varDefs.forEach((key, value) { | |
664 for (var expr in (value.expression as Expressions).expressions) { | |
665 var def = findTerminalVarDefinition(varDefs, value); | |
666 varDefs[key] = def; | |
667 } | |
668 }); | |
669 | |
670 // Resolve all var usages. | |
671 for (var tree in styleSheets) new ResolveVarUsages(varDefs).visitTree(tree); | |
672 | |
673 return styleSheets; | |
674 } | |
675 | |
676 processCustomPseudoElements() { | |
677 var polyFiller = new PseudoElementExpander(_pseudoElements); | |
678 for (var tree in allStyleSheets) { | |
679 polyFiller.visitTree(tree); | |
680 } | |
681 } | |
682 | |
683 /** | |
684 * Given a component or file check if any stylesheets referenced. If so then | |
685 * return a list of all referenced stylesheet dependencies (@imports or <link | |
686 * rel="stylesheet" ..>). | |
687 */ | |
688 Set<StyleSheet> _dependencies(var libraryInfo, {Set<StyleSheet> seen}) { | |
689 if (seen == null) seen = new Set(); | |
690 | |
691 // Used to resolve all pathing information. | |
692 var inputUrl = libraryInfo is FileInfo | |
693 ? libraryInfo.inputUrl | |
694 : (libraryInfo as ComponentInfo).declaringFile.inputUrl; | |
695 | |
696 for (var styleSheet in libraryInfo.styleSheets) { | |
697 if (!seen.contains(styleSheet)) { | |
698 // TODO(terry): VM uses expandos to implement hashes. Currently, it's a | |
699 // linear (not constant) time cost (see dartbug.com/5746). | |
700 // If this bug isn't fixed and performance show's this a | |
701 // a problem we'll need to implement our own hashCode or | |
702 // use a different key for better perf. | |
703 // Add the stylesheet. | |
704 seen.add(styleSheet); | |
705 | |
706 // Any other imports in this stylesheet? | |
707 var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot, | |
708 inputUrl, _messages); | |
709 | |
710 // Process other imports in this stylesheets. | |
711 for (var importSS in urlInfos) { | |
712 var importInfo = info[importSS.resolvedPath]; | |
713 if (importInfo != null) { | |
714 // Add all known stylesheets processed. | |
715 seen.addAll(importInfo.styleSheets); | |
716 // Find dependencies for stylesheet referenced with a | |
717 // @import | |
718 for (var ss in importInfo.styleSheets) { | |
719 var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl, | |
720 _messages); | |
721 for (var url in urls) { | |
722 _dependencies(info[url.resolvedPath], seen: seen); | |
723 } | |
724 } | |
725 } | |
726 } | |
727 } | |
728 } | |
729 | |
730 return seen; | |
731 } | |
732 } | |
OLD | NEW |