Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: pkg/polymer/lib/src/build/linter.dart

Issue 513023002: Step one towards stable error messages with details: (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 import 'dart:convert'; 10 import 'dart:convert';
11 11
12 import 'package:barback/barback.dart'; 12 import 'package:barback/barback.dart';
13 import 'package:code_transformers/assets.dart'; 13 import 'package:code_transformers/assets.dart';
14 import 'package:code_transformers/messages/build_logger.dart';
15 import 'package:code_transformers/messages/messages.dart' show Message;
14 import 'package:html5lib/dom.dart'; 16 import 'package:html5lib/dom.dart';
15 import 'package:html5lib/dom_parsing.dart'; 17 import 'package:html5lib/dom_parsing.dart';
16 import 'package:source_span/source_span.dart'; 18 import 'package:source_span/source_span.dart';
17 19
18 import 'common.dart'; 20 import 'common.dart';
19 import 'utils.dart'; 21 import 'utils.dart';
20 import 'wrapped_logger.dart'; 22 import 'messages.dart';
21 23
22 /// A linter that checks for common Polymer errors and produces warnings to 24 /// A linter that checks for common Polymer errors and produces warnings to
23 /// show on the editor or the command line. Leaves sources unchanged, but 25 /// show on the editor or the command line. Leaves sources unchanged, but
24 /// creates a new asset containing all the warnings. 26 /// creates a new asset containing all the warnings.
25 class Linter extends Transformer with PolymerTransformer { 27 class Linter extends Transformer with PolymerTransformer {
26 final TransformOptions options; 28 final TransformOptions options;
27 29
28 /// Only run on .html files. 30 /// Only run on .html files.
29 final String allowedExtensions = '.html'; 31 final String allowedExtensions = '.html';
30 32
31 Linter(this.options); 33 Linter(this.options);
32 34
33 Future apply(Transform transform) { 35 Future apply(Transform transform) {
34 var seen = new Set<AssetId>(); 36 var seen = new Set<AssetId>();
35 var primary = transform.primaryInput; 37 var primary = transform.primaryInput;
36 var id = primary.id; 38 var id = primary.id;
37 transform.addOutput(primary); // this phase is analysis only 39 transform.addOutput(primary); // this phase is analysis only
38 seen.add(id); 40 seen.add(id);
39 bool isEntryPoint = options.isHtmlEntryPoint(id); 41 bool isEntryPoint = options.isHtmlEntryPoint(id);
40 42
41 var logger = options.releaseMode ? transform.logger : 43 var logger = new BuildLogger(transform,
42 new WrappedLogger(transform, convertErrorsToWarnings: true); 44 convertErrorsToWarnings: !options.releaseMode);
43 45
44 return readPrimaryAsHtml(transform).then((document) { 46 return readPrimaryAsHtml(transform, logger).then((document) {
45 return _collectElements(document, id, transform, logger, seen) 47 return _collectElements(document, id, transform, logger, seen)
46 .then((elements) { 48 .then((elements) {
47 new _LinterVisitor(id, logger, elements, isEntryPoint).run(document); 49 new _LinterVisitor(id, logger, elements, isEntryPoint).run(document);
48 50
49 // Write out the logs collected by our [WrappedLogger]. 51 // Write out the logs collected by our [BuildLogger].
50 if (options.injectBuildLogsInOutput && logger is WrappedLogger) { 52 if (options.injectBuildLogsInOutput && logger is BuildLogger) {
51 return (logger as WrappedLogger).writeOutput(); 53 return (logger as BuildLogger).writeOutput();
52 } 54 }
53 }); 55 });
54 }); 56 });
55 } 57 }
56 58
57 /// Collect into [elements] any data about each polymer-element defined in 59 /// Collect into [elements] any data about each polymer-element defined in
58 /// [document] or any of it's imports, unless they have already been [seen]. 60 /// [document] or any of it's imports, unless they have already been [seen].
59 /// Elements are added in the order they appear, transitive imports are added 61 /// Elements are added in the order they appear, transitive imports are added
60 /// first. 62 /// first.
61 Future<Map<String, _ElementSummary>> _collectElements( 63 Future<Map<String, _ElementSummary>> _collectElements(
62 Document document, AssetId sourceId, Transform transform, 64 Document document, AssetId sourceId, Transform transform,
63 TransformLogger logger, Set<AssetId> seen, 65 BuildLogger logger, Set<AssetId> seen,
64 [Map<String, _ElementSummary> elements]) { 66 [Map<String, _ElementSummary> elements]) {
65 if (elements == null) elements = <String, _ElementSummary>{}; 67 if (elements == null) elements = <String, _ElementSummary>{};
66 return _getImportedIds(document, sourceId, transform, logger) 68 return _getImportedIds(document, sourceId, transform, logger)
67 // Note: the import order is relevant, so we visit in that order. 69 // Note: the import order is relevant, so we visit in that order.
68 .then((ids) => Future.forEach(ids, 70 .then((ids) => Future.forEach(ids,
69 (id) => _readAndCollectElements( 71 (id) => _readAndCollectElements(
70 id, transform, logger, seen, elements))) 72 id, transform, logger, seen, elements)))
71 .then((_) { 73 .then((_) {
72 if (sourceId.package == 'polymer' && 74 if (sourceId.package == 'polymer' &&
73 sourceId.path == 'lib/src/js/polymer/polymer.html' && 75 sourceId.path == 'lib/src/js/polymer/polymer.html' &&
74 elements['polymer-element'] == null) { 76 elements['polymer-element'] == null) {
75 elements['polymer-element'] = 77 elements['polymer-element'] =
76 new _ElementSummary('polymer-element', null, null); 78 new _ElementSummary('polymer-element', null, null);
77 } 79 }
78 return _addElements(document, logger, elements); 80 return _addElements(document, logger, elements);
79 }) 81 })
80 .then((_) => elements); 82 .then((_) => elements);
81 } 83 }
82 84
83 Future _readAndCollectElements(AssetId id, Transform transform, 85 Future _readAndCollectElements(AssetId id, Transform transform,
84 TransformLogger logger, Set<AssetId> seen, 86 BuildLogger logger, Set<AssetId> seen,
85 Map<String, _ElementSummary> elements) { 87 Map<String, _ElementSummary> elements) {
86 if (id == null || seen.contains(id)) return new Future.value(null); 88 if (id == null || seen.contains(id)) return new Future.value(null);
87 seen.add(id); 89 seen.add(id);
88 return readAsHtml(id, transform, showWarnings: false).then( 90 return readAsHtml(id, transform, logger, showWarnings: false).then(
89 (doc) => _collectElements(doc, id, transform, logger, seen, elements)); 91 (doc) => _collectElements(doc, id, transform, logger, seen, elements));
90 } 92 }
91 93
92 Future<List<AssetId>> _getImportedIds( 94 Future<List<AssetId>> _getImportedIds(
93 Document document, AssetId sourceId, Transform transform, 95 Document document, AssetId sourceId, Transform transform,
94 TransformLogger logger) { 96 BuildLogger logger) {
95 var importIds = []; 97 var importIds = [];
96 for (var tag in document.querySelectorAll('link')) { 98 for (var tag in document.querySelectorAll('link')) {
97 if (tag.attributes['rel'] != 'import') continue; 99 if (tag.attributes['rel'] != 'import') continue;
98 var href = tag.attributes['href']; 100 var href = tag.attributes['href'];
99 var span = tag.sourceSpan; 101 var span = tag.sourceSpan;
100 var id = uriToAssetId(sourceId, href, logger, span); 102 var id = uriToAssetId(sourceId, href, logger, span);
101 if (id == null) continue; 103 if (id == null) continue;
102 importIds.add(assetExists(id, transform).then((exists) { 104 importIds.add(assetExists(id, transform).then((exists) {
103 if (exists) return id; 105 if (exists) return id;
104 if (sourceId == transform.primaryInput.id) { 106 if (sourceId == transform.primaryInput.id) {
105 logger.warning('couldn\'t find imported asset "${id.path}" in package' 107 logger.warning(IMPORT_NOT_FOUND.create(
106 ' "${id.package}".', span: span); 108 {'path': id.path, 'package': id.package}), span: span);
107 } 109 }
108 })); 110 }));
109 } 111 }
110 return Future.wait(importIds); 112 return Future.wait(importIds);
111 } 113 }
112 114
113 void _addElements(Document document, TransformLogger logger, 115 void _addElements(Document document, BuildLogger logger,
114 Map<String, _ElementSummary> elements) { 116 Map<String, _ElementSummary> elements) {
115 for (var tag in document.querySelectorAll('polymer-element')) { 117 for (var tag in document.querySelectorAll('polymer-element')) {
116 var name = tag.attributes['name']; 118 var name = tag.attributes['name'];
117 if (name == null) continue; 119 if (name == null) continue;
118 var extendsTag = tag.attributes['extends']; 120 var extendsTag = tag.attributes['extends'];
119 var span = tag.sourceSpan; 121 var span = tag.sourceSpan;
120 var existing = elements[name]; 122 var existing = elements[name];
121 if (existing != null) { 123 if (existing != null) {
122 124
123 // Report warning only once. 125 // Report warning only once.
124 if (existing.hasConflict) continue; 126 if (existing.hasConflict) continue;
125 existing.hasConflict = true; 127 existing.hasConflict = true;
126 logger.warning('duplicate definition for custom tag "$name".', 128 logger.warning(DUPLICATE_DEFINITION.create(
129 {'name': name, 'second': ''}),
127 span: existing.span); 130 span: existing.span);
128 logger.warning('duplicate definition for custom tag "$name" ' 131 logger.warning(DUPLICATE_DEFINITION.create(
129 ' (second definition).', span: span); 132 {'name': name, 'second': ' (second definition).'}),
133 span: span);
130 continue; 134 continue;
131 } 135 }
132 136
133 elements[name] = new _ElementSummary(name, extendsTag, tag.sourceSpan); 137 elements[name] = new _ElementSummary(name, extendsTag, tag.sourceSpan);
134 } 138 }
135 } 139 }
136 } 140 }
137 141
138 142
139 /// Information needed about other polymer-element tags in order to validate 143 /// Information needed about other polymer-element tags in order to validate
(...skipping 14 matching lines...) Expand all
154 if (extendsTag != null && !extendsTag.contains('-')) return extendsTag; 158 if (extendsTag != null && !extendsTag.contains('-')) return extendsTag;
155 return null; 159 return null;
156 } 160 }
157 161
158 _ElementSummary(this.tagName, this.extendsTag, this.span); 162 _ElementSummary(this.tagName, this.extendsTag, this.span);
159 163
160 String toString() => "($tagName <: $extendsTag)"; 164 String toString() => "($tagName <: $extendsTag)";
161 } 165 }
162 166
163 class _LinterVisitor extends TreeVisitor { 167 class _LinterVisitor extends TreeVisitor {
164 TransformLogger _logger; 168 BuildLogger _logger;
165 AssetId _sourceId; 169 AssetId _sourceId;
166 bool _inPolymerElement = false; 170 bool _inPolymerElement = false;
167 bool _dartTagSeen = false; 171 bool _dartTagSeen = false;
168 bool _polymerHtmlSeen = false; 172 bool _polymerHtmlSeen = false;
169 bool _polymerExperimentalHtmlSeen = false; 173 bool _polymerExperimentalHtmlSeen = false;
170 bool _isEntryPoint; 174 bool _isEntryPoint;
171 Map<String, _ElementSummary> _elements; 175 Map<String, _ElementSummary> _elements;
172 176
173 _LinterVisitor( 177 _LinterVisitor(
174 this._sourceId, this._logger, this._elements, this._isEntryPoint) { 178 this._sourceId, this._logger, this._elements, this._isEntryPoint) {
(...skipping 16 matching lines...) Expand all
191 _validateNormalElement(node); 195 _validateNormalElement(node);
192 super.visitElement(node); 196 super.visitElement(node);
193 break; 197 break;
194 } 198 }
195 } 199 }
196 200
197 void run(Document doc) { 201 void run(Document doc) {
198 visit(doc); 202 visit(doc);
199 203
200 if (_isEntryPoint && !_dartTagSeen && !_polymerExperimentalHtmlSeen) { 204 if (_isEntryPoint && !_dartTagSeen && !_polymerExperimentalHtmlSeen) {
201 _logger.warning(USE_INIT_DART, span: doc.body.sourceSpan); 205 _logger.warning(MISSING_INIT_POLYMER, span: doc.body.sourceSpan);
202 } 206 }
203 } 207 }
204 208
205 /// Produce warnings for invalid link-rel tags. 209 /// Produce warnings for invalid link-rel tags.
206 void _validateLinkElement(Element node) { 210 void _validateLinkElement(Element node) {
207 var rel = node.attributes['rel']; 211 var rel = node.attributes['rel'];
208 if (rel != 'import' && rel != 'stylesheet') return; 212 if (rel != 'import' && rel != 'stylesheet') return;
209 213
210 if (rel == 'import' && _dartTagSeen) { 214 if (rel == 'import' && _dartTagSeen) {
211 _logger.warning("Move HTML imports above your Dart script tag.", 215 _logger.warning(MOVE_IMPORTS_UP, span: node.sourceSpan);
212 span: node.sourceSpan);
213 } 216 }
214 217
215 var href = node.attributes['href']; 218 var href = node.attributes['href'];
216 if (href == null || href == '') { 219 if (href == null || href == '') {
217 _logger.warning('link rel="$rel" missing href.', span: node.sourceSpan); 220 _logger.warning(MISSING_HREF.create({'rel': rel}), span: node.sourceSpan);
218 return; 221 return;
219 } 222 }
220 223
221 if (rel != 'import') return; 224 if (rel != 'import') return;
222 225
223 if (_inPolymerElement) { 226 if (_inPolymerElement) {
224 _logger.error(NO_IMPORT_WITHIN_ELEMENT, span: node.sourceSpan); 227 _logger.error(NO_IMPORT_WITHIN_ELEMENT, span: node.sourceSpan);
225 return; 228 return;
226 } 229 }
227 230
228 if (href == POLYMER_EXPERIMENTAL_HTML) { 231 if (href == POLYMER_EXPERIMENTAL_HTML) {
229 _polymerExperimentalHtmlSeen = true; 232 _polymerExperimentalHtmlSeen = true;
230 } 233 }
231 // TODO(sigmund): warn also if href can't be resolved. 234 // TODO(sigmund): warn also if href can't be resolved.
232 } 235 }
233 236
234 /// Produce warnings if using `<element>` instead of `<polymer-element>`. 237 /// Produce warnings if using `<element>` instead of `<polymer-element>`.
235 void _validateElementElement(Element node) { 238 void _validateElementElement(Element node) {
236 _logger.warning('<element> elements are not supported, use' 239 _logger.warning(ELEMENT_DEPRECATED_EONS_AGO, span: node.sourceSpan);
237 ' <polymer-element> instead', span: node.sourceSpan);
238 } 240 }
239 241
240 /// Produce warnings if using `<polymer-element>` in the wrong place or if the 242 /// Produce warnings if using `<polymer-element>` in the wrong place or if the
241 /// definition is not complete. 243 /// definition is not complete.
242 void _validatePolymerElement(Element node) { 244 void _validatePolymerElement(Element node) {
243 if (!_elements.containsKey('polymer-element')) { 245 if (!_elements.containsKey('polymer-element')) {
244 _logger.warning(usePolymerHtmlMessageFrom(_sourceId), 246 _logger.warning(usePolymerHtmlMessageFrom(_sourceId),
245 span: node.sourceSpan); 247 span: node.sourceSpan);
246 } 248 }
247 249
248 if (_inPolymerElement) { 250 if (_inPolymerElement) {
249 _logger.error('Nested polymer element definitions are not allowed.', 251 _logger.error(NESTED_POLYMER_ELEMENT, span: node.sourceSpan);
250 span: node.sourceSpan);
251 return; 252 return;
252 } 253 }
253 254
254 var tagName = node.attributes['name']; 255 var tagName = node.attributes['name'];
255 var extendsTag = node.attributes['extends']; 256 var extendsTag = node.attributes['extends'];
256 257
257 if (tagName == null) { 258 if (tagName == null) {
258 _logger.error('Missing tag name of the custom element. Please include an ' 259 _logger.error(MISSING_TAG_NAME, span: node.sourceSpan);
259 'attribute like \'name="your-tag-name"\'.',
260 span: node.sourceSpan);
261 } else if (!isCustomTagName(tagName)) { 260 } else if (!isCustomTagName(tagName)) {
262 _logger.error('Invalid name "$tagName". Custom element names must have ' 261 _logger.error(INVALID_TAG_NAME.create({'name': tagName}),
263 'at least one dash and can\'t be any of the following names: '
264 '${invalidTagNames.keys.join(", ")}.',
265 span: node.sourceSpan); 262 span: node.sourceSpan);
266 } 263 }
267 264
268 if (_elements[extendsTag] == null && isCustomTagName(extendsTag)) { 265 if (_elements[extendsTag] == null && isCustomTagName(extendsTag)) {
269 _logger.warning('custom element with name "$extendsTag" not found.', 266 _logger.warning(CUSTOM_ELEMENT_NOT_FOUND.create({'tag': extendsTag}),
270 span: node.sourceSpan); 267 span: node.sourceSpan);
271 } 268 }
272 269
273 var attrs = node.attributes['attributes']; 270 var attrs = node.attributes['attributes'];
274 if (attrs != null) { 271 if (attrs != null) {
275 var attrsSpan = node.attributeSpans['attributes']; 272 var attrsSpan = node.attributeSpans['attributes'];
276 273
277 // names='a b c' or names='a,b,c' 274 // names='a b c' or names='a,b,c'
278 // record each name for publishing 275 // record each name for publishing
279 for (var attr in attrs.split(ATTRIBUTES_REGEX)) { 276 for (var attr in attrs.split(ATTRIBUTES_REGEX)) {
(...skipping 18 matching lines...) Expand all
298 if (_isEntryPoint && _polymerExperimentalHtmlSeen) { 295 if (_isEntryPoint && _polymerExperimentalHtmlSeen) {
299 _logger.warning(NO_DART_SCRIPT_AND_EXPERIMENTAL, span: node.sourceSpan); 296 _logger.warning(NO_DART_SCRIPT_AND_EXPERIMENTAL, span: node.sourceSpan);
300 } 297 }
301 _dartTagSeen = true; 298 _dartTagSeen = true;
302 } 299 }
303 300
304 var isEmpty = node.innerHtml.trim() == ''; 301 var isEmpty = node.innerHtml.trim() == '';
305 302
306 if (src == null) { 303 if (src == null) {
307 if (isDart && isEmpty) { 304 if (isDart && isEmpty) {
308 _logger.warning('script tag seems empty.', span: node.sourceSpan); 305 _logger.warning(SCRIPT_TAG_SEEMS_EMPTY, span: node.sourceSpan);
309 } 306 }
310 return; 307 return;
311 } 308 }
312 309
313 if (src.endsWith('.dart') && !isDart) { 310 if (src.endsWith('.dart') && !isDart) {
314 _logger.warning('Wrong script type, expected type="application/dart".', 311 _logger.warning(EXPECTED_DART_MIME_TYPE, span: node.sourceSpan);
315 span: node.sourceSpan);
316 return; 312 return;
317 } 313 }
318 314
319 if (!src.endsWith('.dart') && isDart) { 315 if (!src.endsWith('.dart') && isDart) {
320 _logger.warning('"application/dart" scripts should use the .dart file ' 316 _logger.warning(EXPECTED_DART_EXTENSION, span: node.sourceSpan);
321 'extension.', span: node.sourceSpan);
322 return; 317 return;
323 } 318 }
324 319
325 if (!isEmpty) { 320 if (!isEmpty) {
326 _logger.warning('script tag has "src" attribute and also has script ' 321 _logger.warning(FOUND_BOTH_SCRIPT_SRC_AND_TEXT, span: node.sourceSpan);
327 'text.', span: node.sourceSpan);
328 } 322 }
329 } 323 }
330 324
331 /// Produces warnings for misuses of on-foo event handlers, and for instanting 325 /// Produces warnings for misuses of on-foo event handlers, and for instanting
332 /// custom tags incorrectly. 326 /// custom tags incorrectly.
333 void _validateNormalElement(Element node) { 327 void _validateNormalElement(Element node) {
334 // Event handlers only allowed inside polymer-elements 328 // Event handlers only allowed inside polymer-elements
335 node.attributes.forEach((name, value) { 329 node.attributes.forEach((name, value) {
336 if (name is String && name.startsWith('on')) { 330 if (name is String && name.startsWith('on')) {
337 _validateEventHandler(node, name, value); 331 _validateEventHandler(node, name, value);
(...skipping 14 matching lines...) Expand all
352 hasIsAttribute = true; 346 hasIsAttribute = true;
353 } 347 }
354 348
355 if (customTagName == null || 349 if (customTagName == null ||
356 INTERNALLY_DEFINED_ELEMENTS.contains(customTagName)) { 350 INTERNALLY_DEFINED_ELEMENTS.contains(customTagName)) {
357 return; 351 return;
358 } 352 }
359 353
360 var info = _elements[customTagName]; 354 var info = _elements[customTagName];
361 if (info == null) { 355 if (info == null) {
362 // TODO(jmesserly): this warning is wrong if someone is using raw custom 356 _logger.warning(CUSTOM_ELEMENT_NOT_FOUND.create({'tag': customTagName}),
363 // elements. Is there another way we can handle this warning that won't 357 span: node.sourceSpan);
364 // generate false positives?
365 _logger.warning('definition for Polymer element with tag name '
366 '"$customTagName" not found.', span: node.sourceSpan);
367 return; 358 return;
368 } 359 }
369 360
370 var baseTag = info.baseExtendsTag; 361 var baseTag = info.baseExtendsTag;
371 if (baseTag != null && !hasIsAttribute) { 362 if (baseTag != null && !hasIsAttribute) {
372 _logger.warning( 363 _logger.warning(BAD_INSTANTIATION_MISSING_BASE_TAG.create(
373 'custom element "$customTagName" extends from "$baseTag", but ' 364 {'tag': customTagName, 'base': baseTag}), span: node.sourceSpan);
374 'this tag will not include the default properties of "$baseTag". '
375 'To fix this, either write this tag as <$baseTag '
376 'is="$customTagName"> or remove the "extends" attribute from '
377 'the custom element declaration.', span: node.sourceSpan);
378 return; 365 return;
379 } 366 }
380 367
381 if (hasIsAttribute && baseTag == null) { 368 if (hasIsAttribute && baseTag == null) {
382 _logger.warning( 369 _logger.warning(BAD_INSTANTIATION_BOGUS_BASE_TAG.create(
383 'custom element "$customTagName" doesn\'t declare any type ' 370 {'tag': customTagName, 'base': nodeTag}), span: node.sourceSpan);
384 'extensions. To fix this, either rewrite this tag as '
385 '<$customTagName> or add \'extends="$nodeTag"\' to '
386 'the custom element declaration.', span: node.sourceSpan);
387 return; 371 return;
388 } 372 }
389 373
390 if (hasIsAttribute && baseTag != nodeTag) { 374 if (hasIsAttribute && baseTag != nodeTag) {
391 _logger.warning( 375 _logger.warning(BAD_INSTANTIATION_WRONG_BASE_TAG.create(
392 'custom element "$customTagName" extends from "$baseTag". ' 376 {'tag': customTagName, 'base': baseTag}), span: node.sourceSpan);
393 'Did you mean to write <$baseTag is="$customTagName">?',
394 span: node.sourceSpan);
395 } 377 }
396 } 378 }
397 379
398 /// Validate an attribute on a custom-element. Returns true if valid. 380 /// Validate an attribute on a custom-element. Returns true if valid.
399 bool _validateCustomAttributeName(String name, FileSpan span) { 381 bool _validateCustomAttributeName(String name, FileSpan span) {
400 if (name.contains('-')) { 382 if (name.contains('-')) {
401 var newName = toCamelCase(name); 383 var newName = toCamelCase(name);
402 _logger.warning('PolymerElement no longer recognizes attribute names with ' 384 var alternative = '"$newName" or "${newName.toLowerCase()}"';
403 'dashes such as "$name". Use "$newName" or "${newName.toLowerCase()}" ' 385 _logger.warning(NO_DASHES_IN_CUSTOM_ATTRIBUTES.create(
404 'instead (both forms are equivalent in HTML).', span: span); 386 {'name': name, 'alternative': alternative}), span: span);
405 return false; 387 return false;
406 } 388 }
407 return true; 389 return true;
408 } 390 }
409 391
410 /// Validate event handlers are used correctly. 392 /// Validate event handlers are used correctly.
411 void _validateEventHandler(Element node, String name, String value) { 393 void _validateEventHandler(Element node, String name, String value) {
412 if (!name.startsWith('on-')) return; 394 if (!name.startsWith('on-')) return;
413 395
414 if (!_inPolymerElement) { 396 if (!_inPolymerElement) {
415 _logger.warning('Inline event handlers are only supported inside ' 397 _logger.warning(EVENT_HANDLERS_ONLY_WITHIN_POLYMER,
416 'declarations of <polymer-element>.',
417 span: node.attributeSpans[name]); 398 span: node.attributeSpans[name]);
418 return; 399 return;
419 } 400 }
420 401
421 402
422 // Valid bindings have {{ }}, don't look like method calls foo(bar), and are 403 // Valid bindings have {{ }}, don't look like method calls foo(bar), and are
423 // non empty. 404 // non empty.
424 if (!value.startsWith("{{") || !value.endsWith("}}") || value.contains('(') 405 if (!value.startsWith("{{") || !value.endsWith("}}") || value.contains('(')
425 || value.substring(2, value.length - 2).trim() == '') { 406 || value.substring(2, value.length - 2).trim() == '') {
426 _logger.warning('Invalid event handler body "$value". Declare a method ' 407 _logger.warning(INVALID_EVENT_HANDLER_BODY.create(
427 'in your custom element "void handlerName(event, detail, target)" ' 408 {'value': value, 'name': name}),
428 'and use the form $name="{{handlerName}}".',
429 span: node.attributeSpans[name]); 409 span: node.attributeSpans[name]);
430 } 410 }
431 } 411 }
432 } 412 }
433 413
434 const String ONLY_ONE_TAG = 414 Message usePolymerHtmlMessageFrom(AssetId id) {
435 'Only one "application/dart" script tag per document is allowed.';
436
437 String usePolymerHtmlMessageFrom(AssetId id) {
438 var segments = id.path.split('/'); 415 var segments = id.path.split('/');
439 var upDirCount = 0; 416 var upDirCount = 0;
440 if (segments[0] == 'lib') { 417 if (segments[0] == 'lib') {
441 // lib/foo.html => ../../packages/ 418 // lib/foo.html => ../../packages/
442 upDirCount = segments.length; 419 upDirCount = segments.length;
443 } else if (segments.length > 2) { 420 } else if (segments.length > 2) {
444 // web/a/foo.html => ../packages/ 421 // web/a/foo.html => ../packages/
445 upDirCount = segments.length - 2; 422 upDirCount = segments.length - 2;
446 } 423 }
447 return usePolymerHtmlMessage(upDirCount); 424 var reachOutPrefix = '../' * upDirCount;
425 return USE_POLYMER_HTML.create({'reachOutPrefix': reachOutPrefix});
448 } 426 }
449 427
450 String usePolymerHtmlMessage(int upDirCount) {
451 var reachOutPrefix = '../' * upDirCount;
452 return 'Missing definition for <polymer-element>, please add the following '
453 'HTML import at the top of this file: <link rel="import" '
454 'href="${reachOutPrefix}packages/polymer/polymer.html">.';
455 }
456
457 const String NO_IMPORT_WITHIN_ELEMENT = 'Polymer.dart\'s implementation of '
458 'HTML imports are not supported within polymer element definitions, yet. '
459 'Please move the import out of this <polymer-element>.';
460
461 const String USE_INIT_DART =
462 'To run a polymer application, you need to call "initPolymer". You can '
463 'either include a generic script tag that does this for you:'
464 '\'<script type="application/dart">export "package:polymer/init.dart";'
465 '</script>\' or add your own script tag and call that function. '
466 'Make sure the script tag is placed after all HTML imports.';
467
468 const String NO_DART_SCRIPT_AND_EXPERIMENTAL =
469 'The experimental bootstrap feature doesn\'t support script tags on '
470 'the main document (for now).';
471
472 const List<String> INTERNALLY_DEFINED_ELEMENTS = 428 const List<String> INTERNALLY_DEFINED_ELEMENTS =
473 const ['auto-binding-dart', 'polymer-element']; 429 const ['auto-binding-dart', 'polymer-element'];
OLDNEW
« no previous file with comments | « pkg/polymer/lib/src/build/import_inliner.dart ('k') | pkg/polymer/lib/src/build/log_injector.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698