Library loading refactor, drop analysis error checking (#1845)

* Cleanup library creation pipeline

* Library loading doesn't need to throw analysis errors, just drop that problematic feature

* Crush findLibrary down some more

* Disable test that verifies we stop on analysis errors

* Performance changes

* dartfmt

* Review comments and lint
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index ae313b5..1bdbb6c 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -12,11 +12,6 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:analyzer/dart/analysis/results.dart';
-import 'package:analyzer/error/error.dart';
-import 'package:analyzer/src/error/codes.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/source.dart';
 import 'package:dartdoc/src/dartdoc_options.dart';
 import 'package:dartdoc/src/generator.dart';
 import 'package:dartdoc/src/html/html_generator.dart';
@@ -81,54 +76,6 @@
 
   Stream<String> get onCheckProgress => _onCheckProgress.stream;
 
-  @override
-  Future<void> logAnalysisErrors(Set<Source> sources) async {
-    List<AnalysisErrorInfo> errorInfos = [];
-    // TODO(jcollins-g): figure out why sources can't contain includeExternals
-    // or embedded SDK components without having spurious(?) analysis errors.
-    // That seems wrong. dart-lang/dartdoc#1547
-    for (Source source in sources) {
-      ErrorsResult errorsResult = await driver.getErrors(source.fullName);
-      AnalysisErrorInfo info =
-          new AnalysisErrorInfoImpl(errorsResult.errors, errorsResult.lineInfo);
-      List<_Error> errors = [info]
-          .expand((AnalysisErrorInfo info) {
-            return info.errors.map((error) => new _Error(
-                error, info.lineInfo, config.topLevelPackageMeta.dir.path));
-          })
-          .where((_Error error) => error.isError)
-          .toList()
-            ..sort();
-      // TODO(jcollins-g): Why does the SDK have analysis errors?  Annotations
-      // seem correctly formed.  dart-lang/dartdoc#1547
-      if (errors.isNotEmpty && !source.uri.toString().startsWith('dart:')) {
-        errorInfos.add(info);
-        logWarning(
-            'analysis errors from source: ${source.uri.toString()} (${source.toString()}');
-        errors.forEach(logWarning);
-      }
-    }
-
-    List<_Error> errors = errorInfos
-        .expand((AnalysisErrorInfo info) {
-          return info.errors.map((error) => new _Error(
-              error, info.lineInfo, config.topLevelPackageMeta.dir.path));
-        })
-        .where((_Error error) => error.isError)
-        // TODO(jcollins-g): remove after conversion to analysis driver
-        .where((_Error error) =>
-            error.error.errorCode !=
-            CompileTimeErrorCode.URI_HAS_NOT_BEEN_GENERATED)
-        .toList()
-          ..sort();
-
-    if (errors.isNotEmpty) {
-      int len = errors.length;
-      throw new DartdocFailure(
-          "encountered ${len} analysis error${len == 1 ? '' : 's'}");
-    }
-  }
-
   PackageGraph packageGraph;
 
   /// Generate Dartdoc documentation.
@@ -434,39 +381,3 @@
 
   DartdocResults(this.packageMeta, this.packageGraph, this.outDir);
 }
-
-class _Error implements Comparable<_Error> {
-  final AnalysisError error;
-  final LineInfo lineInfo;
-  final String projectPath;
-
-  _Error(this.error, this.lineInfo, this.projectPath);
-
-  String get description => '${error.message} at ${location}, line ${line}.';
-  bool get isError => error.errorCode.errorSeverity == ErrorSeverity.ERROR;
-  int get line => lineInfo.getLocation(error.offset).lineNumber;
-  String get location {
-    String path = error.source.fullName;
-    if (path.startsWith(projectPath)) {
-      path = path.substring(projectPath.length + 1);
-    }
-    return path;
-  }
-
-  int get severity => error.errorCode.errorSeverity.ordinal;
-
-  String get severityName => error.errorCode.errorSeverity.displayName;
-
-  @override
-  int compareTo(_Error other) {
-    if (severity == other.severity) {
-      int cmp = error.source.fullName.compareTo(other.error.source.fullName);
-      return cmp == 0 ? line - other.line : cmp;
-    } else {
-      return other.severity - severity;
-    }
-  }
-
-  @override
-  String toString() => '[${severityName}] ${description}';
-}
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 40c73d0..86f0137 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -61,6 +61,7 @@
 import 'package:path/path.dart' as pathLib;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:package_config/discovery.dart' as package_config;
