| 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 /// Transfomer that inlines polymer-element definitions from html imports. | 5 /// Transfomer that inlines polymer-element definitions from html imports. |
| 6 library polymer.src.build.import_inliner; | 6 library polymer.src.build.import_inliner; |
| 7 | 7 |
| 8 import 'dart:async'; | 8 import 'dart:async'; |
| 9 import 'dart:convert'; | 9 import 'dart:convert'; |
| 10 | 10 |
| 11 import 'package:analyzer/analyzer.dart'; | 11 import 'package:analyzer/analyzer.dart'; |
| 12 import 'package:analyzer/src/generated/ast.dart'; | 12 import 'package:analyzer/src/generated/ast.dart'; |
| 13 import 'package:barback/barback.dart'; | 13 import 'package:barback/barback.dart'; |
| 14 import 'package:code_transformers/assets.dart'; | 14 import 'package:code_transformers/assets.dart'; |
| 15 import 'package:code_transformers/messages/build_logger.dart'; |
| 15 import 'package:path/path.dart' as path; | 16 import 'package:path/path.dart' as path; |
| 16 import 'package:html5lib/dom.dart' show | 17 import 'package:html5lib/dom.dart' show |
| 17 Document, DocumentFragment, Element, Node; | 18 Document, DocumentFragment, Element, Node; |
| 18 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; | 19 import 'package:html5lib/dom_parsing.dart' show TreeVisitor; |
| 19 import 'package:source_maps/refactor.dart' show TextEditTransaction; | 20 import 'package:source_maps/refactor.dart' show TextEditTransaction; |
| 20 import 'package:source_span/source_span.dart'; | 21 import 'package:source_span/source_span.dart'; |
| 21 | 22 |
| 22 import 'common.dart'; | 23 import 'common.dart'; |
| 23 import 'wrapped_logger.dart'; | 24 import 'messages.dart'; |
| 24 | 25 |
| 25 // TODO(sigmund): move to web_components package (dartbug.com/18037). | 26 // TODO(sigmund): move to web_components package (dartbug.com/18037). |
| 26 class _HtmlInliner extends PolymerTransformer { | 27 class _HtmlInliner extends PolymerTransformer { |
| 27 final TransformOptions options; | 28 final TransformOptions options; |
| 28 final Transform transform; | 29 final Transform transform; |
| 29 final TransformLogger logger; | 30 final BuildLogger logger; |
| 30 final AssetId docId; | 31 final AssetId docId; |
| 31 final seen = new Set<AssetId>(); | 32 final seen = new Set<AssetId>(); |
| 32 final scriptIds = <AssetId>[]; | 33 final scriptIds = <AssetId>[]; |
| 33 final extractedFiles = new Set<AssetId>(); | 34 final extractedFiles = new Set<AssetId>(); |
| 34 bool experimentalBootstrap = false; | 35 bool experimentalBootstrap = false; |
| 35 | 36 |
| 36 /// The number of extracted inline Dart scripts. Used as a counter to give | 37 /// The number of extracted inline Dart scripts. Used as a counter to give |
| 37 /// unique-ish filenames. | 38 /// unique-ish filenames. |
| 38 int inlineScriptCounter = 0; | 39 int inlineScriptCounter = 0; |
| 39 | 40 |
| 40 _HtmlInliner(TransformOptions options, Transform transform) | 41 _HtmlInliner(TransformOptions options, Transform transform) |
| 41 : options = options, | 42 : options = options, |
| 42 transform = transform, | 43 transform = transform, |
| 43 logger = options.releaseMode ? transform.logger : | 44 logger = new BuildLogger(transform, |
| 44 new WrappedLogger(transform, convertErrorsToWarnings: true), | 45 convertErrorsToWarnings: !options.releaseMode), |
| 45 docId = transform.primaryInput.id; | 46 docId = transform.primaryInput.id; |
| 46 | 47 |
| 47 Future apply() { | 48 Future apply() { |
| 48 seen.add(docId); | 49 seen.add(docId); |
| 49 | 50 |
| 50 Document document; | 51 Document document; |
| 51 bool changed = false; | 52 bool changed = false; |
| 52 | 53 |
| 53 return readPrimaryAsHtml(transform).then((doc) { | 54 return readPrimaryAsHtml(transform, logger).then((doc) { |
| 54 document = doc; | 55 document = doc; |
| 55 changed = new _UrlNormalizer(transform, docId, logger).visit(document) | 56 changed = new _UrlNormalizer(transform, docId, logger).visit(document) |
| 56 || changed; | 57 || changed; |
| 57 | 58 |
| 58 experimentalBootstrap = document.querySelectorAll('link').any((link) => | 59 experimentalBootstrap = document.querySelectorAll('link').any((link) => |
| 59 link.attributes['rel'] == 'import' && | 60 link.attributes['rel'] == 'import' && |
| 60 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); | 61 link.attributes['href'] == POLYMER_EXPERIMENTAL_HTML); |
| 61 changed = _extractScripts(document) || changed; | 62 changed = _extractScripts(document) || changed; |
| 62 return _visitImports(document); | 63 return _visitImports(document); |
| 63 }).then((importsFound) { | 64 }).then((importsFound) { |
| 64 changed = changed || importsFound; | 65 changed = changed || importsFound; |
| 65 return _removeScripts(document); | 66 return _removeScripts(document); |
| 66 }).then((scriptsRemoved) { | 67 }).then((scriptsRemoved) { |
| 67 changed = changed || scriptsRemoved; | 68 changed = changed || scriptsRemoved; |
| 68 | 69 |
| 69 var output = transform.primaryInput; | 70 var output = transform.primaryInput; |
| 70 if (changed) output = new Asset.fromString(docId, document.outerHtml); | 71 if (changed) output = new Asset.fromString(docId, document.outerHtml); |
| 71 transform.addOutput(output); | 72 transform.addOutput(output); |
| 72 | 73 |
| 73 // We produce a secondary asset with extra information for later phases. | 74 // We produce a secondary asset with extra information for later phases. |
| 74 transform.addOutput(new Asset.fromString( | 75 transform.addOutput(new Asset.fromString( |
| 75 docId.addExtension('._data'), | 76 docId.addExtension('._data'), |
| 76 JSON.encode({ | 77 JSON.encode({ |
| 77 'experimental_bootstrap': experimentalBootstrap, | 78 'experimental_bootstrap': experimentalBootstrap, |
| 78 'script_ids': scriptIds, | 79 'script_ids': scriptIds, |
| 79 }, toEncodable: (id) => id.serialize()))); | 80 }, toEncodable: (id) => id.serialize()))); |
| 80 | 81 |
| 81 // Write out the logs collected by our [WrappedLogger]. | 82 // Write out the logs collected by our [BuildLogger]. |
| 82 if (options.injectBuildLogsInOutput && logger is WrappedLogger) { | 83 if (options.injectBuildLogsInOutput) { |
| 83 return (logger as WrappedLogger).writeOutput(); | 84 return logger.writeOutput(); |
| 84 } | 85 } |
| 85 }); | 86 }); |
| 86 } | 87 } |
| 87 | 88 |
| 88 /// Visits imports in [document] and add the imported documents to documents. | 89 /// Visits imports in [document] and add the imported documents to documents. |
| 89 /// Documents are added in the order they appear, transitive imports are added | 90 /// Documents are added in the order they appear, transitive imports are added |
| 90 /// first. | 91 /// first. |
| 91 /// | 92 /// |
| 92 /// Returns `true` if and only if the document was changed and should be | 93 /// Returns `true` if and only if the document was changed and should be |
| 93 /// written out. | 94 /// written out. |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 147 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { | 148 tag == 'link' && (rel == 'stylesheet' || rel == 'import')) { |
| 148 // Move the node into the body, where its contents will be placed. | 149 // Move the node into the body, where its contents will be placed. |
| 149 doc.body.insertBefore(node, insertionPoint); | 150 doc.body.insertBefore(node, insertionPoint); |
| 150 } | 151 } |
| 151 } | 152 } |
| 152 } | 153 } |
| 153 | 154 |
| 154 /// Loads an asset identified by [id], visits its imports and collects its | 155 /// Loads an asset identified by [id], visits its imports and collects its |
| 155 /// html imports. Then inlines it into the main document. | 156 /// html imports. Then inlines it into the main document. |
| 156 Future _inlineImport(AssetId id, Element link) { | 157 Future _inlineImport(AssetId id, Element link) { |
| 157 return readAsHtml(id, transform).catchError((error) { | 158 return readAsHtml(id, transform, logger).catchError((error) { |
| 158 logger.error( | 159 logger.error(inlineImportFail.create({'error': error}), |
| 159 "Failed to inline html import: $error", asset: id, | |
| 160 span: link.sourceSpan); | 160 span: link.sourceSpan); |
| 161 }).then((doc) { | 161 }).then((doc) { |
| 162 if (doc == null) return false; | 162 if (doc == null) return false; |
| 163 new _UrlNormalizer(transform, id, logger).visit(doc); | 163 new _UrlNormalizer(transform, id, logger).visit(doc); |
| 164 return _visitImports(doc).then((_) { | 164 return _visitImports(doc).then((_) { |
| 165 // _UrlNormalizer already ensures there is a library name. | 165 // _UrlNormalizer already ensures there is a library name. |
| 166 _extractScripts(doc, injectLibraryName: false); | 166 _extractScripts(doc, injectLibraryName: false); |
| 167 | 167 |
| 168 // TODO(jmesserly): figure out how this is working in vulcanizer. | 168 // TODO(jmesserly): figure out how this is working in vulcanizer. |
| 169 // Do they produce a <body> tag with a <head> and <body> inside? | 169 // Do they produce a <body> tag with a <head> and <body> inside? |
| 170 var imported = new DocumentFragment(); | 170 var imported = new DocumentFragment(); |
| 171 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); | 171 imported.nodes..addAll(doc.head.nodes)..addAll(doc.body.nodes); |
| 172 link.replaceWith(imported); | 172 link.replaceWith(imported); |
| 173 | 173 |
| 174 // Make sure to grab any logs from the inlined import. | 174 // Make sure to grab any logs from the inlined import. |
| 175 if (logger is WrappedLogger) { | 175 return logger.addLogFilesFromAsset(id); |
| 176 return (logger as WrappedLogger).addLogFilesFromAsset(id); | |
| 177 } | |
| 178 }); | 176 }); |
| 179 }); | 177 }); |
| 180 } | 178 } |
| 181 | 179 |
| 182 Future _inlineStylesheet(AssetId id, Element link) { | 180 Future _inlineStylesheet(AssetId id, Element link) { |
| 183 return transform.readInputAsString(id).catchError((error) { | 181 return transform.readInputAsString(id).catchError((error) { |
| 184 // TODO(jakemac): Move this warning to the linter once we can make it run | 182 // TODO(jakemac): Move this warning to the linter once we can make it run |
| 185 // always (see http://dartbug.com/17199). Then hide this error and replace | 183 // always (see http://dartbug.com/17199). Then hide this error and replace |
| 186 // with a comment pointing to the linter error (so we don't double warn). | 184 // with a comment pointing to the linter error (so we don't double warn). |
| 187 logger.warning( | 185 logger.warning(inlineStyleFail.create({'error': error}), |
| 188 "Failed to inline stylesheet: $error", asset: id, | |
| 189 span: link.sourceSpan); | 186 span: link.sourceSpan); |
| 190 }).then((css) { | 187 }).then((css) { |
| 191 if (css == null) return null; | 188 if (css == null) return null; |
| 192 css = new _UrlNormalizer(transform, id, logger).visitCss(css); | 189 css = new _UrlNormalizer(transform, id, logger).visitCss(css); |
| 193 var styleElement = new Element.tag('style')..text = css; | 190 var styleElement = new Element.tag('style')..text = css; |
| 194 // Copy over the extra attributes from the link tag to the style tag. | 191 // Copy over the extra attributes from the link tag to the style tag. |
| 195 // This adds support for no-shim, shim-shadowdom, etc. | 192 // This adds support for no-shim, shim-shadowdom, etc. |
| 196 link.attributes.forEach((key, value) { | 193 link.attributes.forEach((key, value) { |
| 197 if (!IGNORED_LINKED_STYLE_ATTRS.contains(key)) { | 194 if (!IGNORED_LINKED_STYLE_ATTRS.contains(key)) { |
| 198 styleElement.attributes[key] = value; | 195 styleElement.attributes[key] = value; |
| (...skipping 19 matching lines...) Expand all Loading... |
| 218 // We check for extractedFiles because 'hasInput' below is only true for | 215 // We check for extractedFiles because 'hasInput' below is only true for |
| 219 // assets that existed before this transformer runs (hasInput is false | 216 // assets that existed before this transformer runs (hasInput is false |
| 220 // for files created by [_extractScripts]). | 217 // for files created by [_extractScripts]). |
| 221 if (extractedFiles.contains(srcId)) { | 218 if (extractedFiles.contains(srcId)) { |
| 222 scriptIds.add(srcId); | 219 scriptIds.add(srcId); |
| 223 return true; | 220 return true; |
| 224 } | 221 } |
| 225 | 222 |
| 226 return transform.hasInput(srcId).then((exists) { | 223 return transform.hasInput(srcId).then((exists) { |
| 227 if (!exists) { | 224 if (!exists) { |
| 228 logger.warning('Script file at "$src" not found.', | 225 logger.warning(scriptFileNotFound.create({'url': src}), |
| 229 span: script.sourceSpan); | 226 span: script.sourceSpan); |
| 230 } else { | 227 } else { |
| 231 scriptIds.add(srcId); | 228 scriptIds.add(srcId); |
| 232 } | 229 } |
| 233 }); | 230 }); |
| 234 } | 231 } |
| 235 }).then((_) => changed); | 232 }).then((_) => changed); |
| 236 } | 233 } |
| 237 | 234 |
| 238 /// Split inline scripts into their own files. We need to do this for dart2js | 235 /// Split inline scripts into their own files. We need to do this for dart2js |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 335 /// Counter used to ensure that every library name we inject is unique. | 332 /// Counter used to ensure that every library name we inject is unique. |
| 336 int _count = 0; | 333 int _count = 0; |
| 337 | 334 |
| 338 /// Path to the top level folder relative to the transform primaryInput. | 335 /// Path to the top level folder relative to the transform primaryInput. |
| 339 /// This should just be some arbitrary # of ../'s. | 336 /// This should just be some arbitrary # of ../'s. |
| 340 final String topLevelPath; | 337 final String topLevelPath; |
| 341 | 338 |
| 342 /// Whether or not the normalizer has changed something in the tree. | 339 /// Whether or not the normalizer has changed something in the tree. |
| 343 bool changed = false; | 340 bool changed = false; |
| 344 | 341 |
| 345 final TransformLogger logger; | 342 final BuildLogger logger; |
| 346 | 343 |
| 347 _UrlNormalizer(transform, this.sourceId, this.logger) | 344 _UrlNormalizer(transform, this.sourceId, this.logger) |
| 348 : transform = transform, | 345 : transform = transform, |
| 349 topLevelPath = | 346 topLevelPath = |
| 350 '../' * (transform.primaryInput.id.path.split('/').length - 2); | 347 '../' * (transform.primaryInput.id.path.split('/').length - 2); |
| 351 | 348 |
| 352 visit(Node node) { | 349 visit(Node node) { |
| 353 super.visit(node); | 350 super.visit(node); |
| 354 return changed; | 351 return changed; |
| 355 } | 352 } |
| 356 | 353 |
| 357 visitElement(Element node) { | 354 visitElement(Element node) { |
| 358 // TODO(jakemac): Support custom elements that extend html elements which | 355 // TODO(jakemac): Support custom elements that extend html elements which |
| 359 // have url-like attributes. This probably means keeping a list of which | 356 // have url-like attributes. This probably means keeping a list of which |
| 360 // html elements support each url-like attribute. | 357 // html elements support each url-like attribute. |
| 361 if (!isCustomTagName(node.localName)) { | 358 if (!isCustomTagName(node.localName)) { |
| 362 node.attributes.forEach((name, value) { | 359 node.attributes.forEach((name, value) { |
| 363 if (_urlAttributes.contains(name)) { | 360 if (_urlAttributes.contains(name)) { |
| 364 if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) { | 361 if (!name.startsWith('_') && value.contains(_BINDING_REGEX)) { |
| 365 logger.warning( | 362 logger.warning(useUnderscorePrefix.create({'name': name}), |
| 366 'When using bindings with the "$name" attribute you may ' | 363 span: node.sourceSpan, asset: sourceId); |
| 367 'experience errors in certain browsers. Please use the ' | |
| 368 '"_$name" attribute instead. For more information, see ' | |
| 369 'http://goo.gl/5av8cU', span: node.sourceSpan, asset: sourceId); | |
| 370 } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) { | 364 } else if (name.startsWith('_') && !value.contains(_BINDING_REGEX)) { |
| 371 logger.warning( | 365 logger.warning(dontUseUndercorePrefix.create( |
| 372 'The "$name" attribute is only supported when using bindings. ' | 366 {'name': name.substring(1)}), |
| 373 'Please change to the "${name.substring(1)}" attribute.', | |
| 374 span: node.sourceSpan, asset: sourceId); | 367 span: node.sourceSpan, asset: sourceId); |
| 375 } | 368 } |
| 376 if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) { | 369 if (value != '' && !value.trim().startsWith(_BINDING_REGEX)) { |
| 377 node.attributes[name] = _newUrl(value, node.sourceSpan); | 370 node.attributes[name] = _newUrl(value, node.sourceSpan); |
| 378 changed = changed || value != node.attributes[name]; | 371 changed = changed || value != node.attributes[name]; |
| 379 } | 372 } |
| 380 } | 373 } |
| 381 }); | 374 }); |
| 382 } | 375 } |
| 383 if (node.localName == 'style') { | 376 if (node.localName == 'style') { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 396 | 389 |
| 397 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); | 390 static final _URL = new RegExp(r'url\(([^)]*)\)', multiLine: true); |
| 398 static final _QUOTE = new RegExp('["\']', multiLine: true); | 391 static final _QUOTE = new RegExp('["\']', multiLine: true); |
| 399 | 392 |
| 400 /// Visit the CSS text and replace any relative URLs so we can inline it. | 393 /// Visit the CSS text and replace any relative URLs so we can inline it. |
| 401 // Ported from: | 394 // Ported from: |
| 402 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 | 395 // https://github.com/Polymer/vulcanize/blob/c14f63696797cda18dc3d372b78aa3378
acc691f/lib/vulcan.js#L149 |
| 403 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. | 396 // TODO(jmesserly): use csslib here instead? Parsing with RegEx is sadness. |
| 404 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. | 397 // Maybe it's reliable enough for finding URLs in CSS? I'm not sure. |
| 405 String visitCss(String cssText) { | 398 String visitCss(String cssText) { |
| 406 var url = spanUrlFor(sourceId, transform); | 399 var url = spanUrlFor(sourceId, transform, logger); |
| 407 var src = new SourceFile(cssText, url: url); | 400 var src = new SourceFile(cssText, url: url); |
| 408 return cssText.replaceAllMapped(_URL, (match) { | 401 return cssText.replaceAllMapped(_URL, (match) { |
| 409 // Extract the URL, without any surrounding quotes. | 402 // Extract the URL, without any surrounding quotes. |
| 410 var span = src.span(match.start, match.end); | 403 var span = src.span(match.start, match.end); |
| 411 var href = match[1].replaceAll(_QUOTE, ''); | 404 var href = match[1].replaceAll(_QUOTE, ''); |
| 412 href = _newUrl(href, span); | 405 href = _newUrl(href, span); |
| 413 return 'url($href)'; | 406 return 'url($href)'; |
| 414 }); | 407 }); |
| 415 } | 408 } |
| 416 | 409 |
| 417 String visitInlineDart(String code) { | 410 String visitInlineDart(String code) { |
| 418 var unit = parseDirectives(code, suppressErrors: true); | 411 var unit = parseDirectives(code, suppressErrors: true); |
| 419 var file = new SourceFile(code, url: spanUrlFor(sourceId, transform)); | 412 var file = new SourceFile(code, |
| 413 url: spanUrlFor(sourceId, transform, logger)); |
| 420 var output = new TextEditTransaction(code, file); | 414 var output = new TextEditTransaction(code, file); |
| 421 var foundLibraryDirective = false; | 415 var foundLibraryDirective = false; |
| 422 for (Directive directive in unit.directives) { | 416 for (Directive directive in unit.directives) { |
| 423 if (directive is UriBasedDirective) { | 417 if (directive is UriBasedDirective) { |
| 424 var uri = directive.uri.stringValue; | 418 var uri = directive.uri.stringValue; |
| 425 var span = _getSpan(file, directive.uri); | 419 var span = _getSpan(file, directive.uri); |
| 426 | 420 |
| 427 var id = uriToAssetId(sourceId, uri, logger, span, | 421 var id = uriToAssetId(sourceId, uri, logger, span, |
| 428 errorOnAbsolute: false); | 422 errorOnAbsolute: false); |
| 429 if (id == null) continue; | 423 if (id == null) continue; |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 486 if (newPath.startsWith('lib/')) { | 480 if (newPath.startsWith('lib/')) { |
| 487 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}'; | 481 return '${topLevelPath}packages/${id.package}/${newPath.substring(4)}'; |
| 488 } | 482 } |
| 489 | 483 |
| 490 if (newPath.startsWith('asset/')) { | 484 if (newPath.startsWith('asset/')) { |
| 491 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}'; | 485 return '${topLevelPath}assets/${id.package}/${newPath.substring(6)}'; |
| 492 } | 486 } |
| 493 | 487 |
| 494 if (primaryId.package != id.package) { | 488 if (primaryId.package != id.package) { |
| 495 // Techincally we shouldn't get there | 489 // Techincally we shouldn't get there |
| 496 logger.error("don't know how to include $id from $primaryId", span: span); | 490 logger.error(internalErrorDontKnowHowToImport.create({ |
| 491 'target': id, 'source': primaryId, 'extra': ''}), span: span); |
| 497 return href; | 492 return href; |
| 498 } | 493 } |
| 499 | 494 |
| 500 var builder = path.url; | 495 var builder = path.url; |
| 501 return builder.relative(builder.join('/', newPath), | 496 return builder.relative(builder.join('/', newPath), |
| 502 from: builder.join('/', builder.dirname(primaryId.path))); | 497 from: builder.join('/', builder.dirname(primaryId.path))); |
| 503 } | 498 } |
| 504 } | 499 } |
| 505 | 500 |
| 506 /// HTML attributes that expect a URL value. | 501 /// HTML attributes that expect a URL value. |
| (...skipping 21 matching lines...) Expand all Loading... |
| 528 /// style tag except these ones. | 523 /// style tag except these ones. |
| 529 const IGNORED_LINKED_STYLE_ATTRS = | 524 const IGNORED_LINKED_STYLE_ATTRS = |
| 530 const ['charset', 'href', 'href-lang', 'rel', 'rev']; | 525 const ['charset', 'href', 'href-lang', 'rel', 'rev']; |
| 531 | 526 |
| 532 /// Global RegExp objects. | 527 /// Global RegExp objects. |
| 533 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); | 528 final _INVALID_LIB_CHARS_REGEX = new RegExp('[^a-z0-9_]'); |
| 534 final _NUM_REGEX = new RegExp('[0-9]'); | 529 final _NUM_REGEX = new RegExp('[0-9]'); |
| 535 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); | 530 final _BINDING_REGEX = new RegExp(r'(({{.*}})|(\[\[.*\]\]))'); |
| 536 | 531 |
| 537 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); | 532 _getSpan(SourceFile file, AstNode node) => file.span(node.offset, node.end); |
| OLD | NEW |