| 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 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 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 } |
| 130 } | 123 } |
| 131 | 124 |
| 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 { | |
| 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 | 125 |
| 197 /** | 126 /** |
| 198 * Information needed about other polymer-element tags in order to validate | 127 * Information needed about other polymer-element tags in order to validate |
| 199 * how they are used and extended. | 128 * how they are used and extended. |
| 200 * | 129 * |
| 201 * Note: these are only created for polymer-element, because pure custom | 130 * Note: these are only created for polymer-element, because pure custom |
| 202 * elements don't have a declarative form. | 131 * elements don't have a declarative form. |
| 203 */ | 132 */ |
| 204 class _ElementSummary { | 133 class _ElementSummary { |
| 205 final String tagName; | 134 final String tagName; |
| (...skipping 304 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 510 | 439 |
| 511 /** | 440 /** |
| 512 * Returns true if this is a valid custom element name. See: | 441 * 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> | 442 * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn
-custom-element-name> |
| 514 */ | 443 */ |
| 515 bool _isCustomTag(String name) { | 444 bool _isCustomTag(String name) { |
| 516 if (name == null || !name.contains('-')) return false; | 445 if (name == null || !name.contains('-')) return false; |
| 517 return !_invalidTagNames.containsKey(name); | 446 return !_invalidTagNames.containsKey(name); |
| 518 } | 447 } |
| 519 | 448 |
| 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 = | 449 const String USE_INIT_DART = |
| 525 'To run a polymer application, you need to call "initPolymer". You can ' | 450 'To run a polymer application, you need to call "initPolymer". You can ' |
| 526 'either include a generic script tag that does this for you:' | 451 'either include a generic script tag that does this for you:' |
| 527 '\'<script type="application/dart">export "package:polymer/init.dart";' | 452 '\'<script type="application/dart">export "package:polymer/init.dart";' |
| 528 '</script>\' or add your own script tag and call that function. ' | 453 '</script>\' or add your own script tag and call that function. ' |
| 529 'Make sure the script tag is placed after all HTML imports.'; | 454 'Make sure the script tag is placed after all HTML imports.'; |
| 530 | 455 |
| 531 const String BOOT_JS_DEPRECATED = | 456 const String BOOT_JS_DEPRECATED = |
| 532 '"boot.js" is now deprecated. Instead, you can initialize your polymer ' | 457 '"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 ' | 458 '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 ' | 459 'main, then you can include our generic main by adding the following ' |
| 535 'script tag to your page: \'<script type="application/dart">export ' | 460 'script tag to your page: \'<script type="application/dart">export ' |
| 536 '"package:polymer/init.dart";</script>\'. Additionally you need to ' | 461 '"package:polymer/init.dart";</script>\'. Additionally you need to ' |
| 537 'include: \'<script src="packages/browser/dart.js"></script>\' in the page ' | 462 'include: \'<script src="packages/browser/dart.js"></script>\' in the page ' |
| 538 'too. Make sure these script tags come after all HTML imports.'; | 463 'too. Make sure these script tags come after all HTML imports.'; |
| OLD | NEW |