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 |