+import 'package:quiver/iterables.dart' as quiverIterables;
 
 int byName(Nameable a, Nameable b) =>
     compareAsciiLowerCaseNatural(a.name, b.name);
@@ -753,21 +754,24 @@
     return member;
   }
 
-  final Set<ModelElement> _allModelElements = new Set();
+  List<ModelElement> _allModelElements;
   List<ModelElement> get allModelElements {
-    if (_allModelElements.isEmpty) {
-      _allModelElements
-        ..addAll(allInstanceMethods)
-        ..addAll(allInstanceProperties)
-        ..addAll(allAccessors)
-        ..addAll(allOperators)
-        ..addAll(constants)
-        ..addAll(constructors)
-        ..addAll(staticMethods)
-        ..addAll(staticProperties)
-        ..addAll(typeParameters);
+    if (_allModelElements == null) {
+      _allModelElements = new List.from(
+          quiverIterables.concat([
+            allInstanceMethods,
+            allInstanceProperties,
+            allAccessors,
+            allOperators,
+            constants,
+            constructors,
+            staticMethods,
+            staticProperties,
+            typeParameters,
+          ]),
+          growable: false);
     }
-    return _allModelElements.toList();
+    return _allModelElements;
   }
 
   List<ModelElement> _allCanonicalModelElements;
@@ -1110,14 +1114,12 @@
   List<ExecutableElement> __inheritedElements;
   List<ExecutableElement> get _inheritedElements {
     if (__inheritedElements == null) {
-      __inheritedElements = [];
       Map<String, ExecutableElement> cmap = definingLibrary.inheritanceManager
           .getMembersInheritedFromClasses(element);
       Map<String, ExecutableElement> imap = definingLibrary.inheritanceManager
           .getMembersInheritedFromInterfaces(element);
-      __inheritedElements.addAll(cmap.values);
-      __inheritedElements
-          .addAll(imap.values.where((e) => !cmap.containsKey(e.name)));
+      __inheritedElements = new List.from(cmap.values)
+        ..addAll(imap.values.where((e) => !cmap.containsKey(e.name)));
     }
     return __inheritedElements;
   }
@@ -1578,14 +1580,18 @@
   Enum(ClassElement element, Library library, PackageGraph packageGraph)
       : super(element, library, packageGraph);
 
+  List<EnumField> _instanceProperties;
   @override
   List<EnumField> get instanceProperties {
-    return super
-        .instanceProperties
-        .map((Field p) => new ModelElement.from(
-            p.element, p.library, p.packageGraph,
-            getter: p.getter, setter: p.setter) as EnumField)
-        .toList(growable: false);
+    if (_instanceProperties == null) {
+      _instanceProperties = super
+          .instanceProperties
+          .map((Field p) => new ModelElement.from(
+              p.element, p.library, p.packageGraph,
+              getter: p.getter, setter: p.setter) as EnumField)
+          .toList(growable: false);
+    }
+    return _instanceProperties;
   }
 
   @override
@@ -2585,28 +2591,30 @@
   Map<Element, Set<ModelElement>> _modelElementsMap;
   Map<Element, Set<ModelElement>> get modelElementsMap {
     if (_modelElementsMap == null) {
-      final Set<ModelElement> results = new Set();
-      results
-        ..addAll(library.constants)
-        ..addAll(library.functions)
-        ..addAll(library.properties)
-        ..addAll(library.typedefs);
-
-      library.allClasses.forEach((c) {
-        results.add(c);
-        results.addAll(c.allModelElements);
-      });
-
-      library.enums.forEach((e) {
-        results.add(e);
-        results.addAll(e.allModelElements);
-      });
-
-      library.mixins.forEach((m) {
-        results.add(m);
-        results.addAll(m.allModelElements);
-      });
-
+      Iterable<ModelElement> results = quiverIterables.concat([
+        library.constants,
+        library.functions,
+        library.properties,
+        library.typedefs,
+        library.allClasses.expand((c) {
+          return quiverIterables.concat([
+            [c],
+            c.allModelElements
+          ]);
+        }),
+        library.enums.expand((e) {
+          return quiverIterables.concat([
+            [e],
+            e.allModelElements
+          ]);
+        }),
+        library.mixins.expand((m) {
+          return quiverIterables.concat([
+            [m],
+            m.allModelElements
+          ]);
+        }),
+      ]);
       _modelElementsMap = new Map<Element, Set<ModelElement>>();
       results.forEach((modelElement) {
         _modelElementsMap.putIfAbsent(modelElement.element, () => new Set());
@@ -2885,7 +2893,7 @@
       this._element, this._library, this._packageGraph, this._originalMember) {}
 
   factory ModelElement.fromElement(Element e, PackageGraph p) {
-    Library lib = _findOrCreateEnclosingLibraryForStatic(e, p);
+    Library lib = p.findOrCreateLibraryFor(e);
     Accessor getter;
     Accessor setter;
     if (e is PropertyInducingElement) {
@@ -3900,22 +3908,6 @@
     return '<a${classContent} href="${href}">$name</a>';
   }
 
-  // This differs from package.findOrCreateLibraryFor in a small way,
-  // searching for the [Library] associated with this element's enclosing
-  // Library before trying to create one.
-  static Library _findOrCreateEnclosingLibraryForStatic(
-      Element e, PackageGraph packageGraph) {
-    var element = e.getAncestor((l) => l is LibraryElement);
-    var lib;
-    if (element != null) {
-      lib = packageGraph.findLibraryFor(element);
-    }
-    if (lib == null) {
-      lib = packageGraph.findOrCreateLibraryFor(e);
-    }
-    return lib;
-  }
-
   /// Replace &#123;@example ...&#125; in API comments with the content of named file.
   ///
   /// Syntax:
@@ -4649,8 +4641,6 @@
           element, this, new Package.fromPackageMeta(packageMeta, this));
       packageMap[packageMeta.name]._libraries.add(lib);
       allLibraries[element] = lib;
-      assert(!_elementToLibrary.containsKey(lib.element));
-      _elementToLibrary[element] = lib;
     });
 
     // Make sure the default package exists, even if it has no libraries.
@@ -4793,7 +4783,6 @@
     return _sdkLibrarySources;
   }
 
-  final Map<Element, Library> _elementToLibrary = {};
   final Map<String, String> _macros = {};
   final Map<String, String> _htmlFragments = {};
   bool allLibrariesAdded = false;
@@ -5206,27 +5195,6 @@
     return _invisibleAnnotations;
   }
 
