Chromium Code Reviews| 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 * Part of the template compilation that concerns with extracting information | 6 * Part of the template compilation that concerns with extracting information |
| 7 * from the HTML parse tree. | 7 * from the HTML parse tree. |
| 8 */ | 8 */ |
| 9 library analyzer; | 9 library analyzer; |
| 10 | 10 |
| 11 import 'package:html5lib/dom.dart'; | 11 import 'package:html5lib/dom.dart'; |
| 12 import 'package:html5lib/dom_parsing.dart'; | 12 import 'package:html5lib/dom_parsing.dart'; |
| 13 import 'package:source_maps/span.dart' hide SourceFile; | |
| 14 | 13 |
| 15 import 'custom_tag_name.dart'; | 14 import 'custom_tag_name.dart'; |
| 16 import 'dart_parser.dart' show parseDartCode; | |
| 17 import 'files.dart'; | 15 import 'files.dart'; |
| 18 import 'info.dart'; | 16 import 'info.dart'; |
| 19 import 'messages.dart'; | 17 import 'messages.dart'; |
| 20 import 'summary.dart'; | |
| 21 | 18 |
| 22 /** | 19 /** |
| 23 * Finds custom elements in this file and the list of referenced files with | 20 * Finds custom elements in this file and the list of referenced files with |
| 24 * component declarations. This is the first pass of analysis on a file. | 21 * component declarations. This is the first pass of analysis on a file. |
| 25 * | 22 * |
| 26 * Adds emitted error/warning messages to [messages], if [messages] is | 23 * Adds emitted error/warning messages to [messages], if [messages] is |
| 27 * supplied. | 24 * supplied. |
| 28 */ | 25 */ |
| 29 FileInfo analyzeDefinitions(GlobalInfo global, UrlInfo inputUrl, | 26 FileInfo analyzeDefinitions(GlobalInfo global, UrlInfo inputUrl, |
| 30 Document document, Messages messages, {bool isEntryPoint: false}) { | 27 Document document, Messages messages) { |
|
terry
2013/08/28 21:41:52
I we plan of only running in pub deploy should Mes
Jennifer Messerly
2013/08/28 22:10:25
+1 ... please kill Messages :)
Siggi Cherem (dart-lang)
2013/08/29 00:34:15
I'll keep it in mind, but I wont do it just yet. W
| |
| 31 var result = new FileInfo(inputUrl, isEntryPoint); | 28 var result = new FileInfo(inputUrl); |
| 32 var loader = new _ElementLoader(global, result, messages); | 29 var loader = new _ElementLoader(global, result, messages); |
| 33 loader.visit(document); | 30 loader.visit(document); |
| 34 return result; | 31 return result; |
| 35 } | 32 } |
| 36 | 33 |
| 37 /** | 34 /** |
| 38 * Extract relevant information from all files found from the root document. | 35 * Extract relevant information from all files found from the root document. |
| 39 * | 36 * |
| 40 * Adds emitted error/warning messages to [messages], if [messages] is | 37 * Adds emitted error/warning messages to [messages], if [messages] is |
| 41 * supplied. | 38 * supplied. |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 131 node.sourceSpan); | 128 node.sourceSpan); |
| 132 } | 129 } |
| 133 _keepIndentationSpaces = value != 'remove'; | 130 _keepIndentationSpaces = value != 'remove'; |
| 134 } | 131 } |
| 135 | 132 |
| 136 // Invoke super to visit children. | 133 // Invoke super to visit children. |
| 137 super.visitElement(node); | 134 super.visitElement(node); |
| 138 | 135 |
| 139 _keepIndentationSpaces = keepSpaces; | 136 _keepIndentationSpaces = keepSpaces; |
| 140 _currentInfo = lastInfo; | 137 _currentInfo = lastInfo; |
| 141 | |
| 142 if (node.tagName == 'body' || node.parent == null) { | |
| 143 _fileInfo.body = node; | |
| 144 } | |
| 145 } | 138 } |
| 146 | 139 |
| 147 void _analyzeComponent(ComponentInfo component) { | 140 void _analyzeComponent(ComponentInfo component) { |
| 148 var baseTag = component.extendsTag; | 141 var baseTag = component.extendsTag; |
| 149 component.extendsComponent = baseTag == null ? null | 142 component.extendsComponent = baseTag == null ? null |
| 150 : _fileInfo.components[baseTag]; | 143 : _fileInfo.components[baseTag]; |
| 151 if (component.extendsComponent == null && isCustomTag(baseTag)) { | 144 if (component.extendsComponent == null && isCustomTag(baseTag)) { |
| 152 _messages.warning( | 145 _messages.warning( |
| 153 'custom element with tag name ${component.extendsTag} not found.', | 146 'custom element with tag name ${component.extendsTag} not found.', |
| 154 component.element.sourceSpan); | 147 component.element.sourceSpan); |
| 155 } | 148 } |
| 156 | |
| 157 // Now that the component's code has been loaded, we can validate that the | |
| 158 // class exists. | |
| 159 component.findClassDeclaration(_messages); | |
| 160 } | 149 } |
| 161 | 150 |
| 162 void _bindCustomElement(Element node) { | 151 void _bindCustomElement(Element node) { |
| 163 // <fancy-button> | 152 // <fancy-button> |
| 164 var component = _fileInfo.components[node.tagName]; | 153 var component = _fileInfo.components[node.tagName]; |
| 165 if (component == null) { | 154 if (component == null) { |
| 166 // TODO(jmesserly): warn for unknown element tags? | 155 // TODO(jmesserly): warn for unknown element tags? |
| 167 | 156 |
| 168 // <button is="fancy-button"> | 157 // <button is="fancy-button"> |
| 169 var componentName = node.attributes['is']; | 158 var componentName = node.attributes['is']; |
| 170 if (componentName != null) { | 159 if (componentName != null) { |
| 171 component = _fileInfo.components[componentName]; | 160 component = _fileInfo.components[componentName]; |
| 172 } else if (isCustomTag(node.tagName)) { | 161 } else if (isCustomTag(node.tagName)) { |
| 173 componentName = node.tagName; | 162 componentName = node.tagName; |
| 174 } | 163 } |
| 175 if (component == null && componentName != null && | 164 if (component == null && componentName != null && |
| 176 componentName != 'polymer-element') { | 165 componentName != 'polymer-element') { |
| 177 _messages.warning( | 166 _messages.warning( |
| 178 'custom element with tag name $componentName not found.', | 167 'custom element with tag name $componentName not found.', |
| 179 node.sourceSpan); | 168 node.sourceSpan); |
| 180 } | 169 } |
| 181 } | 170 } |
| 182 | 171 |
| 183 if (component != null) { | 172 if (component != null) { |
| 184 if (!component.hasConflict) { | |
| 185 _currentInfo.usedComponents[component] = true; | |
| 186 } | |
| 187 | |
| 188 var baseTag = component.baseExtendsTag; | 173 var baseTag = component.baseExtendsTag; |
| 189 var nodeTag = node.tagName; | 174 var nodeTag = node.tagName; |
| 190 var hasIsAttribute = node.attributes.containsKey('is'); | 175 var hasIsAttribute = node.attributes.containsKey('is'); |
| 191 | 176 |
| 192 if (baseTag != null && !hasIsAttribute) { | 177 if (baseTag != null && !hasIsAttribute) { |
| 193 _messages.warning( | 178 _messages.warning( |
| 194 'custom element "${component.tagName}" extends from "$baseTag", but' | 179 'custom element "${component.tagName}" extends from "$baseTag", but' |
| 195 ' this tag will not include the default properties of "$baseTag". ' | 180 ' this tag will not include the default properties of "$baseTag". ' |
| 196 'To fix this, either write this tag as <$baseTag ' | 181 'To fix this, either write this tag as <$baseTag ' |
| 197 'is="${component.tagName}"> or remove the "extends" attribute from ' | 182 'is="${component.tagName}"> or remove the "extends" attribute from ' |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 263 | 248 |
| 264 /** | 249 /** |
| 265 * Normalizes references in [info]. On the [analyzeDefinitions] phase, the | 250 * Normalizes references in [info]. On the [analyzeDefinitions] phase, the |
| 266 * analyzer extracted names of files and components. Here we link those names | 251 * analyzer extracted names of files and components. Here we link those names |
| 267 * to actual info classes. In particular: | 252 * to actual info classes. In particular: |
| 268 * * we initialize the [FileInfo.components] map in [info] by importing all | 253 * * we initialize the [FileInfo.components] map in [info] by importing all |
| 269 * [declaredComponents], | 254 * [declaredComponents], |
| 270 * * we scan all [info.componentLinks] and import their | 255 * * we scan all [info.componentLinks] and import their |
| 271 * [info.declaredComponents], using [files] to map the href to the file | 256 * [info.declaredComponents], using [files] to map the href to the file |
| 272 * info. Names in [info] will shadow names from imported files. | 257 * info. Names in [info] will shadow names from imported files. |
| 273 * * we fill [LibraryInfo.externalCode] on each component declared in | |
| 274 * [info]. | |
| 275 */ | 258 */ |
| 276 void _normalize(FileInfo info, Map<String, FileInfo> files) { | 259 void _normalize(FileInfo info, Map<String, FileInfo> files) { |
| 277 _attachExtenalScript(info, files); | |
| 278 | |
| 279 for (var component in info.declaredComponents) { | 260 for (var component in info.declaredComponents) { |
| 280 _addComponent(info, component); | 261 _addComponent(info, component); |
| 281 _attachExtenalScript(component, files); | |
| 282 } | 262 } |
| 283 | 263 |
| 284 for (var link in info.componentLinks) { | 264 for (var link in info.componentLinks) { |
| 285 var file = files[link.resolvedPath]; | 265 var file = files[link.resolvedPath]; |
| 286 // We already issued an error for missing files. | 266 // We already issued an error for missing files. |
| 287 if (file == null) continue; | 267 if (file == null) continue; |
| 288 file.declaredComponents.forEach((c) => _addComponent(info, c)); | 268 file.declaredComponents.forEach((c) => _addComponent(info, c)); |
| 289 } | 269 } |
| 290 } | 270 } |
| 291 | 271 |
| 292 /** | |
| 293 * Stores a direct reference in [info] to a dart source file that was loaded | |
| 294 * in a script tag with the 'src' attribute. | |
| 295 */ | |
| 296 void _attachExtenalScript(LibraryInfo info, Map<String, FileInfo> files) { | |
| 297 var externalFile = info.externalFile; | |
| 298 if (externalFile != null) { | |
| 299 info.externalCode = files[externalFile.resolvedPath]; | |
| 300 if (info.externalCode != null) info.externalCode.htmlFile = info; | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 /** Adds a component's tag name to the names in scope for [fileInfo]. */ | 272 /** Adds a component's tag name to the names in scope for [fileInfo]. */ |
| 305 void _addComponent(FileInfo fileInfo, ComponentSummary component) { | 273 void _addComponent(FileInfo fileInfo, ComponentInfo component) { |
| 306 var existing = fileInfo.components[component.tagName]; | 274 var existing = fileInfo.components[component.tagName]; |
| 307 if (existing != null) { | 275 if (existing != null) { |
| 308 if (existing == component) { | 276 if (existing == component) { |
| 309 // This is the same exact component as the existing one. | 277 // This is the same exact component as the existing one. |
| 310 return; | 278 return; |
| 311 } | 279 } |
| 312 | 280 |
| 313 if (existing is ComponentInfo && component is! ComponentInfo) { | 281 if (existing is ComponentInfo && component is! ComponentInfo) { |
| 314 // Components declared in [fileInfo] shadow component names declared in | 282 // Components declared in [fileInfo] shadow component names declared in |
| 315 // imported files. | 283 // imported files. |
| (...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 429 var tagName = node.attributes['name']; | 397 var tagName = node.attributes['name']; |
| 430 var extendsTag = node.attributes['extends']; | 398 var extendsTag = node.attributes['extends']; |
| 431 | 399 |
| 432 if (tagName == null) { | 400 if (tagName == null) { |
| 433 _messages.error('Missing tag name of the component. Please include an ' | 401 _messages.error('Missing tag name of the component. Please include an ' |
| 434 'attribute like \'name="your-tag-name"\'.', | 402 'attribute like \'name="your-tag-name"\'.', |
| 435 node.sourceSpan); | 403 node.sourceSpan); |
| 436 return; | 404 return; |
| 437 } | 405 } |
| 438 | 406 |
| 439 var component = new ComponentInfo(node, _fileInfo, tagName, extendsTag); | 407 var component = new ComponentInfo(node, tagName, extendsTag); |
| 440 _fileInfo.declaredComponents.add(component); | 408 _fileInfo.declaredComponents.add(component); |
| 441 _addComponent(component); | 409 _addComponent(component); |
| 442 | 410 |
| 443 var lastInfo = _currentInfo; | 411 var lastInfo = _currentInfo; |
| 444 _currentInfo = component; | 412 _currentInfo = component; |
| 445 super.visitElement(node); | 413 super.visitElement(node); |
| 446 _currentInfo = lastInfo; | 414 _currentInfo = lastInfo; |
| 447 } | 415 } |
| 448 | 416 |
| 449 /** Adds a component's tag name to the global list. */ | 417 /** Adds a component's tag name to the global list. */ |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 486 node.sourceSpan); | 454 node.sourceSpan); |
| 487 } | 455 } |
| 488 if (src != null && src.endsWith('.dart')) { | 456 if (src != null && src.endsWith('.dart')) { |
| 489 _messages.warning('script tag with .dart source file but no type will ' | 457 _messages.warning('script tag with .dart source file but no type will ' |
| 490 'be treated as JavaScript. Did you forget type="application/dart"?', | 458 'be treated as JavaScript. Did you forget type="application/dart"?', |
| 491 node.sourceSpan); | 459 node.sourceSpan); |
| 492 } | 460 } |
| 493 return; | 461 return; |
| 494 } | 462 } |
| 495 | 463 |
| 496 if (scriptType != 'application/dart') { | 464 if (scriptType != 'application/dart') return; |
| 497 if (_currentInfo is ComponentInfo) { | |
| 498 // TODO(jmesserly): this warning should not be here, but our compiler | |
| 499 // does the wrong thing and it could cause surprising behavior, so let | |
| 500 // the user know! See issue #340 for more info. | |
| 501 // What we should be doing: leave JS component untouched by compiler. | |
| 502 _messages.warning('our custom element implementation does not support ' | |
| 503 'JavaScript components yet. If this is affecting you please let us ' | |
| 504 'know at https://github.com/dart-lang/web-ui/issues/340.', | |
| 505 node.sourceSpan); | |
| 506 } | |
| 507 | |
| 508 return; | |
| 509 } | |
| 510 | 465 |
| 511 if (src != null) { | 466 if (src != null) { |
| 512 if (!src.endsWith('.dart')) { | 467 if (!src.endsWith('.dart')) { |
| 513 _messages.warning('"application/dart" scripts should ' | 468 _messages.warning('"application/dart" scripts should ' |
| 514 'use the .dart file extension.', | 469 'use the .dart file extension.', |
| 515 node.sourceSpan); | 470 node.sourceSpan); |
| 516 } | 471 } |
| 517 | 472 |
| 518 if (node.innerHtml.trim() != '') { | 473 if (node.innerHtml.trim() != '') { |
| 519 _messages.error('script tag has "src" attribute and also has script ' | 474 _messages.error('script tag has "src" attribute and also has script ' |
| 520 'text.', node.sourceSpan); | 475 'text.', node.sourceSpan); |
| 521 } | 476 } |
| 522 | |
| 523 if (_currentInfo.codeAttached) { | |
| 524 _tooManyScriptsError(node); | |
| 525 } else { | |
| 526 _currentInfo.externalFile = UrlInfo.resolve(src, _fileInfo.inputUrl, | |
| 527 node.sourceSpan, _messages); | |
| 528 } | |
| 529 return; | |
| 530 } | |
| 531 | |
| 532 if (node.nodes.length == 0) return; | |
| 533 | |
| 534 // I don't think the html5 parser will emit a tree with more than | |
| 535 // one child of <script> | |
| 536 assert(node.nodes.length == 1); | |
| 537 Text text = node.nodes[0]; | |
| 538 | |
| 539 if (_currentInfo.codeAttached) { | |
| 540 _tooManyScriptsError(node); | |
| 541 } else if (_currentInfo == _fileInfo && !_fileInfo.isEntryPoint) { | |
| 542 _messages.warning('top-level dart code is ignored on ' | |
| 543 ' HTML pages that define components, but are not the entry HTML ' | |
| 544 'file.', node.sourceSpan); | |
| 545 } else { | |
| 546 _currentInfo.inlinedCode = parseDartCode( | |
| 547 _currentInfo.dartCodeUrl.resolvedPath, text.value, | |
| 548 text.sourceSpan.start); | |
| 549 if (_currentInfo.userCode.partOf != null) { | |
| 550 _messages.error('expected a library, not a part.', | |
| 551 node.sourceSpan); | |
| 552 } | |
| 553 } | 477 } |
| 554 } | 478 } |
| 555 | |
| 556 void _tooManyScriptsError(Node node) { | |
| 557 var location = _currentInfo is ComponentInfo ? | |
| 558 'a custom element declaration' : 'the top-level HTML page'; | |
| 559 | |
| 560 _messages.error('there should be only one dart script tag in $location.', | |
| 561 node.sourceSpan); | |
| 562 } | |
| 563 } | 479 } |
| OLD | NEW |