added plugin transformer which initializers can extend to do additional post-processing during transformation
R=jmesserly@google.com
Review URL: https://codereview.chromium.org//888943002
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 598cde8..e52773d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.3.1-dev
+
+* Added `InitializePluginTransformer` class in `plugin_transformer.dart` which
+provides a base transformer class which can be extended to perform custom
+transformations for annotations. These transformers should be included after the
+main `initialize` transformer and work by parsing the bootstrap file so the
+program doesn't need to be re-analyzed.
+
## 0.3.0
* Library initializers now pass a `LibraryIdentifier` to `initialize` instead of
diff --git a/lib/plugin_transformer.dart b/lib/plugin_transformer.dart
new file mode 100644
index 0000000..2caa077
--- /dev/null
+++ b/lib/plugin_transformer.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+library initialize.plugin_transformer;
+
+import 'dart:async';
+import 'package:analyzer/analyzer.dart';
+import 'package:barback/barback.dart';
+import 'package:source_maps/refactor.dart';
+import 'package:source_span/source_span.dart';
+
+/// Abstract transformer which will call [initEntry] for every [InitEntry]
+/// expression found in the bootstrap file. This is used to centralize logic
+/// for initializers which want to do something special at transform time.
+abstract class InitializePluginTransformer extends AggregateTransformer {
+ // Path to the bootstrap file created by the initialize transformer.
+ final String _bootstrapFile;
+
+ // All the extra assets that were found, if child classes override
+ // classifyPrimary this lets them get at the other assets easily.
+ final allAssets = <Asset>[];
+
+ TransformLogger _logger;
+
+ InitializePluginTransformer(this._bootstrapFile);
+
+ classifyPrimary(AssetId id) =>
+ _bootstrapFile == id.path ? _bootstrapFile : null;
+
+ Future apply(AggregateTransform transform) {
+ _logger = transform.logger;
+ var listener = transform.primaryInputs.listen((Asset asset) {
+ allAssets.add(asset);
+ var id = asset.id;
+ if (id.path != _bootstrapFile) return;
+ // Calls initEntry for each InitEntry expression.
+ asset.readAsString().then((dartCode) {
+ var url = id.path.startsWith('lib/')
+ ? 'package:${id.package}/${id.path.substring(4)}'
+ : id.path;
+ var source = new SourceFile(dartCode, url: url);
+ var transaction = new TextEditTransaction(dartCode, source);
+ (parseCompilationUnit(dartCode,
+ suppressErrors: true) as dynamic).declarations.firstWhere(
+ (d) => d.name.toString() ==
+ 'main').functionExpression.body.block.statements.firstWhere(
+ (statement) {
+ return statement.expression.target.toString() == 'initializers' &&
+ statement.expression.methodName.toString() == 'addAll';
+ }).expression.argumentList.arguments[0].elements
+ .where((arg) => arg is InstanceCreationExpression)
+ .forEach((e) => initEntry(e, transaction));
+
+ // Apply any transformations.
+ if (!transaction.hasEdits) {
+ transform.addOutput(asset);
+ } else {
+ var printer = transaction.commit();
+ printer.build(url);
+ transform.addOutput(new Asset.fromString(id, printer.text));
+ }
+ });
+ });
+
+ // Make sure all the assets are read before returning.
+ return listener.asFuture();
+ }
+
+ /// Gets called once for each generated [InitEntry] expression in the
+ /// bootstrap file. A [TextEditTransaction] is supplied so that the user can
+ /// modify the expression however they see fit.
+ void initEntry(
+ InstanceCreationExpression expression, TextEditTransaction transaction);
+
+ /// Convenience method to delete an Initializer expression completely.
+ void removeInitializer(
+ InstanceCreationExpression expression, TextEditTransaction transaction) {
+ // Delete the entire line.
+ var line = transaction.file.getLine(expression.offset);
+ transaction.edit(transaction.file.getOffset(line),
+ transaction.file.getOffset(line + 1), '');
+ }
+}
diff --git a/lib/src/mirror_loader.dart b/lib/src/mirror_loader.dart
index f3e2689..92a00f8 100644
--- a/lib/src/mirror_loader.dart
+++ b/lib/src/mirror_loader.dart
@@ -149,8 +149,8 @@
var filePath;
Uri uri = declaration.uri;
if (uri.scheme == 'file' || uri.scheme.startsWith('http')) {
- filePath = path.url.relative(
- uri.path, from: path.url.dirname(_root.uri.path));
+ filePath = path.url.relative(uri.path,
+ from: path.url.dirname(_root.uri.path));
} else if (uri.scheme == 'package') {
var segments = uri.pathSegments;
package = segments[0];
diff --git a/pubspec.yaml b/pubspec.yaml
index fd842a7..a9a7a2e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: initialize
-version: 0.3.0
+version: 0.3.1-dev
author: Polymer.dart Authors <web@dartlang.org>
description: Generic building blocks for doing static initialization.
homepage: https://github.com/dart-lang/initialize
diff --git a/test/initializer_test.dart b/test/initializer_test.dart
index 098628e..519f521 100644
--- a/test/initializer_test.dart
+++ b/test/initializer_test.dart
@@ -20,7 +20,7 @@
test('annotations are seen in post-order with superclasses first', () {
var expectedNames = [
const LibraryIdentifier(#test_package.bar, 'test_package', 'bar.dart'),
- const LibraryIdentifier(#test_package.foo, 'test_package','foo.dart'),
+ const LibraryIdentifier(#test_package.foo, 'test_package', 'foo.dart'),
const LibraryIdentifier(#initialize.test.foo, null, 'foo.dart'),
foo,
fooBar,
diff --git a/test/plugin_transformer_test.dart b/test/plugin_transformer_test.dart
new file mode 100644
index 0000000..df2a6e4
--- /dev/null
+++ b/test/plugin_transformer_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+library initialize.plugin_transformer_test;
+
+import 'package:analyzer/analyzer.dart';
+import 'package:initialize/plugin_transformer.dart';
+import 'package:source_maps/refactor.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'common.dart';
+
+// Simple plugin which just removes all initMethod initializers.
+class DeleteInitMethodPlugin extends InitializePluginTransformer {
+ DeleteInitMethodPlugin(String bootstrap) : super(bootstrap);
+
+ initEntry(
+ InstanceCreationExpression expression, TextEditTransaction transaction) {
+ var firstArg = expression.argumentList.arguments[0];
+ if (firstArg is PrefixedIdentifier &&
+ firstArg.identifier.toString() == 'initMethod') {
+ removeInitializer(expression, transaction);
+ }
+ }
+}
+
+main() {
+ useCompactVMConfiguration();
+
+ var transformer = new DeleteInitMethodPlugin('web/index.bootstrap.dart');
+
+ testPhases('basic', [[transformer]], {
+ 'a|web/index.bootstrap.dart': '''
+import 'package:initialize/src/static_loader.dart';
+import 'package:initialize/initialize.dart';
+import 'index.dart' as i0;
+
+main() {
+ initializers.addAll([
+ new InitEntry(i0.initMethod, const LibraryIdentifier(#a, null, 'a.dart')),
+ new InitEntry(i0.initMethod, const LibraryIdentifier(#a, 'a', 'a.dart')),
+ new InitEntry(i0.initMethod, i0.a),
+ new InitEntry(const i0.DynamicInit('a()'), i0.a),
+ ]);
+
+ i0.main();
+}
+'''
+ }, {
+ 'a|web/index.bootstrap.dart': '''
+import 'package:initialize/src/static_loader.dart';
+import 'package:initialize/initialize.dart';
+import 'index.dart' as i0;
+
+main() {
+ initializers.addAll([
+ new InitEntry(const i0.DynamicInit('a()'), i0.a),
+ ]);
+
+ i0.main();
+}
+'''
+ }, []);
+}