Index: lib/transformer.dart |
diff --git a/lib/transformer.dart b/lib/transformer.dart |
index 8036761a0657b039dd8eb3d02d201358fb2ec54a..6374bbbcb16805f3e0e2dad069cdd499f26aefe6 100644 |
--- a/lib/transformer.dart |
+++ b/lib/transformer.dart |
@@ -8,7 +8,9 @@ import 'dart:collection' show Queue; |
import 'package:analyzer/src/generated/ast.dart'; |
import 'package:analyzer/src/generated/element.dart'; |
import 'package:barback/barback.dart'; |
+import 'package:code_transformers/assets.dart'; |
import 'package:code_transformers/resolver.dart'; |
+import 'package:glob/glob.dart'; |
import 'package:html5lib/dom.dart' as dom; |
import 'package:html5lib/parser.dart' show parse; |
import 'package:path/path.dart' as path; |
@@ -17,18 +19,18 @@ import 'package:path/path.dart' as path; |
/// logic. |
class InitializeTransformer extends Transformer { |
final Resolvers _resolvers; |
- final String _entryPoint; |
- final String _newEntryPoint; |
- final String _htmlEntryPoint; |
- |
- InitializeTransformer( |
- this._entryPoint, this._newEntryPoint, this._htmlEntryPoint) |
- : _resolvers = new Resolvers.fromMock({ |
- // The list of types below is derived from: |
- // * types that are used internally by the resolver (see |
- // _initializeFrom in resolver.dart). |
- // TODO(jakemac): Move this into code_transformers so it can be shared. |
- 'dart:core': ''' |
+ final Iterable<Glob> _entryPointGlobs; |
+ final bool _errorIfNotFound; |
+ |
+ InitializeTransformer(List<String> entryPoints, {bool errorIfNotFound: true}) |
+ : _entryPointGlobs = entryPoints.map((e) => new Glob(e)), |
+ _errorIfNotFound = errorIfNotFound, |
+ _resolvers = new Resolvers.fromMock({ |
+ // The list of types below is derived from: |
+ // * types that are used internally by the resolver (see |
+ // _initializeFrom in resolver.dart). |
+ // TODO(jakemac): Move this into code_transformers so it can be shared. |
+ 'dart:core': ''' |
library dart.core; |
class Object {} |
class Function {} |
@@ -57,96 +59,109 @@ class InitializeTransformer extends Transformer { |
class List<V> extends Object {} |
class Map<K, V> extends Object {} |
''', |
- 'dart:html': ''' |
+ 'dart:html': ''' |
library dart.html; |
class HtmlElement {} |
''', |
- }); |
+ }); |
- factory InitializeTransformer.asPlugin(BarbackSettings settings) { |
- var entryPoint = settings.configuration['entry_point']; |
- var newEntryPoint = settings.configuration['new_entry_point']; |
- if (newEntryPoint == null) { |
- newEntryPoint = entryPoint.replaceFirst('.dart', '.bootstrap.dart'); |
- } |
- var htmlEntryPoint = settings.configuration['html_entry_point']; |
- return new InitializeTransformer(entryPoint, newEntryPoint, htmlEntryPoint); |
- } |
+ factory InitializeTransformer.asPlugin(BarbackSettings settings) => |
+ new InitializeTransformer(_readFileList(settings, 'entry_points')); |
- bool isPrimary(AssetId id) => |
- _entryPoint == id.path || _htmlEntryPoint == id.path; |
+ bool isPrimary(AssetId id) => _entryPointGlobs.any((g) => g.matches(id.path)); |
Future apply(Transform transform) { |
- if (transform.primaryInput.id.path == _entryPoint) { |
+ if (transform.primaryInput.id.path.endsWith('.dart')) { |
return _buildBootstrapFile(transform); |
- } else if (transform.primaryInput.id.path == _htmlEntryPoint) { |
- return _replaceEntryWithBootstrap(transform); |
+ } else if (transform.primaryInput.id.path.endsWith('.html')) { |
+ return transform.primaryInput.readAsString().then((html) { |
+ var document = parse(html); |
+ var originalDartFile = |
+ _findMainScript(document, transform.primaryInput.id, transform); |
+ return _buildBootstrapFile(transform, primaryId: originalDartFile).then( |
+ (AssetId newDartFile) { |
+ return _replaceEntryWithBootstrap(transform, document, |
+ transform.primaryInput.id, originalDartFile, newDartFile); |
+ }); |
+ }); |
+ } else { |
+ transform.logger.warning( |
+ 'Invalid entry point ${transform.primaryInput.id}. Must be either a ' |
+ '.dart or .html file.'); |
} |
- return null; |
+ return new Future.value(); |
} |
- Future _buildBootstrapFile(Transform transform) { |
- var newEntryPointId = |
- new AssetId(transform.primaryInput.id.package, _newEntryPoint); |
+ // Returns the AssetId of the newly created bootstrap file. |
+ Future<AssetId> _buildBootstrapFile(Transform transform, |
+ {AssetId primaryId}) { |
+ if (primaryId == null) primaryId = transform.primaryInput.id; |
+ var newEntryPointId = new AssetId(primaryId.package, |
+ '${path.url.withoutExtension(primaryId.path)}.initialize.dart'); |
return transform.hasInput(newEntryPointId).then((exists) { |
if (exists) { |
transform.logger |
.error('New entry point file $newEntryPointId already exists.'); |
- } else { |
- return _resolvers.get(transform).then((resolver) { |
- new _BootstrapFileBuilder(resolver, transform, |
- transform.primaryInput.id, newEntryPointId).run(); |
- resolver.release(); |
- }); |
+ return null; |
} |
+ |
+ return _resolvers.get(transform, [primaryId]).then((resolver) { |
+ new _BootstrapFileBuilder(resolver, transform, primaryId, |
+ newEntryPointId, _errorIfNotFound).run(); |
+ resolver.release(); |
+ return newEntryPointId; |
+ }); |
}); |
} |
- Future _replaceEntryWithBootstrap(Transform transform) { |
- // For now at least, _htmlEntryPoint, _entryPoint, and _newEntryPoint need |
- // to be in the same folder. |
- // TODO(jakemac): support package urls with _entryPoint or _newEntryPoint |
- // in `lib`, and _htmlEntryPoint in another directory. |
- var _expectedDir = path.split(_htmlEntryPoint)[0]; |
- if (_expectedDir != path.split(_entryPoint)[0] || |
- _expectedDir != path.split(_newEntryPoint)[0]) { |
+ // Replaces script tags pointing to [originalDartFile] with [newDartFile] in |
+ // [entryPoint]. |
+ void _replaceEntryWithBootstrap(Transform transform, dom.Document document, |
+ AssetId entryPoint, AssetId originalDartFile, AssetId newDartFile) { |
+ var found = false; |
+ var scripts = document |
+ .querySelectorAll('script[type="application/dart"]') |
+ .where((script) => uriToAssetId(entryPoint, script.attributes['src'], |
+ transform.logger, script.sourceSpan) == originalDartFile) |
+ .toList(); |
+ |
+ if (scripts.length != 1) { |
transform.logger.error( |
- 'htmlEntryPoint, entryPoint, and newEntryPoint(if supplied) all must ' |
- 'be in the same top level directory.'); |
+ 'Expected exactly one script pointing to $originalDartFile in ' |
+ '$entryPoint, but found ${scripts.length}.'); |
+ return; |
} |
- |
- return transform.primaryInput.readAsString().then((String html) { |
- var found = false; |
- var doc = parse(html); |
- var scripts = doc.querySelectorAll('script[type="application/dart"]'); |
- for (dom.Element script in scripts) { |
- if (!_isEntryPointScript(script)) continue; |
- script.attributes['src'] = _relativeDartEntryPath(_newEntryPoint); |
- found = true; |
- } |
- if (!found) { |
- transform.logger.error( |
- 'Unable to find script for $_entryPoint in $_htmlEntryPoint.'); |
- } |
- return transform.addOutput( |
- new Asset.fromString(transform.primaryInput.id, doc.outerHtml)); |
- }); |
+ scripts[0].attributes['src'] = path.url.relative(newDartFile.path, |
+ from: path.dirname(entryPoint.path)); |
+ transform.addOutput(new Asset.fromString(entryPoint, document.outerHtml)); |
} |
- // Checks if the src of this script tag is pointing at `_entryPoint`. |
- bool _isEntryPointScript(dom.Element script) => |
- path.normalize(script.attributes['src']) == |
- _relativeDartEntryPath(_entryPoint); |
+ AssetId _findMainScript( |
+ dom.Document document, AssetId entryPoint, Transform transform) { |
+ var scripts = document.querySelectorAll('script[type="application/dart"]'); |
+ if (scripts.length != 1) { |
+ transform.logger.error('Expected exactly one dart script in $entryPoint ' |
+ 'but found ${scripts.length}.'); |
+ return null; |
+ } |
+ |
+ var src = scripts[0].attributes['src']; |
+ // TODO(jakemac): Support inline scripts, |
+ // https://github.com/dart-lang/initialize/issues/20 |
+ if (src == null) { |
+ transform.logger.error('Inline scripts are not supported at this time.'); |
+ return null; |
+ } |
- // The relative path from `_htmlEntryPoint` to `dartEntry`. You must ensure |
- // that neither of these is null before calling this function. |
- String _relativeDartEntryPath(String dartEntry) => |
- path.relative(dartEntry, from: path.dirname(_htmlEntryPoint)); |
+ return uriToAssetId( |
+ entryPoint, src, transform.logger, scripts[0].sourceSpan); |
+ } |
} |
class _BootstrapFileBuilder { |
final Resolver _resolver; |
final Transform _transform; |
+ final bool _errorIfNotFound; |
AssetId _entryPoint; |
AssetId _newEntryPoint; |
@@ -162,12 +177,18 @@ class _BootstrapFileBuilder { |
TransformLogger _logger; |
- _BootstrapFileBuilder( |
- this._resolver, this._transform, this._entryPoint, this._newEntryPoint) { |
+ _BootstrapFileBuilder(this._resolver, this._transform, this._entryPoint, |
+ this._newEntryPoint, this._errorIfNotFound) { |
_logger = _transform.logger; |
_initializeLibrary = |
_resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart')); |
- _initializer = _initializeLibrary.getType('Initializer'); |
+ if (_initializeLibrary != null) { |
+ _initializer = _initializeLibrary.getType('Initializer'); |
+ } else if (_errorIfNotFound) { |
+ _logger.warning('Unable to read "package:initialize/initialize.dart". ' |
+ 'This file must be imported via $_entryPoint or a transitive ' |
+ 'dependency.'); |
+ } |
} |
/// Adds the new entry point file to the transform. Should only be ran once. |
@@ -452,6 +473,9 @@ $initializersBuffer |
} |
bool _isInitializer(InterfaceType type) { |
+ // If `_initializer` wasn't found then it was never loaded (even |
+ // transitively), and so no annotations can be initializers. |
+ if (_initializer == null) return false; |
if (type == null) return false; |
if (type.element.type == _initializer.type) return true; |
if (_isInitializer(type.superclass)) return true; |
@@ -544,3 +568,25 @@ class _InitializerData { |
_InitializerData(this.element, this.annotation); |
} |
+ |
+// Reads a file list from a barback settings configuration field. |
+_readFileList(BarbackSettings settings, String field) { |
+ var value = settings.configuration[field]; |
+ if (value == null) return null; |
+ var files = []; |
+ bool error; |
+ if (value is List) { |
+ files = value; |
+ error = value.any((e) => e is! String); |
+ } else if (value is String) { |
+ files = [value]; |
+ error = false; |
+ } else { |
+ error = true; |
+ } |
+ if (error) { |
+ print('Bad value for "$field" in the initialize transformer. ' |
+ 'Expected either one String or a list of Strings.'); |
+ } |
+ return files; |
+} |