Optimize peak heap usage (#1858)

* asynchronous PackageGraph construction and precaching

* checkpoint

* cleanup

* checkpoint

* Run tools more asynchronously

* Enhacements and dropping of Future.wait

* dartfmt

* Rate-limit the number of tools in flight

* checkpoint

* Workaround for duplicate ResovledLibraryResult objects

* Patch up merge problem and rebuild test package docs

* Use maps to avoid having to filter results

* dartfmt and review comments

* Update test package docs again (quotation marks different in stable branch)

* Clear pubspec overrides (this will only work in sdk-analyzer Travis)

* Change sdk test branch to master (analyzer-0.33 not integrated yet)

* Set correct analyzer version

* Eliminate initialization race where analyzer might not find all the files in time for empty packages

* Update analyzer requirement

* Fix merge error

* Optimize peak heap usage

* Fix bug in library count

* Fix embedder and refactor file generation

* Review comments
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index 1bdbb6c..8e6d186 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -115,7 +115,7 @@
 
     seconds = _stopwatch.elapsedMilliseconds / 1000.0;
     logInfo(
-        "Documented ${packageGraph.publicLibraries.length} public librar${packageGraph.publicLibraries.length == 1 ? 'y' : 'ies'} "
+        "Documented ${packageGraph.localPublicLibraries.length} public librar${packageGraph.localPublicLibraries.length == 1 ? 'y' : 'ies'} "
         "in ${seconds.toStringAsFixed(1)} seconds");
     return new DartdocResults(
         config.topLevelPackageMeta, packageGraph, outputDir);
@@ -123,7 +123,7 @@
 
   Future<DartdocResults> generateDocs() async {
     DartdocResults dartdocResults = await generateDocsBase();
-    if (dartdocResults.packageGraph.publicLibraries.isEmpty) {
+    if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) {
       throw new DartdocFailure(
           "dartdoc could not find any libraries to document");
     }
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 0232f3d..b9d7f61 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -17,10 +17,12 @@
         AnnotatedNode,
         AstNode,
         CommentReference,
+        CompilationUnit,
         Expression,
         InstanceCreationExpression;
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/dart/element/visitor.dart';
 import 'package:analyzer/file_system/file_system.dart' as fileSystem;
 import 'package:analyzer/file_system/physical_file_system.dart';
 import 'package:analyzer/src/source/package_map_resolver.dart';
@@ -439,7 +441,7 @@
     if (_sourceCode == null) {
       if (isSynthetic) {
         _sourceCode = packageGraph
-            ._getModelNodeFor((element as PropertyAccessorElement).variable, this.definingLibrary)
+            ._getModelNodeFor((element as PropertyAccessorElement).variable)
             .sourceCode;
       } else {
         _sourceCode = super.sourceCode;
@@ -535,7 +537,7 @@
 /// Implements the Dart 2.1 "mixin" style of mixin declarations.
 class Mixin extends Class {
   Mixin(ClassElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph) {}
+      : super(element, library, packageGraph);
 
   @override
   bool get isAbstract => false;
@@ -1430,19 +1432,22 @@
   final Element staticElement;
   ModelCommentReference(CommentReference ref)
       : name = ref.identifier.name,
-        staticElement = ref.identifier.staticElement {}
+        staticElement = ref.identifier.staticElement;
 }
 
 /// Stripped down information derived from [AstNode] containing only information
 /// needed for Dartdoc.  Drops link to the [AstNode] after construction.
 class ModelNode {
   final List<ModelCommentReference> commentRefs;
-  final String sourceCode;
   final Element element;
 
+  final int _sourceOffset;
+  final int _sourceEnd;
+
   ModelNode(AstNode sourceNode, this.element)
-      : sourceCode = _sourceCodeFor(sourceNode, element),
-        commentRefs = _commentRefsFor(sourceNode) {}
+      : _sourceOffset = sourceNode?.offset,
+        _sourceEnd = sourceNode?.end,
+        commentRefs = _commentRefsFor(sourceNode);
 
   static List<ModelCommentReference> _commentRefsFor(AstNode node) {
     if (node is AnnotatedNode &&
@@ -1454,11 +1459,11 @@
     return null;
   }
 
-  static String _sourceCodeFor(AstNode node, Element element) {
+  String get sourceCode {
     String contents = getFileContentsFor(element);
-    if (node != null) {
+    if (_sourceOffset != null) {
       // Find the start of the line, so that we can line up all the indents.
-      int i = node.offset;
+      int i = _sourceOffset;
       while (i > 0) {
         i -= 1;
         if (contents[i] == '\n' || contents[i] == '\r') {
@@ -1468,8 +1473,8 @@
       }
 
       // Trim the common indent from the source snippet.
-      var start = node.offset - (node.offset - i);
-      String source = contents.substring(start, node.end);
+      var start = _sourceOffset - (_sourceOffset - i);
+      String source = contents.substring(start, _sourceEnd);
 
       source = const HtmlEscape().convert(source);
       source = stripIndentFromSource(source);
@@ -2060,8 +2065,41 @@
   Accessor get setter;
 }
 
+/// Find all hashable children of a given element that are defined in the
+/// [LibraryElement] given at initialization.
+class _HashableChildLibraryElementVisitor
+    extends GeneralizingElementVisitor<void> {
+  final void Function(Element) libraryProcessor;
+  _HashableChildLibraryElementVisitor(this.libraryProcessor);
+
+  @override
+  void visitElement(Element element) {
+    libraryProcessor(element);
+    super.visitElement(element);
+    return null;
+  }
+
+  @override
+  void visitExportElement(ExportElement element) {
+    // [ExportElement]s are not always hashable; skip them.
+    return null;
+  }
+
+  @override
+  void visitImportElement(ImportElement element) {
+    // [ImportElement]s are not always hashable; skip them.
+    return null;
+  }
+
+  @override
+  void visitParameterElement(ParameterElement element) {
+    // [ParameterElement]s without names do not provide sufficiently distinct
+    // hashes / comparison, so just skip them all. (dart-lang/sdk#30146)
+    return null;
+  }
+}
+
 class Library extends ModelElement with Categorization, TopLevelContainer {
-  final ResolvedLibraryResult libraryResult;
   List<TopLevelVariable> _variables;
   Namespace _exportedNamespace;
   String _name;
@@ -2070,9 +2108,19 @@
     return packageGraph.findButDoNotCreateLibraryFor(element);
   }
 
-  Library._(this.libraryResult, PackageGraph packageGraph, this._package)
+  Library._(ResolvedLibraryResult libraryResult, PackageGraph packageGraph,
+      this._package)
       : super(libraryResult.element, null, packageGraph, null) {
     if (element == null) throw new ArgumentError.notNull('element');
+
+    // Initialize [packageGraph]'s cache of ModelNodes for relevant
+    // elements in this library.
+    Map<String, CompilationUnit> _compilationUnitMap = new Map();
+    _compilationUnitMap.addEntries(libraryResult.units
+        .map((ResolvedUnitResult u) => new MapEntry(u.path, u.unit)));
+    _HashableChildLibraryElementVisitor((Element e) =>
+            packageGraph._populateModelNodeFor(e, _compilationUnitMap))
+        .visitElement(element);
     _exportedNamespace =
         new NamespaceBuilder().createExportNamespaceForLibrary(element);
     _package._allLibraries.add(this);
@@ -2897,7 +2945,7 @@
   // TODO(jcollins-g): make _originalMember optional after dart-lang/sdk#15101
   // is fixed.
   ModelElement(
-      this._element, this._library, this._packageGraph, this._originalMember) {}
+      this._element, this._library, this._packageGraph, this._originalMember);
 
   factory ModelElement.fromElement(Element e, PackageGraph p) {
     Library lib = p.findButDoNotCreateLibraryFor(e);
@@ -3105,7 +3153,7 @@
   ModelNode _modelNode;
   @override
   ModelNode get modelNode =>
-      _modelNode ??= packageGraph._getModelNodeFor(element, definingLibrary);
+      _modelNode ??= packageGraph._getModelNodeFor(element);
 
   List<String> get annotations => annotationsFromMetadata(element.metadata);
 
@@ -4487,7 +4535,7 @@
 class ModelFunctionAnonymous extends ModelFunctionTyped {
   ModelFunctionAnonymous(
       FunctionTypedElement element, PackageGraph packageGraph)
-      : super(element, null, packageGraph) {}
+      : super(element, null, packageGraph);
 
   @override
   ModelElement get enclosingElement {
@@ -4653,73 +4701,73 @@
 }
 
 class PackageGraph {
-  // TODO(jcollins-g): This constructor is convoluted.  Clean this up by
-  // building Libraries and adding them to Packages, then adding Packages
-  // to this graph.
-
-  PackageGraph._(this.config, this.packageMeta, this._packageWarningOptions,
-      this.driver, this.sdk) : session = driver.currentSession {}
-
-  static Future<PackageGraph> setUpPackageGraph(
-      Iterable<ResolvedLibraryResult> libraryResults,
-      Iterable<ResolvedLibraryResult> specialLibraryResults,
-      DartdocOptionContext config,
-      PackageMeta packageMeta,
-      packageWarningOptions,
-      driver,
-      sdk,) async {
-    PackageGraph newGraph =
-        PackageGraph._(config, packageMeta, packageWarningOptions, driver, sdk);
-    assert(newGraph._allConstructedModelElements.isEmpty);
-    assert(newGraph.allLibraries.isEmpty);
-    newGraph._packageWarningCounter =
-        new PackageWarningCounter(newGraph._packageWarningOptions);
-
-    // Build [Library] objects, and link them to [Package]s.
-    libraryResults.forEach((result) {
-      LibraryElement element = result.element;
-      var packageMeta = new PackageMeta.fromElement(element, config);
-      var lib = new Library._(result, newGraph,
-          new Package.fromPackageMeta(packageMeta, newGraph));
-      newGraph.packageMap[packageMeta.name]._libraries.add(lib);
-      newGraph.allLibraries[element] = lib;
-    });
-
+  PackageGraph.UninitializedPackageGraph(
+      this.config,
+      PackageWarningOptions packageWarningOptions,
+      this.driver,
+      this.sdk,
+      this.hasEmbedderSdk)
+      : packageMeta = config.topLevelPackageMeta,
+        session = driver.currentSession,
+        _packageWarningCounter =
+            new PackageWarningCounter(packageWarningOptions) {
     // Make sure the default package exists, even if it has no libraries.
     // This can happen for packages that only contain embedder SDKs.
-    new Package.fromPackageMeta(packageMeta, newGraph);
-    newGraph.allLibrariesAdded = true;
+    new Package.fromPackageMeta(packageMeta, this);
+  }
 
-    // [findOrCreateLibraryFor] already adds to the proper structures.
-    for (ResolvedLibraryResult result in specialLibraryResults) {
-      await newGraph.findOrCreateLibraryFor(result.element);
-    }
+  /// Call during initialization to add a library to this [PackageGraph].
+  ///
+  /// Libraries added in this manner are assumed to be part of documented
+  /// packages, even if includes or embedder.yaml files cause these to
+  /// span packages.
+  void addLibraryToGraph(ResolvedLibraryResult result) {
+    assert(!allLibrariesAdded);
+    LibraryElement element = result.element;
+    var packageMeta = new PackageMeta.fromElement(element, config);
+    var lib = new Library._(
+        result, this, new Package.fromPackageMeta(packageMeta, this));
+    packageMap[packageMeta.name]._libraries.add(lib);
+    allLibraries[element] = lib;
+  }
 
+  /// Call during initialization to add a library possibly containing
+  /// special/non-documented elements to this [PackageGraph].  Must be called
+  /// after any normal libraries.
+  void addSpecialLibraryToGraph(ResolvedLibraryResult result) {
+    allLibrariesAdded = true;
+    assert(!_localDocumentationBuilt);
+    findOrCreateLibraryFor(result);
+  }
+
+  /// Call after all libraries are added.
+  Future<void> initializePackageGraph() async {
+    allLibrariesAdded = true;
+    assert(!_localDocumentationBuilt);
     // From here on in, we might find special objects.  Initialize the
     // specialClasses handler so when we find them, they get added.
-    newGraph.specialClasses = new SpecialClasses();
+    specialClasses = new SpecialClasses();
     // Go through docs of every ModelElement in package to pre-build the macros
     // index.
-    List<Future> precacheFutures = newGraph.precacheLocalDocs().toList();
+    List<Future> precacheFutures = precacheLocalDocs().toList();
     for (Future f in precacheFutures) await f;
-    newGraph._localDocumentationBuilt = true;
+    _localDocumentationBuilt = true;
 
     // Scan all model elements to insure that interceptor and other special
     // objects are found.
     // After the allModelElements traversal to be sure that all packages
     // are picked up.
-    newGraph.documentedPackages.toList().forEach((package) {
+    documentedPackages.toList().forEach((package) {
       package._libraries.sort((a, b) => compareNatural(a.name, b.name));
       package._libraries.forEach((library) {
-        library._allClasses.forEach(newGraph._addToImplementors);
+        library._allClasses.forEach(_addToImplementors);
       });
     });
-    newGraph._implementors.values.forEach((l) => l.sort());
-    newGraph.allImplementorsAdded = true;
+    _implementors.values.forEach((l) => l.sort());
+    allImplementorsAdded = true;
 
     // We should have found all special classes by now.
-    newGraph.specialClasses.assertSpecials();
-    return newGraph;
+    specialClasses.assertSpecials();
   }
 
   /// Generate a list of futures for any docs that actually require precaching.
@@ -4735,12 +4783,14 @@
   // Many ModelElements have the same ModelNode; don't build/cache this data more
   // than once for them.
   final Map<Element, ModelNode> _modelNodes = Map();
-  ModelNode _getModelNodeFor(element, Library definingLibrary) {
-    _modelNodes.putIfAbsent(
-        element, () => ModelNode(definingLibrary.libraryResult.getElementDeclaration(element)?.node , element));
-    return _modelNodes[element];
+  void _populateModelNodeFor(
+      Element element, Map<String, CompilationUnit> compilationUnitMap) {
+    _modelNodes.putIfAbsent(element,
+        () => ModelNode(getAstNode(element, compilationUnitMap), element));
   }
 
+  ModelNode _getModelNodeFor(Element element) => _modelNodes[element];
+
   SpecialClasses specialClasses;
 
   /// It is safe to cache values derived from the _implementors table if this
@@ -4774,8 +4824,7 @@
   // All library objects related to this package; a superset of _libraries.
   final Map<LibraryElement, Library> allLibraries = new Map();
 
-  /// Objects to keep track of warnings.
-  final PackageWarningOptions _packageWarningOptions;
+  /// Keep track of warnings
   PackageWarningCounter _packageWarningCounter;
 
   /// All ModelElements constructed for this package; a superset of [allModelElements].
@@ -4806,6 +4855,8 @@
     return _defaultPackage;
   }
 
+  final bool hasEmbedderSdk;
+
   PackageGraph get packageGraph => this;
 
   /// Map of package name to Package.
@@ -5417,24 +5468,22 @@
   /// This is used when we might need a Library object that isn't actually
   /// a documentation entry point (for elements that have no Library within the
   /// set of canonical Libraries).
-  Future<Library> findOrCreateLibraryFor(Element e) async {
+  Library findOrCreateLibraryFor(ResolvedLibraryResult result) {
     // This is just a cache to avoid creating lots of libraries over and over.
-    if (allLibraries.containsKey(e.library)) {
-      return allLibraries[e.library];
+    if (allLibraries.containsKey(result.element.library)) {
+      return allLibraries[result.element.library];
     }
     // can be null if e is for dynamic
-    if (e.library == null) {
+    if (result.element.library == null) {
       return null;
     }
-
-    ResolvedLibraryResult result =
-        await session.getResolvedLibraryByElement(e.library);
     Library foundLibrary = new Library._(
         result,
         this,
         new Package.fromPackageMeta(
-            new PackageMeta.fromElement(e.library, config), packageGraph));
-    allLibraries[e.library] = foundLibrary;
+            new PackageMeta.fromElement(result.element.library, config),
+            packageGraph));
+    allLibraries[result.element.library] = foundLibrary;
     return foundLibrary;
   }
 
@@ -5829,7 +5878,6 @@
     implements Privacy, Documentable {
   String _name;
   PackageGraph _packageGraph;
-  final _isLocal;
 
   final Map<String, Category> _nameToCategory = {};
 
@@ -5837,15 +5885,13 @@
   factory Package.fromPackageMeta(
       PackageMeta packageMeta, PackageGraph packageGraph) {
     String packageName = packageMeta.name;
-    bool isLocal = packageMeta == packageGraph.packageMeta ||
-        packageGraph.config.autoIncludeDependencies;
-    isLocal = isLocal && !packageGraph.config.isPackageExcluded(packageName);
+
     bool expectNonLocal = false;
 
     if (!packageGraph.packageMap.containsKey(packageName) &&
         packageGraph.allLibrariesAdded) expectNonLocal = true;
     packageGraph.packageMap.putIfAbsent(packageName,
-        () => new Package._(packageName, packageGraph, packageMeta, isLocal));
+        () => new Package._(packageName, packageGraph, packageMeta));
     // Verify that we don't somehow decide to document locally a package picked
     // up after all documented libraries are added, because that breaks the
     // assumption that we've picked up all documented libraries and packages
@@ -5858,7 +5904,7 @@
     return packageGraph.packageMap[packageName];
   }
 
-  Package._(this._name, this._packageGraph, this._packageMeta, this._isLocal);
+  Package._(this._name, this._packageGraph, this._packageMeta);
   @override
   bool get isCanonical => true;
   @override
@@ -5935,9 +5981,20 @@
     return _isPublic;
   }
 
-  /// Returns true if this package is being documented locally.  If it isn't
-  /// documented locally, it still might be documented remotely; see documentedWhere.
-  bool get isLocal => _isLocal;
+  bool _isLocal;
+
+  /// Return true if this is the default package, this is part of an embedder SDK,
+  /// or if [config.autoIncludeDependencies] is true -- but only if the package
+  /// was not excluded on the command line.
+  bool get isLocal {
+    if (_isLocal == null) {
+      _isLocal = (packageMeta == packageGraph.packageMeta ||
+              packageGraph.hasEmbedderSdk && packageMeta.isSdk ||
+              packageGraph.config.autoIncludeDependencies) &&
+          !packageGraph.config.isPackageExcluded(name);
+    }
+    return _isLocal;
+  }
 
   DocumentLocation get documentedWhere {
     if (isLocal) {
@@ -6180,7 +6237,8 @@
   Library get library;
 
   String _sourceCode;
-  String get sourceCode => _sourceCode ??= modelNode.sourceCode;
+  String get sourceCode =>
+      _sourceCode ??= modelNode == null ? '' : modelNode.sourceCode;
 }
 
 abstract class TypeParameters implements ModelElement {
@@ -6407,23 +6465,16 @@
 
   PackageBuilder(this.config);
 
-  Future<void> logAnalysisErrors(Set<Source> sources) async {}
-
   Future<PackageGraph> buildPackageGraph() async {
-    PackageMeta packageMeta = config.topLevelPackageMeta;
-    if (packageMeta.needsPubGet) {
-      packageMeta.runPubGet();
+    if (config.topLevelPackageMeta.needsPubGet) {
+      config.topLevelPackageMeta.runPubGet();
     }
-    Map<LibraryElement, ResolvedLibraryResult> libraryResults = new Map();
-    Map<LibraryElement, ResolvedLibraryResult> specialLibraryResults = new Map();
-    DartSdk findSpecialsSdk = sdk;
-    if (embedderSdk != null && embedderSdk.urlMappings.isNotEmpty) {
-      findSpecialsSdk = embedderSdk;
-    }
-    await getLibraries(libraryResults, specialLibraryResults, getFiles,
-        specialLibraryFiles(findSpecialsSdk).toSet());
-    return await PackageGraph.setUpPackageGraph(libraryResults.values, specialLibraryResults.values,
-        config, config.topLevelPackageMeta, getWarningOptions(), driver, sdk);
+
+    PackageGraph newGraph = new PackageGraph.UninitializedPackageGraph(
+        config, getWarningOptions(), driver, sdk, hasEmbedderSdkFiles);
+    await getLibraries(newGraph);
+    await newGraph.initializePackageGraph();
+    return newGraph;
   }
 
   DartSdk _sdk;
@@ -6590,7 +6641,6 @@
       name = name.substring(directoryCurrentPath.length);
       if (name.startsWith(Platform.pathSeparator)) name = name.substring(1);
     }
-    logInfo('parsing ${name}...');
     JavaFile javaFile = new JavaFile(filePath).getAbsoluteFile();
     Source source = new FileBasedSource(javaFile);
 
@@ -6623,14 +6673,34 @@
     return metas;
   }
 
-  Future<Map<LibraryElement, ResolvedLibraryResult>> _parseLibraries(Set<String> files) async {
-    Map<LibraryElement, ResolvedLibraryResult> libraries = new Map();
+  /// Parse libraries with the analyzer and invoke a callback with the
+  /// result.
+  ///
+  /// Uses the [libraries] parameter to prevent calling
+  /// the callback more than once with the same [LibraryElement].
+  /// Adds [LibraryElement]s found to that parameter.
+  Future<void> _parseLibraries(
+      void Function(ResolvedLibraryResult) libraryAdder,
+      Set<LibraryElement> libraries,
+      Set<String> files,
+      [bool Function(LibraryElement) isLibraryIncluded]) async {
+    isLibraryIncluded ??= (_) => true;
     Set<PackageMeta> lastPass = new Set();
     Set<PackageMeta> current;
     do {
       lastPass = _packageMetasForFiles(files);
-      for (ResolvedLibraryResult r in (await Future.wait(files.map((f) => processLibrary(f)))).where((ResolvedLibraryResult l) => l != null)) {
-        libraries[r.element] = r;
+
+      // Be careful here not to accidentally stack up multiple
+      // ResolvedLibraryResults, as those eat our heap.
+      for (String f in files) {
+        ResolvedLibraryResult r = await processLibrary(f);
+        if (r != null &&
+            !libraries.contains(r.element) &&
+            isLibraryIncluded(r.element)) {
+          logInfo('parsing ${f}...');
+          libraryAdder(r);
+          libraries.add(r.element);
+        }
       }
 
       // Be sure to give the analyzer enough time to find all the files.
@@ -6652,7 +6722,6 @@
         }
       }
     } while (!lastPass.containsAll(current));
-    return libraries;
   }
 
   /// Given a package name, explore the directory and pull out all top level
@@ -6709,48 +6778,65 @@
   /// file might be part of a [DartdocOptionContext], and loads those
   /// objects to find any [DartdocOptionContext.includeExternal] configurations
   /// therein.
-  Iterable<String> _includeExternalsFrom(Iterable<String> files) {
-    Set<String> includeExternalsFound = new Set();
+  Iterable<String> _includeExternalsFrom(Iterable<String> files) sync* {
     for (String file in files) {
       DartdocOptionContext fileContext =
           new DartdocOptionContext.fromContext(config, new File(file));
       if (fileContext.includeExternal != null) {
-        includeExternalsFound.addAll(fileContext.includeExternal);
+        yield* fileContext.includeExternal;
       }
     }
-    return includeExternalsFound;
   }
 
-  Set<String> get getFiles {
-    Set<String> files = new Set();
-    files.addAll(config.topLevelPackageMeta.isSdk
-        ? new Set()
-        : findFilesToDocumentInPackage(
-            config.inputDir, config.autoIncludeDependencies));
+  Set<String> getFiles() {
+    Iterable<String> files;
     if (config.topLevelPackageMeta.isSdk) {
-      files.addAll(getSdkFilesToDocument());
-    } else if (embedderSdk.urlMappings.isNotEmpty &&
-        !config.topLevelPackageMeta.isSdk) {
-      embedderSdk.urlMappings.keys.forEach((String dartUri) {
-        Source source = embedderSdk.mapDartUri(dartUri);
-        files.add(source.fullName);
-      });
+      files = getSdkFilesToDocument();
+    } else {
+      files = findFilesToDocumentInPackage(
+          config.inputDir, config.autoIncludeDependencies);
     }
-
-    files.addAll(_includeExternalsFrom(files));
+    files = quiverIterables.concat([files, _includeExternalsFrom(files)]);
     return new Set.from(files.map((s) => new File(s).absolute.path));
   }
 
-  Future<void> getLibraries(
-      Map<LibraryElement, ResolvedLibraryResult> libraryResults,
-      Map<LibraryElement, ResolvedLibraryResult> specialLibraryResults,
-      Set<String> files,
-      Set<String> specialFiles) async {
-    libraryResults.addAll(await _parseLibraries(files));
-    specialLibraryResults
-        .addAll(await _parseLibraries(specialFiles.difference(files)));
+  Iterable<String> getEmbedderSdkFiles() sync* {
+    if (embedderSdk != null &&
+        embedderSdk.urlMappings.isNotEmpty &&
+        !config.topLevelPackageMeta.isSdk) {
+      for (String dartUri in embedderSdk.urlMappings.keys) {
+        Source source = embedderSdk.mapDartUri(dartUri);
+        yield (new File(source.fullName)).absolute.path;
+      }
+    }
+  }
+
+  bool get hasEmbedderSdkFiles =>
+      embedderSdk != null && getEmbedderSdkFiles().isNotEmpty;
+
+  Future<void> getLibraries(PackageGraph uninitializedPackageGraph) async {
+    DartSdk findSpecialsSdk = sdk;
+    if (embedderSdk != null && embedderSdk.urlMappings.isNotEmpty) {
+      findSpecialsSdk = embedderSdk;
+    }
+    Set<String> files = getFiles()..addAll(getEmbedderSdkFiles());
+    Set<String> specialFiles = specialLibraryFiles(findSpecialsSdk).toSet();
+
+    /// Returns true if this library element should be included according
+    /// to the configuration.
+    bool isLibraryIncluded(LibraryElement libraryElement) {
+      if (config.include.isNotEmpty &&
+          !config.include.contains(libraryElement.name)) {
+        return false;
+      }
+      return true;
+    }
+
+    Set<LibraryElement> foundLibraries = new Set();
+    await _parseLibraries(uninitializedPackageGraph.addLibraryToGraph,
+        foundLibraries, files, isLibraryIncluded);
     if (config.include.isNotEmpty) {
-      Iterable knownLibraryNames = libraryResults.values.map((l) => l.element.name);
+      Iterable knownLibraryNames = foundLibraries.map((l) => l.name);
       Set notFound = new Set.from(config.include)
           .difference(new Set.from(knownLibraryNames))
           .difference(new Set.from(config.exclude));
@@ -6758,9 +6844,10 @@
         throw 'Did not find: [${notFound.join(', ')}] in '
             'known libraries: [${knownLibraryNames.join(', ')}]';
       }
-      libraryResults.removeWhere(
-          (element, result) => !config.include.contains(result.element.name));
     }
+    // Include directive does not apply to special libraries.
+    await _parseLibraries(uninitializedPackageGraph.addSpecialLibraryToGraph,
+        foundLibraries, specialFiles.difference(files));
   }
 
   /// If [dir] contains both a `lib` directory and a `pubspec.yaml` file treat
diff --git a/lib/src/model_utils.dart b/lib/src/model_utils.dart
index 52964d3..984180b 100644
--- a/lib/src/model_utils.dart
+++ b/lib/src/model_utils.dart
@@ -7,7 +7,9 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/generated/source_io.dart';
@@ -15,6 +17,24 @@
 
 final Map<String, String> _fileContents = <String, String>{};
 
+/// Returns the [AstNode] for a given [Element].
+///
+/// Uses a precomputed map of [element.source.fullName] to [CompilationUnit]
+/// to avoid linear traversal in [ResolvedLibraryElementImpl.getElementDeclaration].
+AstNode getAstNode(
+    Element element, Map<String, CompilationUnit> compilationUnitMap) {
+  if (element?.source?.fullName != null &&
+      !element.isSynthetic &&
+      element.nameOffset != -1) {
+    CompilationUnit unit = compilationUnitMap[element.source.fullName];
+    if (unit != null) {
+      var locator = new NodeLocator2(element.nameOffset);
+      return (locator.searchWithin(unit)?.parent);
+    }
+  }
+  return null;
+}
+
 /// Remove elements that aren't documented.
 Iterable<T> filterNonDocumented<T extends Documentable>(
     Iterable<T> maybeDocumentedItems) {
@@ -131,4 +151,4 @@
     line = line.trimRight();
     return line.startsWith(indent) ? line.substring(indent.length) : line;
   }).join('\n');
-}
\ No newline at end of file
+}