-  /// Looks up some [Library] that is reexporting this [Element]; not
-  /// necessarily the canonical [Library].
-  Library findLibraryFor(Element element) {
-    // Maybe we were given an element we already saw, or an element for the
-    // Library itself added by the constructor in [ModelElement.from].
-    if (_elementToLibrary.containsKey(element)) {
-      return _elementToLibrary[element];
-    }
-    Library foundLibrary;
-    if (libraryElementReexportedBy.containsKey(element.library)) {
-      Set<Library> exportedIn = libraryElementReexportedBy[element.library];
-      foundLibrary = exportedIn.firstWhere(
-          (l) =>
-              l.element.location.components[0] ==
-              element.library.location.components[0],
-          orElse: () => null);
-    }
-    if (foundLibrary != null) _elementToLibrary[element] = foundLibrary;
-    return foundLibrary;
-  }
-
   @override
   String toString() => 'PackageGraph built from ${defaultPackage.name}';
 
@@ -5403,16 +5371,13 @@
     if (e.library == null) {
       return null;
     }
-    Library foundLibrary = findLibraryFor(e);
 
-    if (foundLibrary == null) {
-      foundLibrary = new Library._(
-          e.library,
-          this,
-          new Package.fromPackageMeta(
-              new PackageMeta.fromElement(e.library, config), packageGraph));
-      allLibraries[e.library] = foundLibrary;
-    }
+    Library foundLibrary = new Library._(
+        e.library,
+        this,
+        new Package.fromPackageMeta(
+            new PackageMeta.fromElement(e.library, config), packageGraph));
+    allLibraries[e.library] = foundLibrary;
     return foundLibrary;
   }
 
