| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 library initialize.transformer; | 4 library initialize.transformer; |
| 5 | 5 |
| 6 import 'dart:async'; | 6 import 'dart:async'; |
| 7 import 'dart:collection' show Queue; | 7 import 'dart:collection' show Queue; |
| 8 import 'package:analyzer/src/generated/ast.dart'; | 8 import 'package:analyzer/src/generated/ast.dart'; |
| 9 import 'package:analyzer/src/generated/element.dart'; | 9 import 'package:analyzer/src/generated/element.dart'; |
| 10 import 'package:barback/barback.dart'; | 10 import 'package:barback/barback.dart'; |
| 11 import 'package:code_transformers/assets.dart'; |
| 11 import 'package:code_transformers/resolver.dart'; | 12 import 'package:code_transformers/resolver.dart'; |
| 13 import 'package:glob/glob.dart'; |
| 12 import 'package:html5lib/dom.dart' as dom; | 14 import 'package:html5lib/dom.dart' as dom; |
| 13 import 'package:html5lib/parser.dart' show parse; | 15 import 'package:html5lib/parser.dart' show parse; |
| 14 import 'package:path/path.dart' as path; | 16 import 'package:path/path.dart' as path; |
| 15 | 17 |
| 16 /// Removes the mirror-based initialization logic and replaces it with static | 18 /// Removes the mirror-based initialization logic and replaces it with static |
| 17 /// logic. | 19 /// logic. |
| 18 class InitializeTransformer extends Transformer { | 20 class InitializeTransformer extends Transformer { |
| 19 final Resolvers _resolvers; | 21 final Resolvers _resolvers; |
| 20 final String _entryPoint; | 22 final Iterable<Glob> _entryPointGlobs; |
| 21 final String _newEntryPoint; | 23 final bool _errorIfNotFound; |
| 22 final String _htmlEntryPoint; | |
| 23 | 24 |
| 24 InitializeTransformer( | 25 InitializeTransformer(List<String> entryPoints, {bool errorIfNotFound: true}) |
| 25 this._entryPoint, this._newEntryPoint, this._htmlEntryPoint) | 26 : _entryPointGlobs = entryPoints.map((e) => new Glob(e)), |
| 26 : _resolvers = new Resolvers.fromMock({ | 27 _errorIfNotFound = errorIfNotFound, |
| 27 // The list of types below is derived from: | 28 _resolvers = new Resolvers.fromMock({ |
| 28 // * types that are used internally by the resolver (see | 29 // The list of types below is derived from: |
| 29 // _initializeFrom in resolver.dart). | 30 // * types that are used internally by the resolver (see |
| 30 // TODO(jakemac): Move this into code_transformers so it can be shared. | 31 // _initializeFrom in resolver.dart). |
| 31 'dart:core': ''' | 32 // TODO(jakemac): Move this into code_transformers so it can be shared
. |
| 33 'dart:core': ''' |
| 32 library dart.core; | 34 library dart.core; |
| 33 class Object {} | 35 class Object {} |
| 34 class Function {} | 36 class Function {} |
| 35 class StackTrace {} | 37 class StackTrace {} |
| 36 class Symbol {} | 38 class Symbol {} |
| 37 class Type {} | 39 class Type {} |
| 38 | 40 |
| 39 class String extends Object {} | 41 class String extends Object {} |
| 40 class bool extends Object {} | 42 class bool extends Object {} |
| 41 class num extends Object {} | 43 class num extends Object {} |
| 42 class int extends num {} | 44 class int extends num {} |
| 43 class double extends num {} | 45 class double extends num {} |
| 44 class DateTime extends Object {} | 46 class DateTime extends Object {} |
| 45 class Null extends Object {} | 47 class Null extends Object {} |
| 46 | 48 |
| 47 class Deprecated extends Object { | 49 class Deprecated extends Object { |
| 48 final String expires; | 50 final String expires; |
| 49 const Deprecated(this.expires); | 51 const Deprecated(this.expires); |
| 50 } | 52 } |
| 51 const Object deprecated = const Deprecated("next release"); | 53 const Object deprecated = const Deprecated("next release"); |
| 52 class _Override { const _Override(); } | 54 class _Override { const _Override(); } |
| 53 const Object override = const _Override(); | 55 const Object override = const _Override(); |
| 54 class _Proxy { const _Proxy(); } | 56 class _Proxy { const _Proxy(); } |
| 55 const Object proxy = const _Proxy(); | 57 const Object proxy = const _Proxy(); |
| 56 | 58 |
| 57 class List<V> extends Object {} | 59 class List<V> extends Object {} |
| 58 class Map<K, V> extends Object {} | 60 class Map<K, V> extends Object {} |
| 59 ''', | 61 ''', |
| 60 'dart:html': ''' | 62 'dart:html': ''' |
| 61 library dart.html; | 63 library dart.html; |
| 62 class HtmlElement {} | 64 class HtmlElement {} |
| 63 ''', | 65 ''', |
| 66 }); |
| 67 |
| 68 factory InitializeTransformer.asPlugin(BarbackSettings settings) => |
| 69 new InitializeTransformer(_readFileList(settings, 'entry_points')); |
| 70 |
| 71 bool isPrimary(AssetId id) => _entryPointGlobs.any((g) => g.matches(id.path)); |
| 72 |
| 73 Future apply(Transform transform) { |
| 74 if (transform.primaryInput.id.path.endsWith('.dart')) { |
| 75 return _buildBootstrapFile(transform); |
| 76 } else if (transform.primaryInput.id.path.endsWith('.html')) { |
| 77 return transform.primaryInput.readAsString().then((html) { |
| 78 var document = parse(html); |
| 79 var originalDartFile = |
| 80 _findMainScript(document, transform.primaryInput.id, transform); |
| 81 return _buildBootstrapFile(transform, primaryId: originalDartFile).then( |
| 82 (AssetId newDartFile) { |
| 83 return _replaceEntryWithBootstrap(transform, document, |
| 84 transform.primaryInput.id, originalDartFile, newDartFile); |
| 85 }); |
| 64 }); | 86 }); |
| 65 | 87 } else { |
| 66 factory InitializeTransformer.asPlugin(BarbackSettings settings) { | 88 transform.logger.warning( |
| 67 var entryPoint = settings.configuration['entry_point']; | 89 'Invalid entry point ${transform.primaryInput.id}. Must be either a ' |
| 68 var newEntryPoint = settings.configuration['new_entry_point']; | 90 '.dart or .html file.'); |
| 69 if (newEntryPoint == null) { | |
| 70 newEntryPoint = entryPoint.replaceFirst('.dart', '.bootstrap.dart'); | |
| 71 } | 91 } |
| 72 var htmlEntryPoint = settings.configuration['html_entry_point']; | 92 return new Future.value(); |
| 73 return new InitializeTransformer(entryPoint, newEntryPoint, htmlEntryPoint); | |
| 74 } | 93 } |
| 75 | 94 |
| 76 bool isPrimary(AssetId id) => | 95 // Returns the AssetId of the newly created bootstrap file. |
| 77 _entryPoint == id.path || _htmlEntryPoint == id.path; | 96 Future<AssetId> _buildBootstrapFile(Transform transform, |
| 78 | 97 {AssetId primaryId}) { |
| 79 Future apply(Transform transform) { | 98 if (primaryId == null) primaryId = transform.primaryInput.id; |
| 80 if (transform.primaryInput.id.path == _entryPoint) { | 99 var newEntryPointId = new AssetId(primaryId.package, |
| 81 return _buildBootstrapFile(transform); | 100 '${path.url.withoutExtension(primaryId.path)}.initialize.dart'); |
| 82 } else if (transform.primaryInput.id.path == _htmlEntryPoint) { | |
| 83 return _replaceEntryWithBootstrap(transform); | |
| 84 } | |
| 85 return null; | |
| 86 } | |
| 87 | |
| 88 Future _buildBootstrapFile(Transform transform) { | |
| 89 var newEntryPointId = | |
| 90 new AssetId(transform.primaryInput.id.package, _newEntryPoint); | |
| 91 return transform.hasInput(newEntryPointId).then((exists) { | 101 return transform.hasInput(newEntryPointId).then((exists) { |
| 92 if (exists) { | 102 if (exists) { |
| 93 transform.logger | 103 transform.logger |
| 94 .error('New entry point file $newEntryPointId already exists.'); | 104 .error('New entry point file $newEntryPointId already exists.'); |
| 95 } else { | 105 return null; |
| 96 return _resolvers.get(transform).then((resolver) { | |
| 97 new _BootstrapFileBuilder(resolver, transform, | |
| 98 transform.primaryInput.id, newEntryPointId).run(); | |
| 99 resolver.release(); | |
| 100 }); | |
| 101 } | 106 } |
| 107 |
| 108 return _resolvers.get(transform, [primaryId]).then((resolver) { |
| 109 new _BootstrapFileBuilder(resolver, transform, primaryId, |
| 110 newEntryPointId, _errorIfNotFound).run(); |
| 111 resolver.release(); |
| 112 return newEntryPointId; |
| 113 }); |
| 102 }); | 114 }); |
| 103 } | 115 } |
| 104 | 116 |
| 105 Future _replaceEntryWithBootstrap(Transform transform) { | 117 // Replaces script tags pointing to [originalDartFile] with [newDartFile] in |
| 106 // For now at least, _htmlEntryPoint, _entryPoint, and _newEntryPoint need | 118 // [entryPoint]. |
| 107 // to be in the same folder. | 119 void _replaceEntryWithBootstrap(Transform transform, dom.Document document, |
| 108 // TODO(jakemac): support package urls with _entryPoint or _newEntryPoint | 120 AssetId entryPoint, AssetId originalDartFile, AssetId newDartFile) { |
| 109 // in `lib`, and _htmlEntryPoint in another directory. | 121 var found = false; |
| 110 var _expectedDir = path.split(_htmlEntryPoint)[0]; | 122 var scripts = document |
| 111 if (_expectedDir != path.split(_entryPoint)[0] || | 123 .querySelectorAll('script[type="application/dart"]') |
| 112 _expectedDir != path.split(_newEntryPoint)[0]) { | 124 .where((script) => uriToAssetId(entryPoint, script.attributes['src'], |
| 125 transform.logger, script.sourceSpan) == originalDartFile) |
| 126 .toList(); |
| 127 |
| 128 if (scripts.length != 1) { |
| 113 transform.logger.error( | 129 transform.logger.error( |
| 114 'htmlEntryPoint, entryPoint, and newEntryPoint(if supplied) all must ' | 130 'Expected exactly one script pointing to $originalDartFile in ' |
| 115 'be in the same top level directory.'); | 131 '$entryPoint, but found ${scripts.length}.'); |
| 132 return; |
| 133 } |
| 134 scripts[0].attributes['src'] = path.url.relative(newDartFile.path, |
| 135 from: path.dirname(entryPoint.path)); |
| 136 transform.addOutput(new Asset.fromString(entryPoint, document.outerHtml)); |
| 137 } |
| 138 |
| 139 AssetId _findMainScript( |
| 140 dom.Document document, AssetId entryPoint, Transform transform) { |
| 141 var scripts = document.querySelectorAll('script[type="application/dart"]'); |
| 142 if (scripts.length != 1) { |
| 143 transform.logger.error('Expected exactly one dart script in $entryPoint ' |
| 144 'but found ${scripts.length}.'); |
| 145 return null; |
| 116 } | 146 } |
| 117 | 147 |
| 118 return transform.primaryInput.readAsString().then((String html) { | 148 var src = scripts[0].attributes['src']; |
| 119 var found = false; | 149 // TODO(jakemac): Support inline scripts, |
| 120 var doc = parse(html); | 150 // https://github.com/dart-lang/initialize/issues/20 |
| 121 var scripts = doc.querySelectorAll('script[type="application/dart"]'); | 151 if (src == null) { |
| 122 for (dom.Element script in scripts) { | 152 transform.logger.error('Inline scripts are not supported at this time.'); |
| 123 if (!_isEntryPointScript(script)) continue; | 153 return null; |
| 124 script.attributes['src'] = _relativeDartEntryPath(_newEntryPoint); | 154 } |
| 125 found = true; | 155 |
| 126 } | 156 return uriToAssetId( |
| 127 if (!found) { | 157 entryPoint, src, transform.logger, scripts[0].sourceSpan); |
| 128 transform.logger.error( | |
| 129 'Unable to find script for $_entryPoint in $_htmlEntryPoint.'); | |
| 130 } | |
| 131 return transform.addOutput( | |
| 132 new Asset.fromString(transform.primaryInput.id, doc.outerHtml)); | |
| 133 }); | |
| 134 } | 158 } |
| 135 | |
| 136 // Checks if the src of this script tag is pointing at `_entryPoint`. | |
| 137 bool _isEntryPointScript(dom.Element script) => | |
| 138 path.normalize(script.attributes['src']) == | |
| 139 _relativeDartEntryPath(_entryPoint); | |
| 140 | |
| 141 // The relative path from `_htmlEntryPoint` to `dartEntry`. You must ensure | |
| 142 // that neither of these is null before calling this function. | |
| 143 String _relativeDartEntryPath(String dartEntry) => | |
| 144 path.relative(dartEntry, from: path.dirname(_htmlEntryPoint)); | |
| 145 } | 159 } |
| 146 | 160 |
| 147 class _BootstrapFileBuilder { | 161 class _BootstrapFileBuilder { |
| 148 final Resolver _resolver; | 162 final Resolver _resolver; |
| 149 final Transform _transform; | 163 final Transform _transform; |
| 164 final bool _errorIfNotFound; |
| 150 AssetId _entryPoint; | 165 AssetId _entryPoint; |
| 151 AssetId _newEntryPoint; | 166 AssetId _newEntryPoint; |
| 152 | 167 |
| 153 /// The resolved initialize library. | 168 /// The resolved initialize library. |
| 154 LibraryElement _initializeLibrary; | 169 LibraryElement _initializeLibrary; |
| 155 /// The resolved Initializer class from the initialize library. | 170 /// The resolved Initializer class from the initialize library. |
| 156 ClassElement _initializer; | 171 ClassElement _initializer; |
| 157 | 172 |
| 158 /// Queue for intialization annotations. | 173 /// Queue for intialization annotations. |
| 159 final _initQueue = new Queue<_InitializerData>(); | 174 final _initQueue = new Queue<_InitializerData>(); |
| 160 /// All the annotations we have seen for each element | 175 /// All the annotations we have seen for each element |
| 161 final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>(); | 176 final _seenAnnotations = new Map<Element, Set<ElementAnnotation>>(); |
| 162 | 177 |
| 163 TransformLogger _logger; | 178 TransformLogger _logger; |
| 164 | 179 |
| 165 _BootstrapFileBuilder( | 180 _BootstrapFileBuilder(this._resolver, this._transform, this._entryPoint, |
| 166 this._resolver, this._transform, this._entryPoint, this._newEntryPoint) { | 181 this._newEntryPoint, this._errorIfNotFound) { |
| 167 _logger = _transform.logger; | 182 _logger = _transform.logger; |
| 168 _initializeLibrary = | 183 _initializeLibrary = |
| 169 _resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart')); | 184 _resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart')); |
| 170 _initializer = _initializeLibrary.getType('Initializer'); | 185 if (_initializeLibrary != null) { |
| 186 _initializer = _initializeLibrary.getType('Initializer'); |
| 187 } else if (_errorIfNotFound) { |
| 188 _logger.warning('Unable to read "package:initialize/initialize.dart". ' |
| 189 'This file must be imported via $_entryPoint or a transitive ' |
| 190 'dependency.'); |
| 191 } |
| 171 } | 192 } |
| 172 | 193 |
| 173 /// Adds the new entry point file to the transform. Should only be ran once. | 194 /// Adds the new entry point file to the transform. Should only be ran once. |
| 174 void run() { | 195 void run() { |
| 175 var entryLib = _resolver.getLibrary(_entryPoint); | 196 var entryLib = _resolver.getLibrary(_entryPoint); |
| 176 _readLibraries(entryLib); | 197 _readLibraries(entryLib); |
| 177 | 198 |
| 178 _transform.addOutput( | 199 _transform.addOutput( |
| 179 new Asset.fromString(_newEntryPoint, _buildNewEntryPoint(entryLib))); | 200 new Asset.fromString(_newEntryPoint, _buildNewEntryPoint(entryLib))); |
| 180 } | 201 } |
| (...skipping 264 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 445 _logger.error('Unsupported argument to initializer constructor.'); | 466 _logger.error('Unsupported argument to initializer constructor.'); |
| 446 } | 467 } |
| 447 } else { | 468 } else { |
| 448 _logger.error('Only literals and identifiers are allowed for initializer ' | 469 _logger.error('Only literals and identifiers are allowed for initializer ' |
| 449 'expressions, found $expression.'); | 470 'expressions, found $expression.'); |
| 450 } | 471 } |
| 451 return buffer.toString(); | 472 return buffer.toString(); |
| 452 } | 473 } |
| 453 | 474 |
| 454 bool _isInitializer(InterfaceType type) { | 475 bool _isInitializer(InterfaceType type) { |
| 476 // If `_initializer` wasn't found then it was never loaded (even |
| 477 // transitively), and so no annotations can be initializers. |
| 478 if (_initializer == null) return false; |
| 455 if (type == null) return false; | 479 if (type == null) return false; |
| 456 if (type.element.type == _initializer.type) return true; | 480 if (type.element.type == _initializer.type) return true; |
| 457 if (_isInitializer(type.superclass)) return true; | 481 if (_isInitializer(type.superclass)) return true; |
| 458 for (var interface in type.interfaces) { | 482 for (var interface in type.interfaces) { |
| 459 if (_isInitializer(interface)) return true; | 483 if (_isInitializer(interface)) return true; |
| 460 } | 484 } |
| 461 return false; | 485 return false; |
| 462 } | 486 } |
| 463 | 487 |
| 464 /// Retrieves all top-level methods that are visible if you were to import | 488 /// Retrieves all top-level methods that are visible if you were to import |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 537 })).map((import) => import.importedLibrary); | 561 })).map((import) => import.importedLibrary); |
| 538 } | 562 } |
| 539 | 563 |
| 540 // Element/ElementAnnotation pair. | 564 // Element/ElementAnnotation pair. |
| 541 class _InitializerData { | 565 class _InitializerData { |
| 542 final Element element; | 566 final Element element; |
| 543 final ElementAnnotation annotation; | 567 final ElementAnnotation annotation; |
| 544 | 568 |
| 545 _InitializerData(this.element, this.annotation); | 569 _InitializerData(this.element, this.annotation); |
| 546 } | 570 } |
| 571 |
| 572 // Reads a file list from a barback settings configuration field. |
| 573 _readFileList(BarbackSettings settings, String field) { |
| 574 var value = settings.configuration[field]; |
| 575 if (value == null) return null; |
| 576 var files = []; |
| 577 bool error; |
| 578 if (value is List) { |
| 579 files = value; |
| 580 error = value.any((e) => e is! String); |
| 581 } else if (value is String) { |
| 582 files = [value]; |
| 583 error = false; |
| 584 } else { |
| 585 error = true; |
| 586 } |
| 587 if (error) { |
| 588 print('Bad value for "$field" in the initialize transformer. ' |
| 589 'Expected either one String or a list of Strings.'); |
| 590 } |
| 591 return files; |
| 592 } |
| OLD | NEW |