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();
+}
+'''
+  }, []);
+}