@@ -6641,13 +6606,12 @@
   }
 
   /// Parse a single library at [filePath] using the current analysis driver.
-  /// Note: [libraries] and [sources] are output parameters.  Adds a libraryElement
-  /// only if it has a non-private name.
-  Future processLibrary(String filePath, Set<LibraryElement> libraries,
-      Set<Source> sources) async {
+  /// If [filePath] is not a library, returns null.
+  Future<LibraryElement> processLibrary(String filePath) async {
     String name = filePath;
-    if (name.startsWith(Directory.current.path)) {
-      name = name.substring(Directory.current.path.length);
+
+    if (name.startsWith(directoryCurrentPath)) {
+      name = name.substring(directoryCurrentPath.length);
       if (name.startsWith(Platform.pathSeparator)) name = name.substring(1);
     }
     logInfo('parsing ${name}...');
@@ -6666,19 +6630,13 @@
         source = new FileBasedSource(javaFile, uri);
       }
     }
-    // TODO(jcollins-g): Excludes can match on uri or on name.  Fix that.
-    if (!config.isLibraryExcluded(source.uri.toString())) {
-      LibraryElement library =
-          await driver.getLibraryByUri(source.uri.toString());
-      if (library != null) {
-        if (!config.isLibraryExcluded(Library.getLibraryName(library)) &&
-            !config.excludePackages
-                .contains(new PackageMeta.fromElement(library, config)?.name)) {
-          libraries.add(library);
-          sources.add(source);
-        }
-      }
+    var sourceKind = await driver.getSourceKind(filePath);
+    if (sourceKind == SourceKind.LIBRARY) {
+      // Loading libraryElements from part files works, but is painfully slow
+      // and creates many duplicates.
+      return (await driver.getResult(filePath))?.libraryElement;
     }
+    return null;
   }
 
   Set<PackageMeta> _packageMetasForFiles(Iterable<String> files) {
@@ -6689,11 +6647,8 @@
     return metas;
   }
 
-  Future<List<LibraryElement>> _parseLibraries(Set<String> files,
-      {bool throwErrors = true}) async {
-    Set<LibraryElement> libraries = new Set();
-    Set<Source> originalSources;
-    Set<Source> sources = new Set<Source>();
+  Future<List<LibraryElement>> _parseLibraries(Set<String> files) async {
+    Iterable<LibraryElement> libraries = new Iterable.empty();
     Set<PackageMeta> lastPass = new Set();
     Set<PackageMeta> current;
     Set<String> addedFiles = new Set();
@@ -6702,12 +6657,14 @@
       files.difference(addedFiles).forEach((filename) {
         addedFiles.add(filename);
       });
-      await Future.wait(
-          files.map((f) => processLibrary(f, libraries, sources)));
+      libraries = quiverIterables.concat([
+        libraries,
+        (await Future.wait(files.map((f) => processLibrary(f))))
+            .where((LibraryElement l) => l != null)
+      ]);
 
       /// We don't care about upstream analysis errors, so save the first
       /// source list.
-      if (originalSources == null) originalSources = new Set()..addAll(sources);
       files.addAll(driver.knownFiles);
       files.addAll(_includeExternalsFrom(driver.knownFiles));
       current = _packageMetasForFiles(files);
@@ -6725,8 +6682,6 @@
         }
       }
     } while (!lastPass.containsAll(current));
-
-    if (throwErrors) await logAnalysisErrors(originalSources);
     return libraries.toList();
   }
 
