| 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 /// Logic to validate that developers are correctly using Polymer constructs. | 5 /// Logic to validate that developers are correctly using Polymer constructs. |
| 6 /// This is mainly used to produce warnings for feedback in the editor. | 6 /// This is mainly used to produce warnings for feedback in the editor. |
| 7 library polymer.src.build.linter; | 7 library polymer.src.build.linter; |
| 8 | 8 |
| 9 import 'dart:async'; | 9 import 'dart:async'; |
| 10 | 10 |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 69 | 69 |
| 70 Future<List<AssetId>> _getImportedIds( | 70 Future<List<AssetId>> _getImportedIds( |
| 71 Document document, AssetId sourceId, Transform transform) { | 71 Document document, AssetId sourceId, Transform transform) { |
| 72 var importIds = []; | 72 var importIds = []; |
| 73 var logger = transform.logger; | 73 var logger = transform.logger; |
| 74 for (var tag in document.querySelectorAll('link')) { | 74 for (var tag in document.querySelectorAll('link')) { |
| 75 if (tag.attributes['rel'] != 'import') continue; | 75 if (tag.attributes['rel'] != 'import') continue; |
| 76 var href = tag.attributes['href']; | 76 var href = tag.attributes['href']; |
| 77 var span = tag.sourceSpan; | 77 var span = tag.sourceSpan; |
| 78 var id = uriToAssetId(sourceId, href, logger, span); | 78 var id = uriToAssetId(sourceId, href, logger, span); |
| 79 if (id == null || | 79 if (id == null) continue; |
| 80 (id.package == 'polymer' && id.path == 'lib/init.html')) continue; | |
| 81 importIds.add(assetExists(id, transform).then((exists) { | 80 importIds.add(assetExists(id, transform).then((exists) { |
| 82 if (exists) return id; | 81 if (exists) return id; |
| 83 if (sourceId == transform.primaryInput.id) { | 82 if (sourceId == transform.primaryInput.id) { |
| 84 logger.error('couldn\'t find imported asset "${id.path}" in package ' | 83 logger.error('couldn\'t find imported asset "${id.path}" in package ' |
| 85 '"${id.package}".', span: span); | 84 '"${id.package}".', span: span); |
| 86 } | 85 } |
| 87 })); | 86 })); |
| 88 } | 87 } |
| 89 return Future.wait(importIds); | 88 return Future.wait(importIds); |
| 90 } | 89 } |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 134 _ElementSummary(this.tagName, this.extendsTag, this.span); | 133 _ElementSummary(this.tagName, this.extendsTag, this.span); |
| 135 | 134 |
| 136 String toString() => "($tagName <: $extendsTag)"; | 135 String toString() => "($tagName <: $extendsTag)"; |
| 137 } | 136 } |
| 138 | 137 |
| 139 class _LinterVisitor extends TreeVisitor { | 138 class _LinterVisitor extends TreeVisitor { |
| 140 TransformLogger _logger; | 139 TransformLogger _logger; |
| 141 bool _inPolymerElement = false; | 140 bool _inPolymerElement = false; |
| 142 bool _dartTagSeen = false; | 141 bool _dartTagSeen = false; |
| 143 bool _polymerHtmlSeen = false; | 142 bool _polymerHtmlSeen = false; |
| 143 bool _polymerExperimentalHtmlSeen = false; |
| 144 bool _isEntrypoint; | 144 bool _isEntrypoint; |
| 145 Map<String, _ElementSummary> _elements; | 145 Map<String, _ElementSummary> _elements; |
| 146 | 146 |
| 147 _LinterVisitor(this._logger, this._elements, this._isEntrypoint) { | 147 _LinterVisitor(this._logger, this._elements, this._isEntrypoint) { |
| 148 // We normalize the map, so each element has a direct reference to any | 148 // We normalize the map, so each element has a direct reference to any |
| 149 // element it extends from. | 149 // element it extends from. |
| 150 for (var tag in _elements.values) { | 150 for (var tag in _elements.values) { |
| 151 var extendsTag = tag.extendsTag; | 151 var extendsTag = tag.extendsTag; |
| 152 if (extendsTag == null) continue; | 152 if (extendsTag == null) continue; |
| 153 tag.extendsType = _elements[extendsTag]; | 153 tag.extendsType = _elements[extendsTag]; |
| 154 } | 154 } |
| 155 } | 155 } |
| 156 | 156 |
| 157 void visitElement(Element node) { | 157 void visitElement(Element node) { |
| 158 switch (node.localName) { | 158 switch (node.localName) { |
| 159 case 'link': _validateLinkElement(node); break; | 159 case 'link': _validateLinkElement(node); break; |
| 160 case 'element': _validateElementElement(node); break; | 160 case 'element': _validateElementElement(node); break; |
| 161 case 'polymer-element': _validatePolymerElement(node); break; | 161 case 'polymer-element': _validatePolymerElement(node); break; |
| 162 case 'script': _validateScriptElement(node); break; | 162 case 'script': _validateScriptElement(node); break; |
| 163 default: | 163 default: |
| 164 _validateNormalElement(node); | 164 _validateNormalElement(node); |
| 165 super.visitElement(node); | 165 super.visitElement(node); |
| 166 break; | 166 break; |
| 167 } | 167 } |
| 168 } | 168 } |
| 169 | 169 |
| 170 void run(Document doc) { | 170 void run(Document doc) { |
| 171 visit(doc); | 171 visit(doc); |
| 172 | 172 |
| 173 if (_isEntrypoint && !_polymerHtmlSeen) { | 173 if (_isEntrypoint && !_polymerHtmlSeen && !_polymerExperimentalHtmlSeen) { |
| 174 _logger.warning(USE_POLYMER_HTML, span: doc.body.sourceSpan); | 174 _logger.warning(USE_POLYMER_HTML, span: doc.body.sourceSpan); |
| 175 } | 175 } |
| 176 |
| 177 if (_isEntrypoint && !_dartTagSeen && !_polymerExperimentalHtmlSeen) { |
| 178 _logger.warning(USE_INIT_DART, span: doc.body.sourceSpan); |
| 179 } |
| 176 } | 180 } |
| 177 | 181 |
| 178 /// Produce warnings for invalid link-rel tags. | 182 /// Produce warnings for invalid link-rel tags. |
| 179 void _validateLinkElement(Element node) { | 183 void _validateLinkElement(Element node) { |
| 180 var rel = node.attributes['rel']; | 184 var rel = node.attributes['rel']; |
| 181 if (rel != 'import' && rel != 'stylesheet') return; | 185 if (rel != 'import' && rel != 'stylesheet') return; |
| 182 | 186 |
| 183 if (rel == 'import' && _dartTagSeen) { | 187 if (rel == 'import' && _dartTagSeen) { |
| 184 _logger.warning( | 188 _logger.warning("Move HTML imports above your Dart script tag.", |
| 185 "Move HTML imports above your Dart script tag.", | |
| 186 span: node.sourceSpan); | 189 span: node.sourceSpan); |
| 187 } | 190 } |
| 188 | 191 |
| 189 var href = node.attributes['href']; | 192 var href = node.attributes['href']; |
| 190 if (href == null || href == '') { | 193 if (href == null || href == '') { |
| 191 _logger.warning('link rel="$rel" missing href.', span: node.sourceSpan); | 194 _logger.warning('link rel="$rel" missing href.', span: node.sourceSpan); |
| 192 return; | 195 return; |
| 193 } | 196 } |
| 194 | 197 |
| 195 if (href == 'packages/polymer/polymer.html') { | 198 if (href == 'packages/polymer/polymer.html') { |
| 196 _polymerHtmlSeen = true; | 199 _polymerHtmlSeen = true; |
| 200 } else if (href == POLYMER_EXPERIMENTAL_HTML) { |
| 201 _polymerExperimentalHtmlSeen = true; |
| 197 } | 202 } |
| 198 // TODO(sigmund): warn also if href can't be resolved. | 203 // TODO(sigmund): warn also if href can't be resolved. |
| 199 } | 204 } |
| 200 | 205 |
| 201 /// Produce warnings if using `<element>` instead of `<polymer-element>`. | 206 /// Produce warnings if using `<element>` instead of `<polymer-element>`. |
| 202 void _validateElementElement(Element node) { | 207 void _validateElementElement(Element node) { |
| 203 _logger.warning('<element> elements are not supported, use' | 208 _logger.warning('<element> elements are not supported, use' |
| 204 ' <polymer-element> instead', span: node.sourceSpan); | 209 ' <polymer-element> instead', span: node.sourceSpan); |
| 205 } | 210 } |
| 206 | 211 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 244 } | 249 } |
| 245 | 250 |
| 246 var oldValue = _inPolymerElement; | 251 var oldValue = _inPolymerElement; |
| 247 _inPolymerElement = true; | 252 _inPolymerElement = true; |
| 248 super.visitElement(node); | 253 super.visitElement(node); |
| 249 _inPolymerElement = oldValue; | 254 _inPolymerElement = oldValue; |
| 250 } | 255 } |
| 251 | 256 |
| 252 /// Checks for multiple Dart script tags in the same page, which is invalid. | 257 /// Checks for multiple Dart script tags in the same page, which is invalid. |
| 253 void _validateScriptElement(Element node) { | 258 void _validateScriptElement(Element node) { |
| 259 var scriptType = node.attributes['type']; |
| 260 var isDart = scriptType == 'application/dart'; |
| 254 var src = node.attributes['src']; | 261 var src = node.attributes['src']; |
| 262 |
| 263 if (isDart) { |
| 264 if (_dartTagSeen) _logger.warning(ONLY_ONE_TAG, span: node.sourceSpan); |
| 265 if (_isEntrypoint && _polymerExperimentalHtmlSeen) { |
| 266 _logger.warning(NO_DART_SCRIPT_AND_EXPERIMENTAL, span: node.sourceSpan); |
| 267 } |
| 268 _dartTagSeen = true; |
| 269 } |
| 270 |
| 255 if (src == null) return; | 271 if (src == null) return; |
| 256 var type = node.attributes['type']; | |
| 257 bool isDart = type == 'application/dart;component=1' || | |
| 258 type == 'application/dart'; | |
| 259 | 272 |
| 260 if (src.endsWith('.dart') && !isDart) { | 273 if (src.endsWith('.dart') && !isDart) { |
| 261 _logger.warning('Wrong script type, expected type="application/dart" ' | 274 _logger.warning('Wrong script type, expected type="application/dart".', |
| 262 'or type="application/dart;component=1".', span: node.sourceSpan); | 275 span: node.sourceSpan); |
| 263 return; | 276 return; |
| 264 } | 277 } |
| 265 | 278 |
| 266 if (!src.endsWith('.dart') && isDart) { | 279 if (!src.endsWith('.dart') && isDart) { |
| 267 _logger.warning('"$type" scripts should use the .dart file extension.', | 280 _logger.warning('"application/dart" scripts should use the .dart file ' |
| 268 span: node.sourceSpan); | 281 'extension.', span: node.sourceSpan); |
| 269 return; | 282 return; |
| 270 } | 283 } |
| 271 | 284 |
| 272 if (node.innerHtml.trim() != '') { | 285 if (node.innerHtml.trim() != '') { |
| 273 _logger.warning('script tag has "src" attribute and also has script ' | 286 _logger.warning('script tag has "src" attribute and also has script ' |
| 274 'text.', span: node.sourceSpan); | 287 'text.', span: node.sourceSpan); |
| 275 } | 288 } |
| 276 } | 289 } |
| 277 | 290 |
| 278 /// Produces warnings for misuses of on-foo event handlers, and for instanting | 291 /// Produces warnings for misuses of on-foo event handlers, and for instanting |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 370 | 383 |
| 371 if (value.contains('(')) { | 384 if (value.contains('(')) { |
| 372 _logger.warning('Invalid event handler body "$value". Declare a method ' | 385 _logger.warning('Invalid event handler body "$value". Declare a method ' |
| 373 'in your custom element "void handlerName(event, detail, target)" ' | 386 'in your custom element "void handlerName(event, detail, target)" ' |
| 374 'and use the form $name="handlerName".', | 387 'and use the form $name="handlerName".', |
| 375 span: node.attributeSpans[name]); | 388 span: node.attributeSpans[name]); |
| 376 } | 389 } |
| 377 } | 390 } |
| 378 } | 391 } |
| 379 | 392 |
| 393 const String ONLY_ONE_TAG = |
| 394 'Only one "application/dart" script tag per document is allowed.'; |
| 395 |
| 380 const String USE_POLYMER_HTML = | 396 const String USE_POLYMER_HTML = |
| 381 'To run a polymer application you need to include the following HTML ' | 397 'Besides the initPolymer invocation, to run a polymer application you need ' |
| 382 'import: <link rel="import" href="packages/polymer/polymer.html">. This ' | 398 'to include the following HTML import: ' |
| 383 'will include the common polymer logic needed to boostrap your ' | 399 '<link rel="import" href="packages/polymer/polymer.html">. This will ' |
| 384 'application. The old style of initializing polymer with boot.js or ' | 400 'include the common polymer logic needed to boostrap your application.'; |
| 385 'initPolymer are now deprecated. '; | |
| 386 | 401 |
| 402 const String USE_INIT_DART = |
| 403 'To run a polymer application, you need to call "initPolymer". You can ' |
| 404 'either include a generic script tag that does this for you:' |
| 405 '\'<script type="application/dart">export "package:polymer/init.dart";' |
| 406 '</script>\' or add your own script tag and call that function. ' |
| 407 'Make sure the script tag is placed after all HTML imports.'; |
| 408 |
| 409 const String NO_DART_SCRIPT_AND_EXPERIMENTAL = |
| 410 'The experimental bootstrap feature doesn\'t support script tags on ' |
| 411 'the main document (for now).'; |
| OLD | NEW |