- Fix a few issues around exported libraries
- Add support for html imports without adding an html dependency by running from all libraries in the mirror system in reverse order instead of just the root one.

R=sigmund@google.com

Review URL: https://codereview.chromium.org//973073003
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 568e5af..0375a06 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 0.5.1+2
+
+* Fix handling of exported libraries. Specifically, annotations on exported
+libraries will now be reached and imports in exported libraries will now be
+reached.
+* Add support for scripts living in html imports without adding an html
+dependency by crawling all libraries in the mirror system in reverse order,
+instead of just the root one.
+
 ## 0.5.1+1
 
 * Make sure to always use `path.url` in the transformer.
diff --git a/lib/src/mirror_loader.dart b/lib/src/mirror_loader.dart
index 92a00f8..e73f0cc 100644
--- a/lib/src/mirror_loader.dart
+++ b/lib/src/mirror_loader.dart
@@ -8,6 +8,8 @@
 import 'package:path/path.dart' as path;
 import 'package:initialize/initialize.dart';
 
+final _root = currentMirrorSystem().isolate.rootLibrary;
+
 Queue<Function> loadInitializers(
     {List<Type> typeFilter, InitializerFilter customFilter}) {
   return new InitializationCrawler(typeFilter, customFilter).run();
@@ -28,24 +30,60 @@
   // function will be processed.
   final InitializerFilter customFilter;
 
-  // The root library that we start parsing from.
-  LibraryMirror _root;
-
-  InitializationCrawler(this.typeFilter, this.customFilter,
-      {LibraryMirror root}) {
-    _root = root == null ? currentMirrorSystem().isolate.rootLibrary : root;
-  }
+  InitializationCrawler(this.typeFilter, this.customFilter);
 
   // The primary function in this class, invoke it to crawl and collect all the
   // annotations into a queue of init functions.
-  Queue<Function> run() => _readLibraryDeclarations(_root);
+  Queue<Function> run() {
+    var librariesSeen = new Set<LibraryMirror>();
+    var queue = new Queue<Function>();
+
+    var libraries = currentMirrorSystem().libraries;
+    var nonDartOrPackageImports = new List.from(libraries.keys.where(
+        (uri) => uri.scheme != 'package' && uri.scheme != 'dart'));
+
+    for (var import in nonDartOrPackageImports.reversed) {
+      // Always load the package: version of a library if available.
+      var libToRun;
+      if (_isHttpStylePackageUrl(import)) {
+        var packageUri = _packageUriFor(import);
+        libToRun = libraries[packageUri];
+      }
+      if (libToRun == null) libToRun = libraries[import];
+
+      // Dartium creates an extra trampoline lib that loads the main dart script
+      // and breaks our ordering.
+      if (librariesSeen.contains(libToRun) ||
+          libToRun.uri.path.endsWith('\$trampoline')) {
+        continue;
+      }
+      _readLibraryDeclarations(libToRun, librariesSeen, queue);
+    }
+
+    return queue;
+  }
+
+  /// Whether [uri] is an http URI that contains a 'packages' segment, and
+  /// therefore could be converted into a 'package:' URI.
+  bool _isHttpStylePackageUrl(Uri uri) {
+    var uriPath = uri.path;
+    return uri.scheme == _root.uri.scheme &&
+        // Don't process cross-domain uris.
+        uri.authority == _root.uri.authority &&
+        uriPath.endsWith('.dart') &&
+        (uriPath.contains('/packages/') || uriPath.startsWith('packages/'));
+  }
+
+  Uri _packageUriFor(Uri httpUri) {
+    var packagePath = httpUri.path.substring(
+        httpUri.path.lastIndexOf('packages/') + 'packages/'.length);
+    return Uri.parse('package:$packagePath');
+  }
 
   // Reads Initializer annotations on this library and all its dependencies in
   // post-order.
   Queue<Function> _readLibraryDeclarations(LibraryMirror lib,
-      [Set<LibraryMirror> librariesSeen, Queue<Function> queue]) {
-    if (librariesSeen == null) librariesSeen = new Set<LibraryMirror>();
-    if (queue == null) queue = new Queue<Function>();
+      Set<LibraryMirror> librariesSeen, Queue<Function> queue) {
     librariesSeen.add(lib);
 
     // First visit all our dependencies.
@@ -148,6 +186,10 @@
         var package;
         var filePath;
         Uri uri = declaration.uri;
+        // Convert to a package style uri if possible.
+        if (_isHttpStylePackageUrl(uri)) {
+          uri = _packageUriFor(uri);
+        }
         if (uri.scheme == 'file' || uri.scheme.startsWith('http')) {
           filePath = path.url.relative(uri.path,
               from: path.url.dirname(_root.uri.path));
diff --git a/lib/transformer.dart b/lib/transformer.dart
index 90faf5d..903abfa 100644
--- a/lib/transformer.dart
+++ b/lib/transformer.dart
@@ -239,11 +239,11 @@
     seen.add(library);
 
     // Visit all our dependencies.
-    for (var importedLibrary in _sortedLibraryImports(library)) {
+    for (var library in _sortedLibraryDependencies(library)) {
       // Don't include anything from the sdk.
-      if (importedLibrary.isInSdk) continue;
-      if (seen.contains(importedLibrary)) continue;
-      _readLibraries(importedLibrary, seen);
+      if (library.isInSdk) continue;
+      if (seen.contains(library)) continue;
+      _readLibraries(library, seen);
     }
 
     // Read annotations in this order: library, top level methods, classes.
@@ -415,14 +415,21 @@
     }
   }
 
-  Iterable<LibraryElement> _sortedLibraryImports(LibraryElement library) =>
-      (new List.from(library.imports)
-    ..sort((ImportElement a, ImportElement b) {
+  Iterable<LibraryElement> _sortedLibraryDependencies(LibraryElement library) {
+    // TODO(jakemac): Investigate supporting annotations on part-of directives.
+    getLibrary(UriReferencedElement element) {
+      if (element is ImportElement) return element.importedLibrary;
+      if (element is ExportElement) return element.exportedLibrary;
+    }
+
+    return (new List.from(library.imports)
+      ..addAll(library.exports)
+      ..sort((a, b) {
       // dart: imports don't have a uri
       if (a.uri == null && b.uri != null) return -1;
       if (b.uri == null && a.uri != null) return 1;
       if (a.uri == null && b.uri == null) {
-        return a.importedLibrary.name.compareTo(b.importedLibrary.name);
+        return getLibrary(a).name.compareTo(getLibrary(b).name);
       }
 
       // package: imports next
@@ -442,7 +449,8 @@
       var bUri = path.url.relative(b.source.uri.path,
           from: path.url.dirname(library.source.uri.path));
       return aUri.compareTo(bUri);
-    })).map((import) => import.importedLibrary);
+    })).map(getLibrary);
+  }
 }
 
 /// An [Initializer] annotation and the target of that annotation.
diff --git a/pubspec.yaml b/pubspec.yaml
index 8305682..9e1cf9d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: initialize
-version: 0.5.1+1
+version: 0.5.1+2
 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/transformer_test.dart b/test/transformer_test.dart
index e90eefc..dea1545 100644
--- a/test/transformer_test.dart
+++ b/test/transformer_test.dart
@@ -193,6 +193,93 @@
         }
         ''')
   }, []);
+
+  testPhases('exported library annotations', phases, {
+    'a|web/index.dart': '''
+        library web_foo;
+
+        export 'foo.dart';
+        ''',
+    'a|web/foo.dart': '''
+        @constInit
+        library foo;
+
+        import 'package:test_initializers/common.dart';
+
+        @constInit
+        foo() {};
+
+        @constInit
+        class Foo {}
+        ''',
+    // Mock out the Initialize package plus some initializers.
+    'initialize|lib/initialize.dart': mockInitialize,
+    'test_initializers|lib/common.dart': commonInitializers,
+  }, {
+    'a|web/index.initialize.dart': formatter.format('''
+        import 'package:initialize/src/static_loader.dart';
+        import 'package:initialize/initialize.dart';
+        import 'index.dart' as i0;
+        import 'foo.dart' as i1;
+        import 'package:test_initializers/common.dart' as i2;
+
+        main() {
+          initializers.addAll([
+            new InitEntry(i2.constInit, const LibraryIdentifier(#foo, null, 'foo.dart')),
+            new InitEntry(i2.constInit, i1.foo),
+            new InitEntry(i2.constInit, i1.Foo),
+          ]);
+
+          i0.main();
+        }
+        ''')
+  }, []);
+
+  testPhases('imports from exported libraries', phases, {
+    'a|web/index.dart': '''
+        library web_foo;
+
+        export 'foo.dart';
+        ''',
+    'a|web/foo.dart': '''
+        library foo;
+
+        import 'bar.dart';
+        ''',
+    'a|web/bar.dart': '''
+        @constInit
+        library bar;
+
+        import 'package:test_initializers/common.dart';
+
+        @constInit
+        bar() {};
+
+        @constInit
+        class Bar {}
+        ''',
+    // Mock out the Initialize package plus some initializers.
+    'initialize|lib/initialize.dart': mockInitialize,
+    'test_initializers|lib/common.dart': commonInitializers,
+  }, {
+    'a|web/index.initialize.dart': formatter.format('''
+        import 'package:initialize/src/static_loader.dart';
+        import 'package:initialize/initialize.dart';
+        import 'index.dart' as i0;
+        import 'bar.dart' as i1;
+        import 'package:test_initializers/common.dart' as i2;
+
+        main() {
+          initializers.addAll([
+            new InitEntry(i2.constInit, const LibraryIdentifier(#bar, null, 'bar.dart')),
+            new InitEntry(i2.constInit, i1.bar),
+            new InitEntry(i2.constInit, i1.Bar),
+          ]);
+
+          i0.main();
+        }
+        ''')
+  }, []);
 }
 
 class SkipConstructorsPlugin extends InitializerPlugin {