@@ -6822,12 +6777,8 @@
       Set<String> files,
       Set<String> specialFiles) async {
     libraries.addAll(await _parseLibraries(files));
-
-    /// Flutter doesn't seem to like being given the Interceptor library.
-    /// But it doesn't need it, either.  So just skip reporting errors here.
-    specialLibraries.addAll(await _parseLibraries(
-        specialFiles.difference(files),
-        throwErrors: false));
+    specialLibraries
+        .addAll(await _parseLibraries(specialFiles.difference(files)));
     if (config.include.isNotEmpty) {
       Iterable knownLibraryNames = libraries.map((l) => l.name);
       Set notFound = new Set.from(config.include)
diff --git a/lib/src/package_meta.dart b/lib/src/package_meta.dart
index b227f17..3258b7f 100644
--- a/lib/src/package_meta.dart
+++ b/lib/src/package_meta.dart
@@ -38,36 +38,45 @@
 
   PackageMeta(this.dir);
 
+  static List<List<String>> __sdkDirFilePaths;
   static List<List<String>> get _sdkDirFilePaths {
-    List<List<String>> platformSdkDirFilePaths = [];
-    if (Platform.isWindows) {
-      for (List<String> paths in __sdkDirFilePathsPosix) {
-        List<String> windowsPaths = [];
-        for (String path in paths) {
-          windowsPaths.add(pathLib.joinAll(
-              new pathLib.Context(style: pathLib.Style.posix).split(path)));
+    if (__sdkDirFilePaths == null) {
+      __sdkDirFilePaths = [];
+      if (Platform.isWindows) {
+        for (List<String> paths in __sdkDirFilePathsPosix) {
+          List<String> windowsPaths = [];
+          for (String path in paths) {
+            windowsPaths.add(pathLib.joinAll(
+                new pathLib.Context(style: pathLib.Style.posix).split(path)));
+          }
+          __sdkDirFilePaths.add(windowsPaths);
         }
-        platformSdkDirFilePaths.add(windowsPaths);
+      } else {
+        __sdkDirFilePaths = __sdkDirFilePathsPosix;
       }
-    } else {
-      platformSdkDirFilePaths = __sdkDirFilePathsPosix;
     }
-    return platformSdkDirFilePaths;
+    return __sdkDirFilePaths;
   }
 
   /// Returns the directory of the SDK if the given directory is inside a Dart
   /// SDK.  Returns null if the directory isn't a subdirectory of the SDK.
+  static final Map<String, Directory> _sdkDirParent = {};
   static Directory sdkDirParent(Directory dir) {
-    while (dir.existsSync()) {
-      if (_sdkDirFilePaths.every((List<String> l) {
-        return l.any((f) => new File(pathLib.join(dir.path, f)).existsSync());
-      })) {
-        return dir;
+    String dirPathCanonical = pathLib.canonicalize(dir.path);
+    if (!_sdkDirParent.containsKey(dirPathCanonical)) {
+      _sdkDirParent[dirPathCanonical] = null;
+      while (dir.existsSync()) {
+        if (_sdkDirFilePaths.every((List<String> l) {
+          return l.any((f) => new File(pathLib.join(dir.path, f)).existsSync());
+        })) {
+          _sdkDirParent[dirPathCanonical] = dir;
+          break;
+        }
+        if (pathLib.equals(dir.path, dir.parent.path)) break;
+        dir = dir.parent;
       }
-      if (pathLib.equals(dir.path, dir.parent.path)) break;
-      dir = dir.parent;
     }
-    return null;
+    return _sdkDirParent[dirPathCanonical];
   }
 
   @override
diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart
index 910254e..8c6d888 100644
--- a/test/dartdoc_test.dart
+++ b/test/dartdoc_test.dart
@@ -266,7 +266,9 @@
       } catch (e) {
         expect(e is DartdocFailure, isTrue);
       }
-    });
+    },
+        skip: 'Blocked on getting analysis errors with correct interpretation'
+            'from analysis_options');
 
     test('generate docs for a package that does not have a readme', () async {
       Dartdoc dartdoc = await buildDartdoc([], testPackageWithNoReadme);