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 * Logic to validate that developers are correctly using Polymer constructs. | 6 * Logic to validate that developers are correctly using Polymer constructs. |
7 * This is mainly used to produce warnings for feedback in the editor. | 7 * This is mainly used to produce warnings for feedback in the editor. |
8 */ | 8 */ |
9 library polymer.src.build.linter; | 9 library polymer.src.build.linter; |
10 | 10 |
11 import 'dart:io'; | 11 import 'dart:io'; |
12 import 'dart:async'; | 12 import 'dart:async'; |
13 import 'dart:mirrors'; | 13 import 'dart:mirrors'; |
14 import 'dart:convert' show JSON; | 14 import 'dart:convert' show JSON; |
15 | 15 |
16 import 'package:barback/barback.dart'; | 16 import 'package:barback/barback.dart'; |
17 import 'package:html5lib/dom.dart'; | 17 import 'package:html5lib/dom.dart'; |
18 import 'package:html5lib/dom_parsing.dart'; | 18 import 'package:html5lib/dom_parsing.dart'; |
19 import 'package:source_maps/span.dart'; | 19 import 'package:source_maps/span.dart'; |
20 | 20 |
21 import 'common.dart'; | 21 import 'common.dart'; |
22 import 'utils.dart'; | 22 import 'utils.dart'; |
23 | 23 |
24 typedef String MessageFormatter(String kind, String message, Span span); | |
25 | |
26 /** | 24 /** |
27 * A linter that checks for common Polymer errors and produces warnings to | 25 * A linter that checks for common Polymer errors and produces warnings to |
28 * show on the editor or the command line. Leaves sources unchanged, but creates | 26 * show on the editor or the command line. Leaves sources unchanged, but creates |
29 * a new asset containing all the warnings. | 27 * a new asset containing all the warnings. |
30 */ | 28 */ |
31 class Linter extends Transformer with PolymerTransformer { | 29 class Linter extends Transformer with PolymerTransformer { |
32 final TransformOptions options; | 30 final TransformOptions options; |
33 | 31 |
34 /** Only run on .html files. */ | 32 /** Only run on .html files. */ |
35 final String allowedExtensions = '.html'; | 33 final String allowedExtensions = '.html'; |
36 | 34 |
37 final MessageFormatter _formatter; | 35 Linter(this.options); |
38 | |
39 Linter(this.options, [this._formatter]); | |
40 | 36 |
41 Future apply(Transform transform) { | 37 Future apply(Transform transform) { |
42 var wrapper = new _LoggerInterceptor(transform, _formatter); | |
43 var seen = new Set<AssetId>(); | 38 var seen = new Set<AssetId>(); |
44 var primary = transform.primaryInput; | 39 var primary = transform.primaryInput; |
45 var id = primary.id; | 40 var id = primary.id; |
46 wrapper.addOutput(primary); // this phase is analysis only | 41 transform.addOutput(primary); // this phase is analysis only |
47 seen.add(id); | 42 seen.add(id); |
48 return readPrimaryAsHtml(wrapper).then((document) { | 43 return readPrimaryAsHtml(transform).then((document) { |
49 return _collectElements(document, id, wrapper, seen).then((elements) { | 44 return _collectElements(document, id, transform, seen).then((elements) { |
50 bool isEntrypoint = options.isHtmlEntryPoint(id); | 45 bool isEntrypoint = options.isHtmlEntryPoint(id); |
51 new _LinterVisitor(wrapper, elements, isEntrypoint).run(document); | 46 new _LinterVisitor(transform.logger, elements, isEntrypoint) |
52 var messagesId = id.addExtension('.messages'); | 47 .run(document); |
53 wrapper.addOutput(new Asset.fromString(messagesId, | |
54 wrapper._messages.join('\n'))); | |
55 }); | 48 }); |
56 }); | 49 }); |
57 } | 50 } |
58 | 51 |
59 /** | 52 /** |
60 * Collect into [elements] any data about each polymer-element defined in | 53 * Collect into [elements] any data about each polymer-element defined in |
61 * [document] or any of it's imports, unless they have already been [seen]. | 54 * [document] or any of it's imports, unless they have already been [seen]. |
62 * Elements are added in the order they appear, transitive imports are added | 55 * Elements are added in the order they appear, transitive imports are added |
63 * first. | 56 * first. |
64 */ | 57 */ |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
120 logger.warning('duplicate definition for custom tag "$name".', | 113 logger.warning('duplicate definition for custom tag "$name".', |
121 span: existing.span); | 114 span: existing.span); |
122 logger.warning('duplicate definition for custom tag "$name" ' | 115 logger.warning('duplicate definition for custom tag "$name" ' |
123 ' (second definition).', span: span); | 116 ' (second definition).', span: span); |
124 continue; | 117 continue; |
125 } | 118 } |
126 | 119 |
127 elements[name] = new _ElementSummary(name, extendsTag, tag.sourceSpan); | 120 elements[name] = new _ElementSummary(name, extendsTag, tag.sourceSpan); |
128 } | 121 } |
129 } | 122 } |
123 | |
124 String toString() => 'polymer-linter'; | |
130 } | 125 } |
131 | 126 |
132 /** A proxy of [Transform] that returns a different logger. */ | |
133 // TODO(sigmund): get rid of this when barback supports a better way to log | |
134 // messages without printing them. | |
135 class _LoggerInterceptor implements Transform, TransformLogger { | |
Jennifer Messerly
2013/12/12 03:32:55
awesome to see this go
| |
136 final Transform _original; | |
137 final List<String> _messages = []; | |
138 final MessageFormatter _formatter; | |
139 | |
140 _LoggerInterceptor(this._original, MessageFormatter formatter) | |
141 : _formatter = formatter == null ? consoleFormatter : formatter; | |
142 | |
143 TransformLogger get logger => this; | |
144 | |
145 noSuchMethod(Invocation m) => reflect(_original).delegate(m); | |
146 | |
147 // form TransformLogger: | |
148 void warning(String message, {AssetId asset, Span span}) | |
149 => _write('warning', message, span); | |
150 | |
151 void error(String message, {AssetId asset, Span span}) | |
152 => _write('error', message, span); | |
153 | |
154 void _write(String kind, String message, Span span) { | |
155 _messages.add(_formatter(kind, message, span)); | |
156 } | |
157 } | |
158 | |
159 /** | |
160 * Formatter that generates messages using a format that can be parsed | |
161 * by tools, such as the Dart Editor, for reporting error messages. | |
162 */ | |
163 String jsonFormatter(String kind, String message, Span span) { | |
164 return JSON.encode((span == null) | |
165 ? [{'method': 'warning', 'params': {'message': message}}] | |
166 : [{'method': kind, | |
167 'params': { | |
168 'file': span.sourceUrl, | |
169 'message': message, | |
170 'line': span.start.line + 1, | |
171 'charStart': span.start.offset, | |
172 'charEnd': span.end.offset, | |
173 }}]); | |
174 } | |
175 | |
176 /** | |
177 * Formatter that generates messages that are easy to read on the console (used | |
178 * by default). | |
179 */ | |
180 String consoleFormatter(String kind, String message, Span span) { | |
181 var useColors = stdioType(stdout) == StdioType.TERMINAL; | |
182 var levelColor = (kind == 'error') ? _RED_COLOR : _MAGENTA_COLOR; | |
183 var output = new StringBuffer(); | |
184 if (useColors) output.write(levelColor); | |
185 output..write(kind)..write(' '); | |
186 if (useColors) output.write(_NO_COLOR); | |
187 if (span == null) { | |
188 output.write(message); | |
189 } else { | |
190 output.write(span.getLocationMessage(message, | |
191 useColors: useColors, | |
192 color: levelColor)); | |
193 } | |
194 return output.toString(); | |
195 } | |
196 | 127 |
197 /** | 128 /** |
198 * Information needed about other polymer-element tags in order to validate | 129 * Information needed about other polymer-element tags in order to validate |
199 * how they are used and extended. | 130 * how they are used and extended. |
200 * | 131 * |
201 * Note: these are only created for polymer-element, because pure custom | 132 * Note: these are only created for polymer-element, because pure custom |
202 * elements don't have a declarative form. | 133 * elements don't have a declarative form. |
203 */ | 134 */ |
204 class _ElementSummary { | 135 class _ElementSummary { |
205 final String tagName; | 136 final String tagName; |
(...skipping 304 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
510 | 441 |
511 /** | 442 /** |
512 * Returns true if this is a valid custom element name. See: | 443 * Returns true if this is a valid custom element name. See: |
513 * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn -custom-element-name> | 444 * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn -custom-element-name> |
514 */ | 445 */ |
515 bool _isCustomTag(String name) { | 446 bool _isCustomTag(String name) { |
516 if (name == null || !name.contains('-')) return false; | 447 if (name == null || !name.contains('-')) return false; |
517 return !_invalidTagNames.containsKey(name); | 448 return !_invalidTagNames.containsKey(name); |
518 } | 449 } |
519 | 450 |
520 const String _RED_COLOR = '\u001b[31m'; | |
521 const String _MAGENTA_COLOR = '\u001b[35m'; | |
522 const String _NO_COLOR = '\u001b[0m'; | |
523 | |
524 const String USE_INIT_DART = | 451 const String USE_INIT_DART = |
525 'To run a polymer application, you need to call "initPolymer". You can ' | 452 'To run a polymer application, you need to call "initPolymer". You can ' |
526 'either include a generic script tag that does this for you:' | 453 'either include a generic script tag that does this for you:' |
527 '\'<script type="application/dart">export "package:polymer/init.dart";' | 454 '\'<script type="application/dart">export "package:polymer/init.dart";' |
528 '</script>\' or add your own script tag and call that function. ' | 455 '</script>\' or add your own script tag and call that function. ' |
529 'Make sure the script tag is placed after all HTML imports.'; | 456 'Make sure the script tag is placed after all HTML imports.'; |
530 | 457 |
531 const String BOOT_JS_DEPRECATED = | 458 const String BOOT_JS_DEPRECATED = |
532 '"boot.js" is now deprecated. Instead, you can initialize your polymer ' | 459 '"boot.js" is now deprecated. Instead, you can initialize your polymer ' |
533 'application by calling "initPolymer()" in your main. If you don\'t have a ' | 460 'application by calling "initPolymer()" in your main. If you don\'t have a ' |
534 'main, then you can include our generic main by adding the following ' | 461 'main, then you can include our generic main by adding the following ' |
535 'script tag to your page: \'<script type="application/dart">export ' | 462 'script tag to your page: \'<script type="application/dart">export ' |
536 '"package:polymer/init.dart";</script>\'. Additionally you need to ' | 463 '"package:polymer/init.dart";</script>\'. Additionally you need to ' |
537 'include: \'<script src="packages/browser/dart.js"></script>\' in the page ' | 464 'include: \'<script src="packages/browser/dart.js"></script>\' in the page ' |
538 'too. Make sure these script tags come after all HTML imports.'; | 465 'too. Make sure these script tags come after all HTML imports.'; |
OLD | NEW |