| 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 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 214 | 214 |
| 215 _ElementSummary(this.tagName, this.extendsTag, this.span); | 215 _ElementSummary(this.tagName, this.extendsTag, this.span); |
| 216 | 216 |
| 217 String toString() => "($tagName <: $extendsTag)"; | 217 String toString() => "($tagName <: $extendsTag)"; |
| 218 } | 218 } |
| 219 | 219 |
| 220 class _LinterVisitor extends TreeVisitor { | 220 class _LinterVisitor extends TreeVisitor { |
| 221 TransformLogger _logger; | 221 TransformLogger _logger; |
| 222 bool _inPolymerElement = false; | 222 bool _inPolymerElement = false; |
| 223 bool _dartJSSeen = false; | 223 bool _dartJSSeen = false; |
| 224 bool _initSeen = false; | 224 bool _dartTagSeen = false; |
| 225 bool _isEntrypoint; | 225 bool _isEntrypoint; |
| 226 Map<String, _ElementSummary> _elements; | 226 Map<String, _ElementSummary> _elements; |
| 227 | 227 |
| 228 _LinterVisitor(this._logger, this._elements, this._isEntrypoint) { | 228 _LinterVisitor(this._logger, this._elements, this._isEntrypoint) { |
| 229 // 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 |
| 230 // element it extends from. | 230 // element it extends from. |
| 231 for (var tag in _elements.values) { | 231 for (var tag in _elements.values) { |
| 232 var extendsTag = tag.extendsTag; | 232 var extendsTag = tag.extendsTag; |
| 233 if (extendsTag == null) continue; | 233 if (extendsTag == null) continue; |
| 234 tag.extendsType = _elements[extendsTag]; | 234 tag.extendsType = _elements[extendsTag]; |
| 235 } | 235 } |
| 236 } | 236 } |
| 237 | 237 |
| 238 void visitElement(Element node) { | 238 void visitElement(Element node) { |
| 239 switch (node.tagName) { | 239 switch (node.tagName) { |
| 240 case 'link': _validateLinkElement(node); break; | 240 case 'link': _validateLinkElement(node); break; |
| 241 case 'element': _validateElementElement(node); break; | 241 case 'element': _validateElementElement(node); break; |
| 242 case 'polymer-element': _validatePolymerElement(node); break; | 242 case 'polymer-element': _validatePolymerElement(node); break; |
| 243 case 'script': _validateScriptElement(node); break; | 243 case 'script': _validateScriptElement(node); break; |
| 244 default: | 244 default: |
| 245 _validateNormalElement(node); | 245 _validateNormalElement(node); |
| 246 super.visitElement(node); | 246 super.visitElement(node); |
| 247 break; | 247 break; |
| 248 } | 248 } |
| 249 } | 249 } |
| 250 | 250 |
| 251 void run(Document doc) { | 251 void run(Document doc) { |
| 252 visit(doc); | 252 visit(doc); |
| 253 | 253 |
| 254 if (_isEntrypoint && !_initSeen) { | 254 if (_isEntrypoint && !_dartTagSeen) { |
| 255 _logger.error(USE_INIT_DART, span: doc.body.sourceSpan); | 255 _logger.error(USE_INIT_DART, span: doc.body.sourceSpan); |
| 256 } | 256 } |
| 257 | 257 |
| 258 if (_isEntrypoint && !_dartJSSeen) { | 258 if (_isEntrypoint && !_dartJSSeen) { |
| 259 // TODO(sigmund): remove this when webkitStartDart is gone. | 259 // TODO(sigmund): remove this when webkitStartDart is gone. |
| 260 _logger.error(USE_DART_JS, span: doc.body.sourceSpan); | 260 _logger.error(USE_DART_JS, span: doc.body.sourceSpan); |
| 261 } | 261 } |
| 262 } | 262 } |
| 263 | 263 |
| 264 /** Produce warnings for invalid link-rel tags. */ | 264 /** Produce warnings for invalid link-rel tags. */ |
| 265 void _validateLinkElement(Element node) { | 265 void _validateLinkElement(Element node) { |
| 266 var rel = node.attributes['rel']; | 266 var rel = node.attributes['rel']; |
| 267 if (rel != 'import' && rel != 'stylesheet') return; | 267 if (rel != 'import' && rel != 'stylesheet') return; |
| 268 | 268 |
| 269 if (rel == 'import' && _initSeen) { | 269 if (rel == 'import' && _dartTagSeen) { |
| 270 _logger.warning( | 270 _logger.warning( |
| 271 "Move HTML imports above the 'polymer/init.dart' script tag", | 271 "Move HTML imports above your Dart script tag.", |
| 272 span: node.sourceSpan); | 272 span: node.sourceSpan); |
| 273 } | 273 } |
| 274 | 274 |
| 275 var href = node.attributes['href']; | 275 var href = node.attributes['href']; |
| 276 if (href != null && href != '') return; | 276 if (href != null && href != '') return; |
| 277 | 277 |
| 278 // TODO(sigmund): warn also if href can't be resolved. | 278 // TODO(sigmund): warn also if href can't be resolved. |
| 279 _logger.warning('link rel="$rel" missing href.', span: node.sourceSpan); | 279 _logger.warning('link rel="$rel" missing href.', span: node.sourceSpan); |
| 280 } | 280 } |
| 281 | 281 |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 340 * error, we warn about it when src file ends in .dart, but the type is | 340 * error, we warn about it when src file ends in .dart, but the type is |
| 341 * incorrect, or when users write code in an inline script tag of a custom | 341 * incorrect, or when users write code in an inline script tag of a custom |
| 342 * element. | 342 * element. |
| 343 * | 343 * |
| 344 * The hope is that these cases shouldn't break existing valid code, but that | 344 * The hope is that these cases shouldn't break existing valid code, but that |
| 345 * they'll help Polymer authors avoid having their Dart code accidentally | 345 * they'll help Polymer authors avoid having their Dart code accidentally |
| 346 * interpreted as JavaScript by the browser. | 346 * interpreted as JavaScript by the browser. |
| 347 */ | 347 */ |
| 348 void _validateScriptElement(Element node) { | 348 void _validateScriptElement(Element node) { |
| 349 var scriptType = node.attributes['type']; | 349 var scriptType = node.attributes['type']; |
| 350 var isDart = scriptType == 'application/dart'; |
| 350 var src = node.attributes['src']; | 351 var src = node.attributes['src']; |
| 351 | 352 |
| 352 if (scriptType == null) { | 353 if (scriptType == null) { |
| 353 if (src == null && _inPolymerElement) { | 354 if (src == null && _inPolymerElement) { |
| 354 // TODO(sigmund): revisit this check once we start interop with polymer | 355 // TODO(sigmund): revisit this check once we start interop with polymer |
| 355 // elements written in JS. Maybe we need to inspect the contents of the | 356 // elements written in JS. Maybe we need to inspect the contents of the |
| 356 // script to find whether there is an import or something that indicates | 357 // script to find whether there is an import or something that indicates |
| 357 // that the code is indeed using Dart. | 358 // that the code is indeed using Dart. |
| 358 _logger.warning('script tag in polymer element with no type will ' | 359 _logger.warning('script tag in polymer element with no type will ' |
| 359 'be treated as JavaScript. Did you forget type="application/dart"?', | 360 'be treated as JavaScript. Did you forget type="application/dart"?', |
| 360 span: node.sourceSpan); | 361 span: node.sourceSpan); |
| 361 } | 362 } |
| 362 if (src != null && src.endsWith('.dart')) { | 363 } else if (isDart) { |
| 363 _logger.warning('script tag with .dart source file but no type will ' | 364 if (_dartTagSeen) { |
| 364 'be treated as JavaScript. Did you forget type="application/dart"?', | 365 _logger.warning('Only one "application/dart" script tag per document ' |
| 365 span: node.sourceSpan); | 366 'is allowed.', span: node.sourceSpan); |
| 366 } | 367 } |
| 368 _dartTagSeen = true; |
| 367 } | 369 } |
| 368 | 370 |
| 369 if (src == null) return; | 371 if (src == null) return; |
| 370 | 372 |
| 371 if (src == 'packages/polymer/boot.js') { | 373 if (src == 'packages/polymer/boot.js') { |
| 372 _logger.warning(BOOT_JS_DEPRECATED, span: node.sourceSpan); | 374 _logger.warning(BOOT_JS_DEPRECATED, span: node.sourceSpan); |
| 373 return; | 375 return; |
| 374 } | 376 } |
| 375 if (src == 'packages/browser/dart.js' || | 377 if (src == 'packages/browser/dart.js' || |
| 376 src == 'packages/unittest/test_controller.js') { | 378 src == 'packages/unittest/test_controller.js') { |
| 377 _dartJSSeen = true; | 379 _dartJSSeen = true; |
| 378 return; | 380 return; |
| 379 } | 381 } |
| 380 | 382 |
| 381 if (src == 'packages/polymer/init.dart') { | 383 if (src.endsWith('.dart') && !isDart) { |
| 382 _initSeen = true; | 384 _logger.warning('Wrong script type, expected type="application/dart".', |
| 383 if (scriptType != 'application/dart') { | 385 span: node.sourceSpan); |
| 384 _logger.warning('wrong script type, expected type="application/dart".', | |
| 385 span: node.sourceSpan); | |
| 386 } | |
| 387 return; | 386 return; |
| 388 } | 387 } |
| 389 | 388 |
| 390 if (scriptType != 'application/dart') return; | 389 if (!src.endsWith('.dart') && isDart) { |
| 391 | |
| 392 if (!src.endsWith('.dart')) { | |
| 393 _logger.warning('"application/dart" scripts should ' | 390 _logger.warning('"application/dart" scripts should ' |
| 394 'use the .dart file extension.', | 391 'use the .dart file extension.', |
| 395 span: node.sourceSpan); | 392 span: node.sourceSpan); |
| 393 return; |
| 396 } | 394 } |
| 397 | 395 |
| 398 if (node.innerHtml.trim() != '') { | 396 if (node.innerHtml.trim() != '') { |
| 399 _logger.warning('script tag has "src" attribute and also has script ' | 397 _logger.warning('script tag has "src" attribute and also has script ' |
| 400 'text.', span: node.sourceSpan); | 398 'text.', span: node.sourceSpan); |
| 401 } | 399 } |
| 402 } | 400 } |
| 403 | 401 |
| 404 /** | 402 /** |
| 405 * Produces warnings for misuses of on-foo event handlers, and for instanting | 403 * Produces warnings for misuses of on-foo event handlers, and for instanting |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 537 bool _isCustomTag(String name) { | 535 bool _isCustomTag(String name) { |
| 538 if (name == null || !name.contains('-')) return false; | 536 if (name == null || !name.contains('-')) return false; |
| 539 return !_invalidTagNames.containsKey(name); | 537 return !_invalidTagNames.containsKey(name); |
| 540 } | 538 } |
| 541 | 539 |
| 542 const String _RED_COLOR = '\u001b[31m'; | 540 const String _RED_COLOR = '\u001b[31m'; |
| 543 const String _MAGENTA_COLOR = '\u001b[35m'; | 541 const String _MAGENTA_COLOR = '\u001b[35m'; |
| 544 const String _NO_COLOR = '\u001b[0m'; | 542 const String _NO_COLOR = '\u001b[0m'; |
| 545 | 543 |
| 546 const String USE_INIT_DART = | 544 const String USE_INIT_DART = |
| 547 'To run a polymer applications, make sure to include ' | 545 'To run a polymer applications, you need to call "initPolymer". You can ' |
| 546 'either include a generic script tag that does this for you:' |
| 548 '\'<script type="application/dart" src="packages/polymer/init.dart">' | 547 '\'<script type="application/dart" src="packages/polymer/init.dart">' |
| 549 '</script>\' in your page, after all HTML imports.'; | 548 '</script>\' or add your own script tag and call that function. ' |
| 549 'Make sure the script tag is placed after all HTML imports.'; |
| 550 | 550 |
| 551 const String USE_DART_JS = | 551 const String USE_DART_JS = |
| 552 'To run a polymer applications in Dartium, make sure to include' | 552 'To run a polymer applications in Dartium, make sure to include' |
| 553 '\'<script src="packages/browser/dart.js"></script>\' in your page'; | 553 '\'<script src="packages/browser/dart.js"></script>\' in your page'; |
| 554 | 554 |
| 555 const String BOOT_JS_DEPRECATED = | 555 const String BOOT_JS_DEPRECATED = |
| 556 '"boot.js" is now deprecated. Instead, you can initialize your polymer ' | 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 ' | 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 ' | 558 'main, then you can include our generic main by adding the following ' |
| 559 'script tag to your page: \'<script type="application/dart" ' | 559 'script tag to your page: \'<script type="application/dart" ' |
| 560 'src="packages/polymer/init.dart"> </script>\'. Additionally you need to ' | 560 'src="packages/polymer/init.dart"> </script>\'. Additionally you need to ' |
| 561 'include: \'<script src="packages/browser/dart.js"></script>\' in the page ' | 561 'include: \'<script src="packages/browser/dart.js"></script>\' in the page ' |
| 562 'too. Make sure these script tags come after all HTML imports.'; | 562 'too. Make sure these script tags come after all HTML imports.'; |
| OLD | NEW |