| 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 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 40 | 40 |
| 41 Future apply(Transform transform) { | 41 Future apply(Transform transform) { |
| 42 var wrapper = new _LoggerInterceptor(transform, _formatter); | 42 var wrapper = new _LoggerInterceptor(transform, _formatter); |
| 43 var seen = new Set<AssetId>(); | 43 var seen = new Set<AssetId>(); |
| 44 var primary = transform.primaryInput; | 44 var primary = transform.primaryInput; |
| 45 var id = primary.id; | 45 var id = primary.id; |
| 46 wrapper.addOutput(primary); // this phase is analysis only | 46 wrapper.addOutput(primary); // this phase is analysis only |
| 47 seen.add(id); | 47 seen.add(id); |
| 48 return readPrimaryAsHtml(wrapper).then((document) { | 48 return readPrimaryAsHtml(wrapper).then((document) { |
| 49 return _collectElements(document, id, wrapper, seen).then((elements) { | 49 return _collectElements(document, id, wrapper, seen).then((elements) { |
| 50 new _LinterVisitor(wrapper, elements).visit(document); | 50 bool isEntrypoint = options.isHtmlEntryPoint(id); |
| 51 new _LinterVisitor(wrapper, elements, isEntrypoint).run(document); |
| 51 var messagesId = id.addExtension('.messages'); | 52 var messagesId = id.addExtension('.messages'); |
| 52 wrapper.addOutput(new Asset.fromString(messagesId, | 53 wrapper.addOutput(new Asset.fromString(messagesId, |
| 53 wrapper._messages.join('\n'))); | 54 wrapper._messages.join('\n'))); |
| 54 }); | 55 }); |
| 55 }); | 56 }); |
| 56 } | 57 } |
| 57 | 58 |
| 58 /** | 59 /** |
| 59 * Collect into [elements] any data about each polymer-element defined in | 60 * Collect into [elements] any data about each polymer-element defined in |
| 60 * [document] or any of it's imports, unless they have already been [seen]. | 61 * [document] or any of it's imports, unless they have already been [seen]. |
| (...skipping 22 matching lines...) Expand all Loading... |
| 83 | 84 |
| 84 Future<List<AssetId>> _getImportedIds( | 85 Future<List<AssetId>> _getImportedIds( |
| 85 Document document, AssetId sourceId, Tranform transform) { | 86 Document document, AssetId sourceId, Tranform transform) { |
| 86 var importIds = []; | 87 var importIds = []; |
| 87 var logger = transform.logger; | 88 var logger = transform.logger; |
| 88 for (var tag in document.queryAll('link')) { | 89 for (var tag in document.queryAll('link')) { |
| 89 if (tag.attributes['rel'] != 'import') continue; | 90 if (tag.attributes['rel'] != 'import') continue; |
| 90 var href = tag.attributes['href']; | 91 var href = tag.attributes['href']; |
| 91 var span = tag.sourceSpan; | 92 var span = tag.sourceSpan; |
| 92 var id = resolve(sourceId, href, logger, span); | 93 var id = resolve(sourceId, href, logger, span); |
| 93 if (id == null) continue; | 94 if (id == null || |
| 95 (id.package == 'polymer' && id.path == 'lib/init.html')) continue; |
| 94 importIds.add(assetExists(id, transform).then((exists) { | 96 importIds.add(assetExists(id, transform).then((exists) { |
| 95 if (exists) return id; | 97 if (exists) return id; |
| 96 if (sourceId == transform.primaryInput.id) { | 98 if (sourceId == transform.primaryInput.id) { |
| 97 logger.error('couldn\'t find imported asset "${id.path}" in package ' | 99 logger.error('couldn\'t find imported asset "${id.path}" in package ' |
| 98 '"${id.package}".', span: span); | 100 '"${id.package}".', span: span); |
| 99 } | 101 } |
| 100 })); | 102 })); |
| 101 } | 103 } |
| 102 return Future.wait(importIds); | 104 return Future.wait(importIds); |
| 103 } | 105 } |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 211 ? extendsTag : extendsType.baseExtendsTag; | 213 ? extendsTag : extendsType.baseExtendsTag; |
| 212 | 214 |
| 213 _ElementSummary(this.tagName, this.extendsTag, this.span); | 215 _ElementSummary(this.tagName, this.extendsTag, this.span); |
| 214 | 216 |
| 215 String toString() => "($tagName <: $extendsTag)"; | 217 String toString() => "($tagName <: $extendsTag)"; |
| 216 } | 218 } |
| 217 | 219 |
| 218 class _LinterVisitor extends TreeVisitor { | 220 class _LinterVisitor extends TreeVisitor { |
| 219 TransformLogger _logger; | 221 TransformLogger _logger; |
| 220 bool _inPolymerElement = false; | 222 bool _inPolymerElement = false; |
| 223 bool _dartJSSeen = false; |
| 224 bool _initSeen = false; |
| 225 bool _isEntrypoint; |
| 221 Map<String, _ElementSummary> _elements; | 226 Map<String, _ElementSummary> _elements; |
| 222 | 227 |
| 223 _LinterVisitor(this._logger, this._elements) { | 228 _LinterVisitor(this._logger, this._elements, this._isEntrypoint) { |
| 224 // We normalize the map, so each element has a direct reference to any | 229 // We normalize the map, so each element has a direct reference to any |
| 225 // element it extends from. | 230 // element it extends from. |
| 226 for (var tag in _elements.values) { | 231 for (var tag in _elements.values) { |
| 227 var extendsTag = tag.extendsTag; | 232 var extendsTag = tag.extendsTag; |
| 228 if (extendsTag == null) continue; | 233 if (extendsTag == null) continue; |
| 229 tag.extendsType = _elements[extendsTag]; | 234 tag.extendsType = _elements[extendsTag]; |
| 230 } | 235 } |
| 231 } | 236 } |
| 232 | 237 |
| 233 void visitElement(Element node) { | 238 void visitElement(Element node) { |
| 234 switch (node.tagName) { | 239 switch (node.tagName) { |
| 235 case 'link': _validateLinkElement(node); break; | 240 case 'link': _validateLinkElement(node); break; |
| 236 case 'element': _validateElementElement(node); break; | 241 case 'element': _validateElementElement(node); break; |
| 237 case 'polymer-element': _validatePolymerElement(node); break; | 242 case 'polymer-element': _validatePolymerElement(node); break; |
| 238 case 'script': _validateScriptElement(node); break; | 243 case 'script': _validateScriptElement(node); break; |
| 239 default: | 244 default: |
| 240 _validateNormalElement(node); | 245 _validateNormalElement(node); |
| 241 super.visitElement(node); | 246 super.visitElement(node); |
| 242 break; | 247 break; |
| 243 } | 248 } |
| 244 } | 249 } |
| 245 | 250 |
| 251 void run(Document doc) { |
| 252 visit(doc); |
| 253 |
| 254 if (_isEntrypoint && !_initSeen) { |
| 255 _logger.error(USE_INIT_DART, span: doc.body.sourceSpan); |
| 256 } |
| 257 |
| 258 if (_isEntrypoint && !_dartJSSeen) { |
| 259 // TODO(sigmund): remove this when webkitStartDart is gone. |
| 260 _logger.error(USE_DART_JS, span: doc.body.sourceSpan); |
| 261 } |
| 262 } |
| 263 |
| 246 /** Produce warnings for invalid link-rel tags. */ | 264 /** Produce warnings for invalid link-rel tags. */ |
| 247 void _validateLinkElement(Element node) { | 265 void _validateLinkElement(Element node) { |
| 248 var rel = node.attributes['rel']; | 266 var rel = node.attributes['rel']; |
| 249 if (rel != 'import' && rel != 'stylesheet') return; | 267 if (rel != 'import' && rel != 'stylesheet') return; |
| 250 | 268 |
| 269 if (rel == 'import' && _initSeen) { |
| 270 _logger.warning( |
| 271 "Move HTML imports above the 'polymer/init.dart' script tag", |
| 272 span: node.sourceSpan); |
| 273 } |
| 274 |
| 251 var href = node.attributes['href']; | 275 var href = node.attributes['href']; |
| 252 if (href != null && href != '') return; | 276 if (href != null && href != '') return; |
| 253 | 277 |
| 254 // TODO(sigmund): warn also if href can't be resolved. | 278 // TODO(sigmund): warn also if href can't be resolved. |
| 255 _logger.warning('link rel="$rel" missing href.', span: node.sourceSpan); | 279 _logger.warning('link rel="$rel" missing href.', span: node.sourceSpan); |
| 256 } | 280 } |
| 257 | 281 |
| 258 /** Produce warnings if using `<element>` instead of `<polymer-element>`. */ | 282 /** Produce warnings if using `<element>` instead of `<polymer-element>`. */ |
| 259 void _validateElementElement(Element node) { | 283 void _validateElementElement(Element node) { |
| 260 _logger.warning('<element> elements are not supported, use' | 284 _logger.warning('<element> elements are not supported, use' |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 333 // that the code is indeed using Dart. | 357 // that the code is indeed using Dart. |
| 334 _logger.warning('script tag in polymer element with no type will ' | 358 _logger.warning('script tag in polymer element with no type will ' |
| 335 'be treated as JavaScript. Did you forget type="application/dart"?', | 359 'be treated as JavaScript. Did you forget type="application/dart"?', |
| 336 span: node.sourceSpan); | 360 span: node.sourceSpan); |
| 337 } | 361 } |
| 338 if (src != null && src.endsWith('.dart')) { | 362 if (src != null && src.endsWith('.dart')) { |
| 339 _logger.warning('script tag with .dart source file but no type will ' | 363 _logger.warning('script tag with .dart source file but no type will ' |
| 340 'be treated as JavaScript. Did you forget type="application/dart"?', | 364 'be treated as JavaScript. Did you forget type="application/dart"?', |
| 341 span: node.sourceSpan); | 365 span: node.sourceSpan); |
| 342 } | 366 } |
| 367 } |
| 368 |
| 369 if (src == null) return; |
| 370 |
| 371 if (src == 'packages/polymer/boot.js') { |
| 372 _logger.warning(BOOT_JS_DEPRECATED, span: node.sourceSpan); |
| 373 return; |
| 374 } |
| 375 if (src == 'packages/browser/dart.js' || |
| 376 src == 'packages/unittest/test_controller.js') { |
| 377 _dartJSSeen = true; |
| 378 return; |
| 379 } |
| 380 |
| 381 if (src == 'packages/polymer/init.dart') { |
| 382 _initSeen = true; |
| 383 if (scriptType != 'application/dart') { |
| 384 _logger.warning('wrong script type, expected type="application/dart".', |
| 385 span: node.sourceSpan); |
| 386 } |
| 343 return; | 387 return; |
| 344 } | 388 } |
| 345 | 389 |
| 346 if (scriptType != 'application/dart') return; | 390 if (scriptType != 'application/dart') return; |
| 347 | 391 |
| 348 if (src != null) { | 392 if (!src.endsWith('.dart')) { |
| 349 if (!src.endsWith('.dart')) { | 393 _logger.warning('"application/dart" scripts should ' |
| 350 _logger.warning('"application/dart" scripts should ' | 394 'use the .dart file extension.', |
| 351 'use the .dart file extension.', | 395 span: node.sourceSpan); |
| 352 span: node.sourceSpan); | 396 } |
| 353 } | |
| 354 | 397 |
| 355 if (node.innerHtml.trim() != '') { | 398 if (node.innerHtml.trim() != '') { |
| 356 _logger.warning('script tag has "src" attribute and also has script ' | 399 _logger.warning('script tag has "src" attribute and also has script ' |
| 357 'text.', span: node.sourceSpan); | 400 'text.', span: node.sourceSpan); |
| 358 } | |
| 359 } | 401 } |
| 360 } | 402 } |
| 361 | 403 |
| 362 /** | 404 /** |
| 363 * Produces warnings for misuses of on-foo event handlers, and for instanting | 405 * Produces warnings for misuses of on-foo event handlers, and for instanting |
| 364 * custom tags incorrectly. | 406 * custom tags incorrectly. |
| 365 */ | 407 */ |
| 366 void _validateNormalElement(Element node) { | 408 void _validateNormalElement(Element node) { |
| 367 // Event handlers only allowed inside polymer-elements | 409 // Event handlers only allowed inside polymer-elements |
| 368 node.attributes.forEach((name, value) { | 410 node.attributes.forEach((name, value) { |
| (...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 490 | 532 |
| 491 /** | 533 /** |
| 492 * Returns true if this is a valid custom element name. See: | 534 * Returns true if this is a valid custom element name. See: |
| 493 * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn
-custom-element-name> | 535 * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn
-custom-element-name> |
| 494 */ | 536 */ |
| 495 bool _isCustomTag(String name) { | 537 bool _isCustomTag(String name) { |
| 496 if (name == null || !name.contains('-')) return false; | 538 if (name == null || !name.contains('-')) return false; |
| 497 return !_invalidTagNames.containsKey(name); | 539 return !_invalidTagNames.containsKey(name); |
| 498 } | 540 } |
| 499 | 541 |
| 500 final String _RED_COLOR = '\u001b[31m'; | 542 const String _RED_COLOR = '\u001b[31m'; |
| 501 final String _MAGENTA_COLOR = '\u001b[35m'; | 543 const String _MAGENTA_COLOR = '\u001b[35m'; |
| 502 final String _NO_COLOR = '\u001b[0m'; | 544 const String _NO_COLOR = '\u001b[0m'; |
| 545 |
| 546 const String USE_INIT_DART = |
| 547 'To run a polymer applications, make sure to include ' |
| 548 '\'<script type="application/dart" src="packages/polymer/init.dart">' |
| 549 '</script>\' in your page, after all HTML imports.'; |
| 550 |
| 551 const String USE_DART_JS = |
| 552 'To run a polymer applications in Dartium, make sure to include' |
| 553 '\'<script src="packages/browser/dart.js"></script>\' in your page'; |
| 554 |
| 555 const String BOOT_JS_DEPRECATED = |
| 556 '"boot.js" is now deprecated. Instead, you can initialize your polymer ' |
| 557 'application by calling "initPolymer()" in your main. If you don\'t have a ' |
| 558 'main, then you can include our generic main by adding the following ' |
| 559 'script tag to your page: \'<script type="application/dart" ' |
| 560 'src="packages/polymer/init.dart"> </script>\'. Additionally you need to ' |
| 561 'include: \'<script src="packages/browser/dart.js"></script>\' in the page ' |
| 562 'too. Make sure these script tags come after all HTML imports.'; |
| OLD | NEW |