Split up model.dart and move to a subpackage (#2067)

diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index 78eb3ea..0a8e252 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -16,7 +16,7 @@
 import 'package:dartdoc/src/generator.dart';
 import 'package:dartdoc/src/html/html_generator.dart';
 import 'package:dartdoc/src/logging.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/package_meta.dart';
 import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/utils.dart';
@@ -29,7 +29,7 @@
 export 'package:dartdoc/src/dartdoc_options.dart';
 export 'package:dartdoc/src/element_type.dart';
 export 'package:dartdoc/src/generator.dart';
-export 'package:dartdoc/src/model.dart';
+export 'package:dartdoc/src/model/model.dart';
 export 'package:dartdoc/src/package_meta.dart';
 
 const String programName = 'dartdoc';
diff --git a/lib/src/element_type.dart b/lib/src/element_type.dart
index 19a230e..f753c6d 100644
--- a/lib/src/element_type.dart
+++ b/lib/src/element_type.dart
@@ -10,7 +10,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart';
 
 /// Base class representing a type in Dartdoc.  It wraps a [DartType], and
diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart
index 92a96b8..016f2d1 100644
--- a/lib/src/empty_generator.dart
+++ b/lib/src/empty_generator.dart
@@ -3,7 +3,7 @@
 import 'dart:async';
 
 import 'package:dartdoc/src/generator.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart';
 
 /// A generator that does not generate files, but does traverse the [PackageGraph]
diff --git a/lib/src/generator.dart b/lib/src/generator.dart
index bd35988..758659a 100644
--- a/lib/src/generator.dart
+++ b/lib/src/generator.dart
@@ -7,7 +7,7 @@
 
 import 'dart:async' show Stream, Future;
 
-import 'package:dartdoc/src/model.dart' show PackageGraph;
+import 'package:dartdoc/src/model/model.dart' show PackageGraph;
 
 /// An abstract class that defines a generator that generates documentation for
 /// a given package.
diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart
index a0ecb00..93fe221 100644
--- a/lib/src/html/html_generator.dart
+++ b/lib/src/html/html_generator.dart
@@ -14,7 +14,7 @@
 import 'package:dartdoc/src/html/html_generator_instance.dart';
 import 'package:dartdoc/src/html/template_data.dart';
 import 'package:dartdoc/src/html/templates.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:path/path.dart' as path;
 
 typedef Renderer = String Function(String input);
diff --git a/lib/src/html/html_generator_instance.dart b/lib/src/html/html_generator_instance.dart
index 037ec67..1eb7a96 100644
--- a/lib/src/html/html_generator_instance.dart
+++ b/lib/src/html/html_generator_instance.dart
@@ -13,7 +13,7 @@
 import 'package:dartdoc/src/html/template_data.dart';
 import 'package:dartdoc/src/html/templates.dart';
 import 'package:dartdoc/src/logging.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart';
 import 'package:dartdoc/src/warnings.dart';
 import 'package:mustache/mustache.dart';
diff --git a/lib/src/html/template_data.dart b/lib/src/html/template_data.dart
index 7f25e2a..8e1095b 100644
--- a/lib/src/html/template_data.dart
+++ b/lib/src/html/template_data.dart
@@ -2,7 +2,7 @@
 // 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.
 
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 
 abstract class HtmlOptions {
   String get relCanonicalPrefix;
diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart
index cfa3014..5fab0ee 100644
--- a/lib/src/markdown_processor.dart
+++ b/lib/src/markdown_processor.dart
@@ -10,7 +10,7 @@
 
 import 'package:analyzer/dart/element/element.dart';
 import 'package:dartdoc/src/element_type.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/warnings.dart';
 import 'package:html/parser.dart' show parse;
diff --git a/lib/src/model.dart b/lib/src/model.dart
deleted file mode 100644
index b6d4e72..0000000
--- a/lib/src/model.dart
+++ /dev/null
@@ -1,7317 +0,0 @@
-// Copyright (c) 2014, 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.
-
-/// The models used to represent Dart code.
-library dartdoc.models;
-
-import 'dart:async';
-import 'dart:collection' show HashSet, UnmodifiableListView;
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:analyzer/dart/analysis/results.dart';
-import 'package:analyzer/dart/analysis/session.dart';
-import 'package:analyzer/dart/ast/ast.dart'
-    show
-        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 file_system;
-import 'package:analyzer/file_system/physical_file_system.dart';
-import 'package:analyzer/source/line_info.dart';
-import 'package:analyzer/src/context/builder.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
-import 'package:analyzer/src/dart/analysis/driver.dart';
-import 'package:analyzer/src/dart/analysis/file_state.dart';
-import 'package:analyzer/src/dart/analysis/performance_logger.dart';
-import 'package:analyzer/src/dart/element/element.dart';
-import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
-import 'package:analyzer/src/dart/element/member.dart'
-    show ExecutableMember, Member, ParameterMember;
-import 'package:analyzer/src/dart/sdk/sdk.dart';
-import 'package:analyzer/src/generated/engine.dart';
-import 'package:analyzer/src/generated/java_io.dart';
-import 'package:analyzer/src/generated/sdk.dart';
-import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/generated/source_io.dart';
-import 'package:analyzer/src/generated/type_system.dart' show Dart2TypeSystem;
-import 'package:analyzer/src/source/package_map_resolver.dart';
-import 'package:analyzer/src/source/sdk_ext.dart';
-import 'package:args/args.dart';
-import 'package:collection/collection.dart';
-import 'package:crypto/crypto.dart';
-import 'package:dartdoc/src/dartdoc_options.dart';
-import 'package:dartdoc/src/element_type.dart';
-import 'package:dartdoc/src/io_utils.dart';
-import 'package:dartdoc/src/logging.dart';
-import 'package:dartdoc/src/markdown_processor.dart' show Documentation;
-import 'package:dartdoc/src/model_utils.dart' as utils;
-import 'package:dartdoc/src/package_meta.dart' show PackageMeta, FileContents;
-import 'package:dartdoc/src/source_linker.dart';
-import 'package:dartdoc/src/special_elements.dart';
-import 'package:dartdoc/src/tuple.dart';
-import 'package:dartdoc/src/utils.dart';
-import 'package:dartdoc/src/warnings.dart';
-import 'package:package_config/discovery.dart' as package_config;
-import 'package:path/path.dart' as path;
-import 'package:pub_semver/pub_semver.dart';
-import 'package:quiver/iterables.dart' as quiver;
-
-int byName(Nameable a, Nameable b) =>
-    compareAsciiLowerCaseNatural(a.name, b.name);
-
-/// Items mapped less than zero will sort before custom annotations.
-/// Items mapped above zero are sorted after custom annotations.
-/// Items mapped to zero will sort alphabetically among custom annotations.
-/// Custom annotations are assumed to be any annotation or feature not in this
-/// map.
-const Map<String, int> featureOrder = {
-  'read-only': 1,
-  'write-only': 1,
-  'read / write': 1,
-  'covariant': 2,
-  'final': 2,
-  'inherited': 3,
-  'inherited-getter': 3,
-  'inherited-setter': 3,
-  'override': 3,
-  'override-getter': 3,
-  'override-setter': 3,
-  'extended': 3,
-};
-
-int byFeatureOrdering(String a, String b) {
-  int scoreA = 0;
-  int scoreB = 0;
-
-  if (featureOrder.containsKey(a)) scoreA = featureOrder[a];
-  if (featureOrder.containsKey(b)) scoreB = featureOrder[b];
-
-  if (scoreA < scoreB) return -1;
-  if (scoreA > scoreB) return 1;
-  return compareAsciiLowerCaseNatural(a, b);
-}
-
-final RegExp locationSplitter = RegExp(r'(package:|[\\/;.])');
-final RegExp substituteNameVersion = RegExp(r'%([bnv])%');
-
-/// This doc may need to be processed in case it has a template or html
-/// fragment.
-final needsPrecacheRegExp = RegExp(r'{@(template|tool|inject-html)');
-
-final templateRegExp = RegExp(
-    r'[ ]*{@template\s+(.+?)}([\s\S]+?){@endtemplate}[ ]*\n?',
-    multiLine: true);
-final htmlRegExp = RegExp(
-    r'[ ]*{@inject-html\s*}([\s\S]+?){@end-inject-html}[ ]*\n?',
-    multiLine: true);
-final htmlInjectRegExp = RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');
-
-// Matches all tool directives (even some invalid ones). This is so
-// we can give good error messages if the directive is malformed, instead of
-// just silently emitting it as-is.
-final basicToolRegExp = RegExp(
-    r'[ ]*{@tool\s+([^}]+)}\n?([\s\S]+?)\n?{@end-tool}[ ]*\n?',
-    multiLine: true);
-
-/// Regexp to take care of splitting arguments, and handling the quotes
-/// around arguments, if any.
-///
-/// Match group 1 is the "foo=" (or "--foo=") part of the option, if any.
-/// Match group 2 contains the quote character used (which is discarded).
-/// Match group 3 is a quoted arg, if any, without the quotes.
-/// Match group 4 is the unquoted arg, if any.
-final RegExp argMatcher = RegExp(r'([a-zA-Z\-_0-9]+=)?' // option name
-    r'(?:' // Start a new non-capture group for the two possibilities.
-    r'''(["'])((?:\\{2})*|(?:.*?[^\\](?:\\{2})*))\2|''' // with quotes.
-    r'([^ ]+))'); // without quotes.
-
-final categoryRegexp = RegExp(
-    r'[ ]*{@(api|category|subCategory|image|samples) (.+?)}[ ]*\n?',
-    multiLine: true);
-final macroRegExp = RegExp(r'{@macro\s+([^}]+)}');
-
-/// Mixin for subclasses of [ModelElement] representing Elements that can be
-/// extension methods.
-mixin Extendable on ContainerMember {
-  /// Returns this Extendable from the [Extension] in which it was declared.
-  Extendable get definingExtension => throw UnimplementedError;
-
-  @override
-  Container get canonicalEnclosingContainer => throw UnimplementedError;
-}
-
-/// A [ModelElement] that is a [Container] member.
-mixin ContainerMember on ModelElement implements EnclosedElement {
-  /// True if this [ContainerMember] is inherited from a different class.
-  bool get isInherited;
-
-  /// True if this [ContainerMember] is overriding a superclass.
-  bool get isOverride;
-
-  /// True if this [ContainerMember] has a parameter whose type is overridden
-  /// by a subtype.
-  bool get isCovariant;
-
-  /// True if this [ContainerMember] is from an applicable [Extension].
-  /// False otherwise, including if this [ContainerMember]'s [enclosingElement]
-  /// is the extension it was declared in.
-  // TODO(jcollins-g): This semantic is a little confusing, because a declared
-  // extension member element returns false.  The rationale is an
-  // extension member is not extending itself.
-  // FIXME(jcollins-g): Remove concrete implementation after [Extendable] is
-  // implemented.
-  bool get isExtended => false;
-
-  Container _definingEnclosingContainer;
-  Container get definingEnclosingContainer {
-    if (_definingEnclosingContainer == null) {
-      _definingEnclosingContainer =
-          ModelElement.fromElement(element.enclosingElement, packageGraph);
-    }
-    return _definingEnclosingContainer;
-  }
-
-  @override
-  Set<String> get features {
-    Set<String> _features = super.features;
-    if (isOverride) _features.add('override');
-    if (isInherited) _features.add('inherited');
-    if (isCovariant) _features.add('covariant');
-    if (isExtended) _features.add('extended');
-    return _features;
-  }
-
-  bool _canonicalEnclosingContainerIsSet = false;
-  Container _canonicalEnclosingContainer;
-  Container get canonicalEnclosingContainer {
-    if (!_canonicalEnclosingContainerIsSet) {
-      // TODO(jcollins-g): move Extension specific code to [Extendable]
-      if (enclosingElement is! Extension ||
-          (enclosingElement is Extension && enclosingElement.isDocumented)) {
-        _canonicalEnclosingContainer =
-            packageGraph.findCanonicalModelElementFor(enclosingElement.element);
-      }
-      _canonicalEnclosingContainerIsSet = true;
-      assert(_canonicalEnclosingContainer == null ||
-          _canonicalEnclosingContainer.isDocumented);
-    }
-    assert(_canonicalEnclosingContainer == null ||
-        (_canonicalEnclosingContainer.isDocumented));
-    return _canonicalEnclosingContainer;
-  }
-}
-
-/// Mixin for subclasses of ModelElement representing Elements that can be
-/// inherited from one class to another.
-///
-/// We can search the inheritance chain between this instance and
-/// [definingEnclosingContainer] in [Inheritable.canonicalEnclosingContainer],
-/// for the canonical [Class] closest to where this member was defined.  We
-/// can then know that when we find [Inheritable.element] inside that [Class]'s
-/// namespace, that's the one we should treat as canonical and implementors
-/// of this class can use that knowledge to determine canonicalization.
-///
-/// We pick the class closest to the [definingEnclosingElement] so that all
-/// children of that class inheriting the same member will point to the same
-/// place in the documentation, and we pick a canonical class because that's
-/// the one in the public namespace that will be documented.
-mixin Inheritable on ContainerMember {
-  @override
-  ModelElement _buildCanonicalModelElement() {
-    if (canonicalEnclosingContainer is Extension) {
-      return this;
-    }
-    if (canonicalEnclosingContainer is Class) {
-      return (canonicalEnclosingContainer as Class)
-          ?.allCanonicalModelElements
-          ?.firstWhere(
-              (m) =>
-                  m.name == name && m.isPropertyAccessor == isPropertyAccessor,
-              orElse: () => null);
-    }
-    return null;
-  }
-
-  @override
-  Container get canonicalEnclosingContainer {
-    Element searchElement = element;
-    if (!_canonicalEnclosingContainerIsSet) {
-      if (isInherited) {
-        searchElement = searchElement is Member
-            ? PackageGraph.getBasestElement(searchElement)
-            : searchElement;
-        // TODO(jcollins-g): generate warning if an inherited element's definition
-        // is in an intermediate non-canonical class in the inheritance chain?
-        Class previous;
-        Class previousNonSkippable;
-        for (Class c in inheritance.reversed) {
-          // Filter out mixins.
-          if (c.contains(searchElement)) {
-            if ((packageGraph.inheritThrough.contains(previous) &&
-                    c != definingEnclosingContainer) ||
-                (packageGraph.inheritThrough.contains(c) &&
-                    c == definingEnclosingContainer)) {
-              return (previousNonSkippable.memberByExample(this) as Inheritable)
-                  .canonicalEnclosingContainer;
-            }
-            Class canonicalC =
-                packageGraph.findCanonicalModelElementFor(c.element);
-            // TODO(jcollins-g): invert this lookup so traversal is recursive
-            // starting from the ModelElement.
-            if (canonicalC != null) {
-              assert(canonicalC.isCanonical);
-              assert(canonicalC.contains(searchElement));
-              _canonicalEnclosingContainer = canonicalC;
-              break;
-            }
-          }
-          previous = c;
-          if (!packageGraph.inheritThrough.contains(c)) {
-            previousNonSkippable = c;
-          }
-        }
-        // This is still OK because we're never supposed to cloak public
-        // classes.
-        if (definingEnclosingContainer.isCanonical &&
-            definingEnclosingContainer.isPublic) {
-          assert(definingEnclosingContainer == _canonicalEnclosingContainer);
-        }
-        _canonicalEnclosingContainerIsSet = true;
-        assert(_canonicalEnclosingContainer == null ||
-            _canonicalEnclosingContainer.isDocumented);
-      }
-    }
-    return super.canonicalEnclosingContainer;
-  }
-
-  List<Class> get inheritance {
-    List<Class> inheritance = [];
-    inheritance.addAll((enclosingElement as Class).inheritanceChain);
-    Class object = packageGraph.specialClasses[SpecialClass.object];
-    if (!inheritance.contains(definingEnclosingContainer) &&
-        definingEnclosingContainer != null) {
-      assert(definingEnclosingContainer == object);
-    }
-    // Unless the code explicitly extends dart-core's Object, we won't get
-    // an entry here.  So add it.
-    if (inheritance.last != object && object != null) {
-      inheritance.add(object);
-    }
-    assert(inheritance.where((e) => e == object).length == 1);
-    return inheritance;
-  }
-
-  Inheritable get overriddenElement;
-
-  bool _isOverride;
-
-  @override
-  bool get isOverride {
-    if (_isOverride == null) {
-      // The canonical version of the enclosing element -- not canonicalEnclosingElement,
-      // as that is the element enclosing the canonical version of this element,
-      // two different things.  Defaults to the enclosing element.
-      //
-      // We use canonical elements here where possible to deal with reexports
-      // as seen in Flutter.
-      if (enclosingElement is Extension) {
-        _isOverride = false;
-        return _isOverride;
-      }
-      Class enclosingCanonical = enclosingElement.canonicalModelElement;
-      // The container in which this element was defined, canonical if available.
-      Container definingCanonical =
-          definingEnclosingContainer.canonicalModelElement ??
-              definingEnclosingContainer;
-      // The canonical version of the element we're overriding, if available.
-      ModelElement overriddenCanonical =
-          overriddenElement?.canonicalModelElement ?? overriddenElement;
-
-      // We have to have an overridden element for it to be possible for this
-      // element to be an override.
-      _isOverride = overriddenElement != null &&
-          // The defining class and the enclosing class for this element
-          // must be the same (element is defined here).
-          enclosingCanonical == definingCanonical &&
-          // If the overridden element isn't public, we shouldn't be an
-          // override in most cases.  Approximation until #1623 is fixed.
-          overriddenCanonical.isPublic;
-      assert(!(_isOverride && isInherited));
-    }
-    return _isOverride;
-  }
-
-  int _overriddenDepth;
-  @override
-  int get overriddenDepth {
-    if (_overriddenDepth == null) {
-      _overriddenDepth = 0;
-      Inheritable e = this;
-      while (e.overriddenElement != null) {
-        _overriddenDepth += 1;
-        e = e.overriddenElement;
-      }
-    }
-    return _overriddenDepth;
-  }
-}
-
-/// A getter or setter that is a member of a [Container].
-class ContainerAccessor extends Accessor with ContainerMember, Inheritable {
-  /// Factory will return an [ContainerAccessor] with isInherited = true
-  /// if [element] is in [inheritedAccessors].
-  factory ContainerAccessor.from(PropertyAccessorElement element,
-      Set<PropertyAccessorElement> inheritedAccessors, Class enclosingClass) {
-    ContainerAccessor accessor;
-    if (element == null) return null;
-    if (inheritedAccessors.contains(element)) {
-      accessor = ModelElement.from(
-          element, enclosingClass.library, enclosingClass.packageGraph,
-          enclosingContainer: enclosingClass);
-    } else {
-      accessor = ModelElement.from(
-          element, enclosingClass.library, enclosingClass.packageGraph);
-    }
-    return accessor;
-  }
-
-  ModelElement _enclosingElement;
-  bool _isInherited = false;
-
-  @override
-  bool get isCovariant => isSetter && parameters.first.isCovariant;
-
-  ContainerAccessor(PropertyAccessorElement element, Library library,
-      PackageGraph packageGraph)
-      : super(element, library, packageGraph, null);
-
-  ContainerAccessor.inherited(PropertyAccessorElement element, Library library,
-      PackageGraph packageGraph, this._enclosingElement,
-      {Member originalMember})
-      : super(element, library, packageGraph, originalMember) {
-    _isInherited = true;
-  }
-
-  @override
-  bool get isInherited => _isInherited;
-
-  @override
-  Container get enclosingElement {
-    if (_enclosingElement == null) {
-      _enclosingElement = super.enclosingElement;
-    }
-    return _enclosingElement;
-  }
-
-  bool _overriddenElementIsSet = false;
-  ModelElement _overriddenElement;
-
-  @override
-  ContainerAccessor get overriddenElement {
-    assert(packageGraph.allLibrariesAdded);
-    if (!_overriddenElementIsSet) {
-      _overriddenElementIsSet = true;
-      Element parent = element.enclosingElement;
-      if (parent is ClassElement) {
-        for (InterfaceType t in parent.allSupertypes) {
-          Element accessor = this.isGetter
-              ? t.getGetter(element.name)
-              : t.getSetter(element.name);
-          if (accessor != null) {
-            if (accessor is Member) {
-              accessor = PackageGraph.getBasestElement(accessor);
-            }
-            Class parentClass =
-                ModelElement.fromElement(t.element, packageGraph);
-            List<Field> possibleFields = [];
-            possibleFields.addAll(parentClass.allInstanceFields);
-            possibleFields.addAll(parentClass.staticProperties);
-            String fieldName = accessor.name.replaceFirst('=', '');
-            Field foundField = possibleFields.firstWhere(
-                (f) => f.element.name == fieldName,
-                orElse: () => null);
-            if (foundField != null) {
-              if (this.isGetter) {
-                _overriddenElement = foundField.getter;
-              } else {
-                _overriddenElement = foundField.setter;
-              }
-              assert(!(_overriddenElement as ContainerAccessor).isInherited);
-              break;
-            }
-          }
-        }
-      }
-    }
-    return _overriddenElement;
-  }
-}
-
-/// Getters and setters.
-class Accessor extends ModelElement implements EnclosedElement {
-  GetterSetterCombo _enclosingCombo;
-
-  Accessor(PropertyAccessorElement element, Library library,
-      PackageGraph packageGraph, Member originalMember)
-      : super(element, library, packageGraph, originalMember);
-
-  String get linkedReturnType {
-    assert(isGetter);
-    return modelType.createLinkedReturnTypeName();
-  }
-
-  GetterSetterCombo get enclosingCombo {
-    assert(_enclosingCombo != null);
-    return _enclosingCombo;
-  }
-
-  bool get isSynthetic => element.isSynthetic;
-
-  @override
-  String get sourceCode {
-    if (_sourceCode == null) {
-      if (isSynthetic) {
-        _sourceCode = packageGraph
-            ._getModelNodeFor((element as PropertyAccessorElement).variable)
-            .sourceCode;
-      } else {
-        _sourceCode = super.sourceCode;
-      }
-    }
-    return _sourceCode;
-  }
-
-  @override
-  List<ModelElement> get computeDocumentationFrom {
-    return super.computeDocumentationFrom;
-  }
-
-  @override
-  String _computeDocumentationComment() {
-    if (isSynthetic) {
-      String docComment =
-          (element as PropertyAccessorElement).variable.documentationComment;
-      // If we're a setter, only display something if we have something different than the getter.
-      // TODO(jcollins-g): modify analyzer to do this itself?
-      if (isGetter ||
-          // TODO(jcollins-g): @nodoc reading from comments is at the wrong abstraction level here.
-          (docComment != null &&
-              (docComment.contains('<nodoc>') ||
-                  docComment.contains('@nodoc'))) ||
-          (isSetter &&
-              enclosingCombo.hasGetter &&
-              enclosingCombo.getter.documentationComment != docComment)) {
-        return stripComments(docComment);
-      } else {
-        return '';
-      }
-    }
-    return stripComments(super._computeDocumentationComment());
-  }
-
-  @override
-  void warn(PackageWarning kind,
-      {String message,
-      Iterable<Locatable> referredFrom,
-      Iterable<String> extendedDebug}) {
-    enclosingCombo.warn(kind,
-        message: message,
-        referredFrom: referredFrom,
-        extendedDebug: extendedDebug);
-  }
-
-  @override
-  ModelElement get enclosingElement {
-    if (_accessor.enclosingElement is CompilationUnitElement) {
-      return packageGraph.findButDoNotCreateLibraryFor(
-          _accessor.enclosingElement.enclosingElement);
-    }
-
-    return ModelElement.from(_accessor.enclosingElement, library, packageGraph);
-  }
-
-  @override
-  bool get isCanonical => enclosingCombo.isCanonical;
-
-  @override
-  String get href {
-    return enclosingCombo.href;
-  }
-
-  bool get isGetter => _accessor.isGetter;
-
-  bool get isSetter => _accessor.isSetter;
-
-  @override
-  String get kind => 'accessor';
-
-  @override
-  String get namePart {
-    if (_namePart == null) {
-      _namePart = super.namePart.split('=').first;
-    }
-    return _namePart;
-  }
-
-  PropertyAccessorElement get _accessor => (element as PropertyAccessorElement);
-}
-
-/// 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);
-
-  @override
-  bool get isAbstract => false;
-
-  @override
-  List<Class> get inheritanceChain {
-    if (_inheritanceChain == null) {
-      _inheritanceChain = [];
-      _inheritanceChain.add(this);
-
-      // Mix-in interfaces come before other interfaces.
-      _inheritanceChain.addAll(superclassConstraints
-          .expand((ParameterizedElementType i) =>
-              (i.element as Class).inheritanceChain)
-          .where((Class c) =>
-              c != packageGraph.specialClasses[SpecialClass.object]));
-
-      // Interfaces need to come last, because classes in the superChain might
-      // implement them even when they aren't mentioned.
-      _inheritanceChain.addAll(
-          interfaces.expand((e) => (e.element as Class).inheritanceChain));
-    }
-    return _inheritanceChain.toList(growable: false);
-  }
-
-  List<ParameterizedElementType> _superclassConstraints;
-
-  /// Returns a list of superclass constraints for this mixin.
-  Iterable<ParameterizedElementType> get superclassConstraints {
-    if (_superclassConstraints == null) {
-      _superclassConstraints = (element as ClassElement)
-          .superclassConstraints
-          .map<ParameterizedElementType>(
-              (InterfaceType i) => ElementType.from(i, library, packageGraph))
-          .toList();
-    }
-    return _superclassConstraints;
-  }
-
-  bool get hasPublicSuperclassConstraints =>
-      publicSuperclassConstraints.isNotEmpty;
-
-  Iterable<ParameterizedElementType> get publicSuperclassConstraints =>
-      utils.filterNonPublic(superclassConstraints);
-
-  @override
-  bool get hasModifiers => super.hasModifiers || hasPublicSuperclassConstraints;
-
-  @override
-  String get fileName => "${name}-mixin.html";
-
-  @override
-  String get kind => 'mixin';
-}
-
-// Can be either a Class or Extension, used in the package graph and template data.
-// Aggregates some of the common getters.
-abstract class Container extends ModelElement {
-  List<Method> _allMethods;
-  List<Field> _constants;
-  List<Operator> _operators;
-  List<Method> _staticMethods;
-  List<Method> _instanceMethods;
-  List<Field> _fields;
-  List<Field> _staticFields;
-  List<Field> _instanceFields;
-  List<TypeParameter> _typeParameters;
-
-  Container(Element element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph, null);
-
-  bool get isClass => element is ClassElement;
-
-  bool get isExtension => element is ExtensionElement;
-
-  List<Method> get _methods => _allMethods;
-
-  List<Method> get instanceMethods {
-    if (_instanceMethods != null) return _instanceMethods;
-
-    _instanceMethods = _methods
-        .where((m) => !m.isStatic && !m.isOperator)
-        .toList(growable: false)
-          ..sort(byName);
-    return _instanceMethods;
-  }
-
-  bool get hasPublicMethods =>
-      utils.filterNonPublic(instanceMethods).isNotEmpty;
-
-  Iterable<Method> get allPublicInstanceMethods =>
-      utils.filterNonPublic(instanceMethods);
-
-  List<Method> get staticMethods {
-    if (_staticMethods != null) return _staticMethods;
-
-    _staticMethods = _methods.where((m) => m.isStatic).toList(growable: false)
-      ..sort(byName);
-
-    return _staticMethods;
-  }
-
-  bool get hasPublicStaticMethods =>
-      utils.filterNonPublic(staticMethods).isNotEmpty;
-
-  Iterable<Method> get publicStaticMethods =>
-      utils.filterNonPublic(staticMethods);
-
-  List<Operator> get operators {
-    if (_operators != null) return _operators;
-    _operators = _methods
-        .where((m) => m.isOperator)
-        .cast<Operator>()
-        .toList(growable: false)
-          ..sort(byName);
-    return _operators;
-  }
-
-  Iterable<Operator> get allOperators => operators;
-
-  bool get hasPublicOperators => publicOperators.isNotEmpty;
-
-  Iterable<Operator> get allPublicOperators =>
-      utils.filterNonPublic(allOperators);
-
-  Iterable<Operator> get publicOperators => utils.filterNonPublic(operators);
-
-  List<Field> get _allFields => [];
-
-  List<Field> get staticProperties {
-    if (_staticFields != null) return _staticFields;
-    _staticFields = _allFields
-        .where((f) => f.isStatic && !f.isConst)
-        .toList(growable: false)
-          ..sort(byName);
-
-    return _staticFields;
-  }
-
-  Iterable<Field> get publicStaticProperties =>
-      utils.filterNonPublic(staticProperties);
-
-  bool get hasPublicStaticProperties => publicStaticProperties.isNotEmpty;
-
-  List<Field> get instanceProperties {
-    if (_instanceFields != null) return _instanceFields;
-    _instanceFields = _allFields
-        .where((f) => !f.isStatic && !f.isInherited && !f.isConst)
-        .toList(growable: false)
-          ..sort(byName);
-
-    return _instanceFields;
-  }
-
-  Iterable<Field> get publicInstanceProperties =>
-      utils.filterNonPublic(instanceProperties);
-
-  bool get hasPublicProperties => publicInstanceProperties.isNotEmpty;
-
-  Iterable<Field> get allInstanceFields => instanceProperties;
-
-  Iterable<Field> get allPublicInstanceProperties =>
-      utils.filterNonPublic(allInstanceFields);
-
-  bool isInheritingFrom(Container other) => false;
-
-  List<Field> get constants {
-    if (_constants != null) return _constants;
-    _constants = _allFields.where((f) => f.isConst).toList(growable: false)
-      ..sort(byName);
-
-    return _constants;
-  }
-
-  Iterable<Field> get publicConstants => utils.filterNonPublic(constants);
-
-  bool get hasPublicConstants => publicConstants.isNotEmpty;
-
-  Iterable<Accessor> get allAccessors => quiver.concat([
-        allInstanceFields.expand((f) => f.allAccessors),
-        constants.map((c) => c.getter)
-      ]);
-}
-
-class Class extends Container
-    with TypeParameters, Categorization
-    implements EnclosedElement {
-  List<DefinedElementType> _mixins;
-  DefinedElementType supertype;
-  List<DefinedElementType> _interfaces;
-  List<Constructor> _constructors;
-  List<Operator> _inheritedOperators;
-  List<Method> _inheritedMethods;
-  List<Field> _inheritedProperties;
-
-  Class(ClassElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph) {
-    packageGraph.specialClasses.addSpecial(this);
-    _mixins = _cls.mixins
-        .map((f) {
-          DefinedElementType t = ElementType.from(f, library, packageGraph);
-          return t;
-        })
-        .where((mixin) => mixin != null)
-        .toList(growable: false);
-
-    if (_cls.supertype != null && _cls.supertype.element.supertype != null) {
-      supertype = ElementType.from(_cls.supertype, library, packageGraph);
-    }
-
-    _interfaces = _cls.interfaces
-        .map((f) =>
-            ElementType.from(f, library, packageGraph) as DefinedElementType)
-        .toList(growable: false);
-  }
-
-  Constructor _defaultConstructor;
-
-  Constructor get defaultConstructor {
-    if (_defaultConstructor == null) {
-      _defaultConstructor = constructors
-          .firstWhere((c) => c.isDefaultConstructor, orElse: () => null);
-    }
-    return _defaultConstructor;
-  }
-
-  bool get hasPotentiallyApplicableExtensions =>
-      potentiallyApplicableExtensions.isNotEmpty;
-
-  List<Extension> _potentiallyApplicableExtensions;
-  Iterable<Extension> get potentiallyApplicableExtensions {
-    if (_potentiallyApplicableExtensions == null) {
-      _potentiallyApplicableExtensions = utils
-          .filterNonDocumented(packageGraph.extensions)
-          .where((e) => e.couldApplyTo(this))
-          .toList(growable: false)
-            ..sort(byName);
-    }
-    return _potentiallyApplicableExtensions;
-  }
-
-  Iterable<Method> get allInstanceMethods =>
-      quiver.concat([instanceMethods, inheritedMethods]);
-
-  @override
-  Iterable<Method> get allPublicInstanceMethods =>
-      utils.filterNonPublic(allInstanceMethods);
-
-  bool get allPublicInstanceMethodsInherited =>
-      instanceMethods.every((f) => f.isInherited);
-
-  @override
-  Iterable<Field> get allInstanceFields =>
-      quiver.concat([instanceProperties, inheritedProperties]);
-
-  bool get allPublicInstancePropertiesInherited =>
-      allPublicInstanceProperties.every((f) => f.isInherited);
-
-  @override
-  Iterable<Operator> get allOperators =>
-      quiver.concat([operators, inheritedOperators]);
-
-  bool get allPublicOperatorsInherited =>
-      allPublicOperators.every((f) => f.isInherited);
-
-  Map<Element, ModelElement> _allElements;
-
-  Map<Element, ModelElement> get allElements {
-    if (_allElements == null) {
-      _allElements = Map();
-      for (ModelElement me in allModelElements) {
-        assert(!_allElements.containsKey(me.element));
-        _allElements[me.element] = me;
-      }
-    }
-    return _allElements;
-  }
-
-  Map<String, List<ModelElement>> _allModelElementsByNamePart;
-
-  /// Helper for [_MarkdownCommentReference._getResultsForClass].
-  Map<String, List<ModelElement>> get allModelElementsByNamePart {
-    if (_allModelElementsByNamePart == null) {
-      _allModelElementsByNamePart = {};
-      for (ModelElement me in allModelElements) {
-        _allModelElementsByNamePart.update(
-            me.namePart, (List<ModelElement> v) => v..add(me),
-            ifAbsent: () => <ModelElement>[me]);
-      }
-    }
-    return _allModelElementsByNamePart;
-  }
-
-  /// This class might be canonical for elements it does not contain.
-  /// See [Inheritable.canonicalEnclosingContainer].
-  bool contains(Element element) => allElements.containsKey(element);
-
-  Map<String, List<ModelElement>> _membersByName;
-
-  /// Given a ModelElement that is a member of some other class, return
-  /// a member of this class that has the same name and return type.
-  ///
-  /// This enables object substitution for canonicalization, such as Interceptor
-  /// for Object.
-  ModelElement memberByExample(ModelElement example) {
-    if (_membersByName == null) {
-      _membersByName = Map();
-      for (ModelElement me in allModelElements) {
-        if (!_membersByName.containsKey(me.name)) {
-          _membersByName[me.name] = List();
-        }
-        _membersByName[me.name].add(me);
-      }
-    }
-    ModelElement member;
-    Iterable<ModelElement> possibleMembers = _membersByName[example.name]
-        .where((e) => e.runtimeType == example.runtimeType);
-    if (example.runtimeType == Accessor) {
-      possibleMembers = possibleMembers.where(
-          (e) => (example as Accessor).isGetter == (e as Accessor).isGetter);
-    }
-    member = possibleMembers.first;
-    assert(possibleMembers.length == 1);
-    return member;
-  }
-
-  List<ModelElement> _allModelElements;
-
-  List<ModelElement> get allModelElements {
-    if (_allModelElements == null) {
-      _allModelElements = List.from(
-          quiver.concat([
-            allInstanceMethods,
-            allInstanceFields,
-            allAccessors,
-            allOperators,
-            constants,
-            constructors,
-            staticMethods,
-            staticProperties,
-            typeParameters,
-          ]),
-          growable: false);
-    }
-    return _allModelElements;
-  }
-
-  List<ModelElement> _allCanonicalModelElements;
-
-  List<ModelElement> get allCanonicalModelElements {
-    return (_allCanonicalModelElements ??=
-        allModelElements.where((e) => e.isCanonical).toList());
-  }
-
-  List<Constructor> get constructors {
-    if (_constructors != null) return _constructors;
-
-    _constructors = _cls.constructors.map((e) {
-      return ModelElement.from(e, library, packageGraph) as Constructor;
-    }).toList(growable: true)
-      ..sort(byName);
-
-    return _constructors;
-  }
-
-  Iterable<Constructor> get publicConstructors =>
-      utils.filterNonPublic(constructors);
-
-  /// Returns the library that encloses this element.
-  @override
-  ModelElement get enclosingElement => library;
-
-  @override
-  String get fileName => "${name}-class.html";
-
-  String get fullkind {
-    if (isAbstract) return 'abstract $kind';
-    return kind;
-  }
-
-  bool get hasPublicConstructors => publicConstructors.isNotEmpty;
-
-  bool get hasPublicImplementors => publicImplementors.isNotEmpty;
-
-  bool get hasInstanceProperties => instanceProperties.isNotEmpty;
-
-  bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
-
-  @override
-  bool get hasPublicMethods =>
-      publicInstanceMethods.isNotEmpty || publicInheritedMethods.isNotEmpty;
-
-  bool get hasPublicMixins => publicMixins.isNotEmpty;
-
-  bool get hasModifiers =>
-      hasPublicMixins ||
-      hasAnnotations ||
-      hasPublicInterfaces ||
-      hasPublicSuperChainReversed ||
-      hasPublicImplementors ||
-      hasPotentiallyApplicableExtensions;
-
-  @override
-  bool get hasPublicOperators =>
-      publicOperators.isNotEmpty || publicInheritedOperators.isNotEmpty;
-
-  @override
-  bool get hasPublicProperties =>
-      publicInheritedProperties.isNotEmpty ||
-      publicInstanceProperties.isNotEmpty;
-
-  @override
-  bool get hasPublicStaticMethods => publicStaticMethods.isNotEmpty;
-
-  bool get hasPublicSuperChainReversed => publicSuperChainReversed.isNotEmpty;
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
-  }
-
-  /// Returns all the implementors of this class.
-  Iterable<Class> get publicImplementors {
-    return utils.filterNonPublic(utils.findCanonicalFor(
-        packageGraph.implementors[href] != null
-            ? packageGraph.implementors[href]
-            : []));
-  }
-
-  List<Method> get inheritedMethods {
-    if (_inheritedMethods == null) {
-      _inheritedMethods = <Method>[];
-      Set<String> methodNames = _methods.map((m) => m.element.name).toSet();
-
-      Set<ExecutableElement> inheritedMethodElements =
-          _inheritedElements.where((e) {
-        return (e is MethodElement &&
-            !e.isOperator &&
-            e is! PropertyAccessorElement &&
-            !methodNames.contains(e.name));
-      }).toSet();
-
-      for (ExecutableElement e in inheritedMethodElements) {
-        Method m = ModelElement.from(e, library, packageGraph,
-            enclosingContainer: this);
-        _inheritedMethods.add(m);
-      }
-      _inheritedMethods.sort(byName);
-    }
-    return _inheritedMethods;
-  }
-
-  Iterable get publicInheritedMethods =>
-      utils.filterNonPublic(inheritedMethods);
-
-  bool get hasPublicInheritedMethods => publicInheritedMethods.isNotEmpty;
-
-  List<Operator> get inheritedOperators {
-    if (_inheritedOperators == null) {
-      _inheritedOperators = [];
-      Set<String> operatorNames = _operators.map((o) => o.element.name).toSet();
-
-      Set<ExecutableElement> inheritedOperatorElements =
-          _inheritedElements.where((e) {
-        return (e is MethodElement &&
-            e.isOperator &&
-            !operatorNames.contains(e.name));
-      }).toSet();
-      for (ExecutableElement e in inheritedOperatorElements) {
-        Operator o = ModelElement.from(e, library, packageGraph,
-            enclosingContainer: this);
-        _inheritedOperators.add(o);
-      }
-      _inheritedOperators.sort(byName);
-    }
-    return _inheritedOperators;
-  }
-
-  Iterable<Operator> get publicInheritedOperators =>
-      utils.filterNonPublic(inheritedOperators);
-
-  List<Field> get inheritedProperties {
-    if (_inheritedProperties == null) {
-      _inheritedProperties = _allFields.where((f) => f.isInherited).toList()
-        ..sort(byName);
-    }
-    return _inheritedProperties;
-  }
-
-  Iterable<Field> get publicInheritedProperties =>
-      utils.filterNonPublic(inheritedProperties);
-
-  Iterable<Method> get publicInstanceMethods => instanceMethods;
-
-  List<DefinedElementType> get interfaces => _interfaces;
-
-  Iterable<DefinedElementType> get publicInterfaces =>
-      utils.filterNonPublic(interfaces);
-
-  bool get isAbstract => _cls.isAbstract;
-
-  @override
-  bool get isCanonical => super.isCanonical && isPublic;
-
-  bool get isErrorOrException {
-    bool _doCheck(ClassElement element) {
-      return (element.library.isDartCore &&
-          (element.name == 'Exception' || element.name == 'Error'));
-    }
-
-    // if this class is itself Error or Exception, return true
-    if (_doCheck(_cls)) return true;
-
-    return _cls.allSupertypes.map((t) => t.element).any(_doCheck);
-  }
-
-  /// Returns true if [other] is a parent class for this class.
-  @override
-  bool isInheritingFrom(covariant Class other) =>
-      superChain.map((et) => (et.element as Class)).contains(other);
-
-  @override
-  String get kind => 'class';
-
-  List<DefinedElementType> get mixins => _mixins;
-
-  Iterable<DefinedElementType> get publicMixins =>
-      utils.filterNonPublic(mixins);
-
-  @override
-  DefinedElementType get modelType => super.modelType;
-
-  /// Not the same as superChain as it may include mixins.
-  /// It's really not even the same as ordinary Dart inheritance, either,
-  /// because we pretend that interfaces are part of the inheritance chain
-  /// to include them in the set of things we might link to for documentation
-  /// purposes in abstract classes.
-  List<Class> _inheritanceChain;
-
-  List<Class> get inheritanceChain {
-    if (_inheritanceChain == null) {
-      _inheritanceChain = [];
-      _inheritanceChain.add(this);
-
-      /// Caching should make this recursion a little less painful.
-      for (Class c in mixins.reversed.map((e) => (e.element as Class))) {
-        _inheritanceChain.addAll(c.inheritanceChain);
-      }
-
-      for (Class c in superChain.map((e) => (e.element as Class))) {
-        _inheritanceChain.addAll(c.inheritanceChain);
-      }
-
-      /// Interfaces need to come last, because classes in the superChain might
-      /// implement them even when they aren't mentioned.
-      _inheritanceChain.addAll(
-          interfaces.expand((e) => (e.element as Class).inheritanceChain));
-    }
-    return _inheritanceChain.toList(growable: false);
-  }
-
-  List<DefinedElementType> get superChain {
-    List<DefinedElementType> typeChain = [];
-    DefinedElementType parent = supertype;
-    while (parent != null) {
-      typeChain.add(parent);
-      if (parent.type is InterfaceType) {
-        // Avoid adding [Object] to the superChain (_supertype already has this
-        // check)
-        if ((parent.type as InterfaceType)?.superclass?.superclass == null) {
-          parent = null;
-        } else {
-          parent = ElementType.from(
-              (parent.type as InterfaceType).superclass, library, packageGraph);
-        }
-      } else {
-        parent = (parent.element as Class).supertype;
-      }
-    }
-    return typeChain;
-  }
-
-  Iterable<DefinedElementType> get publicSuperChain =>
-      utils.filterNonPublic(superChain);
-
-  Iterable<DefinedElementType> get publicSuperChainReversed =>
-      publicSuperChain.toList().reversed;
-
-  List<ExecutableElement> __inheritedElements;
-
-  List<ExecutableElement> get _inheritedElements {
-    if (__inheritedElements == null) {
-      var classElement = element as ClassElement;
-      if (classElement.isDartCoreObject) {
-        return __inheritedElements = <ExecutableElement>[];
-      }
-
-      var inheritance = definingLibrary.inheritanceManager;
-      var classType = classElement.thisType;
-      var cmap = inheritance.getInheritedConcreteMap(classType);
-      var imap = inheritance.getInheritedMap(classType);
-
-      var combinedMap = <String, ExecutableElement>{};
-      for (var nameObj in cmap.keys) {
-        combinedMap[nameObj.name] = cmap[nameObj];
-      }
-      for (var nameObj in imap.keys) {
-        combinedMap[nameObj.name] ??= imap[nameObj];
-      }
-
-      __inheritedElements = combinedMap.values.toList();
-    }
-    return __inheritedElements;
-  }
-
-  /// Internal only because subclasses are allowed to override how
-  /// these are mapped to [allInheritedFields] and so forth.
-  @override
-  List<Field> get _allFields {
-    if (_fields != null) return _fields;
-    _fields = [];
-    Set<PropertyAccessorElement> inheritedAccessors = Set()
-      ..addAll(_inheritedElements.whereType<PropertyAccessorElement>());
-
-    // This structure keeps track of inherited accessors, allowing lookup
-    // by field name (stripping the '=' from setters).
-    Map<String, List<PropertyAccessorElement>> accessorMap = Map();
-    for (PropertyAccessorElement accessorElement in inheritedAccessors) {
-      String name = accessorElement.name.replaceFirst('=', '');
-      accessorMap.putIfAbsent(name, () => []);
-      accessorMap[name].add(accessorElement);
-    }
-
-    // For half-inherited fields, the analyzer only links the non-inherited
-    // to the [FieldElement].  Compose our [Field] class by hand by looking up
-    // inherited accessors that may be related.
-    for (FieldElement f in _cls.fields) {
-      PropertyAccessorElement getterElement = f.getter;
-      if (getterElement == null && accessorMap.containsKey(f.name)) {
-        getterElement = accessorMap[f.name]
-            .firstWhere((e) => e.isGetter, orElse: () => null);
-      }
-      PropertyAccessorElement setterElement = f.setter;
-      if (setterElement == null && accessorMap.containsKey(f.name)) {
-        setterElement = accessorMap[f.name]
-            .firstWhere((e) => e.isSetter, orElse: () => null);
-      }
-      _addSingleField(getterElement, setterElement, inheritedAccessors, f);
-      accessorMap.remove(f.name);
-    }
-
-    // Now we only have inherited accessors who aren't associated with
-    // anything in cls._fields.
-    for (String fieldName in accessorMap.keys) {
-      List<PropertyAccessorElement> elements = accessorMap[fieldName].toList();
-      PropertyAccessorElement getterElement =
-          elements.firstWhere((e) => e.isGetter, orElse: () => null);
-      PropertyAccessorElement setterElement =
-          elements.firstWhere((e) => e.isSetter, orElse: () => null);
-      _addSingleField(getterElement, setterElement, inheritedAccessors);
-    }
-
-    _fields.sort(byName);
-    return _fields;
-  }
-
-  /// Add a single Field to _fields.
-  ///
-  /// If [f] is not specified, pick the FieldElement from the PropertyAccessorElement
-  /// whose enclosing class inherits from the other (defaulting to the getter)
-  /// and construct a Field using that.
-  void _addSingleField(
-      PropertyAccessorElement getterElement,
-      PropertyAccessorElement setterElement,
-      Set<PropertyAccessorElement> inheritedAccessors,
-      [FieldElement f]) {
-    ContainerAccessor getter =
-        ContainerAccessor.from(getterElement, inheritedAccessors, this);
-    ContainerAccessor setter =
-        ContainerAccessor.from(setterElement, inheritedAccessors, this);
-    // Rebind getterElement/setterElement as ModelElement.from can resolve
-    // MultiplyInheritedExecutableElements or resolve Members.
-    getterElement = getter?.element;
-    setterElement = setter?.element;
-    assert(!(getter == null && setter == null));
-    if (f == null) {
-      // Pick an appropriate FieldElement to represent this element.
-      // Only hard when dealing with a synthetic Field.
-      if (getter != null && setter == null) {
-        f = getterElement.variable;
-      } else if (getter == null && setter != null) {
-        f = setterElement.variable;
-      } else
-      /* getter != null && setter != null */ {
-        // In cases where a Field is composed of two Accessors defined in
-        // different places in the inheritance chain, there are two FieldElements
-        // for this single Field we're trying to compose.  Pick the one closest
-        // to this class on the inheritance chain.
-        if ((setter.enclosingElement)
-            .isInheritingFrom(getter.enclosingElement)) {
-          f = setterElement.variable;
-        } else {
-          f = getterElement.variable;
-        }
-      }
-    }
-    Field field;
-    if ((getter == null || getter.isInherited) &&
-        (setter == null || setter.isInherited)) {
-      // Field is 100% inherited.
-      field = ModelElement.from(f, library, packageGraph,
-          enclosingContainer: this, getter: getter, setter: setter);
-    } else {
-      // Field is <100% inherited (could be half-inherited).
-      // TODO(jcollins-g): Navigation is probably still confusing for
-      // half-inherited fields when traversing the inheritance tree.  Make
-      // this better, somehow.
-      field = ModelElement.from(f, library, packageGraph,
-          getter: getter, setter: setter);
-    }
-    _fields.add(field);
-  }
-
-  ClassElement get _cls => (element as ClassElement);
-
-  @override
-  List<Method> get _methods {
-    if (_allMethods != null) return _allMethods;
-
-    _allMethods = _cls.methods.map((e) {
-      return ModelElement.from(e, library, packageGraph) as Method;
-    }).toList(growable: false)
-      ..sort(byName);
-
-    return _allMethods;
-  }
-
-  // a stronger hash?
-  @override
-  List<TypeParameter> get typeParameters {
-    if (_typeParameters == null) {
-      _typeParameters = _cls.typeParameters.map((f) {
-        var lib = Library(f.enclosingElement.library, packageGraph);
-        return ModelElement.from(f, lib, packageGraph) as TypeParameter;
-      }).toList();
-    }
-    return _typeParameters;
-  }
-
-  @override
-  bool operator ==(o) =>
-      o is Class &&
-      name == o.name &&
-      o.library.name == library.name &&
-      o.library.package.name == library.package.name;
-}
-
-/// Extension methods
-class Extension extends Container
-    with TypeParameters, Categorization
-    implements EnclosedElement {
-  DefinedElementType extendedType;
-
-  Extension(
-      ExtensionElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph) {
-    extendedType =
-        ElementType.from(_extension.extendedType, library, packageGraph);
-  }
-
-  bool couldApplyTo(Class c) => _couldApplyTo(c.modelType);
-
-  /// Return true if this extension could apply to [t].
-  bool _couldApplyTo(DefinedElementType t) {
-    return t.instantiatedType == extendedType.instantiatedType ||
-        (t.instantiatedType.element == extendedType.instantiatedType.element &&
-            isSubtypeOf(t)) ||
-        isBoundSupertypeTo(t);
-  }
-
-  /// The instantiated to bounds [extendedType] of this extension is a subtype of
-  /// [t].
-  bool isSubtypeOf(DefinedElementType t) => packageGraph.typeSystem
-      .isSubtypeOf(extendedType.instantiatedType, t.instantiatedType);
-
-  bool isBoundSupertypeTo(DefinedElementType t) =>
-      _isBoundSupertypeTo(t.type, HashSet());
-
-  /// Returns true if at least one supertype (including via mixins and
-  /// interfaces) is equivalent to or a subtype of [extendedType] when
-  /// instantiated to bounds.
-  bool _isBoundSupertypeTo(
-      InterfaceType superType, HashSet<InterfaceType> visited) {
-    ClassElement superClass = superType?.element;
-    if (visited.contains(superType)) return false;
-    visited.add(superType);
-    if (superClass == extendedType.type.element &&
-        (superType == extendedType.instantiatedType ||
-            packageGraph.typeSystem
-                .isSubtypeOf(superType, extendedType.instantiatedType))) {
-      return true;
-    }
-    List<InterfaceType> supertypes = [];
-    ClassElementImpl.collectAllSupertypes(supertypes, superType, null);
-    for (InterfaceType toVisit in supertypes) {
-      if (_isBoundSupertypeTo(toVisit, visited)) return true;
-    }
-    return false;
-  }
-
-  @override
-  ModelElement get enclosingElement => library;
-
-  ExtensionElement get _extension => (element as ExtensionElement);
-
-  @override
-  String get kind => 'extension';
-
-  @override
-  List<Method> get _methods {
-    if (_allMethods != null) return _allMethods;
-
-    _allMethods = _extension.methods.map((e) {
-      return ModelElement.from(e, library, packageGraph) as Method;
-    }).toList(growable: false)
-      ..sort(byName);
-
-    return _allMethods;
-  }
-
-  @override
-  List<Field> get _allFields {
-    if (_fields != null) return _fields;
-    _fields = _extension.fields.map((f) {
-      Accessor getter, setter;
-      if (f.getter != null) {
-        getter = ContainerAccessor(f.getter, library, packageGraph);
-      }
-      if (f.setter != null) {
-        setter = ContainerAccessor(f.setter, library, packageGraph);
-      }
-      return ModelElement.from(f, library, packageGraph,
-          getter: getter, setter: setter) as Field;
-    }).toList(growable: false)
-      ..sort(byName);
-
-    return _fields;
-  }
-
-  // a stronger hash?
-  @override
-  List<TypeParameter> get typeParameters {
-    if (_typeParameters == null) {
-      _typeParameters = _extension.typeParameters.map((f) {
-        var lib = Library(f.enclosingElement.library, packageGraph);
-        return ModelElement.from(f, lib, packageGraph) as TypeParameter;
-      }).toList();
-    }
-    return _typeParameters;
-  }
-
-  @override
-  ParameterizedElementType get modelType => super.modelType;
-
-  List<ModelElement> _allModelElements;
-
-  List<ModelElement> get allModelElements {
-    if (_allModelElements == null) {
-      _allModelElements = List.from(
-          quiver.concat([
-            instanceMethods,
-            allInstanceFields,
-            allAccessors,
-            allOperators,
-            constants,
-            staticMethods,
-            staticProperties,
-            typeParameters,
-          ]),
-          growable: false);
-    }
-    return _allModelElements;
-  }
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
-  }
-}
-
-class Constructor extends ModelElement
-    with TypeParameters
-    implements EnclosedElement {
-  Constructor(
-      ConstructorElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph, null);
-
-  @override
-  CharacterLocation get characterLocation {
-    if (element.isSynthetic) {
-      // Make warnings for a synthetic constructor refer to somewhere reasonable
-      // since a synthetic constructor has no definition independent of the
-      // parent class.
-      return enclosingElement.characterLocation;
-    }
-    return super.characterLocation;
-  }
-
-  @override
-  // TODO(jcollins-g): Revisit this when dart-lang/sdk#31517 is implemented.
-  List<TypeParameter> get typeParameters =>
-      (enclosingElement as Class).typeParameters;
-
-  @override
-  ModelElement get enclosingElement =>
-      ModelElement.from(_constructor.enclosingElement, library, packageGraph);
-
-  String get fullKind {
-    if (isConst) return 'const $kind';
-    if (isFactory) return 'factory $kind';
-    return kind;
-  }
-
-  @override
-  String get fullyQualifiedName {
-    if (isDefaultConstructor) return super.fullyQualifiedName;
-    return '${library.name}.$name';
-  }
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$name.html';
-  }
-
-  @override
-  bool get isConst => _constructor.isConst;
-
-  bool get isDefaultConstructor => name == enclosingElement.name;
-
-  bool get isFactory => _constructor.isFactory;
-
-  @override
-  String get kind => 'constructor';
-
-  @override
-  DefinedElementType get modelType => super.modelType;
-
-  String _name;
-
-  @override
-  String get name {
-    if (_name == null) {
-      String constructorName = element.name;
-      if (constructorName.isEmpty) {
-        _name = enclosingElement.name;
-      } else {
-        _name = '${enclosingElement.name}.$constructorName';
-      }
-    }
-    return _name;
-  }
-
-  String _nameWithGenerics;
-
-  @override
-  String get nameWithGenerics {
-    if (_nameWithGenerics == null) {
-      String constructorName = element.name;
-      if (constructorName.isEmpty) {
-        _nameWithGenerics = '${enclosingElement.name}${genericParameters}';
-      } else {
-        _nameWithGenerics =
-            '${enclosingElement.name}${genericParameters}.$constructorName';
-      }
-    }
-    return _nameWithGenerics;
-  }
-
-  String get shortName {
-    if (name.contains('.')) {
-      return name.substring(_constructor.enclosingElement.name.length + 1);
-    } else {
-      return name;
-    }
-  }
-
-  ConstructorElement get _constructor => (element as ConstructorElement);
-}
-
-/// Bridges the gap between model elements and packages,
-/// both of which have documentation.
-abstract class Documentable extends Nameable {
-  String get documentation;
-
-  String get documentationAsHtml;
-
-  bool get hasDocumentation;
-
-  bool get hasExtendedDocumentation;
-
-  String get oneLineDoc;
-
-  PackageGraph get packageGraph;
-
-  bool get isDocumented;
-
-  DartdocOptionContext get config;
-}
-
-/// Mixin implementing dartdoc categorization for ModelElements.
-abstract class Categorization implements ModelElement {
-  @override
-  String _buildDocumentationAddition(String rawDocs) =>
-      _stripAndSetDartdocCategories(rawDocs ??= '');
-
-  /// Parse {@category ...} and related information in API comments, stripping
-  /// out that information from the given comments and returning the stripped
-  /// version.
-  String _stripAndSetDartdocCategories(String rawDocs) {
-    Set<String> _categorySet = Set();
-    Set<String> _subCategorySet = Set();
-    _hasCategorization = false;
-
-    rawDocs = rawDocs.replaceAllMapped(categoryRegexp, (match) {
-      _hasCategorization = true;
-      switch (match[1]) {
-        case 'category':
-        case 'api':
-          _categorySet.add(match[2].trim());
-          break;
-        case 'subCategory':
-          _subCategorySet.add(match[2].trim());
-          break;
-        case 'image':
-          _image = match[2].trim();
-          break;
-        case 'samples':
-          _samples = match[2].trim();
-          break;
-      }
-      return '';
-    });
-
-    if (_categorySet.isEmpty) {
-      // All objects are in the default category if not specified.
-      _categorySet.add(null);
-    }
-    if (_subCategorySet.isEmpty) {
-      // All objects are in the default subcategory if not specified.
-      _subCategorySet.add(null);
-    }
-    _categoryNames = _categorySet.toList()..sort();
-    _subCategoryNames = _subCategorySet.toList()..sort();
-    _image ??= '';
-    _samples ??= '';
-    return rawDocs;
-  }
-
-  bool get hasSubCategoryNames =>
-      subCategoryNames.length > 1 || subCategoryNames.first != null;
-  List<String> _subCategoryNames;
-
-  /// Either a set of strings containing all declared subcategories for this symbol,
-  /// or a set containing Null if none were declared.
-  List<String> get subCategoryNames {
-    // TODO(jcollins-g): avoid side-effect dependency
-    if (_subCategoryNames == null) documentationLocal;
-    return _subCategoryNames;
-  }
-
-  @override
-  bool get hasCategoryNames =>
-      categoryNames.length > 1 || categoryNames.first != null;
-  List<String> _categoryNames;
-
-  /// Either a set of strings containing all declared categories for this symbol,
-  /// or a set containing Null if none were declared.
-  List<String> get categoryNames {
-    // TODO(jcollins-g): avoid side-effect dependency
-    if (_categoryNames == null) documentationLocal;
-    return _categoryNames;
-  }
-
-  bool get hasImage => image.isNotEmpty;
-  String _image;
-
-  /// Either a URI to a defined image, or the empty string if none
-  /// was declared.
-  String get image {
-    // TODO(jcollins-g): avoid side-effect dependency
-    if (_image == null) documentationLocal;
-    return _image;
-  }
-
-  bool get hasSamples => samples.isNotEmpty;
-  String _samples;
-
-  /// Either a URI to documentation with samples, or the empty string if none
-  /// was declared.
-  String get samples {
-    // TODO(jcollins-g): avoid side-effect dependency
-    if (_samples == null) documentationLocal;
-    return _samples;
-  }
-
-  bool _hasCategorization;
-
-  Iterable<Category> _categories;
-
-  Iterable<Category> get categories {
-    if (_categories == null) {
-      _categories = categoryNames
-          .map((n) => package.nameToCategory[n])
-          .where((c) => c != null)
-          .toList()
-            ..sort();
-    }
-    return _categories;
-  }
-
-  Iterable<Category> get displayedCategories {
-    if (config.showUndocumentedCategories) return categories;
-    return categories.where((c) => c.isDocumented);
-  }
-
-  /// True if categories, subcategories, a documentation icon, or samples were
-  /// declared.
-  bool get hasCategorization {
-    if (_hasCategorization == null) documentationLocal;
-    return _hasCategorization;
-  }
-}
-
-/// A stripped down [CommentReference] containing only that information needed
-/// for Dartdoc.  Drops link to the [CommentReference] after construction.
-class ModelCommentReference {
-  final String name;
-  final Element staticElement;
-
-  ModelCommentReference(CommentReference ref)
-      : name = ref.identifier.name,
-        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 Element element;
-
-  final int _sourceOffset;
-  final int _sourceEnd;
-
-  ModelNode(AstNode sourceNode, this.element)
-      : _sourceOffset = sourceNode?.offset,
-        _sourceEnd = sourceNode?.end,
-        commentRefs = _commentRefsFor(sourceNode);
-
-  static List<ModelCommentReference> _commentRefsFor(AstNode node) {
-    if (node is AnnotatedNode &&
-        node?.documentationComment?.references != null) {
-      return node.documentationComment.references
-          .map((c) => ModelCommentReference(c))
-          .toList(growable: false);
-    }
-    return null;
-  }
-
-  String get sourceCode {
-    String contents = utils.getFileContentsFor(element);
-    if (_sourceOffset != null) {
-      // Find the start of the line, so that we can line up all the indents.
-      int i = _sourceOffset;
-      while (i > 0) {
-        i -= 1;
-        if (contents[i] == '\n' || contents[i] == '\r') {
-          i += 1;
-          break;
-        }
-      }
-
-      // Trim the common indent from the source snippet.
-      var start = _sourceOffset - (_sourceOffset - i);
-      String source = contents.substring(start, _sourceEnd);
-
-      source = const HtmlEscape().convert(source);
-      source = utils.stripIndentFromSource(source);
-      source = utils.stripDartdocCommentsFromSource(source);
-
-      return source.trim();
-    } else {
-      return '';
-    }
-  }
-}
-
-/// Classes extending this class have canonicalization support in Dartdoc.
-abstract class Canonicalization implements Locatable, Documentable {
-  bool get isCanonical;
-
-  Library get canonicalLibrary;
-
-  List<ModelCommentReference> _commentRefs;
-
-  List<ModelCommentReference> get commentRefs => _commentRefs;
-
-  /// Pieces of the location split by [locationSplitter] (removing package: and
-  /// slashes).
-  Set<String> get locationPieces;
-
-  List<ScoredCandidate> scoreCanonicalCandidates(List<Library> libraries) {
-    return libraries.map((l) => scoreElementWithLibrary(l)).toList()..sort();
-  }
-
-  ScoredCandidate scoreElementWithLibrary(Library lib) {
-    ScoredCandidate scoredCandidate = ScoredCandidate(this, lib);
-    Iterable<String> resplit(Set<String> items) sync* {
-      for (String item in items) {
-        for (String subItem in item.split('_')) {
-          yield subItem;
-        }
-      }
-    }
-
-    // Large boost for @canonicalFor, essentially overriding all other concerns.
-    if (lib.canonicalFor.contains(fullyQualifiedName)) {
-      scoredCandidate.alterScore(5.0, 'marked @canonicalFor');
-    }
-    // Penalty for deprecated libraries.
-    if (lib.isDeprecated) scoredCandidate.alterScore(-1.0, 'is deprecated');
-    // Give a big boost if the library has the package name embedded in it.
-    if (lib.package.namePieces.intersection(lib.namePieces).isEmpty) {
-      scoredCandidate.alterScore(1.0, 'embeds package name');
-    }
-    // Give a tiny boost for libraries with long names, assuming they're
-    // more specific (and therefore more likely to be the owner of this symbol).
-    scoredCandidate.alterScore(.01 * lib.namePieces.length, 'name is long');
-    // If we don't know the location of this element, return our best guess.
-    // TODO(jcollins-g): is that even possible?
-    assert(locationPieces.isNotEmpty);
-    if (locationPieces.isEmpty) return scoredCandidate;
-    // The more pieces we have of the location in our library name, the more we should boost our score.
-    scoredCandidate.alterScore(
-        lib.namePieces.intersection(locationPieces).length.toDouble() /
-            locationPieces.length.toDouble(),
-        'element location shares parts with name');
-    // If pieces of location at least start with elements of our library name, boost the score a little bit.
-    double scoreBoost = 0.0;
-    for (String piece in resplit(locationPieces)) {
-      for (String namePiece in lib.namePieces) {
-        if (piece.startsWith(namePiece)) {
-          scoreBoost += 0.001;
-        }
-      }
-    }
-    scoredCandidate.alterScore(
-        scoreBoost, 'element location parts start with parts of name');
-    return scoredCandidate;
-  }
-}
-
-class Dynamic extends ModelElement {
-  Dynamic(Element element, PackageGraph packageGraph)
-      : super(element, null, packageGraph, null);
-
-  /// [dynamic] is not a real object, and so we can't document it, so there
-  /// can be nothing canonical for it.
-  @override
-  ModelElement get canonicalModelElement => null;
-
-  @override
-  ModelElement get enclosingElement => throw UnsupportedError('');
-
-  /// And similiarly, even if someone references it directly it can have
-  /// no hyperlink.
-  @override
-  String get href => null;
-
-  @override
-  String get kind => 'dynamic';
-
-  @override
-  String get linkedName => 'dynamic';
-}
-
-/// An element that is enclosed by some other element.
-///
-/// Libraries are not enclosed.
-abstract class EnclosedElement {
-  ModelElement get enclosingElement;
-}
-
-// TODO(jcollins-g): Consider Enum as subclass of Container?
-class Enum extends Class {
-  Enum(ClassElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph);
-
-  List<EnumField> _instanceProperties;
-
-  @override
-  List<EnumField> get instanceProperties {
-    if (_instanceProperties == null) {
-      _instanceProperties = super
-          .instanceProperties
-          .map((Field p) => ModelElement.from(
-              p.element, p.library, p.packageGraph,
-              getter: p.getter, setter: p.setter) as EnumField)
-          .toList(growable: false);
-    }
-    return _instanceProperties;
-  }
-
-  @override
-  String get kind => 'enum';
-}
-
-/// Enum's fields are virtual, so we do a little work to create
-/// usable values for the docs.
-class EnumField extends Field {
-  int _index;
-
-  EnumField(FieldElement element, Library library, PackageGraph packageGraph,
-      Accessor getter, Accessor setter)
-      : super(element, library, packageGraph, getter, setter);
-
-  EnumField.forConstant(this._index, FieldElement element, Library library,
-      PackageGraph packageGraph, Accessor getter)
-      : super(element, library, packageGraph, getter, null);
-
-  @override
-  String get constantValueBase {
-    if (name == 'values') {
-      return 'const List&lt;<wbr><span class="type-parameter">${_field.enclosingElement.name}</span>&gt;';
-    } else {
-      return 'const ${_field.enclosingElement.name}($_index)';
-    }
-  }
-
-  @override
-  List<ModelElement> get documentationFrom {
-    if (name == 'values' || name == 'index') return [this];
-    return super.documentationFrom;
-  }
-
-  @override
-  String get documentation {
-    if (name == 'values') {
-      return 'A constant List of the values in this enum, in order of their declaration.';
-    } else if (name == 'index') {
-      return 'The integer index of this enum.';
-    } else {
-      return super.documentation;
-    }
-  }
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(!(canonicalLibrary == null || canonicalEnclosingContainer == null));
-    assert(canonicalLibrary == library);
-    assert(canonicalEnclosingContainer == enclosingElement);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${(enclosingElement as Class).fileName}';
-  }
-
-  @override
-  String get linkedName => name;
-
-  @override
-  bool get isCanonical {
-    if (name == 'index') return false;
-    // If this is something inherited from Object, e.g. hashCode, let the
-    // normal rules apply.
-    if (_index == null) {
-      return super.isCanonical;
-    }
-    // TODO(jcollins-g): We don't actually document this as a separate entity;
-    //                   do that or change this to false and deal with the
-    //                   consequences.
-    return true;
-  }
-
-  @override
-  String get oneLineDoc => documentationAsHtml;
-
-  @override
-  Inheritable get overriddenElement => null;
-}
-
-class Field extends ModelElement
-    with GetterSetterCombo, ContainerMember, Inheritable
-    implements EnclosedElement {
-  bool _isInherited = false;
-  Container _enclosingClass;
-  @override
-  final ContainerAccessor getter;
-  @override
-  final ContainerAccessor setter;
-
-  Field(FieldElement element, Library library, PackageGraph packageGraph,
-      this.getter, this.setter)
-      : super(element, library, packageGraph, null) {
-    assert(getter != null || setter != null);
-    if (getter != null) getter._enclosingCombo = this;
-    if (setter != null) setter._enclosingCombo = this;
-    _setModelType();
-  }
-
-  factory Field.inherited(
-      FieldElement element,
-      Class enclosingClass,
-      Library library,
-      PackageGraph packageGraph,
-      Accessor getter,
-      Accessor setter) {
-    Field newField = Field(element, library, packageGraph, getter, setter);
-    newField._isInherited = true;
-    newField._enclosingClass = enclosingClass;
-    // Can't set _isInherited to true if this is the defining element, because
-    // that would mean it isn't inherited.
-    assert(newField.enclosingElement != newField.definingEnclosingContainer);
-    return newField;
-  }
-
-  @override
-  String get documentation {
-    // Verify that hasSetter and hasGetterNoSetter are mutually exclusive,
-    // to prevent displaying more or less than one summary.
-    if (isPublic) {
-      Set<bool> assertCheck = Set()
-        ..addAll([hasPublicSetter, hasPublicGetterNoSetter]);
-      assert(assertCheck.containsAll([true, false]));
-    }
-    documentationFrom;
-    return super.documentation;
-  }
-
-  @override
-  ModelElement get enclosingElement {
-    if (_enclosingClass == null) {
-      _enclosingClass =
-          ModelElement.from(_field.enclosingElement, library, packageGraph);
-    }
-    return _enclosingClass;
-  }
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalEnclosingContainer == enclosingElement);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$fileName';
-  }
-
-  @override
-  bool get isConst => _field.isConst;
-
-  /// Returns true if the FieldElement is covariant, or if the first parameter
-  /// for the setter is covariant.
-  @override
-  bool get isCovariant =>
-      setter?.isCovariant == true || (_field as FieldElementImpl).isCovariant;
-
-  @override
-  bool get isFinal {
-    /// isFinal returns true for the field even if it has an explicit getter
-    /// (which means we should not document it as "final").
-    if (hasExplicitGetter) return false;
-    return _field.isFinal;
-  }
-
-  @override
-  bool get isInherited => _isInherited;
-
-  @override
-  String get kind => isConst ? 'constant' : 'property';
-
-  @override
-  List<String> get annotations {
-    List<String> all_annotations = List<String>();
-    all_annotations.addAll(super.annotations);
-
-    if (element is PropertyInducingElement) {
-      var pie = element as PropertyInducingElement;
-      all_annotations.addAll(annotationsFromMetadata(pie.getter?.metadata));
-      all_annotations.addAll(annotationsFromMetadata(pie.setter?.metadata));
-    }
-    return all_annotations.toList(growable: false);
-  }
-
-  @override
-  Set<String> get features {
-    Set<String> allFeatures = super.features..addAll(comboFeatures);
-    // Combo features can indicate 'inherited' and 'override' if
-    // either the getter or setter has one of those properties, but that's not
-    // really specific enough for [Field]s that have public getter/setters.
-    if (hasPublicGetter && hasPublicSetter) {
-      if (getter.isInherited && setter.isInherited) {
-        allFeatures.add('inherited');
-      } else {
-        allFeatures.remove('inherited');
-        if (getter.isInherited) allFeatures.add('inherited-getter');
-        if (setter.isInherited) allFeatures.add('inherited-setter');
-      }
-      if (getter.isOverride && setter.isOverride) {
-        allFeatures.add('override');
-      } else {
-        allFeatures.remove('override');
-        if (getter.isOverride) allFeatures.add('override-getter');
-        if (setter.isOverride) allFeatures.add('override-setter');
-      }
-    } else {
-      if (isInherited) allFeatures.add('inherited');
-      if (isOverride) allFeatures.add('override');
-    }
-    return allFeatures;
-  }
-
-  @override
-  String _computeDocumentationComment() {
-    String docs = getterSetterDocumentationComment;
-    if (docs.isEmpty) return _field.documentationComment;
-    return docs;
-  }
-
-  FieldElement get _field => (element as FieldElement);
-
-  @override
-  String get fileName => isConst ? '$name-constant.html' : '$name.html';
-
-  @override
-  String get sourceCode {
-    if (_sourceCode == null) {
-      // We could use a set to figure the dupes out, but that would lose ordering.
-      String fieldSourceCode = modelNode.sourceCode ?? '';
-      String getterSourceCode = getter?.sourceCode ?? '';
-      String setterSourceCode = setter?.sourceCode ?? '';
-      StringBuffer buffer = StringBuffer();
-      if (fieldSourceCode.isNotEmpty) {
-        buffer.write(fieldSourceCode);
-      }
-      if (buffer.isNotEmpty) buffer.write('\n\n');
-      if (fieldSourceCode != getterSourceCode) {
-        if (getterSourceCode != setterSourceCode) {
-          buffer.write(getterSourceCode);
-          if (buffer.isNotEmpty) buffer.write('\n\n');
-        }
-      }
-      if (fieldSourceCode != setterSourceCode) {
-        buffer.write(setterSourceCode);
-      }
-      _sourceCode = buffer.toString();
-    }
-    return _sourceCode;
-  }
-
-  void _setModelType() {
-    if (hasGetter) {
-      _modelType = getter.modelType;
-    }
-  }
-
-  @override
-  Inheritable get overriddenElement => null;
-}
-
-/// Mixin for top-level variables and fields (aka properties)
-mixin GetterSetterCombo on ModelElement {
-  Accessor get getter;
-
-  Iterable<Accessor> get allAccessors sync* {
-    for (Accessor a in [getter, setter]) {
-      if (a != null) yield a;
-    }
-  }
-
-  Set<String> get comboFeatures {
-    Set<String> allFeatures = Set();
-    if (hasExplicitGetter && hasPublicGetter) {
-      allFeatures.addAll(getter.features);
-    }
-    if (hasExplicitSetter && hasPublicSetter) {
-      allFeatures.addAll(setter.features);
-    }
-    if (readOnly && !isFinal && !isConst) allFeatures.add('read-only');
-    if (writeOnly) allFeatures.add('write-only');
-    if (readWrite) allFeatures.add('read / write');
-    return allFeatures;
-  }
-
-  @override
-  ModelElement enclosingElement;
-
-  bool get isInherited;
-
-  Expression get constantInitializer =>
-      (element as ConstVariableElement).constantInitializer;
-
-  String linkifyConstantValue(String original) {
-    if (constantInitializer is! InstanceCreationExpression) return original;
-    String constructorName = (constantInitializer as InstanceCreationExpression)
-        .constructorName
-        .toString();
-    Element staticElement =
-        (constantInitializer as InstanceCreationExpression).staticElement;
-    Constructor target = ModelElement.fromElement(staticElement, packageGraph);
-    Class targetClass = target.enclosingElement;
-    // TODO(jcollins-g): this logic really should be integrated into Constructor,
-    // but that's not trivial because of linkedName's usage.
-    if (targetClass.name == target.name) {
-      return original.replaceAll(constructorName, "${target.linkedName}");
-    }
-    return original.replaceAll("${targetClass.name}.${target.name}",
-        "${targetClass.linkedName}.${target.linkedName}");
-  }
-
-  String _buildConstantValueBase() {
-    String result = constantInitializer?.toString() ?? '';
-    return const HtmlEscape(HtmlEscapeMode.unknown).convert(result);
-  }
-
-  @override
-  CharacterLocation get characterLocation {
-    // Handle all synthetic possibilities.  Ordinarily, warnings for
-    // explicit setters/getters will be handled by those objects, but
-    // if a warning comes up for an enclosing synthetic field we have to
-    // put it somewhere.  So pick an accessor.
-    if (element.isSynthetic) {
-      if (hasExplicitGetter) return getter.characterLocation;
-      if (hasExplicitSetter) return setter.characterLocation;
-      assert(false, 'Field and accessors can not all be synthetic');
-    }
-    return super.characterLocation;
-  }
-
-  String get constantValue => linkifyConstantValue(constantValueBase);
-
-  String get constantValueTruncated =>
-      linkifyConstantValue(truncateString(constantValueBase, 200));
-  String _constantValueBase;
-
-  String get constantValueBase =>
-      _constantValueBase ??= _buildConstantValueBase();
-
-  bool get hasPublicGetter => hasGetter && getter.isPublic;
-
-  bool get hasPublicSetter => hasSetter && setter.isPublic;
-
-  @override
-  bool get isPublic => hasPublicGetter || hasPublicSetter;
-
-  @override
-  List<ModelElement> get documentationFrom {
-    if (_documentationFrom == null) {
-      _documentationFrom = [];
-      if (hasPublicGetter) {
-        _documentationFrom.addAll(getter.documentationFrom);
-      } else if (hasPublicSetter) {
-        _documentationFrom.addAll(setter.documentationFrom);
-      }
-      if (_documentationFrom.isEmpty ||
-          _documentationFrom.every((e) => e.documentationComment == '')) {
-        _documentationFrom = computeDocumentationFrom;
-      }
-    }
-    return _documentationFrom;
-  }
-
-  bool get hasAccessorsWithDocs => (hasPublicGetter &&
-          !getter.isSynthetic &&
-          getter.documentation.isNotEmpty ||
-      hasPublicSetter &&
-          !setter.isSynthetic &&
-          setter.documentation.isNotEmpty);
-
-  bool get getterSetterBothAvailable => (hasPublicGetter &&
-      getter.documentation.isNotEmpty &&
-      hasPublicSetter &&
-      setter.documentation.isNotEmpty);
-
-  @override
-  String get oneLineDoc {
-    if (_oneLineDoc == null) {
-      if (!hasAccessorsWithDocs) {
-        _oneLineDoc = computeOneLineDoc();
-      } else {
-        StringBuffer buffer = StringBuffer();
-        if (hasPublicGetter && getter.oneLineDoc.isNotEmpty) {
-          buffer.write('${getter.oneLineDoc}');
-        }
-        if (hasPublicSetter && setter.oneLineDoc.isNotEmpty) {
-          buffer.write('${getterSetterBothAvailable ? "" : setter.oneLineDoc}');
-        }
-        _oneLineDoc = buffer.toString();
-      }
-    }
-    return _oneLineDoc;
-  }
-
-  String get getterSetterDocumentationComment {
-    var buffer = StringBuffer();
-
-    if (hasPublicGetter && !getter.isSynthetic) {
-      assert(getter.documentationFrom.length == 1);
-      // We have to check against dropTextFrom here since documentationFrom
-      // doesn't yield the real elements for GetterSetterCombos.
-      if (!config.dropTextFrom
-          .contains(getter.documentationFrom.first.element.library.name)) {
-        String docs = getter.documentationFrom.first.documentationComment;
-        if (docs != null) buffer.write(docs);
-      }
-    }
-
-    if (hasPublicSetter && !setter.isSynthetic) {
-      assert(setter.documentationFrom.length == 1);
-      if (!config.dropTextFrom
-          .contains(setter.documentationFrom.first.element.library.name)) {
-        String docs = setter.documentationFrom.first.documentationComment;
-        if (docs != null) {
-          if (buffer.isNotEmpty) buffer.write('\n\n');
-          buffer.write(docs);
-        }
-      }
-    }
-    return buffer.toString();
-  }
-
-  String get linkedReturnType {
-    if (hasGetter) {
-      return getter.linkedReturnType;
-    } else {
-      return setter.linkedParamsNoMetadataOrNames;
-    }
-  }
-
-  @override
-  bool get canHaveParameters => hasSetter;
-
-  @override
-  List<Parameter> get parameters => setter.parameters;
-
-  @override
-  String get linkedParamsNoMetadata {
-    if (hasSetter) return setter.linkedParamsNoMetadata;
-    return null;
-  }
-
-  bool get hasExplicitGetter => hasPublicGetter && !getter.isSynthetic;
-
-  bool get hasExplicitSetter => hasPublicSetter && !setter.isSynthetic;
-
-  bool get hasGetter => getter != null;
-
-  bool get hasNoGetterSetter => !hasGetterOrSetter;
-
-  bool get hasGetterOrSetter => hasExplicitGetter || hasExplicitSetter;
-
-  bool get hasSetter => setter != null;
-
-  bool get hasPublicGetterNoSetter => (hasPublicGetter && !hasPublicSetter);
-
-  String get arrow {
-    // →
-    if (readOnly) return r'&#8594;';
-    // ←
-    if (writeOnly) return r'&#8592;';
-    // ↔
-    if (readWrite) return r'&#8596;';
-    throw UnsupportedError(
-        'GetterSetterCombo must be one of readOnly, writeOnly, or readWrite');
-  }
-
-  bool get readOnly => hasPublicGetter && !hasPublicSetter;
-
-  bool get readWrite => hasPublicGetter && hasPublicSetter;
-
-  bool get writeOnly => hasPublicSetter && !hasPublicGetter;
-
-  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 {
-  List<TopLevelVariable> _variables;
-  List<Element> _exportedAndLocalElements;
-  String _name;
-
-  factory Library(LibraryElement element, PackageGraph packageGraph) {
-    return packageGraph.findButDoNotCreateLibraryFor(element);
-  }
-
-  Library._(ResolvedLibraryResult libraryResult, PackageGraph packageGraph,
-      this._package)
-      : super(libraryResult.element, null, packageGraph, null) {
-    if (element == null) throw ArgumentError.notNull('element');
-
-    // Initialize [packageGraph]'s cache of ModelNodes for relevant
-    // elements in this library.
-    Map<String, CompilationUnit> _compilationUnitMap = Map();
-    _compilationUnitMap.addEntries(libraryResult.units
-        .map((ResolvedUnitResult u) => MapEntry(u.path, u.unit)));
-    _HashableChildLibraryElementVisitor((Element e) =>
-            packageGraph._populateModelNodeFor(e, _compilationUnitMap))
-        .visitElement(element);
-
-    // Initialize the list of elements defined in this library and
-    // exported via its export directives.
-    Set<Element> exportedAndLocalElements =
-        _libraryElement.exportNamespace.definedNames.values.toSet();
-    // TODO(jcollins-g): Consider switch to [_libraryElement.topLevelElements].
-    exportedAndLocalElements
-        .addAll(getDefinedElements(_libraryElement.definingCompilationUnit));
-    for (CompilationUnitElement cu in _libraryElement.parts) {
-      exportedAndLocalElements.addAll(getDefinedElements(cu));
-    }
-    _exportedAndLocalElements = exportedAndLocalElements.toList();
-
-    _package._allLibraries.add(this);
-  }
-
-  static Iterable<Element> getDefinedElements(
-      CompilationUnitElement compilationUnit) {
-    return quiver.concat([
-      compilationUnit.accessors,
-      compilationUnit.enums,
-      compilationUnit.extensions,
-      compilationUnit.functions,
-      compilationUnit.functionTypeAliases,
-      compilationUnit.mixins,
-      compilationUnit.topLevelVariables,
-      compilationUnit.types,
-    ]);
-  }
-
-  List<String> _allOriginalModelElementNames;
-
-  bool get isInSdk => _libraryElement.isInSdk;
-
-  final Package _package;
-
-  @override
-  Package get package {
-    // Everything must be in a package.  TODO(jcollins-g): Support other things
-    // that look like packages.
-    assert(_package != null);
-    return _package;
-  }
-
-  /// [allModelElements] resolved to their original names.
-  ///
-  /// A collection of [ModelElement.fullyQualifiedName]s for [ModelElement]s
-  /// documented with this library, but these ModelElements and names correspond
-  /// to the defining library where each originally came from with respect
-  /// to inheritance and reexporting.  Most useful for error reporting.
-  Iterable<String> get allOriginalModelElementNames {
-    if (_allOriginalModelElementNames == null) {
-      _allOriginalModelElementNames = allModelElements.map((e) {
-        Accessor getter;
-        Accessor setter;
-        if (e is GetterSetterCombo) {
-          if (e.hasGetter) {
-            getter = ModelElement.fromElement(e.getter.element, packageGraph);
-          }
-          if (e.hasSetter) {
-            setter = ModelElement.fromElement(e.setter.element, packageGraph);
-          }
-        }
-        return ModelElement.from(
-                e.element,
-                packageGraph.findButDoNotCreateLibraryFor(e.element),
-                packageGraph,
-                getter: getter,
-                setter: setter)
-            .fullyQualifiedName;
-      }).toList();
-    }
-    return _allOriginalModelElementNames;
-  }
-
-  @override
-  CharacterLocation get characterLocation {
-    if (element.nameOffset == -1) {
-      assert(isAnonymous,
-          'Only anonymous libraries are allowed to have no declared location');
-      return CharacterLocation(1, 1);
-    }
-    return super.characterLocation;
-  }
-
-  @override
-  CompilationUnitElement get compilationUnitElement =>
-      (element as LibraryElement).definingCompilationUnit;
-
-  @override
-  Iterable<Class> get classes => allClasses.where((c) => !c.isErrorOrException);
-
-  @override
-  Iterable<Extension> get extensions {
-    if (_extensions == null) {
-      _extensions = _exportedAndLocalElements
-          .whereType<ExtensionElement>()
-          .map((e) => ModelElement.from(e, this, packageGraph) as Extension)
-          .toList(growable: false)
-            ..sort(byName);
-    }
-    return _extensions;
-  }
-
-  SdkLibrary get sdkLib {
-    if (packageGraph.sdkLibrarySources.containsKey(element.librarySource)) {
-      return packageGraph.sdkLibrarySources[element.librarySource];
-    }
-    return null;
-  }
-
-  @override
-  bool get isPublic {
-    if (!super.isPublic) return false;
-    if (sdkLib != null && (sdkLib.isInternal || !sdkLib.isDocumented)) {
-      return false;
-    }
-    if (config.isLibraryExcluded(name) ||
-        config.isLibraryExcluded(element.librarySource.uri.toString())) {
-      return false;
-    }
-    return true;
-  }
-
-  @override
-  Iterable<TopLevelVariable> get constants {
-    if (_constants == null) {
-      // _getVariables() is already sorted.
-      _constants =
-          _getVariables().where((v) => v.isConst).toList(growable: false);
-    }
-    return _constants;
-  }
-
-  Set<Library> _packageImportedExportedLibraries;
-
-  /// Returns all libraries either imported by or exported by any public library
-  /// this library's package.  (Not [PackageGraph], but sharing a package name).
-  ///
-  /// Note: will still contain non-public libraries because those can be
-  /// imported or exported.
-  // TODO(jcollins-g): move this to [Package] once it really knows about
-  // more than one package.
-  Set<Library> get packageImportedExportedLibraries {
-    if (_packageImportedExportedLibraries == null) {
-      _packageImportedExportedLibraries = Set();
-      packageGraph.publicLibraries
-          .where((l) => l.packageName == packageName)
-          .forEach((l) {
-        _packageImportedExportedLibraries.addAll(l.importedExportedLibraries);
-      });
-    }
-    return _packageImportedExportedLibraries;
-  }
-
-  Set<Library> _importedExportedLibraries;
-
-  /// Returns all libraries either imported by or exported by this library,
-  /// recursively.
-  Set<Library> get importedExportedLibraries {
-    if (_importedExportedLibraries == null) {
-      _importedExportedLibraries = Set();
-      Set<LibraryElement> importedExportedLibraryElements = Set();
-      importedExportedLibraryElements
-          .addAll((element as LibraryElement).importedLibraries);
-      importedExportedLibraryElements
-          .addAll((element as LibraryElement).exportedLibraries);
-      for (LibraryElement l in importedExportedLibraryElements) {
-        Library lib = ModelElement.from(l, library, packageGraph);
-        _importedExportedLibraries.add(lib);
-        _importedExportedLibraries.addAll(lib.importedExportedLibraries);
-      }
-    }
-    return _importedExportedLibraries;
-  }
-
-  Map<String, Set<Library>> _prefixToLibrary;
-
-  /// Map of import prefixes ('import "foo" as prefix;') to [Library].
-  Map<String, Set<Library>> get prefixToLibrary {
-    if (_prefixToLibrary == null) {
-      _prefixToLibrary = {};
-      // It is possible to have overlapping prefixes.
-      for (ImportElement i in (element as LibraryElement).imports) {
-        // Ignore invalid imports.
-        if (i.prefix?.name != null && i.importedLibrary != null) {
-          _prefixToLibrary.putIfAbsent(i.prefix?.name, () => Set());
-          _prefixToLibrary[i.prefix?.name]
-              .add(ModelElement.from(i.importedLibrary, library, packageGraph));
-        }
-      }
-    }
-    return _prefixToLibrary;
-  }
-
-  String _dirName;
-
-  String get dirName {
-    if (_dirName == null) {
-      _dirName = name;
-      if (isAnonymous) {
-        _dirName = nameFromPath;
-      }
-      _dirName = _dirName.replaceAll(':', '-').replaceAll('/', '_');
-    }
-    return _dirName;
-  }
-
-  Set<String> _canonicalFor;
-
-  Set<String> get canonicalFor {
-    if (_canonicalFor == null) {
-      // TODO(jcollins-g): restructure to avoid using side effects.
-      _buildDocumentationAddition(documentationComment);
-    }
-    return _canonicalFor;
-  }
-
-  /// Hide canonicalFor from doc while leaving a note to ourselves to
-  /// help with ambiguous canonicalization determination.
-  ///
-  /// Example:
-  ///   {@canonicalFor libname.ClassName}
-  @override
-  String _buildDocumentationAddition(String rawDocs) {
-    rawDocs = super._buildDocumentationAddition(rawDocs);
-    Set<String> newCanonicalFor = Set();
-    Set<String> notFoundInAllModelElements = Set();
-    final canonicalRegExp = RegExp(r'{@canonicalFor\s([^}]+)}');
-    rawDocs = rawDocs.replaceAllMapped(canonicalRegExp, (Match match) {
-      newCanonicalFor.add(match.group(1));
-      notFoundInAllModelElements.add(match.group(1));
-      return '';
-    });
-    if (notFoundInAllModelElements.isNotEmpty) {
-      notFoundInAllModelElements.removeAll(allOriginalModelElementNames);
-    }
-    for (String notFound in notFoundInAllModelElements) {
-      warn(PackageWarning.ignoredCanonicalFor, message: notFound);
-    }
-    // TODO(jcollins-g): warn if a macro/tool _does_ generate an unexpected
-    // canonicalFor?
-    if (_canonicalFor == null) {
-      _canonicalFor = newCanonicalFor;
-    }
-    return rawDocs;
-  }
-
-  /// Libraries are not enclosed by anything.
-  @override
-  ModelElement get enclosingElement => null;
-
-  @override
-  List<Enum> get enums {
-    if (_enums == null) {
-      _enums = _exportedAndLocalElements
-          .whereType<ClassElement>()
-          .where((element) => element.isEnum)
-          .map((e) => ModelElement.from(e, this, packageGraph) as Enum)
-          .toList(growable: false)
-            ..sort(byName);
-    }
-    return _enums;
-  }
-
-  @override
-  List<Mixin> get mixins {
-    if (_mixins == null) {
-      /// Can not be [MixinElementImpl] because [ClassHandle]s are sometimes
-      /// returned from _exportedElements.
-      _mixins = _exportedAndLocalElements
-          .whereType<ClassElement>()
-          .where((ClassElement c) => c.isMixin)
-          .map((e) => ModelElement.from(e, this, packageGraph) as Mixin)
-          .toList(growable: false)
-            ..sort(byName);
-    }
-    return _mixins;
-  }
-
-  @override
-  List<Class> get exceptions {
-    if (_exceptions == null) {
-      _exceptions =
-          allClasses.where((c) => c.isErrorOrException).toList(growable: false);
-    }
-    return _exceptions;
-  }
-
-  @override
-  String get fileName => '$dirName-library.html';
-
-  @override
-  List<ModelFunction> get functions {
-    if (_functions == null) {
-      _functions =
-          _exportedAndLocalElements.whereType<FunctionElement>().map((e) {
-        return ModelElement.from(e, this, packageGraph) as ModelFunction;
-      }).toList(growable: false)
-            ..sort(byName);
-    }
-    return _functions;
-  }
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    return '${package.baseHref}${library.dirName}/$fileName';
-  }
-
-  InheritanceManager3 _inheritanceManager;
-
-  InheritanceManager3 get inheritanceManager {
-    if (_inheritanceManager == null) {
-      var typeSystem = element.context.typeSystem;
-      _inheritanceManager = InheritanceManager3(typeSystem);
-    }
-    return _inheritanceManager;
-  }
-
-  bool get isAnonymous => element.name == null || element.name.isEmpty;
-
-  @override
-  String get kind => 'library';
-
-  @override
-  Library get library => this;
-
-  @override
-  String get name {
-    if (_name == null) {
-      _name = getLibraryName(element);
-    }
-    return _name;
-  }
-
-  String _nameFromPath;
-
-  /// Generate a name for this library based on its location.
-  ///
-  /// nameFromPath provides filename collision-proofing for anonymous libraries
-  /// by incorporating more from the location of the anonymous library into
-  /// the name calculation.  Simple cases (such as an anonymous library in
-  /// 'lib') are the same, but this will include slashes and possibly colons
-  /// for anonymous libraries in subdirectories or other packages.
-  String get nameFromPath {
-    if (_nameFromPath == null) {
-      _nameFromPath = getNameFromPath(element, packageGraph.driver, package);
-    }
-    return _nameFromPath;
-  }
-
-  /// The real package, as opposed to the package we are documenting it with,
-  /// [PackageGraph.name]
-  String get packageName => packageMeta?.name ?? '';
-
-  /// The real packageMeta, as opposed to the package we are documenting with.
-  PackageMeta _packageMeta;
-
-  PackageMeta get packageMeta {
-    if (_packageMeta == null) {
-      _packageMeta = PackageMeta.fromElement(element, config);
-    }
-    return _packageMeta;
-  }
-
-  /// All variables ("properties") except constants.
-  @override
-  Iterable<TopLevelVariable> get properties {
-    if (_properties == null) {
-      _properties =
-          _getVariables().where((v) => !v.isConst).toList(growable: false);
-    }
-    return _properties;
-  }
-
-  @override
-  List<Typedef> get typedefs {
-    if (_typedefs == null) {
-      _typedefs = _exportedAndLocalElements
-          .whereType<FunctionTypeAliasElement>()
-          .map((e) => ModelElement.from(e, this, packageGraph) as Typedef)
-          .toList(growable: false)
-            ..sort(byName);
-    }
-    return _typedefs;
-  }
-
-  List<Class> get allClasses {
-    if (_classes == null) {
-      _classes = _exportedAndLocalElements
-          .whereType<ClassElement>()
-          .where((e) => !e.isMixin && !e.isEnum)
-          .map((e) => ModelElement.from(e, this, packageGraph) as Class)
-          .toList(growable: false)
-            ..sort(byName);
-    }
-    return _classes;
-  }
-
-  LibraryElement get _libraryElement => (element as LibraryElement);
-
-  Class getClassByName(String name) {
-    return allClasses.firstWhere((it) => it.name == name, orElse: () => null);
-  }
-
-  List<TopLevelVariable> _getVariables() {
-    if (_variables == null) {
-      Set<TopLevelVariableElement> elements = _exportedAndLocalElements
-          .whereType<TopLevelVariableElement>()
-          .toSet();
-      elements.addAll(_exportedAndLocalElements
-          .whereType<PropertyAccessorElement>()
-          .map((a) => a.variable));
-      _variables = [];
-      for (TopLevelVariableElement element in elements) {
-        Accessor getter;
-        if (element.getter != null) {
-          getter = ModelElement.from(element.getter, this, packageGraph);
-        }
-        Accessor setter;
-        if (element.setter != null) {
-          setter = ModelElement.from(element.setter, this, packageGraph);
-        }
-        ModelElement me = ModelElement.from(element, this, packageGraph,
-            getter: getter, setter: setter);
-        _variables.add(me);
-      }
-      _variables.sort(byName);
-    }
-    return _variables;
-  }
-
-  /// Reverses URIs if needed to get a package URI.
-  /// Not the same as [PackageGraph.name] because there we always strip all
-  /// path components; this function only strips the package prefix if the
-  /// library is part of the default package or if it is being documented
-  /// remotely.
-  static String getNameFromPath(
-      LibraryElement element, AnalysisDriver driver, Package package) {
-    String name;
-    if (element.source.uri.toString().startsWith('dart:')) {
-      name = element.source.uri.toString();
-    } else {
-      name = driver.sourceFactory.restoreUri(element.source).toString();
-    }
-    PackageMeta hidePackage;
-    if (package.documentedWhere == DocumentLocation.remote) {
-      hidePackage = package.packageMeta;
-    } else {
-      hidePackage = package.packageGraph.packageMeta;
-    }
-    // restoreUri must not result in another file URI.
-    assert(!name.startsWith('file:'));
-
-    String defaultPackagePrefix = 'package:$hidePackage/';
-    if (name.startsWith(defaultPackagePrefix)) {
-      name = name.substring(defaultPackagePrefix.length, name.length);
-    }
-    if (name.endsWith('.dart')) {
-      name = name.substring(0, name.length - '.dart'.length);
-    }
-    assert(!name.startsWith('file:'));
-    return name;
-  }
-
-  static String getLibraryName(LibraryElement element) {
-    var source = element.source;
-
-    if (source.uri.isScheme('dart')) {
-      return '${source.uri}';
-    }
-
-    var name = element.name;
-    if (name != null && name.isNotEmpty) {
-      return name;
-    }
-
-    name = path.basename(source.fullName);
-    if (name.endsWith('.dart')) {
-      name = name.substring(0, name.length - '.dart'.length);
-    }
-    return name;
-  }
-
-  Map<String, Set<ModelElement>> _modelElementsNameMap;
-
-  /// Map of [fullyQualifiedNameWithoutLibrary] to all matching [ModelElement]s
-  /// in this library.  Used for code reference lookups.
-  Map<String, Set<ModelElement>> get modelElementsNameMap {
-    if (_modelElementsNameMap == null) {
-      _modelElementsNameMap = Map<String, Set<ModelElement>>();
-      allModelElements.forEach((ModelElement modelElement) {
-        _modelElementsNameMap.putIfAbsent(
-            modelElement.fullyQualifiedNameWithoutLibrary, () => Set());
-        _modelElementsNameMap[modelElement.fullyQualifiedNameWithoutLibrary]
-            .add(modelElement);
-      });
-    }
-    return _modelElementsNameMap;
-  }
-
-  Map<Element, Set<ModelElement>> _modelElementsMap;
-
-  Map<Element, Set<ModelElement>> get modelElementsMap {
-    if (_modelElementsMap == null) {
-      Iterable<ModelElement> results = quiver.concat([
-        library.constants,
-        library.functions,
-        library.properties,
-        library.typedefs,
-        library.extensions.expand((e) {
-          return quiver.concat([
-            [e],
-            e.allModelElements
-          ]);
-        }),
-        library.allClasses.expand((c) {
-          return quiver.concat([
-            [c],
-            c.allModelElements
-          ]);
-        }),
-        library.enums.expand((e) {
-          return quiver.concat([
-            [e],
-            e.allModelElements
-          ]);
-        }),
-        library.mixins.expand((m) {
-          return quiver.concat([
-            [m],
-            m.allModelElements
-          ]);
-        }),
-      ]);
-      _modelElementsMap = Map<Element, Set<ModelElement>>();
-      results.forEach((modelElement) {
-        _modelElementsMap.putIfAbsent(modelElement.element, () => Set());
-        _modelElementsMap[modelElement.element].add(modelElement);
-      });
-      _modelElementsMap.putIfAbsent(element, () => Set());
-      _modelElementsMap[element].add(this);
-    }
-    return _modelElementsMap;
-  }
-
-  List<ModelElement> _allModelElements;
-
-  Iterable<ModelElement> get allModelElements {
-    if (_allModelElements == null) {
-      _allModelElements = [];
-      for (Set<ModelElement> modelElements in modelElementsMap.values) {
-        _allModelElements.addAll(modelElements);
-      }
-    }
-    return _allModelElements;
-  }
-
-  List<ModelElement> _allCanonicalModelElements;
-
-  Iterable<ModelElement> get allCanonicalModelElements {
-    return (_allCanonicalModelElements ??=
-        allModelElements.where((e) => e.isCanonical).toList());
-  }
-}
-
-class Method extends ModelElement
-    with ContainerMember, Inheritable, TypeParameters
-    implements EnclosedElement {
-  bool _isInherited = false;
-  Container _enclosingContainer;
-  @override
-  List<TypeParameter> typeParameters = [];
-
-  Method(MethodElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph, null) {
-    _calcTypeParameters();
-  }
-
-  Method.inherited(MethodElement element, this._enclosingContainer,
-      Library library, PackageGraph packageGraph,
-      {Member originalMember})
-      : super(element, library, packageGraph, originalMember) {
-    _isInherited = true;
-    _calcTypeParameters();
-  }
-
-  void _calcTypeParameters() {
-    typeParameters = _method.typeParameters.map((f) {
-      return ModelElement.from(f, library, packageGraph) as TypeParameter;
-    }).toList();
-  }
-
-  @override
-  CharacterLocation get characterLocation {
-    if (enclosingElement is Enum && name == 'toString') {
-      // The toString() method on Enums is special, treated as not having
-      // a definition location by the analyzer yet not being inherited, either.
-      // Just directly override our location with the Enum definition --
-      // this is OK because Enums can not inherit from each other nor
-      // have their definitions split between files.
-      return enclosingElement.characterLocation;
-    }
-    return super.characterLocation;
-  }
-
-  @override
-  ModelElement get enclosingElement {
-    if (_enclosingContainer == null) {
-      _enclosingContainer =
-          ModelElement.from(_method.enclosingElement, library, packageGraph);
-    }
-    return _enclosingContainer;
-  }
-
-  String get fullkind {
-    if (_method.isAbstract) return 'abstract $kind';
-    return kind;
-  }
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(!(canonicalLibrary == null || canonicalEnclosingContainer == null));
-    assert(canonicalLibrary == library);
-    assert(canonicalEnclosingContainer == enclosingElement);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/${fileName}';
-  }
-
-  @override
-  bool get isInherited => _isInherited;
-
-  bool get isOperator => false;
-
-  @override
-  Set<String> get features {
-    Set<String> allFeatures = super.features;
-    if (isInherited) allFeatures.add('inherited');
-    return allFeatures;
-  }
-
-  @override
-  bool get isStatic => _method.isStatic;
-
-  @override
-  String get kind => 'method';
-
-  String get linkedReturnType => modelType.createLinkedReturnTypeName();
-
-  @override
-  DefinedElementType get modelType => super.modelType;
-
-  @override
-  Method get overriddenElement {
-    if (_enclosingContainer is Extension) {
-      return null;
-    }
-    ClassElement parent = element.enclosingElement;
-    for (InterfaceType t in parent.allSupertypes) {
-      Element e = t.getMethod(element.name);
-      if (e != null) {
-        assert(e.enclosingElement is ClassElement);
-        return ModelElement.fromElement(e, packageGraph);
-      }
-    }
-    return null;
-  }
-
-  MethodElement get _method => (element as MethodElement);
-
-  /// Methods can not be covariant; always returns false.
-  @override
-  bool get isCovariant => false;
-}
-
-/// This class represents the score for a particular element; how likely
-/// it is that this is the canonical element.
-class ScoredCandidate implements Comparable<ScoredCandidate> {
-  final List<String> reasons = [];
-
-  /// The canonicalization element being scored.
-  final Canonicalization element;
-  final Library library;
-
-  /// The score accumulated so far.  Higher means it is more likely that this
-  /// is the intended canonical Library.
-  double score = 0.0;
-
-  ScoredCandidate(this.element, this.library);
-
-  void alterScore(double scoreDelta, String reason) {
-    score += scoreDelta;
-    if (scoreDelta != 0) {
-      reasons.add(
-          "${reason} (${scoreDelta >= 0 ? '+' : ''}${scoreDelta.toStringAsPrecision(4)})");
-    }
-  }
-
-  @override
-  int compareTo(ScoredCandidate other) {
-    //assert(element == other.element);
-    return score.compareTo(other.score);
-  }
-
-  @override
-  String toString() =>
-      "${library.name}: ${score.toStringAsPrecision(4)} - ${reasons.join(', ')}";
-}
-
-// TODO(jcollins-g): Implement resolution per ECMA-408 4th edition, page 39 #22.
-/// Resolves this very rare case incorrectly by picking the closest element in
-/// the inheritance and interface chains from the analyzer.
-ModelElement resolveMultiplyInheritedElement(
-    MultiplyInheritedExecutableElement e,
-    Library library,
-    PackageGraph packageGraph,
-    Class enclosingClass) {
-  Iterable<Inheritable> inheritables = e.inheritedElements
-      .map((ee) => ModelElement.fromElement(ee, packageGraph) as Inheritable);
-  Inheritable foundInheritable;
-  int lowIndex = enclosingClass.inheritanceChain.length;
-  for (var inheritable in inheritables) {
-    int index =
-        enclosingClass.inheritanceChain.indexOf(inheritable.enclosingElement);
-    if (index < lowIndex) {
-      foundInheritable = inheritable;
-      lowIndex = index;
-    }
-  }
-  return ModelElement.from(foundInheritable.element, library, packageGraph,
-      enclosingContainer: enclosingClass);
-}
-
-/// Classes implementing this have a public/private distinction.
-abstract class Privacy {
-  bool get isPublic;
-}
-
-/// This class is the foundation of Dartdoc's model for source code.
-/// All ModelElements are contained within a [PackageGraph], and laid out in a
-/// structure that mirrors the availability of identifiers in the various
-/// namespaces within that package.  For example, multiple [Class] objects
-/// for a particular identifier ([ModelElement.element]) may show up in
-/// different [Library]s as the identifier is reexported.
-///
-/// However, ModelElements have an additional concept vital to generating
-/// documentation: canonicalization.
-///
-/// A ModelElement is canonical if it is the element in the namespace where that
-/// element 'comes from' in the public interface to this [PackageGraph].  That often
-/// means the [ModelElement.library] is contained in [PackageGraph.libraries], but
-/// there are many exceptions and ambiguities the code tries to address here.
-///
-/// Non-canonical elements should refer to their canonical counterparts, making
-/// it easy to calculate links via [ModelElement.href] without having to
-/// know in a particular namespace which elements are canonical or not.
-/// A number of [PackageGraph] methods, such as [PackageGraph.findCanonicalModelElementFor]
-/// can help with this.
-///
-/// When documenting, Dartdoc should only write out files corresponding to
-/// canonical instances of ModelElement ([ModelElement.isCanonical]).  This
-/// helps prevent subtle bugs as generated output for a non-canonical
-/// ModelElement will reference itself as part of the "wrong" [Library]
-/// from the public interface perspective.
-abstract class ModelElement extends Canonicalization
-    with Privacy, Warnable, Locatable, Nameable, SourceCodeMixin, Indexable
-    implements Comparable, Documentable {
-  final Element _element;
-
-  // TODO(jcollins-g): This really wants a "member that has a type" class.
-  final Member _originalMember;
-  final Library _library;
-
-  ElementType _modelType;
-  String _rawDocs;
-  Documentation __documentation;
-  UnmodifiableListView<Parameter> _parameters;
-  String _linkedName;
-
-  String _fullyQualifiedName;
-  String _fullyQualifiedNameWithoutLibrary;
-
-  // TODO(jcollins-g): make _originalMember optional after dart-lang/sdk#15101
-  // is fixed.
-  ModelElement(
-      this._element, this._library, this._packageGraph, this._originalMember);
-
-  factory ModelElement.fromElement(Element e, PackageGraph p) {
-    Library lib = p.findButDoNotCreateLibraryFor(e);
-    Accessor getter;
-    Accessor setter;
-    if (e is PropertyInducingElement) {
-      getter = e.getter != null ? ModelElement.from(e.getter, lib, p) : null;
-      setter = e.setter != null ? ModelElement.from(e.setter, lib, p) : null;
-    }
-    return ModelElement.from(e, lib, p, getter: getter, setter: setter);
-  }
-
-  // TODO(jcollins-g): this way of using the optional parameter is messy,
-  // clean that up.
-  // TODO(jcollins-g): Refactor this into class-specific factories that
-  // call this one.
-  // TODO(jcollins-g): Enforce construction restraint.
-  // TODO(jcollins-g): Allow e to be null and drop extraneous null checks.
-  // TODO(jcollins-g): Auto-vivify element's defining library for library
-  // parameter when given a null.
-  /// Do not construct any ModelElements unless they are from this constructor.
-  /// Specify enclosingContainer if and only if this is to be an inherited or
-  /// extended object.
-  factory ModelElement.from(
-      Element e, Library library, PackageGraph packageGraph,
-      {Container enclosingContainer, Accessor getter, Accessor setter}) {
-    assert(packageGraph != null && e != null);
-    assert(library != null ||
-        e is ParameterElement ||
-        e is TypeParameterElement ||
-        e is GenericFunctionTypeElementImpl ||
-        e.kind == ElementKind.DYNAMIC);
-
-    Member originalMember;
-    // TODO(jcollins-g): Refactor object model to instantiate 'ModelMembers'
-    //                   for members?
-    if (e is Member) {
-      var basest = PackageGraph.getBasestElement(e);
-      originalMember = e;
-      e = basest;
-    }
-    Tuple3<Element, Library, Container> key =
-        Tuple3(e, library, enclosingContainer);
-    ModelElement newModelElement;
-    if (e.kind != ElementKind.DYNAMIC &&
-        packageGraph._allConstructedModelElements.containsKey(key)) {
-      newModelElement = packageGraph._allConstructedModelElements[key];
-      assert(newModelElement.element is! MultiplyInheritedExecutableElement);
-    } else {
-      if (e.kind == ElementKind.DYNAMIC) {
-        newModelElement = Dynamic(e, packageGraph);
-      }
-      if (e is MultiplyInheritedExecutableElement) {
-        newModelElement = resolveMultiplyInheritedElement(
-            e, library, packageGraph, enclosingContainer);
-      } else {
-        if (e is LibraryElement) {
-          newModelElement = Library(e, packageGraph);
-        }
-        // Also handles enums
-        if (e is ClassElement) {
-          if (e.isMixin) {
-            newModelElement = Mixin(e, library, packageGraph);
-          } else if (e.isEnum) {
-            newModelElement = Enum(e, library, packageGraph);
-          } else {
-            newModelElement = Class(e, library, packageGraph);
-          }
-        }
-        if (e is ExtensionElement) {
-          newModelElement = Extension(e, library, packageGraph);
-        }
-        if (e is FunctionElement) {
-          newModelElement = ModelFunction(e, library, packageGraph);
-        } else if (e is GenericFunctionTypeElement) {
-          // TODO(scheglov) "e" cannot be both GenericFunctionTypeElement,
-          // and FunctionTypeAliasElement or GenericTypeAliasElement.
-          if (e is FunctionTypeAliasElement) {
-            assert(e.name != '');
-            newModelElement = ModelFunctionTypedef(e, library, packageGraph);
-          } else {
-            if (e.enclosingElement is GenericTypeAliasElement) {
-              assert(e.enclosingElement.name != '');
-              newModelElement = ModelFunctionTypedef(e, library, packageGraph);
-            } else {
-              // Allowing null here is allowed as a workaround for
-              // dart-lang/sdk#32005.
-              assert(e.name == '' || e.name == null);
-              newModelElement = ModelFunctionAnonymous(e, packageGraph);
-            }
-          }
-        }
-        if (e is FunctionTypeAliasElement) {
-          newModelElement = Typedef(e, library, packageGraph);
-        }
-        if (e is FieldElement) {
-          if (enclosingContainer == null) {
-            if (e.isEnumConstant) {
-              int index =
-                  e.computeConstantValue().getField(e.name).toIntValue();
-              newModelElement = EnumField.forConstant(
-                  index, e, library, packageGraph, getter);
-              // ignore: unnecessary_cast
-            } else if (e.enclosingElement is ExtensionElement) {
-              newModelElement = Field(e, library, packageGraph, getter, setter);
-            } else if (e.enclosingElement is ClassElement &&
-                (e.enclosingElement as ClassElement).isEnum) {
-              newModelElement =
-                  EnumField(e, library, packageGraph, getter, setter);
-            } else {
-              newModelElement = Field(e, library, packageGraph, getter, setter);
-            }
-          } else {
-            // EnumFields can't be inherited, so this case is simpler.
-            newModelElement = Field.inherited(
-                e, enclosingContainer, library, packageGraph, getter, setter);
-          }
-        }
-        if (e is ConstructorElement) {
-          newModelElement = Constructor(e, library, packageGraph);
-        }
-        if (e is MethodElement && e.isOperator) {
-          if (enclosingContainer == null) {
-            newModelElement = Operator(e, library, packageGraph);
-          } else {
-            newModelElement = Operator.inherited(
-                e, enclosingContainer, library, packageGraph,
-                originalMember: originalMember);
-          }
-        }
-        if (e is MethodElement && !e.isOperator) {
-          if (enclosingContainer == null) {
-            newModelElement = Method(e, library, packageGraph);
-          } else {
-            newModelElement = Method.inherited(
-                e, enclosingContainer, library, packageGraph,
-                originalMember: originalMember);
-          }
-        }
-        if (e is TopLevelVariableElement) {
-          assert(getter != null || setter != null);
-          newModelElement =
-              TopLevelVariable(e, library, packageGraph, getter, setter);
-        }
-        if (e is PropertyAccessorElement) {
-          // TODO(jcollins-g): why test for ClassElement in enclosingElement?
-          if (e.enclosingElement is ClassElement ||
-              e is MultiplyInheritedExecutableElement) {
-            if (enclosingContainer == null) {
-              newModelElement = ContainerAccessor(e, library, packageGraph);
-            } else {
-              newModelElement = ContainerAccessor.inherited(
-                  e, library, packageGraph, enclosingContainer,
-                  originalMember: originalMember);
-            }
-          } else {
-            newModelElement = Accessor(e, library, packageGraph, null);
-          }
-        }
-        if (e is TypeParameterElement) {
-          newModelElement = TypeParameter(e, library, packageGraph);
-        }
-        if (e is ParameterElement) {
-          newModelElement = Parameter(e, library, packageGraph,
-              originalMember: originalMember);
-        }
-      }
-    }
-
-    if (newModelElement == null) throw "Unknown type ${e.runtimeType}";
-    if (enclosingContainer != null) assert(newModelElement is Inheritable);
-    // TODO(jcollins-g): Reenable Parameter caching when dart-lang/sdk#30146
-    //                   is fixed?
-    if (library != null && newModelElement is! Parameter) {
-      library.packageGraph._allConstructedModelElements[key] = newModelElement;
-      if (newModelElement is Inheritable) {
-        Tuple2<Element, Library> iKey = Tuple2(e, library);
-        library.packageGraph._allInheritableElements
-            .putIfAbsent(iKey, () => Set());
-        library.packageGraph._allInheritableElements[iKey].add(newModelElement);
-      }
-    }
-    if (newModelElement is GetterSetterCombo) {
-      assert(getter == null || newModelElement?.getter?.enclosingCombo != null);
-      assert(setter == null || newModelElement?.setter?.enclosingCombo != null);
-    }
-
-    assert(newModelElement.element is! MultiplyInheritedExecutableElement);
-    return newModelElement;
-  }
-
-  /// Stub for mustache4dart, or it will search enclosing elements to find
-  /// names for members.
-  bool get hasCategoryNames => false;
-
-  Set<Library> get exportedInLibraries {
-    return library
-        .packageGraph.libraryElementReexportedBy[this.element.library];
-  }
-
-  ModelNode _modelNode;
-
-  @override
-  ModelNode get modelNode =>
-      _modelNode ??= packageGraph._getModelNodeFor(element);
-
-  List<String> get annotations => annotationsFromMetadata(element.metadata);
-
-  /// Returns linked annotations from a given metadata set, with escaping.
-  List<String> annotationsFromMetadata(List<ElementAnnotation> md) {
-    List<String> annotationStrings = [];
-    if (md == null) return annotationStrings;
-    for (ElementAnnotation a in md) {
-      String annotation = (const HtmlEscape()).convert(a.toSource());
-      Element annotationElement = a.element;
-
-      ClassElement annotationClassElement;
-      if (annotationElement is ExecutableElement) {
-        annotationElement =
-            (annotationElement as ExecutableElement).returnType.element;
-      }
-      if (annotationElement is ClassElement) {
-        annotationClassElement = annotationElement;
-      }
-      ModelElement annotationModelElement =
-          packageGraph.findCanonicalModelElementFor(annotationElement);
-      // annotationElement can be null if the element can't be resolved.
-      Class annotationClass = packageGraph
-          .findCanonicalModelElementFor(annotationClassElement) as Class;
-      if (annotationClass == null &&
-          annotationElement != null &&
-          annotationClassElement != null) {
-        annotationClass =
-            ModelElement.fromElement(annotationClassElement, packageGraph)
-                as Class;
-      }
-      // Some annotations are intended to be invisible (@pragma)
-      if (annotationClass == null ||
-          !packageGraph.invisibleAnnotations.contains(annotationClass)) {
-        if (annotationModelElement != null) {
-          annotation = annotation.replaceFirst(
-              annotationModelElement.name, annotationModelElement.linkedName);
-        }
-        annotationStrings.add(annotation);
-      }
-    }
-    return annotationStrings;
-  }
-
-  bool _isPublic;
-
-  @override
-  bool get isPublic {
-    if (_isPublic == null) {
-      if (name == '') {
-        _isPublic = false;
-      } else if (this is! Library && (library == null || !library.isPublic)) {
-        _isPublic = false;
-      } else if (enclosingElement is Class &&
-          !(enclosingElement as Class).isPublic) {
-        _isPublic = false;
-      } else if (enclosingElement is Extension &&
-          !(enclosingElement as Extension).isPublic) {
-        _isPublic = false;
-      } else {
-        String docComment = documentationComment;
-        if (docComment == null) {
-          _isPublic = utils.hasPublicName(element);
-        } else {
-          _isPublic = utils.hasPublicName(element) &&
-              !(docComment.contains('@nodoc') ||
-                  docComment.contains('<nodoc>'));
-        }
-      }
-    }
-    return _isPublic;
-  }
-
-  @override
-  List<ModelCommentReference> get commentRefs {
-    if (_commentRefs == null) {
-      _commentRefs = [];
-      for (ModelElement from in documentationFrom) {
-        List<ModelElement> checkReferences = [from];
-        if (from is Accessor) {
-          checkReferences.add(from.enclosingCombo);
-        }
-        for (ModelElement e in checkReferences) {
-          _commentRefs.addAll(e.modelNode.commentRefs ?? []);
-        }
-      }
-    }
-    return _commentRefs;
-  }
-
-  DartdocOptionContext _config;
-
-  @override
-  DartdocOptionContext get config {
-    if (_config == null) {
-      _config =
-          DartdocOptionContext.fromContextElement(packageGraph.config, element);
-    }
-    return _config;
-  }
-
-  @override
-  Set<String> get locationPieces {
-    return Set.from(element.location
-        .toString()
-        .split(locationSplitter)
-        .where((s) => s.isNotEmpty));
-  }
-
-  Set<String> get features {
-    Set<String> allFeatures = Set<String>();
-    allFeatures.addAll(annotations);
-
-    // Replace the @override annotation with a feature that explicitly
-    // indicates whether an override has occurred.
-    allFeatures.remove('@override');
-
-    // Drop the plain "deprecated" annotation, that's indicated via
-    // strikethroughs. Custom @Deprecated() will still appear.
-    allFeatures.remove('@deprecated');
-    // const and static are not needed here because const/static elements get
-    // their own sections in the doc.
-    if (isFinal) allFeatures.add('final');
-    return allFeatures;
-  }
-
-  String get featuresAsString {
-    List<String> allFeatures = features.toList()..sort(byFeatureOrdering);
-    return allFeatures.join(', ');
-  }
-
-  bool get canHaveParameters =>
-      element is ExecutableElement ||
-      element is FunctionTypedElement ||
-      element is FunctionTypeAliasElement;
-
-  ModelElement _buildCanonicalModelElement() {
-    Container preferredClass;
-    if (enclosingElement is Class || enclosingElement is Extension) {
-      preferredClass = enclosingElement;
-    }
-    return packageGraph.findCanonicalModelElementFor(element,
-        preferredClass: preferredClass);
-  }
-
-  // Returns the canonical ModelElement for this ModelElement, or null
-  // if there isn't one.
-  ModelElement _canonicalModelElement;
-
-  ModelElement get canonicalModelElement =>
-      _canonicalModelElement ??= _buildCanonicalModelElement();
-
-  List<ModelElement> _documentationFrom;
-
-  // TODO(jcollins-g): untangle when mixins can call super
-  @override
-  List<ModelElement> get documentationFrom {
-    if (_documentationFrom == null) {
-      _documentationFrom = computeDocumentationFrom;
-    }
-    return _documentationFrom;
-  }
-
-  bool get hasSourceHref => sourceHref.isNotEmpty;
-  String _sourceHref;
-
-  String get sourceHref {
-    _sourceHref ??= SourceLinker.fromElement(this).href();
-    return _sourceHref;
-  }
-
-  /// Returns the ModelElement(s) from which we will get documentation.
-  /// Can be more than one if this is a Field composing documentation from
-  /// multiple Accessors.
-  ///
-  /// This getter will walk up the inheritance hierarchy
-  /// to find docs, if the current class doesn't have docs
-  /// for this element.
-  List<ModelElement> get computeDocumentationFrom {
-    List<ModelElement> docFrom;
-
-    if (documentationComment == null &&
-        canOverride() &&
-        this is Inheritable &&
-        (this as Inheritable).overriddenElement != null) {
-      docFrom = (this as Inheritable).overriddenElement.documentationFrom;
-    } else if (this is Inheritable && (this as Inheritable).isInherited) {
-      Inheritable thisInheritable = (this as Inheritable);
-      ModelElement fromThis = ModelElement.fromElement(
-          element, thisInheritable.definingEnclosingContainer.packageGraph);
-      docFrom = fromThis.documentationFrom;
-    } else {
-      docFrom = [this];
-    }
-    return docFrom;
-  }
-
-  String _buildDocumentationLocal() => _buildDocumentationBaseSync();
-
-  /// Override this to add more features to the documentation builder in a
-  /// subclass.
-  String _buildDocumentationAddition(String docs) => docs ??= '';
-
-  /// Separate from _buildDocumentationLocal for overriding.
-  String _buildDocumentationBaseSync() {
-    assert(_rawDocs == null,
-        'reentrant calls to _buildDocumentation* not allowed');
-    // Do not use the sync method if we need to evaluate tools or templates.
-    assert(!isCanonical ||
-        !needsPrecacheRegExp.hasMatch(documentationComment ?? ''));
-    if (config.dropTextFrom.contains(element.library.name)) {
-      _rawDocs = '';
-    } else {
-      _rawDocs = documentationComment ?? '';
-      _rawDocs = stripComments(_rawDocs) ?? '';
-      _rawDocs = _injectExamples(_rawDocs);
-      _rawDocs = _injectYouTube(_rawDocs);
-      _rawDocs = _injectAnimations(_rawDocs);
-      _rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
-    }
-    _rawDocs = _buildDocumentationAddition(_rawDocs);
-    return _rawDocs;
-  }
-
-  /// Separate from _buildDocumentationLocal for overriding.  Can only be
-  /// used as part of [PackageGraph.setUpPackageGraph].
-  Future<String> _buildDocumentationBase() async {
-    assert(_rawDocs == null,
-        'reentrant calls to _buildDocumentation* not allowed');
-    // Do not use the sync method if we need to evaluate tools or templates.
-    if (config.dropTextFrom.contains(element.library.name)) {
-      _rawDocs = '';
-    } else {
-      _rawDocs = documentationComment ?? '';
-      _rawDocs = stripComments(_rawDocs) ?? '';
-      // Must evaluate tools first, in case they insert any other directives.
-      _rawDocs = await _evaluateTools(_rawDocs);
-      _rawDocs = _injectExamples(_rawDocs);
-      _rawDocs = _injectYouTube(_rawDocs);
-      _rawDocs = _injectAnimations(_rawDocs);
-      _rawDocs = _stripMacroTemplatesAndAddToIndex(_rawDocs);
-      _rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
-    }
-    _rawDocs = _buildDocumentationAddition(_rawDocs);
-    return _rawDocs;
-  }
-
-  /// Returns the documentation for this literal element unless
-  /// [config.dropTextFrom] indicates it should not be returned.  Macro
-  /// definitions are stripped, but macros themselves are not injected.  This
-  /// is a two stage process to avoid ordering problems.
-  String _documentationLocal;
-
-  String get documentationLocal =>
-      _documentationLocal ??= _buildDocumentationLocal();
-
-  /// Returns the docs, stripped of their leading comments syntax.
-  @override
-  String get documentation {
-    return _injectMacros(
-        documentationFrom.map((e) => e.documentationLocal).join('<p>'));
-  }
-
-  Library get definingLibrary =>
-      packageGraph.findButDoNotCreateLibraryFor(element);
-
-  Library _canonicalLibrary;
-
-  // _canonicalLibrary can be null so we can't check against null to see whether
-  // we tried to compute it before.
-  bool _canonicalLibraryIsSet = false;
-
-  @override
-  Library get canonicalLibrary {
-    if (!_canonicalLibraryIsSet) {
-      // This is not accurate if we are constructing the Package.
-      assert(packageGraph.allLibrariesAdded);
-      // Since we're may be looking for a library, find the [Element] immediately
-      // contained by a [CompilationUnitElement] in the tree.
-      Element topLevelElement = element;
-      while (topLevelElement != null &&
-          topLevelElement.enclosingElement is! LibraryElement &&
-          topLevelElement.enclosingElement is! CompilationUnitElement &&
-          topLevelElement.enclosingElement != null) {
-        topLevelElement = topLevelElement.enclosingElement;
-      }
-
-      // Privately named elements can never have a canonical library, so
-      // just shortcut them out.
-      if (!utils.hasPublicName(element)) {
-        _canonicalLibrary = null;
-      } else if (!packageGraph.localPublicLibraries.contains(definingLibrary)) {
-        List<Library> candidateLibraries = definingLibrary.exportedInLibraries
-            ?.where((l) =>
-                l.isPublic &&
-                l.package.documentedWhere != DocumentLocation.missing)
-            ?.toList();
-
-        if (candidateLibraries != null) {
-          candidateLibraries = candidateLibraries.where((l) {
-            Element lookup = (l.element as LibraryElement)
-                .exportNamespace
-                .definedNames[topLevelElement?.name];
-            if (lookup is PropertyAccessorElement) {
-              lookup = (lookup as PropertyAccessorElement).variable;
-            }
-            if (topLevelElement == lookup) return true;
-            return false;
-          }).toList();
-
-          // Avoid claiming canonicalization for elements outside of this element's
-          // defining package.
-          // TODO(jcollins-g): Make the else block unconditional.
-          if (candidateLibraries.isNotEmpty &&
-              !candidateLibraries
-                  .any((l) => l.package == definingLibrary.package)) {
-            warn(PackageWarning.reexportedPrivateApiAcrossPackages,
-                message: definingLibrary.package.fullyQualifiedName,
-                referredFrom: candidateLibraries);
-          } else {
-            candidateLibraries
-                .removeWhere((l) => l.package != definingLibrary.package);
-          }
-
-          // Start with our top-level element.
-          ModelElement warnable =
-              ModelElement.fromElement(topLevelElement, packageGraph);
-          if (candidateLibraries.length > 1) {
-            // Heuristic scoring to determine which library a human likely
-            // considers this element to be primarily 'from', and therefore,
-            // canonical.  Still warn if the heuristic isn't that confident.
-            List<ScoredCandidate> scoredCandidates =
-                warnable.scoreCanonicalCandidates(candidateLibraries);
-            candidateLibraries =
-                scoredCandidates.map((s) => s.library).toList();
-            double secondHighestScore =
-                scoredCandidates[scoredCandidates.length - 2].score;
-            double highestScore = scoredCandidates.last.score;
-            double confidence = highestScore - secondHighestScore;
-            String message =
-                "${candidateLibraries.map((l) => l.name)} -> ${candidateLibraries.last.name} (confidence ${confidence.toStringAsPrecision(4)})";
-            List<String> debugLines = [];
-            debugLines.addAll(scoredCandidates.map((s) => '${s.toString()}'));
-
-            if (confidence < config.ambiguousReexportScorerMinConfidence) {
-              warnable.warn(PackageWarning.ambiguousReexport,
-                  message: message, extendedDebug: debugLines);
-            }
-          }
-          if (candidateLibraries.isNotEmpty) {
-            _canonicalLibrary = candidateLibraries.last;
-          }
-        }
-      } else {
-        _canonicalLibrary = definingLibrary;
-      }
-      // Only pretend when not linking to remote packages.
-      if (this is Inheritable && !config.linkToRemote) {
-        if ((this as Inheritable).isInherited &&
-            _canonicalLibrary == null &&
-            packageGraph.publicLibraries.contains(library)) {
-          // In the event we've inherited a field from an object that isn't directly reexported,
-          // we may need to pretend we are canonical for this.
-          _canonicalLibrary = library;
-        }
-      }
-      _canonicalLibraryIsSet = true;
-    }
-    assert(_canonicalLibrary == null ||
-        packageGraph.publicLibraries.contains(_canonicalLibrary));
-    return _canonicalLibrary;
-  }
-
-  @override
-  bool get isCanonical {
-    if (library == canonicalLibrary) {
-      if (this is Inheritable) {
-        Inheritable i = (this as Inheritable);
-        // If we're the defining element, or if the defining element is not
-        // in the set of libraries being documented, then this element
-        // should be treated as canonical (given library == canonicalLibrary).
-        if (i.enclosingElement == i.canonicalEnclosingContainer) {
-          return true;
-        } else {
-          return false;
-        }
-      }
-      // If there's no inheritance to deal with, we're done.
-      return true;
-    }
-    return false;
-  }
-
-  String _htmlDocumentation;
-
-  @override
-  String get documentationAsHtml {
-    if (_htmlDocumentation != null) return _htmlDocumentation;
-    _htmlDocumentation = _injectHtmlFragments(_documentation.asHtml);
-    return _htmlDocumentation;
-  }
-
-  @override
-  Element get element => _element;
-
-  @override
-  String get location {
-    // Call nothing from here that can emit warnings or you'll cause stack overflows.
-    if (characterLocation != null) {
-      return "(${path.toUri(sourceFileName)}:${characterLocation.toString()})";
-    }
-    return "(${path.toUri(sourceFileName)})";
-  }
-
-  /// Returns a link to extended documentation, or the empty string if that
-  /// does not exist.
-  String get extendedDocLink {
-    if (hasExtendedDocumentation) {
-      return '<a href="${href}">[...]</a>';
-    }
-    return '';
-  }
-
-  String get fileName => "${name}.html";
-
-  /// Returns the fully qualified name.
-  ///
-  /// For example: libraryName.className.methodName
-  @override
-  String get fullyQualifiedName {
-    return (_fullyQualifiedName ??= _buildFullyQualifiedName());
-  }
-
-  String get fullyQualifiedNameWithoutLibrary {
-    // Remember, periods are legal in library names.
-    if (_fullyQualifiedNameWithoutLibrary == null) {
-      _fullyQualifiedNameWithoutLibrary =
-          fullyQualifiedName.replaceFirst("${library.fullyQualifiedName}.", '');
-    }
-    return _fullyQualifiedNameWithoutLibrary;
-  }
-
-  String get sourceFileName => element.source.fullName;
-
-  CharacterLocation _characterLocation;
-  bool _characterLocationIsSet = false;
-
-  @override
-  CharacterLocation get characterLocation {
-    if (!_characterLocationIsSet) {
-      LineInfo lineInfo = compilationUnitElement.lineInfo;
-      _characterLocationIsSet = true;
-      assert(element.nameOffset >= 0,
-          'Invalid location data for element: $fullyQualifiedName');
-      assert(lineInfo != null,
-          'No lineInfo data available for element: $fullyQualifiedName');
-      if (element.nameOffset >= 0) {
-        _characterLocation = lineInfo?.getLocation(element.nameOffset);
-      }
-    }
-    return _characterLocation;
-  }
-
-  CompilationUnitElement get compilationUnitElement =>
-      element.getAncestor((e) => e is CompilationUnitElement);
-
-  bool get hasAnnotations => annotations.isNotEmpty;
-
-  @override
-  bool get hasDocumentation =>
-      documentation != null && documentation.isNotEmpty;
-
-  @override
-  bool get hasExtendedDocumentation =>
-      href != null && _documentation.hasExtendedDocs;
-
-  bool get hasParameters => parameters.isNotEmpty;
-
-  /// If canonicalLibrary (or canonicalEnclosingElement, for Inheritable
-  /// subclasses) is null, href should be null.
-  @override
-  String get href;
-
-  String get htmlId => name;
-
-  bool get isAsynchronous =>
-      isExecutable && (element as ExecutableElement).isAsynchronous;
-
-  bool get isConst => false;
-
-  bool get isDeprecated {
-    // If element.metadata is empty, it might be because this is a property
-    // where the metadata belongs to the individual getter/setter
-    if (element.metadata.isEmpty && element is PropertyInducingElement) {
-      var pie = element as PropertyInducingElement;
-
-      // The getter or the setter might be null – so the stored value may be
-      // `true`, `false`, or `null`
-      var getterDeprecated = pie.getter?.metadata?.any((a) => a.isDeprecated);
-      var setterDeprecated = pie.setter?.metadata?.any((a) => a.isDeprecated);
-
-      var deprecatedValues =
-          [getterDeprecated, setterDeprecated].where((a) => a != null).toList();
-
-      // At least one of these should be non-null. Otherwise things are weird
-      assert(deprecatedValues.isNotEmpty);
-
-      // If there are both a setter and getter, only show the property as
-      // deprecated if both are deprecated.
-      return deprecatedValues.every((d) => d);
-    }
-    return element.metadata.any((a) => a.isDeprecated);
-  }
-
-  @override
-  bool get isDocumented => isCanonical && isPublic;
-
-  bool get isExecutable => element is ExecutableElement;
-
-  bool get isFinal => false;
-
-  bool get isLocalElement => element is LocalElement;
-
-  bool get isPropertyAccessor => element is PropertyAccessorElement;
-
-  bool get isPropertyInducer => element is PropertyInducingElement;
-
-  bool get isStatic {
-    if (isPropertyInducer) {
-      return (element as PropertyInducingElement).isStatic;
-    }
-    return false;
-  }
-
-  /// A human-friendly name for the kind of element this is.
-  @override
-  String get kind;
-
-  @override
-  Library get library => _library;
-
-  String get linkedName {
-    if (_linkedName == null) {
-      _linkedName = _calculateLinkedName();
-    }
-    return _linkedName;
-  }
-
-  String get linkedParams => utils.linkedParams(parameters);
-
-  String get linkedParamsLines => utils.linkedParams(parameters).trim();
-
-  String get linkedParamsNoMetadata =>
-      utils.linkedParams(parameters, showMetadata: false);
-
-  String get linkedParamsNoMetadataOrNames {
-    return utils.linkedParams(parameters,
-        showMetadata: false, showNames: false);
-  }
-
-  ElementType get modelType {
-    if (_modelType == null) {
-      // TODO(jcollins-g): Need an interface for a "member with a type" (or changed object model).
-      if (_originalMember != null &&
-          (_originalMember is ExecutableMember ||
-              _originalMember is ParameterMember)) {
-        if (_originalMember is ExecutableMember) {
-          _modelType = ElementType.from(
-              (_originalMember as ExecutableMember).type,
-              library,
-              packageGraph);
-        } else {
-          // ParameterMember
-          _modelType = ElementType.from(
-              (_originalMember as ParameterMember).type, library, packageGraph);
-        }
-      } else if (element is ExecutableElement ||
-          element is FunctionTypedElement ||
-          element is ParameterElement ||
-          element is TypeDefiningElement ||
-          element is PropertyInducingElement) {
-        _modelType =
-            ElementType.from((element as dynamic).type, library, packageGraph);
-      }
-    }
-    return _modelType;
-  }
-
-  @override
-  String get name => element.name;
-
-  // TODO(jcollins-g): refactor once dartdoc will only run in a VM where mixins
-  // calling super is allowed (SDK constraint >= 2.1.0).
-  String computeOneLineDoc() =>
-      '${_documentation.asOneLiner}${extendedDocLink.isEmpty ? "" : " $extendedDocLink"}';
-  String _oneLineDoc;
-
-  @override
-  String get oneLineDoc {
-    if (_oneLineDoc == null) {
-      _oneLineDoc = computeOneLineDoc();
-    }
-    return _oneLineDoc;
-  }
-
-  Member get originalMember => _originalMember;
-
-  final PackageGraph _packageGraph;
-
-  @override
-  PackageGraph get packageGraph => _packageGraph;
-
-  @override
-  Package get package => library.package;
-
-  bool get isPublicAndPackageDocumented =>
-      isPublic && library.packageGraph.packageDocumentedFor(this);
-
-  List<Parameter> _allParameters;
-
-  // TODO(jcollins-g): This is in the wrong place.  Move parts to GetterSetterCombo,
-  // elsewhere as appropriate?
-  List<Parameter> get allParameters {
-    if (_allParameters == null) {
-      final Set<Parameter> recursedParameters = Set();
-      final Set<Parameter> newParameters = Set();
-      if (this is GetterSetterCombo &&
-          (this as GetterSetterCombo).setter != null) {
-        newParameters.addAll((this as GetterSetterCombo).setter.parameters);
-      } else {
-        if (canHaveParameters) newParameters.addAll(parameters);
-      }
-      while (newParameters.isNotEmpty) {
-        recursedParameters.addAll(newParameters);
-        newParameters.clear();
-        for (Parameter p in recursedParameters) {
-          var l = p.modelType.parameters
-              .where((pm) => !recursedParameters.contains(pm));
-          newParameters.addAll(l);
-        }
-      }
-      _allParameters = recursedParameters.toList();
-    }
-    return _allParameters;
-  }
-
-  List<Parameter> get parameters {
-    if (!canHaveParameters) {
-      throw StateError("$element cannot have parameters");
-    }
-
-    if (_parameters == null) {
-      List<ParameterElement> params;
-
-      if (element is ExecutableElement) {
-        if (_originalMember != null) {
-          assert(_originalMember is ExecutableMember);
-          params = (_originalMember as ExecutableMember).parameters;
-        } else {
-          params = (element as ExecutableElement).parameters;
-        }
-      }
-      if (params == null && element is FunctionTypedElement) {
-        if (_originalMember != null) {
-          params = (_originalMember as dynamic).parameters;
-        } else {
-          params = (element as FunctionTypedElement).parameters;
-        }
-      }
-      if (params == null && element is FunctionTypeAliasElement) {
-        params = (element as FunctionTypeAliasElement).function.parameters;
-      }
-
-      _parameters = UnmodifiableListView<Parameter>(params
-          .map((p) => ModelElement.from(p, library, packageGraph) as Parameter)
-          .toList());
-    }
-    return _parameters;
-  }
-
-  @override
-  void warn(PackageWarning kind,
-      {String message,
-      Iterable<Locatable> referredFrom,
-      Iterable<String> extendedDebug}) {
-    packageGraph.warnOnElement(this, kind,
-        message: message,
-        referredFrom: referredFrom,
-        extendedDebug: extendedDebug);
-  }
-
-  String _computeDocumentationComment() => element.documentationComment;
-
-  bool _documentationCommentComputed = false;
-  String _documentationComment;
-
-  String get documentationComment {
-    if (_documentationCommentComputed == false) {
-      _documentationComment = _computeDocumentationComment();
-      _documentationCommentComputed = true;
-    }
-    return _documentationComment;
-  }
-
-  /// Unconditionally precache local documentation.
-  ///
-  /// Use only in factory for [PackageGraph].
-  Future _precacheLocalDocs() async {
-    _documentationLocal = await _buildDocumentationBase();
-  }
-
-  Documentation get _documentation {
-    if (__documentation != null) return __documentation;
-    __documentation = Documentation.forElement(this);
-    return __documentation;
-  }
-
-  bool canOverride() =>
-      element is ClassMemberElement || element is PropertyAccessorElement;
-
-  @override
-  int compareTo(dynamic other) {
-    if (other is ModelElement) {
-      return name.toLowerCase().compareTo(other.name.toLowerCase());
-    } else {
-      return 0;
-    }
-  }
-
-  @override
-  String toString() => '$runtimeType $name';
-
-  String _buildFullyQualifiedName([ModelElement e, String fqName]) {
-    e ??= this;
-    fqName ??= e.name;
-
-    if (e is! EnclosedElement || e.enclosingElement == null) {
-      return fqName;
-    }
-
-    return _buildFullyQualifiedName(
-        e.enclosingElement, '${e.enclosingElement.name}.$fqName');
-  }
-
-  String _calculateLinkedName() {
-    // If we're calling this with an empty name, we probably have the wrong
-    // element associated with a ModelElement or there's an analysis bug.
-    assert(name.isNotEmpty ||
-        this.element?.kind == ElementKind.DYNAMIC ||
-        this is ModelFunction);
-
-    if (href == null) {
-      if (isPublicAndPackageDocumented) {
-        warn(PackageWarning.noCanonicalFound);
-      }
-      return htmlEscape.convert(name);
-    }
-
-    var classContent = isDeprecated ? ' class="deprecated"' : '';
-    return '<a${classContent} href="${href}">$name</a>';
-  }
-
-  /// Replace &#123;@example ...&#125; in API comments with the content of named file.
-  ///
-  /// Syntax:
-  ///
-  ///     &#123;@example PATH [region=NAME] [lang=NAME]&#125;
-  ///
-  /// If PATH is `dir/file.ext` and region is `r` then we'll look for the file
-  /// named `dir/file-r.ext.md`, relative to the project root directory of the
-  /// project for which the docs are being generated.
-  ///
-  /// Examples: (escaped in this comment to show literal values in dartdoc's
-  ///            dartdoc)
-  ///
-  ///     &#123;@example examples/angular/quickstart/web/main.dart&#125;
-  ///     &#123;@example abc/def/xyz_component.dart region=template lang=html&#125;
-  ///
-  String _injectExamples(String rawdocs) {
-    final dirPath = package.packageMeta.dir.path;
-    RegExp exampleRE = RegExp(r'{@example\s+([^}]+)}');
-    return rawdocs.replaceAllMapped(exampleRE, (match) {
-      var args = _getExampleArgs(match[1]);
-      if (args == null) {
-        // Already warned about an invalid parameter if this happens.
-        return '';
-      }
-      var lang =
-          args['lang'] ?? path.extension(args['src']).replaceFirst('.', '');
-
-      var replacement = match[0]; // default to fully matched string.
-
-      var fragmentFile = File(path.join(dirPath, args['file']));
-      if (fragmentFile.existsSync()) {
-        replacement = fragmentFile.readAsStringSync();
-        if (lang.isNotEmpty) {
-          replacement = replacement.replaceFirst('```', '```$lang');
-        }
-      } else {
-        // TODO(jcollins-g): move this to Package.warn system
-        var filePath =
-            this.element.source.fullName.substring(dirPath.length + 1);
-
-        logWarning(
-            'warning: ${filePath}: @example file not found, ${fragmentFile.path}');
-      }
-      return replacement;
-    });
-  }
-
-  static Future<String> _replaceAllMappedAsync(
-      String string, Pattern exp, Future<String> replace(Match match)) async {
-    StringBuffer replaced = StringBuffer();
-    int currentIndex = 0;
-    for (Match match in exp.allMatches(string)) {
-      String prefix = match.input.substring(currentIndex, match.start);
-      currentIndex = match.end;
-      replaced..write(prefix)..write(await replace(match));
-    }
-    replaced.write(string.substring(currentIndex));
-    return replaced.toString();
-  }
-
-  /// Replace &#123;@tool ...&#125&#123;@end-tool&#125; in API comments with the
-  /// output of an external tool.
-  ///
-  /// Looks for tools invocations, looks up their bound executables in the
-  /// options, and executes them with the source comment material as input,
-  /// returning the output of the tool. If a named tool isn't configured in the
-  /// options file, then it will not be executed, and dartdoc will quit with an
-  /// error.
-  ///
-  /// Tool command line arguments are passed to the tool, with the token
-  /// `$INPUT` replaced with the absolute path to a temporary file containing
-  /// the content for the tool to read and produce output from. If the tool
-  /// doesn't need any input, then no `$INPUT` is needed.
-  ///
-  /// Nested tool directives will not be evaluated, but tools may generate other
-  /// directives in their output and those will be evaluated.
-  ///
-  /// Syntax:
-  ///
-  ///     &#123;@tool TOOL [Tool arguments]&#125;
-  ///     Content to send to tool.
-  ///     &#123;@end-tool&#125;
-  ///
-  /// Examples:
-  ///
-  /// In `dart_options.yaml`:
-  ///
-  /// ```yaml
-  /// dartdoc:
-  ///   tools:
-  ///     # Prefixes the given input with "## "
-  ///     # Path is relative to project root.
-  ///     prefix: "bin/prefix.dart"
-  ///     # Prints the date
-  ///     date: "/bin/date"
-  /// ```
-  ///
-  /// In code:
-  ///
-  /// _This:_
-  ///
-  ///     &#123;@tool prefix $INPUT&#125;
-  ///     Content to send to tool.
-  ///     &#123;@end-tool&#125;
-  ///     &#123;@tool date --iso-8601=minutes --utc&#125;
-  ///     &#123;@end-tool&#125;
-  ///
-  /// _Produces:_
-  ///
-  /// ## Content to send to tool.
-  /// 2018-09-18T21:15+00:00
-  Future<String> _evaluateTools(String rawDocs) async {
-    if (config.allowTools) {
-      int invocationIndex = 0;
-      return await _replaceAllMappedAsync(rawDocs, basicToolRegExp,
-          (basicMatch) async {
-        List<String> args = _splitUpQuotedArgs(basicMatch[1]).toList();
-        // Tool name must come first.
-        if (args.isEmpty) {
-          warn(PackageWarning.toolError,
-              message:
-                  'Must specify a tool to execute for the @tool directive.');
-          return Future.value('');
-        }
-        // Count the number of invocations of tools in this dartdoc block,
-        // so that tools can differentiate different blocks from each other.
-        invocationIndex++;
-        return await config.tools.runner.run(
-            args,
-            (String message) async =>
-                warn(PackageWarning.toolError, message: message),
-            content: basicMatch[2],
-            environment: {
-              'SOURCE_LINE': characterLocation?.lineNumber.toString(),
-              'SOURCE_COLUMN': characterLocation?.columnNumber.toString(),
-              'SOURCE_PATH': (sourceFileName == null ||
-                      package?.packagePath == null)
-                  ? null
-                  : path.relative(sourceFileName, from: package.packagePath),
-              'PACKAGE_PATH': package?.packagePath,
-              'PACKAGE_NAME': package?.name,
-              'LIBRARY_NAME': library?.fullyQualifiedName,
-              'ELEMENT_NAME': fullyQualifiedNameWithoutLibrary,
-              'INVOCATION_INDEX': invocationIndex.toString(),
-              'PACKAGE_INVOCATION_INDEX':
-                  (package.toolInvocationIndex++).toString(),
-            }..removeWhere((key, value) => value == null));
-      });
-    } else {
-      return rawDocs;
-    }
-  }
-
-  /// Replace &#123;@youtube ...&#125; in API comments with some HTML to embed
-  /// a YouTube video.
-  ///
-  /// Syntax:
-  ///
-  ///     &#123;@youtube WIDTH HEIGHT URL&#125;
-  ///
-  /// Example:
-  ///
-  ///     &#123;@youtube 560 315 https://www.youtube.com/watch?v=oHg5SJYRHA0&#125;
-  ///
-  /// Which will embed a YouTube player into the page that plays the specified
-  /// video.
-  ///
-  /// The width and height must be positive integers specifying the dimensions
-  /// of the video in pixels. The height and width are used to calculate the
-  /// aspect ratio of the video; the video is always rendered to take up all
-  /// available horizontal space to accommodate different screen sizes on
-  /// desktop and mobile.
-  ///
-  /// The video URL must have the following format:
-  /// https://www.youtube.com/watch?v=oHg5SJYRHA0. This format can usually be
-  /// found in the address bar of the browser when viewing a YouTube video.
-  String _injectYouTube(String rawDocs) {
-    // Matches all youtube directives (even some invalid ones). This is so
-    // we can give good error messages if the directive is malformed, instead of
-    // just silently emitting it as-is.
-    final RegExp basicAnimationRegExp = RegExp(r'''{@youtube\s+([^}]+)}''');
-
-    // Matches YouTube IDs from supported YouTube URLs.
-    final RegExp validYouTubeUrlRegExp =
-        RegExp('https://www\.youtube\.com/watch\\?v=([^&]+)\$');
-
-    return rawDocs.replaceAllMapped(basicAnimationRegExp, (basicMatch) {
-      final ArgParser parser = ArgParser();
-      final ArgResults args = _parseArgs(basicMatch[1], parser, 'youtube');
-      if (args == null) {
-        // Already warned about an invalid parameter if this happens.
-        return '';
-      }
-      final List<String> positionalArgs = args.rest.sublist(0);
-      if (positionalArgs.length != 3) {
-        warn(PackageWarning.invalidParameter,
-            message: 'Invalid @youtube directive, "${basicMatch[0]}"\n'
-                'YouTube directives must be of the form "{@youtube WIDTH '
-                'HEIGHT URL}"');
-        return '';
-      }
-
-      final int width = int.tryParse(positionalArgs[0]);
-      if (width == null || width <= 0) {
-        warn(PackageWarning.invalidParameter,
-            message: 'A @youtube directive has an invalid width, '
-                '"${positionalArgs[0]}". The width must be a positive integer.');
-      }
-
-      final int height = int.tryParse(positionalArgs[1]);
-      if (height == null || height <= 0) {
-        warn(PackageWarning.invalidParameter,
-            message: 'A @youtube directive has an invalid height, '
-                '"${positionalArgs[1]}". The height must be a positive integer.');
-      }
-
-      final Match url = validYouTubeUrlRegExp.firstMatch(positionalArgs[2]);
-      if (url == null) {
-        warn(PackageWarning.invalidParameter,
-            message: 'A @youtube directive has an invalid URL: '
-                '"${positionalArgs[2]}". Supported YouTube URLs have the '
-                'follwing format: https://www.youtube.com/watch?v=oHg5SJYRHA0.');
-        return '';
-      }
-      final String youTubeId = url.group(url.groupCount);
-      final String aspectRatio = (height / width * 100).toStringAsFixed(2);
-
-      // Blank lines before and after, and no indenting at the beginning and end
-      // is needed so that Markdown doesn't confuse this with code, so be
-      // careful of whitespace here.
-      return '''
-
-<p style="position: relative;
-          padding-top: $aspectRatio%;">
-  <iframe src="https://www.youtube.com/embed/$youTubeId?rel=0"
-          frameborder="0"
-          allow="accelerometer;
-                 autoplay;
-                 encrypted-media;
-                 gyroscope;
-                 picture-in-picture"
-          allowfullscreen
-          style="position: absolute;
-                 top: 0;
-                 left: 0;
-                 width: 100%;
-                 height: 100%;">
-  </iframe>
-</p>
-
-'''; // String must end at beginning of line, or following inline text will be
-      // indented.
-    });
-  }
-
-  /// Replace &#123;@animation ...&#125; in API comments with some HTML to manage an
-  /// MPEG 4 video as an animation.
-  ///
-  /// Syntax:
-  ///
-  ///     &#123;@animation WIDTH HEIGHT URL [id=ID]&#125;
-  ///
-  /// Example:
-  ///
-  ///     &#123;@animation 300 300 https://example.com/path/to/video.mp4 id="my_video"&#125;
-  ///
-  /// Which will render the HTML necessary for embedding a simple click-to-play
-  /// HTML5 video player with no controls that has an HTML id of "my_video".
-  ///
-  /// The optional ID should be a unique id that is a valid JavaScript
-  /// identifier, and will be used as the id for the video tag. If no ID is
-  /// supplied, then a unique identifier (starting with "animation_") will be
-  /// generated.
-  ///
-  /// The width and height must be integers specifying the dimensions of the
-  /// video file in pixels.
-  String _injectAnimations(String rawDocs) {
-    // Matches all animation directives (even some invalid ones). This is so
-    // we can give good error messages if the directive is malformed, instead of
-    // just silently emitting it as-is.
-    final RegExp basicAnimationRegExp = RegExp(r'''{@animation\s+([^}]+)}''');
-
-    // Matches valid javascript identifiers.
-    final RegExp validIdRegExp = RegExp(r'^[a-zA-Z_]\w*$');
-
-    // Make sure we have a set to keep track of used IDs for this href.
-    package.usedAnimationIdsByHref[href] ??= {};
-
-    String getUniqueId(String base) {
-      int animationIdCount = 1;
-      String id = '$base$animationIdCount';
-      // We check for duplicate IDs so that we make sure not to collide with
-      // user-supplied ids on the same page.
-      while (package.usedAnimationIdsByHref[href].contains(id)) {
-        animationIdCount++;
-        id = '$base$animationIdCount';
-      }
-      return id;
-    }
-
-    return rawDocs.replaceAllMapped(basicAnimationRegExp, (basicMatch) {
-      final ArgParser parser = ArgParser();
-      parser.addOption('id');
-      final ArgResults args = _parseArgs(basicMatch[1], parser, 'animation');
-      if (args == null) {
-        // Already warned about an invalid parameter if this happens.
-        return '';
-      }
-      final List<String> positionalArgs = args.rest.sublist(0);
-      String uniqueId;
-      bool wasDeprecated = false;
-      if (positionalArgs.length == 4) {
-        // Supports the original form of the animation tag for backward
-        // compatibility.
-        uniqueId = positionalArgs.removeAt(0);
-        wasDeprecated = true;
-      } else if (positionalArgs.length == 3) {
-        uniqueId = args['id'] ?? getUniqueId('animation_');
-      } else {
-        warn(PackageWarning.invalidParameter,
-            message: 'Invalid @animation directive, "${basicMatch[0]}"\n'
-                'Animation directives must be of the form "{@animation WIDTH '
-                'HEIGHT URL [id=ID]}"');
-        return '';
-      }
-
-      if (!validIdRegExp.hasMatch(uniqueId)) {
-        warn(PackageWarning.invalidParameter,
-            message: 'An animation has an invalid identifier, "$uniqueId". The '
-                'identifier can only contain letters, numbers and underscores, '
-                'and must not begin with a number.');
-        return '';
-      }
-      if (package.usedAnimationIdsByHref[href].contains(uniqueId)) {
-        warn(PackageWarning.invalidParameter,
-            message: 'An animation has a non-unique identifier, "$uniqueId". '
-                'Animation identifiers must be unique.');
-        return '';
-      }
-      package.usedAnimationIdsByHref[href].add(uniqueId);
-
-      int width;
-      try {
-        width = int.parse(positionalArgs[0]);
-      } on FormatException {
-        warn(PackageWarning.invalidParameter,
-            message: 'An animation has an invalid width ($uniqueId), '
-                '"${positionalArgs[0]}". The width must be an integer.');
-        return '';
-      }
-
-      int height;
-      try {
-        height = int.parse(positionalArgs[1]);
-      } on FormatException {
-        warn(PackageWarning.invalidParameter,
-            message: 'An animation has an invalid height ($uniqueId), '
-                '"${positionalArgs[1]}". The height must be an integer.');
-        return '';
-      }
-
-      Uri movieUrl;
-      try {
-        movieUrl = Uri.parse(positionalArgs[2]);
-      } on FormatException catch (e) {
-        warn(PackageWarning.invalidParameter,
-            message: 'An animation URL could not be parsed ($uniqueId): '
-                '${positionalArgs[2]}\n$e');
-        return '';
-      }
-      final String overlayId = '${uniqueId}_play_button_';
-
-      // Only warn about deprecation if some other warning didn't occur.
-      if (wasDeprecated) {
-        warn(PackageWarning.deprecated,
-            message:
-                'Deprecated form of @animation directive, "${basicMatch[0]}"\n'
-                'Animation directives are now of the form "{@animation '
-                'WIDTH HEIGHT URL [id=ID]}" (id is an optional '
-                'parameter)');
-      }
-
-      // Blank lines before and after, and no indenting at the beginning and end
-      // is needed so that Markdown doesn't confuse this with code, so be
-      // careful of whitespace here.
-      return '''
-
-<div style="position: relative;">
-  <div id="${overlayId}"
-       onclick="var $uniqueId = document.getElementById('$uniqueId');
-                if ($uniqueId.paused) {
-                  $uniqueId.play();
-                  this.style.display = 'none';
-                } else {
-                  $uniqueId.pause();
-                  this.style.display = 'block';
-                }"
-       style="position:absolute;
-              width:${width}px;
-              height:${height}px;
-              z-index:100000;
-              background-position: center;
-              background-repeat: no-repeat;
-              background-image: url(static-assets/play_button.svg);">
-  </div>
-  <video id="$uniqueId"
-         style="width:${width}px; height:${height}px;"
-         onclick="var $overlayId = document.getElementById('$overlayId');
-                  if (this.paused) {
-                    this.play();
-                    $overlayId.style.display = 'none';
-                  } else {
-                    this.pause();
-                    $overlayId.style.display = 'block';
-                  }" loop>
-    <source src="$movieUrl" type="video/mp4"/>
-  </video>
-</div>
-
-'''; // String must end at beginning of line, or following inline text will be
-      // indented.
-    });
-  }
-
-  /// Replace &lt;<dartdoc-html>[digest]</dartdoc-html>&gt; in API comments with
-  /// the contents of the HTML fragment earlier defined by the
-  /// &#123;@inject-html&#125; directive. The [digest] is a SHA1 of the contents
-  /// of the HTML fragment, automatically generated upon parsing the
-  /// &#123;@inject-html&#125; directive.
-  ///
-  /// This markup is generated and inserted by [_stripHtmlAndAddToIndex] when it
-  /// removes the HTML fragment in preparation for markdown processing. It isn't
-  /// meant to be used at a user level.
-  ///
-  /// Example:
-  ///
-  /// You place the fragment in a dartdoc comment:
-  ///
-  ///     Some comments
-  ///     &#123;@inject-html&#125;
-  ///     &lt;p&gt;[HTML contents!]&lt;/p&gt;
-  ///     &#123;@endtemplate&#125;
-  ///     More comments
-  ///
-  /// and [_stripHtmlAndAddToIndex] will replace your HTML fragment with this:
-  ///
-  ///     Some comments
-  ///     &lt;dartdoc-html&gt;4cc02f877240bf69855b4c7291aba8a16e5acce0&lt;/dartdoc-html&gt;
-  ///     More comments
-  ///
-  /// Which will render in the final HTML output as:
-  ///
-  ///     Some comments
-  ///     &lt;p&gt;[HTML contents!]&lt;/p&gt;
-  ///     More comments
-  ///
-  /// And the HTML fragment will not have been processed or changed by Markdown,
-  /// but just injected verbatim.
-  String _injectHtmlFragments(String rawDocs) {
-    if (!config.injectHtml) return rawDocs;
-
-    return rawDocs.replaceAllMapped(htmlInjectRegExp, (match) {
-      String fragment = packageGraph.getHtmlFragment(match[1]);
-      if (fragment == null) {
-        warn(PackageWarning.unknownHtmlFragment, message: match[1]);
-      }
-      return fragment;
-    });
-  }
-
-  /// Replace &#123;@macro ...&#125; in API comments with the contents of the macro
-  ///
-  /// Syntax:
-  ///
-  ///     &#123;@macro NAME&#125;
-  ///
-  /// Example:
-  ///
-  /// You define the template in any comment for a documentable entity like:
-  ///
-  ///     &#123;@template foo&#125;
-  ///     Foo contents!
-  ///     &#123;@endtemplate&#125;
-  ///
-  /// and them somewhere use it like this:
-  ///
-  ///     Some comments
-  ///     &#123;@macro foo&#125;
-  ///     More comments
-  ///
-  /// Which will render
-  ///
-  ///     Some comments
-  ///     Foo contents!
-  ///     More comments
-  ///
-  String _injectMacros(String rawDocs) {
-    return rawDocs.replaceAllMapped(macroRegExp, (match) {
-      String macro = packageGraph.getMacro(match[1]);
-      if (macro == null) {
-        warn(PackageWarning.unknownMacro, message: match[1]);
-      }
-      return macro;
-    });
-  }
-
-  /// Parse and remove &#123;@template ...&#125; in API comments and store them
-  /// in the index on the package.
-  ///
-  /// Syntax:
-  ///
-  ///     &#123;@template NAME&#125;
-  ///     The contents of the macro
-  ///     &#123;@endtemplate&#125;
-  ///
-  String _stripMacroTemplatesAndAddToIndex(String rawDocs) {
-    return rawDocs.replaceAllMapped(templateRegExp, (match) {
-      packageGraph._addMacro(match[1].trim(), match[2].trim());
-      return "{@macro ${match[1].trim()}}";
-    });
-  }
-
-  /// Parse and remove &#123;@inject-html ...&#125; in API comments and store
-  /// them in the index on the package, replacing them with a SHA1 hash of the
-  /// contents, where the HTML will be re-injected after Markdown processing of
-  /// the rest of the text is complete.
-  ///
-  /// Syntax:
-  ///
-  ///     &#123;@inject-html&#125;
-  ///     <p>The HTML to inject.</p>
-  ///     &#123;@end-inject-html&#125;
-  ///
-  String _stripHtmlAndAddToIndex(String rawDocs) {
-    if (!config.injectHtml) return rawDocs;
-    return rawDocs.replaceAllMapped(htmlRegExp, (match) {
-      String fragment = match[1];
-      String digest = sha1.convert(fragment.codeUnits).toString();
-      packageGraph._addHtmlFragment(digest, fragment);
-      // The newlines are so that Markdown will pass this through without
-      // touching it.
-      return '\n<dartdoc-html>$digest</dartdoc-html>\n';
-    });
-  }
-
-  /// Helper to process arguments given as a (possibly quoted) string.
-  ///
-  /// First, this will split the given [argsAsString] into separate arguments,
-  /// taking any quoting (either ' or " are accepted) into account, including
-  /// handling backslash-escaped quotes.
-  ///
-  /// Then, it will prepend "--" to any args that start with an identifier
-  /// followed by an equals sign, allowing the argument parser to treat any
-  /// "foo=bar" argument as "--foo=bar". It does handle quoted args like
-  /// "foo='bar baz'" too, returning just bar (without quotes) for the foo
-  /// value.
-  Iterable<String> _splitUpQuotedArgs(String argsAsString,
-      {bool convertToArgs = false}) {
-    final Iterable<Match> matches = argMatcher.allMatches(argsAsString);
-    // Remove quotes around args, and if convertToArgs is true, then for any
-    // args that look like assignments (start with valid option names followed
-    // by an equals sign), add a "--" in front so that they parse as options.
-    return matches.map<String>((Match match) {
-      var option = '';
-      if (convertToArgs && match[1] != null && !match[1].startsWith('-')) {
-        option = '--';
-      }
-      if (match[2] != null) {
-        // This arg has quotes, so strip them.
-        return '$option${match[1] ?? ''}${match[3] ?? ''}${match[4] ?? ''}';
-      }
-      return '$option${match[0]}';
-    });
-  }
-
-  /// Helper to process arguments given as a (possibly quoted) string.
-  ///
-  /// First, this will split the given [argsAsString] into separate arguments
-  /// with [_splitUpQuotedArgs] it then parses the resulting argument list
-  /// normally with [argParser] and returns the result.
-  ArgResults _parseArgs(
-      String argsAsString, ArgParser argParser, String directiveName) {
-    var args = _splitUpQuotedArgs(argsAsString, convertToArgs: true);
-    try {
-      return argParser.parse(args);
-    } on ArgParserException catch (e) {
-      warn(PackageWarning.invalidParameter,
-          message: 'The {@$directiveName ...} directive was called with '
-              'invalid parameters. $e');
-      return null;
-    }
-  }
-
-  /// Helper for _injectExamples used to process @example arguments.
-  /// Returns a map of arguments. The first unnamed argument will have key 'src'.
-  /// The computed file path, constructed from 'src' and 'region' will have key
-  /// 'file'.
-  Map<String, String> _getExampleArgs(String argsAsString) {
-    ArgParser parser = ArgParser();
-    parser.addOption('lang');
-    parser.addOption('region');
-    ArgResults results = _parseArgs(argsAsString, parser, 'example');
-    if (results == null) {
-      return null;
-    }
-
-    // Extract PATH and fix the path separators.
-    final String src = results.rest.isEmpty
-        ? ''
-        : results.rest.first.replaceAll('/', Platform.pathSeparator);
-    final Map<String, String> args = <String, String>{
-      'src': src,
-      'lang': results['lang'],
-      'region': results['region'] ?? '',
-    };
-
-    // Compute 'file' from region and src.
-    final fragExtension = '.md';
-    var file = src + fragExtension;
-    var region = args['region'] ?? '';
-    if (region.isNotEmpty) {
-      var dir = path.dirname(src);
-      var basename = path.basenameWithoutExtension(src);
-      var ext = path.extension(src);
-      file = path.join(dir, '$basename-$region$ext$fragExtension');
-    }
-    args['file'] = config.examplePathPrefix == null
-        ? file
-        : path.join(config.examplePathPrefix, file);
-    return args;
-  }
-}
-
-/// A [ModelElement] for a [FunctionElement] that isn't part of a type definition.
-class ModelFunction extends ModelFunctionTyped with Categorization {
-  ModelFunction(
-      FunctionElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph);
-
-  @override
-  bool get isStatic {
-    return _func.isStatic;
-  }
-
-  @override
-  String get name => element.name ?? '';
-
-  @override
-  FunctionElement get _func => (element as FunctionElement);
-}
-
-/// A [ModelElement] for a [FunctionTypedElement] that is an
-/// explicit typedef.
-///
-/// Distinct from ModelFunctionTypedef in that it doesn't
-/// have a name, but we document it as "Function" to match how these are
-/// written in declarations.
-class ModelFunctionAnonymous extends ModelFunctionTyped {
-  ModelFunctionAnonymous(
-      FunctionTypedElement element, PackageGraph packageGraph)
-      : super(element, null, packageGraph);
-
-  @override
-  ModelElement get enclosingElement {
-    // These are not considered to be a part of libraries, so we can simply
-    // blindly instantiate a ModelElement for their enclosing element.
-    return ModelElement.fromElement(element.enclosingElement, packageGraph);
-  }
-
-  @override
-  String get name => 'Function';
-
-  @override
-  String get linkedName => 'Function';
-
-  @override
-  bool get isPublic => false;
-}
-
-/// A [ModelElement] for a [FunctionTypedElement] that is part of an
-/// explicit typedef.
-class ModelFunctionTypedef extends ModelFunctionTyped {
-  ModelFunctionTypedef(
-      FunctionTypedElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph);
-
-  @override
-  String get name {
-    Element e = element;
-    while (e != null) {
-      if (e is FunctionTypeAliasElement || e is GenericTypeAliasElement) {
-        return e.name;
-      }
-      e = e.enclosingElement;
-    }
-    assert(false);
-    return super.name;
-  }
-}
-
-class ModelFunctionTyped extends ModelElement
-    with TypeParameters
-    implements EnclosedElement {
-  @override
-  List<TypeParameter> typeParameters = [];
-
-  ModelFunctionTyped(
-      FunctionTypedElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph, null) {
-    _calcTypeParameters();
-  }
-
-  void _calcTypeParameters() {
-    typeParameters = _func.typeParameters.map((f) {
-      return ModelElement.from(f, library, packageGraph) as TypeParameter;
-    }).toList();
-  }
-
-  @override
-  ModelElement get enclosingElement => library;
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
-  }
-
-  @override
-  String get kind => 'function';
-
-  String get linkedReturnType => modelType.createLinkedReturnTypeName();
-
-  // Food for mustache. TODO(jcollins-g): what about enclosing elements?
-  bool get isInherited => false;
-
-  @override
-  DefinedElementType get modelType => super.modelType;
-
-  FunctionTypedElement get _func => (element as FunctionTypedElement);
-}
-
-/// Something that has a name.
-abstract class Nameable {
-  String get name;
-
-  String get fullyQualifiedName => name;
-
-  Set<String> _namePieces;
-
-  Set<String> get namePieces {
-    if (_namePieces == null) {
-      _namePieces = Set()
-        ..addAll(name.split(locationSplitter).where((s) => s.isNotEmpty));
-    }
-    return _namePieces;
-  }
-
-  String _namePart;
-
-  /// Utility getter/cache for [_MarkdownCommentReference._getResultsForClass].
-  String get namePart {
-    // TODO(jcollins-g): This should really be the same as 'name', but isn't
-    // because of accessors and operators.
-    if (_namePart == null) {
-      _namePart = fullyQualifiedName.split('.').last;
-    }
-    return _namePart;
-  }
-
-  @override
-  String toString() => name;
-}
-
-/// Something able to be indexed.
-abstract class Indexable implements Nameable {
-  String get href;
-
-  String get kind;
-
-  int get overriddenDepth => 0;
-}
-
-class Operator extends Method {
-  static const Map<String, String> friendlyNames = {
-    "[]": "get",
-    "[]=": "put",
-    "~": "bitwise_negate",
-    "==": "equals",
-    "-": "minus",
-    "+": "plus",
-    "*": "multiply",
-    "/": "divide",
-    "<": "less",
-    ">": "greater",
-    ">=": "greater_equal",
-    "<=": "less_equal",
-    "<<": "shift_left",
-    ">>": "shift_right",
-    "^": "bitwise_exclusive_or",
-    "unary-": "unary_minus",
-    "|": "bitwise_or",
-    "&": "bitwise_and",
-    "~/": "truncate_divide",
-    "%": "modulo"
-  };
-
-  Operator(MethodElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph);
-
-  Operator.inherited(MethodElement element, Class enclosingClass,
-      Library library, PackageGraph packageGraph, {Member originalMember})
-      : super.inherited(element, enclosingClass, library, packageGraph,
-            originalMember: originalMember) {
-    _isInherited = true;
-  }
-
-  @override
-  String get fileName {
-    var actualName = super.name;
-    if (friendlyNames.containsKey(actualName)) {
-      return "operator_${friendlyNames[actualName]}.html";
-    } else {
-      return '$actualName.html';
-    }
-  }
-
-  @override
-  String get fullyQualifiedName =>
-      '${library.name}.${enclosingElement.name}.${super.name}';
-
-  @override
-  bool get isOperator => true;
-
-  @override
-  String get name {
-    return 'operator ${super.name}';
-  }
-}
-
-class PackageGraph {
-  PackageGraph.UninitializedPackageGraph(
-      this.config, this.driver, this.typeSystem, this.sdk, this.hasEmbedderSdk)
-      : packageMeta = config.topLevelPackageMeta,
-        session = driver.currentSession {
-    _packageWarningCounter = PackageWarningCounter(this);
-    // Make sure the default package exists, even if it has no libraries.
-    // This can happen for packages that only contain embedder SDKs.
-    Package.fromPackageMeta(packageMeta, this);
-  }
-
-  /// 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 = PackageMeta.fromElement(element, config);
-    var lib =
-        Library._(result, this, 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.
-    specialClasses = SpecialClasses();
-    // Go through docs of every ModelElement in package to pre-build the macros
-    // index.  Uses toList() in order to get all the precaching on the stack.
-    List<Future> precacheFutures = precacheLocalDocs().toList();
-    for (Future f in precacheFutures) {
-      await f;
-    }
-    _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.
-    documentedPackages.toList().forEach((package) {
-      package._libraries.sort((a, b) => compareNatural(a.name, b.name));
-      package._libraries.forEach((library) {
-        library.allClasses.forEach(_addToImplementors);
-        _extensions.addAll(library.extensions);
-      });
-    });
-    _implementors.values.forEach((l) => l.sort());
-    allImplementorsAdded = true;
-    allExtensionsAdded = true;
-
-    // We should have found all special classes by now.
-    specialClasses.assertSpecials();
-  }
-
-  /// Generate a list of futures for any docs that actually require precaching.
-  Iterable<Future> precacheLocalDocs() sync* {
-    // Prevent reentrancy.
-    Set<ModelElement> precachedElements = Set();
-
-    Iterable<Future> precacheOneElement(ModelElement m) sync* {
-      for (ModelElement d
-          in m.documentationFrom.where((d) => d.documentationComment != null)) {
-        if (needsPrecacheRegExp.hasMatch(d.documentationComment) &&
-            !precachedElements.contains(d)) {
-          precachedElements.add(d);
-          yield d._precacheLocalDocs();
-          // TopLevelVariables get their documentation from getters and setters,
-          // so should be precached if either has a template.
-          if (m is TopLevelVariable) {
-            precachedElements.add(m);
-            yield m._precacheLocalDocs();
-          }
-        }
-      }
-    }
-
-    for (ModelElement m in allModelElements) {
-      // Skip if there is a canonicalModelElement somewhere else we can run this
-      // for.  Not the same as allCanonicalModelElements since we need to run
-      // for any ModelElement that might not have a canonical ModelElement,
-      // too.
-      if (m.canonicalModelElement != null && !m.isCanonical) continue;
-      yield* precacheOneElement(m);
-    }
-  }
-
-  // Many ModelElements have the same ModelNode; don't build/cache this data more
-  // than once for them.
-  final Map<Element, ModelNode> _modelNodes = Map();
-
-  void _populateModelNodeFor(
-      Element element, Map<String, CompilationUnit> compilationUnitMap) {
-    _modelNodes.putIfAbsent(
-        element,
-        () =>
-            ModelNode(utils.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
-  /// is true.
-  bool allImplementorsAdded = false;
-
-  /// It is safe to cache values derived from the [_extensions] table if this
-  /// is true.
-  bool allExtensionsAdded = false;
-
-  Map<String, List<Class>> get implementors {
-    assert(allImplementorsAdded);
-    return _implementors;
-  }
-
-  Iterable<Extension> get extensions {
-    assert(allExtensionsAdded);
-    return _extensions;
-  }
-
-  Map<String, Set<ModelElement>> _findRefElementCache;
-
-  Map<String, Set<ModelElement>> get findRefElementCache {
-    if (_findRefElementCache == null) {
-      assert(packageGraph.allLibrariesAdded);
-      _findRefElementCache = Map();
-      for (final modelElement
-          in utils.filterNonDocumented(packageGraph.allLocalModelElements)) {
-        _findRefElementCache.putIfAbsent(
-            modelElement.fullyQualifiedNameWithoutLibrary, () => Set());
-        _findRefElementCache.putIfAbsent(
-            modelElement.fullyQualifiedName, () => Set());
-        _findRefElementCache[modelElement.fullyQualifiedName].add(modelElement);
-        _findRefElementCache[modelElement.fullyQualifiedNameWithoutLibrary]
-            .add(modelElement);
-      }
-    }
-    return _findRefElementCache;
-  }
-
-  // All library objects related to this package; a superset of _libraries.
-  final Map<LibraryElement, Library> allLibraries = Map();
-
-  /// Keep track of warnings
-  PackageWarningCounter _packageWarningCounter;
-
-  /// All ModelElements constructed for this package; a superset of [allModelElements].
-  final Map<Tuple3<Element, Library, Container>, ModelElement>
-      _allConstructedModelElements = Map();
-
-  /// Anything that might be inheritable, place here for later lookup.
-  final Map<Tuple2<Element, Library>, Set<ModelElement>>
-      _allInheritableElements = Map();
-
-  /// Map of Class.href to a list of classes implementing that class
-  final Map<String, List<Class>> _implementors = Map();
-
-  /// A list of extensions that exist in the package graph.
-  final List<Extension> _extensions = [];
-
-  /// PackageMeta for the default package.
-  final PackageMeta packageMeta;
-
-  /// Name of the default package.
-  String get defaultPackageName => packageMeta.name;
-
-  /// Dartdoc's configuration flags.
-  final DartdocOptionContext config;
-
-  Package _defaultPackage;
-
-  Package get defaultPackage {
-    if (_defaultPackage == null) {
-      _defaultPackage = Package.fromPackageMeta(packageMeta, this);
-    }
-    return _defaultPackage;
-  }
-
-  final bool hasEmbedderSdk;
-
-  bool get hasFooterVersion => !config.excludeFooterVersion;
-
-  PackageGraph get packageGraph => this;
-
-  /// Map of package name to Package.
-  final Map<String, Package> packageMap = {};
-
-  /// TODO(brianwilkerson) Replace the driver with the session.
-  final AnalysisDriver driver;
-  final AnalysisSession session;
-  final Dart2TypeSystem typeSystem;
-  final DartSdk sdk;
-
-  Map<Source, SdkLibrary> _sdkLibrarySources;
-
-  Map<Source, SdkLibrary> get sdkLibrarySources {
-    if (_sdkLibrarySources == null) {
-      _sdkLibrarySources = Map();
-      for (SdkLibrary lib in sdk?.sdkLibraries) {
-        _sdkLibrarySources[sdk.mapDartUri(lib.shortName)] = lib;
-      }
-    }
-    return _sdkLibrarySources;
-  }
-
-  final Map<String, String> _macros = {};
-  final Map<String, String> _htmlFragments = {};
-  bool allLibrariesAdded = false;
-  bool _localDocumentationBuilt = false;
-
-  /// Returns true if there's at least one library documented in the package
-  /// that has the same package path as the library for the given element.
-  /// Usable as a cross-check for dartdoc's canonicalization to generate
-  /// warnings for ModelElement.isPublicAndPackageDocumented.
-  Set<String> _allRootDirs;
-
-  bool packageDocumentedFor(ModelElement element) {
-    if (_allRootDirs == null) {
-      _allRootDirs = Set()
-        ..addAll(publicLibraries.map((l) => l.packageMeta?.resolvedDir));
-    }
-    return (_allRootDirs.contains(element.library.packageMeta?.resolvedDir));
-  }
-
-  PackageWarningCounter get packageWarningCounter => _packageWarningCounter;
-
-  final Set<Tuple3<Element, PackageWarning, String>> _warnAlreadySeen = Set();
-
-  void warnOnElement(Warnable warnable, PackageWarning kind,
-      {String message,
-      Iterable<Locatable> referredFrom,
-      Iterable<String> extendedDebug}) {
-    var newEntry = Tuple3(warnable?.element, kind, message);
-    if (_warnAlreadySeen.contains(newEntry)) {
-      return;
-    }
-    // Warnings can cause other warnings.  Queue them up via the stack but
-    // don't allow warnings we're already working on to get in there.
-    _warnAlreadySeen.add(newEntry);
-    _warnOnElement(warnable, kind,
-        message: message,
-        referredFrom: referredFrom,
-        extendedDebug: extendedDebug);
-    _warnAlreadySeen.remove(newEntry);
-  }
-
-  void _warnOnElement(Warnable warnable, PackageWarning kind,
-      {String message,
-      Iterable<Locatable> referredFrom,
-      Iterable<String> extendedDebug}) {
-    if (warnable != null) {
-      // This sort of warning is only applicable to top level elements.
-      if (kind == PackageWarning.ambiguousReexport) {
-        while (warnable.enclosingElement is! Library &&
-            warnable.enclosingElement != null) {
-          warnable = warnable.enclosingElement;
-        }
-      }
-    } else {
-      // If we don't have an element, we need a message to disambiguate.
-      assert(message != null);
-    }
-    if (_packageWarningCounter.hasWarning(warnable, kind, message)) {
-      return;
-    }
-    // Some kinds of warnings it is OK to drop if we're not documenting them.
-    // TODO(jcollins-g): drop this and use new flag system instead.
-    if (warnable != null &&
-        skipWarningIfNotDocumentedFor.contains(kind) &&
-        !warnable.isDocumented) {
-      return;
-    }
-    // Elements that are part of the Dart SDK can have colons in their FQNs.
-    // This confuses IntelliJ and makes it so it can't link to the location
-    // of the error in the console window, so separate out the library from
-    // the path.
-    // TODO(jcollins-g): What about messages that may include colons?  Substituting
-    //                   them out doesn't work as well there since it might confuse
-    //                   the user, yet we still want IntelliJ to link properly.
-    final warnableName = _safeWarnableName(warnable);
-
-    String warnablePrefix = 'from';
-    String referredFromPrefix = 'referred to by';
-    String warningMessage;
-    switch (kind) {
-      case PackageWarning.noCanonicalFound:
-        // Fix these warnings by adding libraries with --include, or by using
-        // --auto-include-dependencies.
-        // TODO(jcollins-g): pipeline references through linkedName for error
-        //                   messages and warn for non-public canonicalization
-        //                   errors.
-        warningMessage =
-            "no canonical library found for ${warnableName}, not linking";
-        break;
-      case PackageWarning.ambiguousReexport:
-        // Fix these warnings by adding the original library exporting the
-        // symbol with --include, by using --auto-include-dependencies,
-        // or by using --exclude to hide one of the libraries involved
-        warningMessage =
-            "ambiguous reexport of ${warnableName}, canonicalization candidates: ${message}";
-        break;
-      case PackageWarning.noLibraryLevelDocs:
-        warningMessage =
-            "${warnable.fullyQualifiedName} has no library level documentation comments";
-        break;
-      case PackageWarning.ambiguousDocReference:
-        warningMessage = "ambiguous doc reference ${message}";
-        break;
-      case PackageWarning.ignoredCanonicalFor:
-        warningMessage =
-            "library says it is {@canonicalFor ${message}} but ${message} can't be canonical there";
-        break;
-      case PackageWarning.packageOrderGivesMissingPackageName:
-        warningMessage =
-            "--package-order gives invalid package name: '${message}'";
-        break;
-      case PackageWarning.reexportedPrivateApiAcrossPackages:
-        warningMessage =
-            "private API of ${message} is reexported by libraries in other packages: ";
-        break;
-      case PackageWarning.notImplemented:
-        warningMessage = message;
-        break;
-      case PackageWarning.unresolvedDocReference:
-        warningMessage = "unresolved doc reference [${message}]";
-        referredFromPrefix = 'in documentation inherited from';
-        break;
-      case PackageWarning.unknownMacro:
-        warningMessage = "undefined macro [${message}]";
-        break;
-      case PackageWarning.unknownHtmlFragment:
-        warningMessage = "undefined HTML fragment identifier [${message}]";
-        break;
-      case PackageWarning.brokenLink:
-        warningMessage = 'dartdoc generated a broken link to: ${message}';
-        warnablePrefix = 'to element';
-        referredFromPrefix = 'linked to from';
-        break;
-      case PackageWarning.orphanedFile:
-        warningMessage = 'dartdoc generated a file orphan: ${message}';
-        break;
-      case PackageWarning.unknownFile:
-        warningMessage =
-            'dartdoc detected an unknown file in the doc tree: ${message}';
-        break;
-      case PackageWarning.missingFromSearchIndex:
-        warningMessage =
-            'dartdoc generated a file not in the search index: ${message}';
-        break;
-      case PackageWarning.typeAsHtml:
-        // The message for this warning can contain many punctuation and other symbols,
-        // so bracket with a triple quote for defense.
-        warningMessage = 'generic type handled as HTML: """${message}"""';
-        break;
-      case PackageWarning.invalidParameter:
-        warningMessage = 'invalid parameter to dartdoc directive: ${message}';
-        break;
-      case PackageWarning.toolError:
-        warningMessage = 'tool execution failed: ${message}';
-        break;
-      case PackageWarning.deprecated:
-        warningMessage = 'deprecated dartdoc usage: ${message}';
-        break;
-      case PackageWarning.unresolvedExport:
-        warningMessage = 'unresolved export uri: ${message}';
-        break;
-    }
-
-    List<String> messageParts = [warningMessage];
-    if (warnable != null) {
-      messageParts.add("$warnablePrefix $warnableName: ${warnable.location}");
-    }
-    if (referredFrom != null) {
-      for (Locatable referral in referredFrom) {
-        if (referral != warnable) {
-          var referredFromStrings = _safeWarnableName(referral);
-          messageParts.add(
-              "$referredFromPrefix $referredFromStrings: ${referral.location}");
-        }
-      }
-    }
-    if (config.verboseWarnings && extendedDebug != null) {
-      messageParts.addAll(extendedDebug.map((s) => "    $s"));
-    }
-    String fullMessage;
-    if (messageParts.length <= 2) {
-      fullMessage = messageParts.join(', ');
-    } else {
-      fullMessage = messageParts.join('\n    ');
-    }
-
-    packageWarningCounter.addWarning(warnable, kind, message, fullMessage);
-  }
-
-  String _safeWarnableName(Locatable locatable) {
-    if (locatable == null) {
-      return '<unknown>';
-    }
-
-    return locatable.fullyQualifiedName.replaceFirst(':', '-');
-  }
-
-  List<Package> get packages => packageMap.values.toList();
-
-  List<Package> _publicPackages;
-
-  List<Package> get publicPackages {
-    if (_publicPackages == null) {
-      assert(allLibrariesAdded);
-      // Help the user if they pass us a package that doesn't exist.
-      for (String packageName in config.packageOrder) {
-        if (!packages.map((p) => p.name).contains(packageName)) {
-          warnOnElement(
-              null, PackageWarning.packageOrderGivesMissingPackageName,
-              message:
-                  "${packageName}, packages: ${packages.map((p) => p.name).join(',')}");
-        }
-      }
-      _publicPackages = packages.where((p) => p.isPublic).toList()..sort();
-    }
-    return _publicPackages;
-  }
-
-  /// Local packages are to be documented locally vs. remote or not at all.
-  List<Package> get localPackages =>
-      publicPackages.where((p) => p.isLocal).toList();
-
-  /// Documented packages are documented somewhere (local or remote).
-  Iterable<Package> get documentedPackages =>
-      packages.where((p) => p.documentedWhere != DocumentLocation.missing);
-
-  Map<LibraryElement, Set<Library>> _libraryElementReexportedBy = Map();
-
-  /// Prevent cycles from breaking our stack.
-  Set<Tuple2<Library, LibraryElement>> _reexportsTagged = Set();
-
-  void _tagReexportsFor(
-      final Library topLevelLibrary, final LibraryElement libraryElement,
-      [ExportElement lastExportedElement]) {
-    Tuple2<Library, LibraryElement> key =
-        Tuple2(topLevelLibrary, libraryElement);
-    if (_reexportsTagged.contains(key)) {
-      return;
-    }
-    _reexportsTagged.add(key);
-    if (libraryElement == null) {
-      // The first call to _tagReexportFor should not have a null libraryElement.
-      assert(lastExportedElement != null);
-      warnOnElement(
-          findButDoNotCreateLibraryFor(lastExportedElement.enclosingElement),
-          PackageWarning.unresolvedExport,
-          message: '"${lastExportedElement.uri}"',
-          referredFrom: <Locatable>[topLevelLibrary]);
-      return;
-    }
-    _libraryElementReexportedBy.putIfAbsent(libraryElement, () => Set());
-    _libraryElementReexportedBy[libraryElement].add(topLevelLibrary);
-    for (ExportElement exportedElement in libraryElement.exports) {
-      _tagReexportsFor(
-          topLevelLibrary, exportedElement.exportedLibrary, exportedElement);
-    }
-  }
-
-  int _lastSizeOfAllLibraries = 0;
-
-  Map<LibraryElement, Set<Library>> get libraryElementReexportedBy {
-    // Table must be reset if we're still in the middle of adding libraries.
-    if (allLibraries.keys.length != _lastSizeOfAllLibraries) {
-      _lastSizeOfAllLibraries = allLibraries.keys.length;
-      _libraryElementReexportedBy = Map<LibraryElement, Set<Library>>();
-      _reexportsTagged = Set();
-      for (Library library in publicLibraries) {
-        _tagReexportsFor(library, library.element);
-      }
-    }
-    return _libraryElementReexportedBy;
-  }
-
-  /// A lookup index for hrefs to allow warnings to indicate where a broken
-  /// link or orphaned file may have come from.  Not cached because
-  /// [ModelElement]s can be created at any time and we're basing this
-  /// on more than just [allLocalModelElements] to make the error messages
-  /// comprehensive.
-  Map<String, Set<ModelElement>> get allHrefs {
-    Map<String, Set<ModelElement>> hrefMap = Map();
-    // TODO(jcollins-g ): handle calculating hrefs causing new elements better
-    //                    than toList().
-    for (ModelElement modelElement
-        in _allConstructedModelElements.values.toList()) {
-      // Technically speaking we should be able to use canonical model elements
-      // only here, but since the warnings that depend on this debug
-      // canonicalization problems, don't limit ourselves in case an href is
-      // generated for something non-canonical.
-      if (modelElement is Dynamic) continue;
-      // TODO: see [Accessor.enclosingCombo]
-      if (modelElement is Accessor) continue;
-      if (modelElement.href == null) continue;
-      hrefMap.putIfAbsent(modelElement.href, () => Set());
-      hrefMap[modelElement.href].add(modelElement);
-    }
-    for (Package package in packageMap.values) {
-      for (Library library in package.libraries) {
-        if (library.href == null) continue;
-        hrefMap.putIfAbsent(library.href, () => Set());
-        hrefMap[library.href].add(library);
-      }
-    }
-    return hrefMap;
-  }
-
-  void _addToImplementors(Class c) {
-    assert(!allImplementorsAdded);
-    _implementors.putIfAbsent(c.href, () => []);
-    void _checkAndAddClass(Class key, Class implClass) {
-      _implementors.putIfAbsent(key.href, () => []);
-      List list = _implementors[key.href];
-
-      if (!list.any((l) => l.element == c.element)) {
-        list.add(implClass);
-      }
-    }
-
-    if (c._mixins.isNotEmpty) {
-      c._mixins.forEach((t) {
-        _checkAndAddClass(t.element, c);
-      });
-    }
-    if (c.supertype != null) {
-      _checkAndAddClass(c.supertype.element, c);
-    }
-    if (c.interfaces.isNotEmpty) {
-      c.interfaces.forEach((t) {
-        _checkAndAddClass(t.element, c);
-      });
-    }
-  }
-
-  List<Library> get libraries =>
-      packages.expand((p) => p.libraries).toList()..sort();
-
-  List<Library> _publicLibraries;
-
-  Iterable<Library> get publicLibraries {
-    if (_publicLibraries == null) {
-      assert(allLibrariesAdded);
-      _publicLibraries = utils.filterNonPublic(libraries).toList();
-    }
-    return _publicLibraries;
-  }
-
-  List<Library> _localLibraries;
-
-  Iterable<Library> get localLibraries {
-    if (_localLibraries == null) {
-      assert(allLibrariesAdded);
-      _localLibraries = localPackages.expand((p) => p.libraries).toList()
-        ..sort();
-    }
-    return _localLibraries;
-  }
-
-  List<Library> _localPublicLibraries;
-
-  Iterable<Library> get localPublicLibraries {
-    if (_localPublicLibraries == null) {
-      assert(allLibrariesAdded);
-      _localPublicLibraries = utils.filterNonPublic(localLibraries).toList();
-    }
-    return _localPublicLibraries;
-  }
-
-  Set<Class> _inheritThrough;
-
-  /// Return the set of [Class]es objects should inherit through if they
-  /// show up in the inheritance chain.  Do not call before interceptorElement is
-  /// found.  Add classes here if they are similar to Interceptor in that they
-  /// are to be ignored even when they are the implementors of [Inheritable]s,
-  /// and the class these inherit from should instead claim implementation.
-  Set<Class> get inheritThrough {
-    if (_inheritThrough == null) {
-      _inheritThrough = Set();
-      _inheritThrough.add(specialClasses[SpecialClass.interceptor]);
-    }
-    return _inheritThrough;
-  }
-
-  Set<Class> _invisibleAnnotations;
-
-  /// Returns the set of [Class] objects that are similar to pragma
-  /// in that we should never count them as documentable annotations.
-  Set<Class> get invisibleAnnotations {
-    if (_invisibleAnnotations == null) {
-      _invisibleAnnotations = Set();
-      _invisibleAnnotations.add(specialClasses[SpecialClass.pragma]);
-    }
-    return _invisibleAnnotations;
-  }
-
-  @override
-  String toString() => 'PackageGraph built from ${defaultPackage.name}';
-
-  final Map<Element, Library> _canonicalLibraryFor = Map();
-
-  /// Tries to find a top level library that references this element.
-  Library findCanonicalLibraryFor(Element e) {
-    assert(allLibrariesAdded);
-    Element searchElement = e;
-    if (e is PropertyAccessorElement) {
-      searchElement = e.variable;
-    }
-    if (e is GenericFunctionTypeElement) {
-      searchElement = e.enclosingElement;
-    }
-
-    if (_canonicalLibraryFor.containsKey(e)) {
-      return _canonicalLibraryFor[e];
-    }
-    _canonicalLibraryFor[e] = null;
-    for (Library library in publicLibraries) {
-      if (library.modelElementsMap.containsKey(searchElement)) {
-        for (ModelElement modelElement
-            in library.modelElementsMap[searchElement]) {
-          if (modelElement.isCanonical) {
-            _canonicalLibraryFor[e] = library;
-            break;
-          }
-        }
-      }
-    }
-    return _canonicalLibraryFor[e];
-  }
-
-  // TODO(jcollins-g): Revise when dart-lang/sdk#29600 is fixed.
-  static Element getBasestElement(Element possibleMember) {
-    Element element = possibleMember;
-    while (element is Member) {
-      element = (element as Member).baseElement;
-    }
-    return element;
-  }
-
-  /// Tries to find a canonical ModelElement for this element.  If we know
-  /// this element is related to a particular class, pass preferredClass to
-  /// disambiguate.
-  ///
-  /// This doesn't know anything about [PackageGraph.inheritThrough] and probably
-  /// shouldn't, so using it with [Inheritable]s without special casing is
-  /// not advised.
-  ModelElement findCanonicalModelElementFor(Element e,
-      {Container preferredClass}) {
-    assert(allLibrariesAdded);
-    Library lib = findCanonicalLibraryFor(e);
-    if (preferredClass != null && preferredClass is Container) {
-      Container canonicalClass =
-          findCanonicalModelElementFor(preferredClass.element);
-      if (canonicalClass != null) preferredClass = canonicalClass;
-    }
-    if (lib == null && preferredClass != null) {
-      lib = findCanonicalLibraryFor(preferredClass.element);
-    }
-    ModelElement modelElement;
-    // For elements defined in extensions, they are canonical.
-    if (e?.enclosingElement is ExtensionElement) {
-      lib ??= Library(e.enclosingElement.library, packageGraph);
-      // (TODO:keertip) Find a better way to exclude members of extensions
-      //  when libraries are specified using the "--include" flag
-      if (lib?.isDocumented == true) {
-        return ModelElement.from(e, lib, packageGraph);
-      }
-    }
-    // TODO(jcollins-g): Special cases are pretty large here.  Refactor to split
-    // out into helpers.
-    // TODO(jcollins-g): The data structures should be changed to eliminate guesswork
-    // with member elements.
-    if (e is ClassMemberElement || e is PropertyAccessorElement) {
-      if (e is Member) e = getBasestElement(e);
-      Set<ModelElement> candidates = Set();
-      Tuple2<Element, Library> iKey = Tuple2(e, lib);
-      Tuple4<Element, Library, Class, ModelElement> key =
-          Tuple4(e, lib, null, null);
-      Tuple4<Element, Library, Class, ModelElement> keyWithClass =
-          Tuple4(e, lib, preferredClass, null);
-      if (_allConstructedModelElements.containsKey(key)) {
-        candidates.add(_allConstructedModelElements[key]);
-      }
-      if (_allConstructedModelElements.containsKey(keyWithClass)) {
-        candidates.add(_allConstructedModelElements[keyWithClass]);
-      }
-      if (candidates.isEmpty && _allInheritableElements.containsKey(iKey)) {
-        candidates.addAll(
-            _allInheritableElements[iKey].where((me) => me.isCanonical));
-      }
-      Class canonicalClass = findCanonicalModelElementFor(e.enclosingElement);
-      if (canonicalClass != null) {
-        candidates.addAll(canonicalClass.allCanonicalModelElements.where((m) {
-          if (m.element == e) return true;
-          return false;
-        }));
-      }
-      Set<ModelElement> matches = Set()
-        ..addAll(candidates.where((me) => me.isCanonical));
-
-      // It's possible to find accessors but no combos.  Be sure that if we
-      // have Accessors, we find their combos too.
-      if (matches.any((me) => me is Accessor)) {
-        List<GetterSetterCombo> combos =
-            matches.whereType<Accessor>().map((a) => a.enclosingCombo).toList();
-        matches.addAll(combos);
-        assert(combos.every((c) => c.isCanonical));
-      }
-
-      // This is for situations where multiple classes may actually be canonical
-      // for an inherited element whose defining Class is not canonical.
-      if (matches.length > 1 &&
-          preferredClass != null &&
-          preferredClass is Class) {
-        // Search for matches inside our superchain.
-        List<Class> superChain = preferredClass.superChain
-            .map((et) => et.element)
-            .cast<Class>()
-            .toList();
-        superChain.add(preferredClass);
-        matches.removeWhere((me) =>
-            !superChain.contains((me as EnclosedElement).enclosingElement));
-        // Assumed all matches are EnclosedElement because we've been told about a
-        // preferredClass.
-        Set<Class> enclosingElements = Set()
-          ..addAll(matches
-              .map((me) => (me as EnclosedElement).enclosingElement as Class));
-        for (Class c in superChain.reversed) {
-          if (enclosingElements.contains(c)) {
-            matches.removeWhere(
-                (me) => (me as EnclosedElement).enclosingElement != c);
-          }
-          if (matches.length <= 1) break;
-        }
-      }
-
-      // Prefer a GetterSetterCombo to Accessors.
-      if (matches.any((me) => me is GetterSetterCombo)) {
-        matches.removeWhere((me) => me is Accessor);
-      }
-
-      assert(matches.length <= 1);
-      if (matches.isNotEmpty) {
-        modelElement = matches.first;
-      }
-    } else {
-      if (lib != null) {
-        Accessor getter;
-        Accessor setter;
-        if (e is PropertyInducingElement) {
-          if (e.getter != null) {
-            getter = ModelElement.from(e.getter, lib, packageGraph);
-          }
-          if (e.setter != null) {
-            setter = ModelElement.from(e.setter, lib, packageGraph);
-          }
-        }
-        modelElement = ModelElement.from(e, lib, packageGraph,
-            getter: getter, setter: setter);
-      }
-      assert(modelElement is! Inheritable);
-      if (modelElement != null && !modelElement.isCanonical) {
-        modelElement = null;
-      }
-    }
-    // Prefer Fields.
-    if (e is PropertyAccessorElement && modelElement is Accessor) {
-      modelElement = (modelElement as Accessor).enclosingCombo;
-    }
-    return modelElement;
-  }
-
-  /// 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).
-  Library findButDoNotCreateLibraryFor(Element e) {
-    // This is just a cache to avoid creating lots of libraries over and over.
-    if (allLibraries.containsKey(e.library)) {
-      return allLibraries[e.library];
-    }
-    return null;
-  }
-
-  /// 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).
-  Library findOrCreateLibraryFor(ResolvedLibraryResult result) {
-    // This is just a cache to avoid creating lots of libraries over and over.
-    if (allLibraries.containsKey(result.element.library)) {
-      return allLibraries[result.element.library];
-    }
-    // can be null if e is for dynamic
-    if (result.element.library == null) {
-      return null;
-    }
-    Library foundLibrary = Library._(
-        result,
-        this,
-        Package.fromPackageMeta(
-            PackageMeta.fromElement(result.element.library, config),
-            packageGraph));
-    allLibraries[result.element.library] = foundLibrary;
-    return foundLibrary;
-  }
-
-  List<ModelElement> _allModelElements;
-
-  Iterable<ModelElement> get allModelElements {
-    assert(allLibrariesAdded);
-    if (_allModelElements == null) {
-      _allModelElements = [];
-      Set<Package> packagesToDo = packages.toSet();
-      Set<Package> completedPackages = Set();
-      while (packagesToDo.length > completedPackages.length) {
-        packagesToDo.difference(completedPackages).forEach((Package p) {
-          Set<Library> librariesToDo = p.allLibraries.toSet();
-          Set<Library> completedLibraries = Set();
-          while (librariesToDo.length > completedLibraries.length) {
-            librariesToDo
-                .difference(completedLibraries)
-                .forEach((Library library) {
-              _allModelElements.addAll(library.allModelElements);
-              completedLibraries.add(library);
-            });
-            librariesToDo.addAll(p.allLibraries);
-          }
-          completedPackages.add(p);
-        });
-        packagesToDo.addAll(packages);
-      }
-    }
-    return _allModelElements;
-  }
-
-  List<ModelElement> _allLocalModelElements;
-
-  Iterable<ModelElement> get allLocalModelElements {
-    assert(allLibrariesAdded);
-    if (_allLocalModelElements == null) {
-      _allLocalModelElements = [];
-      this.localLibraries.forEach((library) {
-        _allLocalModelElements.addAll(library.allModelElements);
-      });
-    }
-    return _allLocalModelElements;
-  }
-
-  List<ModelElement> _allCanonicalModelElements;
-
-  Iterable<ModelElement> get allCanonicalModelElements {
-    return (_allCanonicalModelElements ??=
-        allLocalModelElements.where((e) => e.isCanonical).toList());
-  }
-
-  String getMacro(String name) {
-    assert(_localDocumentationBuilt);
-    return _macros[name];
-  }
-
-  void _addMacro(String name, String content) {
-    assert(!_localDocumentationBuilt);
-    _macros[name] = content;
-  }
-
-  String getHtmlFragment(String name) {
-    assert(_localDocumentationBuilt);
-    return _htmlFragments[name];
-  }
-
-  void _addHtmlFragment(String name, String content) {
-    assert(!_localDocumentationBuilt);
-    _htmlFragments[name] = content;
-  }
-}
-
-/// A set of [Class]es, [Enum]s, [TopLevelVariable]s, [ModelFunction]s,
-/// [Property]s, and [Typedef]s, possibly initialized after construction by
-/// accessing private member variables.  Do not call any methods or members
-/// excepting [name] and the private Lists below before finishing initialization
-/// of a [TopLevelContainer].
-abstract class TopLevelContainer implements Nameable {
-  List<Class> _classes;
-  List<Extension> _extensions;
-  List<Enum> _enums;
-  List<Mixin> _mixins;
-  List<Class> _exceptions;
-  List<TopLevelVariable> _constants;
-  List<TopLevelVariable> _properties;
-  List<ModelFunction> _functions;
-  List<Typedef> _typedefs;
-
-  Iterable<Class> get classes => _classes;
-
-  Iterable<Extension> get extensions => _extensions;
-
-  Iterable<Enum> get enums => _enums;
-
-  Iterable<Mixin> get mixins => _mixins;
-
-  Iterable<Class> get exceptions => _exceptions;
-
-  Iterable<TopLevelVariable> get constants => _constants;
-
-  Iterable<TopLevelVariable> get properties => _properties;
-
-  Iterable<ModelFunction> get functions => _functions;
-
-  Iterable<Typedef> get typedefs => _typedefs;
-
-  bool get hasPublicClasses => publicClasses.isNotEmpty;
-
-  bool get hasPublicExtensions => publicExtensions.isNotEmpty;
-
-  bool get hasPublicConstants => publicConstants.isNotEmpty;
-
-  bool get hasPublicEnums => publicEnums.isNotEmpty;
-
-  bool get hasPublicExceptions => publicExceptions.isNotEmpty;
-
-  bool get hasPublicFunctions => publicFunctions.isNotEmpty;
-
-  bool get hasPublicMixins => publicMixins.isNotEmpty;
-
-  bool get hasPublicProperties => publicProperties.isNotEmpty;
-
-  bool get hasPublicTypedefs => publicTypedefs.isNotEmpty;
-
-  Iterable<Class> get publicClasses => utils.filterNonPublic(classes);
-
-  Iterable<Extension> get publicExtensions => utils.filterNonPublic(extensions);
-
-  Iterable<TopLevelVariable> get publicConstants =>
-      utils.filterNonPublic(constants);
-
-  Iterable<Enum> get publicEnums => utils.filterNonPublic(enums);
-
-  Iterable<Class> get publicExceptions => utils.filterNonPublic(exceptions);
-
-  Iterable<ModelFunction> get publicFunctions =>
-      utils.filterNonPublic(functions);
-
-  Iterable<Mixin> get publicMixins => utils.filterNonPublic(mixins);
-
-  Iterable<TopLevelVariable> get publicProperties =>
-      utils.filterNonPublic(properties);
-
-  Iterable<Typedef> get publicTypedefs => utils.filterNonPublic(typedefs);
-}
-
-/// A set of libraries, initialized after construction by accessing [_libraries].
-/// Do not cache return values of any methods or members excepting [_libraries]
-/// and [name] before finishing initialization of a [LibraryContainer].
-abstract class LibraryContainer
-    implements Nameable, Comparable<LibraryContainer> {
-  final List<Library> _libraries = [];
-
-  List<Library> get libraries => _libraries;
-
-  PackageGraph get packageGraph;
-
-  Iterable<Library> get publicLibraries => utils.filterNonPublic(libraries);
-
-  bool get hasPublicLibraries => publicLibraries.isNotEmpty;
-
-  /// The name of the container or object that this LibraryContainer is a part
-  /// of.  Used for sorting in [containerOrder].
-  String get enclosingName;
-
-  /// Order by which this container should be sorted.
-  List<String> get containerOrder;
-
-  /// Sorting key.  [containerOrder] should contain these.
-  String get sortKey => name;
-
-  /// Does this container represent the SDK?  This can be false for containers
-  /// that only represent a part of the SDK.
-  bool get isSdk => false;
-
-  /// Returns:
-  /// -1 if this container is listed in [containerOrder].
-  /// 0 if this container is named the same as the [enclosingName].
-  /// 1 if this container represents the SDK.
-  /// 2 if this group has a name that contains the name [enclosingName].
-  /// 3 otherwise.
-  int get _group {
-    if (containerOrder.contains(sortKey)) return -1;
-    if (equalsIgnoreAsciiCase(sortKey, enclosingName)) return 0;
-    if (isSdk) return 1;
-    if (sortKey.toLowerCase().contains(enclosingName.toLowerCase())) return 2;
-    return 3;
-  }
-
-  @override
-  int compareTo(LibraryContainer other) {
-    if (_group == other._group) {
-      if (_group == -1) {
-        return Comparable.compare(containerOrder.indexOf(sortKey),
-            containerOrder.indexOf(other.sortKey));
-      } else {
-        return sortKey.toLowerCase().compareTo(other.sortKey.toLowerCase());
-      }
-    }
-    return Comparable.compare(_group, other._group);
-  }
-}
-
-abstract class MarkdownFileDocumentation
-    implements Documentable, Canonicalization {
-  DocumentLocation get documentedWhere;
-
-  @override
-  String get documentation => documentationFile?.contents;
-
-  Documentation __documentation;
-
-  Documentation get _documentation {
-    if (__documentation != null) return __documentation;
-    __documentation = Documentation.forElement(this);
-    return __documentation;
-  }
-
-  @override
-  String get documentationAsHtml => _documentation.asHtml;
-
-  @override
-  bool get hasDocumentation =>
-      documentationFile != null && documentationFile.contents.isNotEmpty;
-
-  @override
-  bool get hasExtendedDocumentation =>
-      documentation != null && documentation.isNotEmpty;
-
-  @override
-  bool get isDocumented;
-
-  @override
-  String get oneLineDoc => __documentation.asOneLiner;
-
-  FileContents get documentationFile;
-
-  @override
-  String get location => path.toUri(documentationFile.file.path).toString();
-
-  @override
-  Set<String> get locationPieces => Set.from(<String>[location]);
-}
-
-/// A category is a subcategory of a package, containing libraries tagged
-/// with a @category identifier.
-class Category extends Nameable
-    with
-        Warnable,
-        Locatable,
-        Canonicalization,
-        MarkdownFileDocumentation,
-        LibraryContainer,
-        TopLevelContainer,
-        Indexable
-    implements Documentable {
-  /// All libraries in [libraries] must come from [package].
-  @override
-  Package package;
-  final String _name;
-  @override
-  DartdocOptionContext config;
-  final Set<Categorization> _allItems = Set();
-
-  Category(this._name, this.package, this.config) {
-    _enums = [];
-    _exceptions = [];
-    _classes = [];
-    _constants = [];
-    _properties = [];
-    _functions = [];
-    _mixins = [];
-    _typedefs = [];
-    _extensions = [];
-  }
-
-  void addItem(Categorization c) {
-    if (_allItems.contains(c)) return;
-    _allItems.add(c);
-    if (c is Library) {
-      _libraries.add(c);
-    } else if (c is Mixin) {
-      _mixins.add(c);
-    } else if (c is Enum) {
-      _enums.add(c);
-    } else if (c is Class) {
-      if (c.isErrorOrException) {
-        _exceptions.add(c);
-      } else {
-        _classes.add(c);
-      }
-    } else if (c is TopLevelVariable) {
-      if (c.isConst) {
-        _constants.add(c);
-      } else {
-        _properties.add(c);
-      }
-    } else if (c is ModelFunction) {
-      _functions.add(c);
-    } else if (c is Typedef) {
-      _typedefs.add(c);
-    } else if (c is Extension) {
-      _extensions.add(c);
-    } else {
-      throw UnimplementedError("Unrecognized element");
-    }
-  }
-
-  @override
-  // TODO(jcollins-g): make [Category] a [Warnable]?
-  Warnable get enclosingElement => null;
-
-  @override
-  Element get element => null;
-
-  @override
-  String get name => categoryDefinition?.displayName ?? _name;
-
-  @override
-  String get sortKey => _name;
-
-  @override
-  List<String> get containerOrder => config.categoryOrder;
-
-  @override
-  String get enclosingName => package.name;
-
-  @override
-  PackageGraph get packageGraph => package.packageGraph;
-
-  @override
-  Library get canonicalLibrary => null;
-
-  @override
-  List<Locatable> get documentationFrom => [this];
-
-  @override
-  DocumentLocation get documentedWhere => package.documentedWhere;
-
-  bool _isDocumented;
-
-  @override
-  bool get isDocumented {
-    if (_isDocumented == null) {
-      _isDocumented = documentedWhere != DocumentLocation.missing &&
-          documentationFile != null;
-    }
-    return _isDocumented;
-  }
-
-  @override
-  String get fullyQualifiedName => name;
-
-  @override
-  String get href =>
-      isCanonical ? '${package.baseHref}topics/${name}-topic.html' : null;
-
-  String get linkedName {
-    String unbrokenCategoryName = name.replaceAll(' ', '&nbsp;');
-    if (isDocumented) {
-      return '<a href="$href">$unbrokenCategoryName</a>';
-    } else {
-      return unbrokenCategoryName;
-    }
-  }
-
-  String _categoryNumberClass;
-
-  /// The position in the container order for this category.
-  String get categoryNumberClass {
-    if (_categoryNumberClass == null) {
-      _categoryNumberClass = "cp-${package.categories.indexOf(this)}";
-    }
-    return _categoryNumberClass;
-  }
-
-  /// Category name used in template as part of the class.
-  String get spanClass => name.split(' ').join('-').toLowerCase();
-
-  CategoryDefinition get categoryDefinition =>
-      config.categories.categoryDefinitions[sortKey];
-
-  @override
-  bool get isCanonical => categoryDefinition != null;
-
-  @override
-  String get kind => 'Topic';
-
-  FileContents _documentationFile;
-
-  @override
-  FileContents get documentationFile {
-    if (_documentationFile == null) {
-      if (categoryDefinition?.documentationMarkdown != null) {
-        _documentationFile =
-            FileContents(File(categoryDefinition.documentationMarkdown));
-      }
-    }
-    return _documentationFile;
-  }
-
-  @override
-  void warn(PackageWarning kind,
-      {String message,
-      Iterable<Locatable> referredFrom,
-      Iterable<String> extendedDebug}) {
-    packageGraph.warnOnElement(this, kind,
-        message: message,
-        referredFrom: referredFrom,
-        extendedDebug: extendedDebug);
-  }
-}
-
-/// For a given package, indicate with this enum whether it should be documented
-/// [local]ly, whether we should treat the package as [missing] and any references
-/// to it made canonical to this package, or [remote], indicating that
-/// we can build hrefs to an external source.
-enum DocumentLocation {
-  local,
-  missing,
-  remote,
-}
-
-/// A [LibraryContainer] that contains [Library] objects related to a particular
-/// package.
-class Package extends LibraryContainer
-    with Nameable, Locatable, Canonicalization, Warnable
-    implements Privacy, Documentable {
-  String _name;
-  PackageGraph _packageGraph;
-
-  final Map<String, Category> _nameToCategory = {};
-
-  // Creates a package, if necessary, and adds it to the [packageGraph].
-  factory Package.fromPackageMeta(
-      PackageMeta packageMeta, PackageGraph packageGraph) {
-    String packageName = packageMeta.name;
-
-    bool expectNonLocal = false;
-
-    if (!packageGraph.packageMap.containsKey(packageName) &&
-        packageGraph.allLibrariesAdded) expectNonLocal = true;
-    packageGraph.packageMap.putIfAbsent(
-        packageName, () => 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
-    // before allLibrariesAdded is true.
-    assert(
-        !(expectNonLocal &&
-            packageGraph.packageMap[packageName].documentedWhere ==
-                DocumentLocation.local),
-        'Found more libraries to document after allLibrariesAdded was set to true');
-    return packageGraph.packageMap[packageName];
-  }
-
-  Package._(this._name, this._packageGraph, this._packageMeta);
-
-  @override
-  bool get isCanonical => true;
-
-  @override
-  Library get canonicalLibrary => null;
-
-  /// Number of times we have invoked a tool for this package.
-  int toolInvocationIndex = 0;
-
-  // The animation IDs that have already been used, indexed by the [href] of the
-  // object that contains them.
-  Map<String, Set<String>> usedAnimationIdsByHref = {};
-
-  /// Pieces of the location split by [locationSplitter] (removing package: and
-  /// slashes).
-  @override
-  Set<String> get locationPieces => Set();
-
-  final Set<Library> _allLibraries = Set();
-
-  bool get hasHomepage =>
-      packageMeta.homepage != null && packageMeta.homepage.isNotEmpty;
-
-  String get homepage => packageMeta.homepage;
-
-  String get kind => (isSdk) ? 'SDK' : 'package';
-
-  @override
-  List<Locatable> get documentationFrom => [this];
-
-  /// Returns all libraries added to this package.  May include non-documented
-  /// libraries, but is not guaranteed to include a complete list of
-  /// non-documented libraries unless they are all referenced by documented ones.
-  /// Not sorted.
-  Set<Library> get allLibraries => _allLibraries;
-
-  /// Return true if the code has defined non-default categories for libraries
-  /// in this package.
-  bool get hasCategories => categories.isNotEmpty;
-
-  LibraryContainer get defaultCategory => nameToCategory[null];
-
-  String _documentationAsHtml;
-
-  @override
-  String get documentationAsHtml {
-    if (_documentationAsHtml != null) return _documentationAsHtml;
-    _documentationAsHtml = Documentation.forElement(this).asHtml;
-
-    return _documentationAsHtml;
-  }
-
-  @override
-  String get documentation {
-    return hasDocumentationFile ? documentationFile.contents : null;
-  }
-
-  @override
-  bool get hasDocumentation =>
-      documentationFile != null && documentationFile.contents.isNotEmpty;
-
-  @override
-  bool get hasExtendedDocumentation => documentation.isNotEmpty;
-
-  // TODO: Clients should use [documentationFile] so they can act differently on
-  // plain text or markdown.
-  bool get hasDocumentationFile => documentationFile != null;
-
-  FileContents get documentationFile => packageMeta.getReadmeContents();
-
-  @override
-  String get oneLineDoc => '';
-
-  @override
-  bool get isDocumented =>
-      isFirstPackage || documentedWhere != DocumentLocation.missing;
-
-  @override
-  Warnable get enclosingElement => null;
-
-  bool _isPublic;
-
-  @override
-  bool get isPublic {
-    if (_isPublic == null) _isPublic = libraries.any((l) => l.isPublic);
-    return _isPublic;
-  }
-
-  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) {
-      if (isPublic) {
-        return DocumentLocation.local;
-      } else {
-        // Possible if excludes result in a "documented" package not having
-        // any actual documentation.
-        return DocumentLocation.missing;
-      }
-    } else {
-      if (config.linkToRemote && config.linkToUrl.isNotEmpty && isPublic) {
-        return DocumentLocation.remote;
-      } else {
-        return DocumentLocation.missing;
-      }
-    }
-  }
-
-  @override
-  String get enclosingName => packageGraph.defaultPackageName;
-
-  @override
-  String get fullyQualifiedName => 'package:$name';
-
-  String _baseHref;
-
-  String get baseHref {
-    if (_baseHref == null) {
-      if (documentedWhere == DocumentLocation.remote) {
-        _baseHref =
-            config.linkToUrl.replaceAllMapped(substituteNameVersion, (m) {
-          switch (m.group(1)) {
-            // Return the prerelease tag of the release if a prerelease,
-            // or 'stable' otherwise. Mostly coded around
-            // the Dart SDK's use of dev/stable, but theoretically applicable
-            // elsewhere.
-            case 'b':
-              {
-                Version version = Version.parse(packageMeta.version);
-                return version.isPreRelease
-                    ? version.preRelease.first
-                    : 'stable';
-              }
-            case 'n':
-              return name;
-            // The full version string of the package.
-            case 'v':
-              return packageMeta.version;
-            default:
-              assert(false, 'Unsupported case: ${m.group(1)}');
-              return null;
-          }
-        });
-        if (!_baseHref.endsWith('/')) _baseHref = '${_baseHref}/';
-      } else {
-        _baseHref = '';
-      }
-    }
-    return _baseHref;
-  }
-
-  @override
-  String get href => '${baseHref}index.html';
-
-  @override
-  String get location => path.toUri(packageMeta.resolvedDir).toString();
-
-  @override
-  String get name => _name;
-
-  @override
-  Package get package => this;
-
-  @override
-  PackageGraph get packageGraph => _packageGraph;
-
-  // Workaround for mustache4dart issue where templates do not recognize
-  // inherited properties as being in-context.
-  @override
-  Iterable<Library> get publicLibraries {
-    assert(libraries.every((l) => l.packageMeta == _packageMeta));
-    return super.publicLibraries;
-  }
-
-  /// A map of category name to the category itself.
-  Map<String, Category> get nameToCategory {
-    if (_nameToCategory.isEmpty) {
-      Category categoryFor(String category) {
-        _nameToCategory.putIfAbsent(
-            category, () => Category(category, this, config));
-        return _nameToCategory[category];
-      }
-
-      _nameToCategory[null] = Category(null, this, config);
-      for (Categorization c in libraries.expand(
-          (l) => l.allCanonicalModelElements.whereType<Categorization>())) {
-        for (String category in c.categoryNames) {
-          categoryFor(category).addItem(c);
-        }
-      }
-    }
-    return _nameToCategory;
-  }
-
-  List<Category> _categories;
-
-  List<Category> get categories {
-    if (_categories == null) {
-      _categories = nameToCategory.values.where((c) => c.name != null).toList()
-        ..sort();
-    }
-    return _categories;
-  }
-
-  Iterable<LibraryContainer> get categoriesWithPublicLibraries =>
-      categories.where((c) => c.publicLibraries.isNotEmpty);
-
-  Iterable<Category> get documentedCategories =>
-      categories.where((c) => c.isDocumented);
-
-  bool get hasDocumentedCategories => documentedCategories.isNotEmpty;
-
-  DartdocOptionContext _config;
-
-  @override
-  DartdocOptionContext get config {
-    if (_config == null) {
-      _config = DartdocOptionContext.fromContext(
-          packageGraph.config, Directory(packagePath));
-    }
-    return _config;
-  }
-
-  /// Is this the package at the top of the list?  We display the first
-  /// package specially (with "Libraries" rather than the package name).
-  bool get isFirstPackage =>
-      packageGraph.localPackages.isNotEmpty &&
-      identical(packageGraph.localPackages.first, this);
-
-  @override
-  bool get isSdk => packageMeta.isSdk;
-
-  String _packagePath;
-
-  String get packagePath {
-    if (_packagePath == null) {
-      _packagePath = path.canonicalize(packageMeta.dir.path);
-    }
-    return _packagePath;
-  }
-
-  String get version => packageMeta.version ?? '0.0.0-unknown';
-
-  @override
-  void warn(PackageWarning kind,
-      {String message,
-      Iterable<Locatable> referredFrom,
-      Iterable<String> extendedDebug}) {
-    packageGraph.warnOnElement(this, kind,
-        message: message,
-        referredFrom: referredFrom,
-        extendedDebug: extendedDebug);
-  }
-
-  final PackageMeta _packageMeta;
-
-  PackageMeta get packageMeta => _packageMeta;
-
-  @override
-  Element get element => null;
-
-  @override
-  List<String> get containerOrder => config.packageOrder;
-}
-
-class Parameter extends ModelElement implements EnclosedElement {
-  Parameter(
-      ParameterElement element, Library library, PackageGraph packageGraph,
-      {Member originalMember})
-      : super(element, library, packageGraph, originalMember);
-
-  String get defaultValue {
-    if (!hasDefaultValue) return null;
-    return _parameter.defaultValueCode;
-  }
-
-  @override
-  ModelElement get enclosingElement => (_parameter.enclosingElement != null)
-      ? ModelElement.from(_parameter.enclosingElement, library, packageGraph)
-      : null;
-
-  bool get hasDefaultValue {
-    return _parameter.defaultValueCode != null &&
-        _parameter.defaultValueCode.isNotEmpty;
-  }
-
-  @override
-  String get href {
-    throw StateError('href not implemented for parameters');
-  }
-
-  @override
-  String get htmlId {
-    if (_parameter.enclosingElement != null) {
-      String enclosingName = _parameter.enclosingElement.name;
-      if (_parameter.enclosingElement is GenericFunctionTypeElement) {
-        // TODO(jcollins-g): Drop when GenericFunctionTypeElement populates name.
-        // Also, allowing null here is allowed as a workaround for
-        // dart-lang/sdk#32005.
-        for (Element e = _parameter.enclosingElement;
-            e.enclosingElement != null;
-            e = e.enclosingElement) {
-          enclosingName = e.name;
-          if (enclosingName != null && enclosingName.isNotEmpty) break;
-        }
-      }
-      return '${enclosingName}-param-${name}';
-    } else {
-      return 'param-${name}';
-    }
-  }
-
-  @override
-  int get hashCode => _element == null ? 0 : _element.hashCode;
-
-  @override
-  bool operator ==(Object object) =>
-      object is Parameter && (_parameter.type == object._parameter.type);
-
-  bool get isCovariant => _parameter.isCovariant;
-
-  bool get isOptional => _parameter.isOptional;
-
-  bool get isOptionalNamed => _parameter.isNamed;
-
-  bool get isOptionalPositional => _parameter.isOptionalPositional;
-
-  @override
-  String get kind => 'parameter';
-
-  ParameterElement get _parameter => element as ParameterElement;
-}
-
-abstract class SourceCodeMixin implements Documentable {
-  ModelNode get modelNode;
-
-  CharacterLocation get characterLocation;
-
-  Element get element;
-
-  bool get hasSourceCode => config.includeSource && sourceCode.isNotEmpty;
-
-  Library get library;
-
-  String _sourceCode;
-
-  String get sourceCode =>
-      _sourceCode ??= modelNode == null ? '' : modelNode.sourceCode;
-}
-
-abstract class TypeParameters implements ModelElement {
-  String get nameWithGenerics => '$name$genericParameters';
-
-  String get nameWithLinkedGenerics => '$name$linkedGenericParameters';
-
-  bool get hasGenericParameters => typeParameters.isNotEmpty;
-
-  String get genericParameters {
-    if (typeParameters.isEmpty) return '';
-    return '&lt;<wbr><span class="type-parameter">${typeParameters.map((t) => t.name).join('</span>, <span class="type-parameter">')}</span>&gt;';
-  }
-
-  String get linkedGenericParameters {
-    if (typeParameters.isEmpty) return '';
-    return '<span class="signature">&lt;<wbr><span class="type-parameter">${typeParameters.map((t) => t.linkedName).join('</span>, <span class="type-parameter">')}</span>&gt;</span>';
-  }
-
-  @override
-  DefinedElementType get modelType;
-
-  List<TypeParameter> get typeParameters;
-}
-
-/// Top-level variables. But also picks up getters and setters?
-class TopLevelVariable extends ModelElement
-    with Canonicalization, GetterSetterCombo, SourceCodeMixin, Categorization
-    implements EnclosedElement {
-  @override
-  final Accessor getter;
-  @override
-  final Accessor setter;
-
-  TopLevelVariable(TopLevelVariableElement element, Library library,
-      PackageGraph packageGraph, this.getter, this.setter)
-      : super(element, library, packageGraph, null) {
-    if (getter != null) {
-      getter._enclosingCombo = this;
-    }
-    if (setter != null) {
-      setter._enclosingCombo = this;
-    }
-  }
-
-  @override
-  bool get isInherited => false;
-
-  @override
-  String get documentation {
-    // Verify that hasSetter and hasGetterNoSetter are mutually exclusive,
-    // to prevent displaying more or less than one summary.
-    if (isPublic) {
-      Set<bool> assertCheck = Set()
-        ..addAll([hasPublicSetter, hasPublicGetterNoSetter]);
-      assert(assertCheck.containsAll([true, false]));
-    }
-    return super.documentation;
-  }
-
-  @override
-  ModelElement get enclosingElement => library;
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
-  }
-
-  @override
-  bool get isConst => _variable.isConst;
-
-  @override
-  bool get isFinal {
-    /// isFinal returns true for the variable even if it has an explicit getter
-    /// (which means we should not document it as "final").
-    if (hasExplicitGetter) return false;
-    return _variable.isFinal;
-  }
-
-  @override
-  String get kind => isConst ? 'top-level constant' : 'top-level property';
-
-  @override
-  Set<String> get features => super.features..addAll(comboFeatures);
-
-  @override
-  String _computeDocumentationComment() {
-    String docs = getterSetterDocumentationComment;
-    if (docs.isEmpty) return _variable.documentationComment;
-    return docs;
-  }
-
-  @override
-  String get fileName => isConst ? '$name-constant.html' : '$name.html';
-
-  @override
-  DefinedElementType get modelType => super.modelType;
-
-  TopLevelVariableElement get _variable => (element as TopLevelVariableElement);
-}
-
-class Typedef extends ModelElement
-    with SourceCodeMixin, TypeParameters, Categorization
-    implements EnclosedElement {
-  Typedef(FunctionTypeAliasElement element, Library library,
-      PackageGraph packageGraph)
-      : super(element, library, packageGraph, null);
-
-  @override
-  ModelElement get enclosingElement => library;
-
-  @override
-  String get nameWithGenerics => '$name${super.genericParameters}';
-
-  @override
-  String get genericParameters {
-    if (element is GenericTypeAliasElement) {
-      List<TypeParameterElement> genericTypeParameters =
-          (element as GenericTypeAliasElement).function.typeParameters;
-      if (genericTypeParameters.isNotEmpty) {
-        return '&lt;<wbr><span class="type-parameter">${genericTypeParameters.map((t) => t.name).join('</span>, <span class="type-parameter">')}</span>&gt;';
-      }
-    } // else, all types are resolved.
-    return '';
-  }
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${library.dirName}/$fileName';
-  }
-
-  // Food for mustache.
-  bool get isInherited => false;
-
-  @override
-  String get kind => 'typedef';
-
-  String get linkedReturnType => modelType.createLinkedReturnTypeName();
-
-  @override
-  DefinedElementType get modelType => super.modelType;
-
-  FunctionTypeAliasElement get _typedef =>
-      (element as FunctionTypeAliasElement);
-
-  @override
-  List<TypeParameter> get typeParameters => _typedef.typeParameters.map((f) {
-        return ModelElement.from(f, library, packageGraph) as TypeParameter;
-      }).toList();
-}
-
-class TypeParameter extends ModelElement {
-  TypeParameter(
-      TypeParameterElement element, Library library, PackageGraph packageGraph)
-      : super(element, library, packageGraph, null);
-
-  @override
-  ModelElement get enclosingElement => (element.enclosingElement != null)
-      ? ModelElement.from(element.enclosingElement, library, packageGraph)
-      : null;
-
-  @override
-  String get href {
-    if (!identical(canonicalModelElement, this)) {
-      return canonicalModelElement?.href;
-    }
-    assert(canonicalLibrary != null);
-    assert(canonicalLibrary == library);
-    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$name';
-  }
-
-  @override
-  String get kind => 'type parameter';
-
-  ElementType _boundType;
-
-  ElementType get boundType {
-    if (_boundType == null) {
-      var bound = _typeParameter.bound;
-      if (bound != null) {
-        _boundType = ElementType.from(bound, library, packageGraph);
-      }
-    }
-    return _boundType;
-  }
-
-  String _name;
-
-  @override
-  String get name {
-    if (_name == null) {
-      _name = _typeParameter.bound != null
-          ? '${_typeParameter.name} extends ${boundType.nameWithGenerics}'
-          : _typeParameter.name;
-    }
-    return _name;
-  }
-
-  @override
-  String get linkedName {
-    if (_linkedName == null) {
-      _linkedName = _typeParameter.bound != null
-          ? '${_typeParameter.name} extends ${boundType.linkedName}'
-          : _typeParameter.name;
-    }
-    return _linkedName;
-  }
-
-  TypeParameterElement get _typeParameter => element as TypeParameterElement;
-}
-
-/// Everything you need to instantiate a PackageGraph object for documenting.
-class PackageBuilder {
-  final DartdocOptionContext config;
-
-  PackageBuilder(this.config);
-
-  Future<PackageGraph> buildPackageGraph() async {
-    if (config.topLevelPackageMeta.needsPubGet) {
-      config.topLevelPackageMeta.runPubGet();
-    }
-
-    PackageGraph newGraph = PackageGraph.UninitializedPackageGraph(
-        config,
-        driver,
-        await driver.currentSession.typeSystem,
-        sdk,
-        hasEmbedderSdkFiles);
-    await getLibraries(newGraph);
-    await newGraph.initializePackageGraph();
-    return newGraph;
-  }
-
-  DartSdk _sdk;
-
-  DartSdk get sdk {
-    if (_sdk == null) {
-      _sdk = FolderBasedDartSdk(PhysicalResourceProvider.INSTANCE,
-          PhysicalResourceProvider.INSTANCE.getFolder(config.sdkDir));
-    }
-    return _sdk;
-  }
-
-  EmbedderSdk _embedderSdk;
-
-  EmbedderSdk get embedderSdk {
-    if (_embedderSdk == null && !config.topLevelPackageMeta.isSdk) {
-      _embedderSdk = EmbedderSdk(PhysicalResourceProvider.INSTANCE,
-          EmbedderYamlLocator(packageMap).embedderYamls);
-    }
-    return _embedderSdk;
-  }
-
-  static Map<String, List<file_system.Folder>> _calculatePackageMap(
-      file_system.Folder dir) {
-    Map<String, List<file_system.Folder>> map = Map();
-    var info = package_config.findPackagesFromFile(dir.toUri());
-
-    for (String name in info.packages) {
-      Uri uri = info.asMap()[name];
-      String packagePath = path.normalize(path.fromUri(uri));
-      file_system.Resource resource =
-          PhysicalResourceProvider.INSTANCE.getResource(packagePath);
-      if (resource is file_system.Folder) {
-        map[name] = [resource];
-      }
-    }
-
-    return map;
-  }
-
-  Map<String, List<file_system.Folder>> _packageMap;
-
-  Map<String, List<file_system.Folder>> get packageMap {
-    if (_packageMap == null) {
-      file_system.Folder cwd =
-          PhysicalResourceProvider.INSTANCE.getResource(config.inputDir);
-      _packageMap = _calculatePackageMap(cwd);
-    }
-    return _packageMap;
-  }
-
-  DartUriResolver _embedderResolver;
-
-  DartUriResolver get embedderResolver {
-    if (_embedderResolver == null) {
-      _embedderResolver = DartUriResolver(embedderSdk);
-    }
-    return _embedderResolver;
-  }
-
-  SourceFactory get sourceFactory {
-    List<UriResolver> resolvers = [];
-    resolvers.add(SdkExtUriResolver(packageMap));
-    final UriResolver packageResolver =
-        PackageMapUriResolver(PhysicalResourceProvider.INSTANCE, packageMap);
-    UriResolver sdkResolver;
-    if (embedderSdk == null || embedderSdk.urlMappings.isEmpty) {
-      // The embedder uri resolver has no mappings. Use the default Dart SDK
-      // uri resolver.
-      sdkResolver = DartUriResolver(sdk);
-    } else {
-      // The embedder uri resolver has mappings, use it instead of the default
-      // Dart SDK uri resolver.
-      sdkResolver = embedderResolver;
-    }
-
-    /// [AnalysisDriver] seems to require package resolvers that
-    /// never resolve to embedded SDK files, and the resolvers list must still
-    /// contain a DartUriResolver.  This hack won't be necessary once analyzer
-    /// has a clean public API.
-    resolvers.add(PackageWithoutSdkResolver(packageResolver, sdkResolver));
-    resolvers.add(sdkResolver);
-    resolvers.add(
-        file_system.ResourceUriResolver(PhysicalResourceProvider.INSTANCE));
-
-    assert(
-        resolvers.any((UriResolver resolver) => resolver is DartUriResolver));
-    SourceFactory sourceFactory = SourceFactory(resolvers);
-    return sourceFactory;
-  }
-
-  AnalysisDriver _driver;
-
-  AnalysisDriver get driver {
-    if (_driver == null) {
-      PerformanceLog log = PerformanceLog(null);
-      AnalysisDriverScheduler scheduler = AnalysisDriverScheduler(log);
-      AnalysisOptionsImpl options = AnalysisOptionsImpl();
-
-      // TODO(jcollins-g): pass in an ExperimentStatus instead?
-      options.enabledExperiments = config.enableExperiment
-        ..add('extension-methods');
-
-      // TODO(jcollins-g): Make use of currently not existing API for managing
-      //                   many AnalysisDrivers
-      // TODO(jcollins-g): make use of DartProject isApi()
-      _driver = AnalysisDriver(
-          scheduler,
-          log,
-          PhysicalResourceProvider.INSTANCE,
-          MemoryByteStore(),
-          FileContentOverlay(),
-          null,
-          sourceFactory,
-          options);
-      driver.results.listen((_) {});
-      driver.exceptions.listen((_) {});
-      scheduler.start();
-    }
-    return _driver;
-  }
-
-  /// Return an Iterable with the sdk files we should parse.
-  /// Filter can be String or RegExp (technically, anything valid for
-  /// [String.contains])
-  Iterable<String> getSdkFilesToDocument() sync* {
-    for (var sdkLib in sdk.sdkLibraries) {
-      Source source = sdk.mapDartUri(sdkLib.shortName);
-      yield source.fullName;
-    }
-  }
-
-  /// Parse a single library at [filePath] using the current analysis driver.
-  /// If [filePath] is not a library, returns null.
-  Future<ResolvedLibraryResult> processLibrary(String filePath) async {
-    String name = filePath;
-
-    if (name.startsWith(directoryCurrentPath)) {
-      name = name.substring(directoryCurrentPath.length);
-      if (name.startsWith(Platform.pathSeparator)) name = name.substring(1);
-    }
-    JavaFile javaFile = JavaFile(filePath).getAbsoluteFile();
-    Source source = FileBasedSource(javaFile);
-
-    // TODO(jcollins-g): remove the manual reversal using embedderSdk when we
-    // upgrade to analyzer-0.30 (where DartUriResolver implements
-    // restoreAbsolute)
-    Uri uri = embedderSdk?.fromFileUri(source.uri)?.uri;
-    if (uri != null) {
-      source = FileBasedSource(javaFile, uri);
-    } else {
-      uri = driver.sourceFactory.restoreUri(source);
-      if (uri != null) {
-        source = FileBasedSource(javaFile, uri);
-      }
-    }
-    var sourceKind = await driver.getSourceKind(filePath);
-    // Allow dart source files with inappropriate suffixes (#1897).  Those
-    // do not show up as SourceKind.LIBRARY.
-    if (sourceKind != SourceKind.PART) {
-      // Loading libraryElements from part files works, but is painfully slow
-      // and creates many duplicates.
-      return await driver.currentSession.getResolvedLibrary(source.fullName);
-    }
-    return null;
-  }
-
-  Set<PackageMeta> _packageMetasForFiles(Iterable<String> files) {
-    Set<PackageMeta> metas = Set();
-    for (String filename in files) {
-      metas.add(PackageMeta.fromFilename(filename));
-    }
-    return metas;
-  }
-
-  /// 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 = Set();
-    Set<PackageMeta> current;
-    do {
-      lastPass = _packageMetasForFiles(files);
-
-      // 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.
-      await driver.discoverAvailableFiles();
-      files.addAll(driver.knownFiles);
-      files.addAll(_includeExternalsFrom(driver.knownFiles));
-      current = _packageMetasForFiles(files);
-      // To get canonicalization correct for non-locally documented packages
-      // (so we can generate the right hyperlinks), it's vital that we
-      // add all libraries in dependent packages.  So if the analyzer
-      // discovers some files in a package we haven't seen yet, add files
-      // for that package.
-      for (PackageMeta meta in current.difference(lastPass)) {
-        if (meta.isSdk) {
-          files.addAll(getSdkFilesToDocument());
-        } else {
-          files.addAll(
-              findFilesToDocumentInPackage(meta.dir.path, false, false));
-        }
-      }
-    } while (!lastPass.containsAll(current));
-  }
-
-  /// Given a package name, explore the directory and pull out all top level
-  /// library files in the "lib" directory to document.
-  Iterable<String> findFilesToDocumentInPackage(
-      String basePackageDir, bool autoIncludeDependencies,
-      [bool filterExcludes = true]) sync* {
-    final String sep = path.separator;
-
-    Set<String> packageDirs = Set()..add(basePackageDir);
-
-    if (autoIncludeDependencies) {
-      Map<String, Uri> info = package_config
-          .findPackagesFromFile(
-              Uri.file(path.join(basePackageDir, 'pubspec.yaml')))
-          .asMap();
-      for (String packageName in info.keys) {
-        if (!filterExcludes || !config.exclude.contains(packageName)) {
-          packageDirs.add(path.dirname(info[packageName].toFilePath()));
-        }
-      }
-    }
-
-    for (String packageDir in packageDirs) {
-      var packageLibDir = path.join(packageDir, 'lib');
-      var packageLibSrcDir = path.join(packageLibDir, 'src');
-      // To avoid analyzing package files twice, only files with paths not
-      // containing '/packages' will be added. The only exception is if the file
-      // to analyze already has a '/package' in its path.
-      for (var lib
-          in listDir(packageDir, recursive: true, listDir: _packageDirList)) {
-        if (lib.endsWith('.dart') &&
-            (!lib.contains('${sep}packages${sep}') ||
-                packageDir.contains('${sep}packages${sep}'))) {
-          // Only include libraries within the lib dir that are not in lib/src
-          if (path.isWithin(packageLibDir, lib) &&
-              !path.isWithin(packageLibSrcDir, lib)) {
-            // Only add the file if it does not contain 'part of'
-            var contents = File(lib).readAsStringSync();
-
-            if (contents.contains(newLinePartOfRegexp) ||
-                contents.startsWith(partOfRegexp)) {
-              // NOOP: it's a part file
-            } else {
-              yield lib;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  /// Calculate includeExternals based on a list of files.  Assumes each
-  /// file might be part of a [DartdocOptionContext], and loads those
-  /// objects to find any [DartdocOptionContext.includeExternal] configurations
-  /// therein.
-  Iterable<String> _includeExternalsFrom(Iterable<String> files) sync* {
-    for (String file in files) {
-      DartdocOptionContext fileContext =
-          DartdocOptionContext.fromContext(config, File(file));
-      if (fileContext.includeExternal != null) {
-        yield* fileContext.includeExternal;
-      }
-    }
-  }
-
-  Set<String> getFiles() {
-    Iterable<String> files;
-    if (config.topLevelPackageMeta.isSdk) {
-      files = getSdkFilesToDocument();
-    } else {
-      files = findFilesToDocumentInPackage(
-          config.inputDir, config.autoIncludeDependencies);
-    }
-    files = quiver.concat([files, _includeExternalsFrom(files)]);
-    return Set.from(files.map((s) => File(s).absolute.path));
-  }
-
-  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 (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 = Set();
-    await _parseLibraries(uninitializedPackageGraph.addLibraryToGraph,
-        foundLibraries, files, isLibraryIncluded);
-    if (config.include.isNotEmpty) {
-      Iterable knownLibraryNames = foundLibraries.map((l) => l.name);
-      Set notFound = Set.from(config.include)
-          .difference(Set.from(knownLibraryNames))
-          .difference(Set.from(config.exclude));
-      if (notFound.isNotEmpty) {
-        throw 'Did not find: [${notFound.join(', ')}] in '
-            'known libraries: [${knownLibraryNames.join(', ')}]';
-      }
-    }
-    // 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
-  /// it like a package and only return the `lib` dir.
-  ///
-  /// This ensures that packages don't have non-`lib` content documented.
-  static Iterable<FileSystemEntity> _packageDirList(Directory dir) sync* {
-    var entities = dir.listSync();
-
-    var pubspec = entities.firstWhere(
-        (e) => e is File && path.basename(e.path) == 'pubspec.yaml',
-        orElse: () => null);
-
-    var libDir = entities.firstWhere(
-        (e) => e is Directory && path.basename(e.path) == 'lib',
-        orElse: () => null);
-
-    if (pubspec != null && libDir != null) {
-      yield libDir;
-    } else {
-      yield* entities;
-    }
-  }
-}
-
-/// This class resolves package URIs, but only if a given SdkResolver doesn't
-/// resolve them.
-///
-/// TODO(jcollins-g): remove this hackery when a clean public API to analyzer
-/// exists, and port dartdoc to it.
-class PackageWithoutSdkResolver extends UriResolver {
-  final UriResolver _packageResolver;
-  final UriResolver _sdkResolver;
-
-  PackageWithoutSdkResolver(this._packageResolver, this._sdkResolver);
-
-  @override
-  Source resolveAbsolute(Uri uri, [Uri actualUri]) {
-    if (_sdkResolver.resolveAbsolute(uri, actualUri) == null) {
-      return _packageResolver.resolveAbsolute(uri, actualUri);
-    }
-    return null;
-  }
-
-  @override
-  Uri restoreAbsolute(Source source) {
-    Uri resolved;
-    try {
-      resolved = _sdkResolver.restoreAbsolute(source);
-    } catch (ArgumentError) {
-      // SDK resolvers really don't like being thrown package paths.
-    }
-    if (resolved == null) {
-      return _packageResolver.restoreAbsolute(source);
-    }
-    return null;
-  }
-}
diff --git a/lib/src/model/accessor.dart b/lib/src/model/accessor.dart
new file mode 100644
index 0000000..8b08f4b
--- /dev/null
+++ b/lib/src/model/accessor.dart
@@ -0,0 +1,209 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/member.dart' show Member;
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/utils.dart';
+import 'package:dartdoc/src/warnings.dart';
+
+/// Getters and setters.
+class Accessor extends ModelElement implements EnclosedElement {
+  GetterSetterCombo enclosingCombo;
+
+  Accessor(PropertyAccessorElement element, Library library,
+      PackageGraph packageGraph, Member originalMember)
+      : super(element, library, packageGraph, originalMember);
+
+  String get linkedReturnType {
+    assert(isGetter);
+    return modelType.createLinkedReturnTypeName();
+  }
+
+  bool get isSynthetic => element.isSynthetic;
+
+  String _sourceCode;
+
+  @override
+  String get sourceCode {
+    if (_sourceCode == null) {
+      if (isSynthetic) {
+        _sourceCode = packageGraph
+            .getModelNodeFor((element as PropertyAccessorElement).variable)
+            .sourceCode;
+      } else {
+        _sourceCode = super.sourceCode;
+      }
+    }
+    return _sourceCode;
+  }
+
+  @override
+  List<ModelElement> get computeDocumentationFrom {
+    return super.computeDocumentationFrom;
+  }
+
+  @override
+  String computeDocumentationComment() {
+    if (isSynthetic) {
+      String docComment =
+          (element as PropertyAccessorElement).variable.documentationComment;
+      // If we're a setter, only display something if we have something different than the getter.
+      // TODO(jcollins-g): modify analyzer to do this itself?
+      if (isGetter ||
+          // TODO(jcollins-g): @nodoc reading from comments is at the wrong abstraction level here.
+          (docComment != null &&
+              (docComment.contains('<nodoc>') ||
+                  docComment.contains('@nodoc'))) ||
+          (isSetter &&
+              enclosingCombo.hasGetter &&
+              enclosingCombo.getter.documentationComment != docComment)) {
+        return stripComments(docComment);
+      } else {
+        return '';
+      }
+    }
+    return stripComments(super.computeDocumentationComment());
+  }
+
+  @override
+  void warn(PackageWarning kind,
+      {String message,
+      Iterable<Locatable> referredFrom,
+      Iterable<String> extendedDebug}) {
+    enclosingCombo.warn(kind,
+        message: message,
+        referredFrom: referredFrom,
+        extendedDebug: extendedDebug);
+  }
+
+  @override
+  ModelElement get enclosingElement {
+    if (_accessor.enclosingElement is CompilationUnitElement) {
+      return packageGraph.findButDoNotCreateLibraryFor(
+          _accessor.enclosingElement.enclosingElement);
+    }
+
+    return ModelElement.from(_accessor.enclosingElement, library, packageGraph);
+  }
+
+  @override
+  bool get isCanonical => enclosingCombo.isCanonical;
+
+  @override
+  String get href {
+    return enclosingCombo.href;
+  }
+
+  bool get isGetter => _accessor.isGetter;
+
+  bool get isSetter => _accessor.isSetter;
+
+  @override
+  String get kind => 'accessor';
+
+  String _namePart;
+
+  @override
+  String get namePart {
+    if (_namePart == null) {
+      _namePart = super.namePart.split('=').first;
+    }
+    return _namePart;
+  }
+
+  PropertyAccessorElement get _accessor => (element as PropertyAccessorElement);
+}
+
+/// A getter or setter that is a member of a [Container].
+class ContainerAccessor extends Accessor with ContainerMember, Inheritable {
+  /// Factory will return an [ContainerAccessor] with isInherited = true
+  /// if [element] is in [inheritedAccessors].
+  factory ContainerAccessor.from(PropertyAccessorElement element,
+      Set<PropertyAccessorElement> inheritedAccessors, Class enclosingClass) {
+    ContainerAccessor accessor;
+    if (element == null) return null;
+    if (inheritedAccessors.contains(element)) {
+      accessor = ModelElement.from(
+          element, enclosingClass.library, enclosingClass.packageGraph,
+          enclosingContainer: enclosingClass);
+    } else {
+      accessor = ModelElement.from(
+          element, enclosingClass.library, enclosingClass.packageGraph);
+    }
+    return accessor;
+  }
+
+  ModelElement _enclosingElement;
+  bool _isInherited = false;
+
+  @override
+  bool get isCovariant => isSetter && parameters.first.isCovariant;
+
+  ContainerAccessor(PropertyAccessorElement element, Library library,
+      PackageGraph packageGraph)
+      : super(element, library, packageGraph, null);
+
+  ContainerAccessor.inherited(PropertyAccessorElement element, Library library,
+      PackageGraph packageGraph, this._enclosingElement,
+      {Member originalMember})
+      : super(element, library, packageGraph, originalMember) {
+    _isInherited = true;
+  }
+
+  @override
+  bool get isInherited => _isInherited;
+
+  @override
+  Container get enclosingElement {
+    if (_enclosingElement == null) {
+      _enclosingElement = super.enclosingElement;
+    }
+    return _enclosingElement;
+  }
+
+  bool _overriddenElementIsSet = false;
+  ModelElement _overriddenElement;
+
+  @override
+  ContainerAccessor get overriddenElement {
+    assert(packageGraph.allLibrariesAdded);
+    if (!_overriddenElementIsSet) {
+      _overriddenElementIsSet = true;
+      Element parent = element.enclosingElement;
+      if (parent is ClassElement) {
+        for (InterfaceType t in parent.allSupertypes) {
+          Element accessor = this.isGetter
+              ? t.getGetter(element.name)
+              : t.getSetter(element.name);
+          if (accessor != null) {
+            if (accessor is Member) {
+              accessor = PackageGraph.getBasestElement(accessor);
+            }
+            Class parentClass =
+                ModelElement.fromElement(t.element, packageGraph);
+            List<Field> possibleFields = [];
+            possibleFields.addAll(parentClass.allInstanceFields);
+            possibleFields.addAll(parentClass.staticProperties);
+            String fieldName = accessor.name.replaceFirst('=', '');
+            Field foundField = possibleFields.firstWhere(
+                (f) => f.element.name == fieldName,
+                orElse: () => null);
+            if (foundField != null) {
+              if (this.isGetter) {
+                _overriddenElement = foundField.getter;
+              } else {
+                _overriddenElement = foundField.setter;
+              }
+              assert(!(_overriddenElement as ContainerAccessor).isInherited);
+              break;
+            }
+          }
+        }
+      }
+    }
+    return _overriddenElement;
+  }
+}
diff --git a/lib/src/model/canonicalization.dart b/lib/src/model/canonicalization.dart
new file mode 100644
index 0000000..0438dbb
--- /dev/null
+++ b/lib/src/model/canonicalization.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2019, 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.
+
+import 'package:dartdoc/src/model/model.dart';
+
+/// Classes extending this class have canonicalization support in Dartdoc.
+abstract class Canonicalization implements Locatable, Documentable {
+  bool get isCanonical;
+
+  Library get canonicalLibrary;
+
+  List<ModelCommentReference> get commentRefs => null;
+
+  /// Pieces of the location, split to remove 'package:' and slashes.
+  Set<String> get locationPieces;
+
+  List<ScoredCandidate> scoreCanonicalCandidates(List<Library> libraries) {
+    return libraries.map((l) => scoreElementWithLibrary(l)).toList()..sort();
+  }
+
+  ScoredCandidate scoreElementWithLibrary(Library lib) {
+    ScoredCandidate scoredCandidate = ScoredCandidate(this, lib);
+    Iterable<String> resplit(Set<String> items) sync* {
+      for (String item in items) {
+        for (String subItem in item.split('_')) {
+          yield subItem;
+        }
+      }
+    }
+
+    // Large boost for @canonicalFor, essentially overriding all other concerns.
+    if (lib.canonicalFor.contains(fullyQualifiedName)) {
+      scoredCandidate.alterScore(5.0, 'marked @canonicalFor');
+    }
+    // Penalty for deprecated libraries.
+    if (lib.isDeprecated) scoredCandidate.alterScore(-1.0, 'is deprecated');
+    // Give a big boost if the library has the package name embedded in it.
+    if (lib.package.namePieces.intersection(lib.namePieces).isEmpty) {
+      scoredCandidate.alterScore(1.0, 'embeds package name');
+    }
+    // Give a tiny boost for libraries with long names, assuming they're
+    // more specific (and therefore more likely to be the owner of this symbol).
+    scoredCandidate.alterScore(.01 * lib.namePieces.length, 'name is long');
+    // If we don't know the location of this element, return our best guess.
+    // TODO(jcollins-g): is that even possible?
+    assert(locationPieces.isNotEmpty);
+    if (locationPieces.isEmpty) return scoredCandidate;
+    // The more pieces we have of the location in our library name, the more we should boost our score.
+    scoredCandidate.alterScore(
+        lib.namePieces.intersection(locationPieces).length.toDouble() /
+            locationPieces.length.toDouble(),
+        'element location shares parts with name');
+    // If pieces of location at least start with elements of our library name, boost the score a little bit.
+    double scoreBoost = 0.0;
+    for (String piece in resplit(locationPieces)) {
+      for (String namePiece in lib.namePieces) {
+        if (piece.startsWith(namePiece)) {
+          scoreBoost += 0.001;
+        }
+      }
+    }
+    scoredCandidate.alterScore(
+        scoreBoost, 'element location parts start with parts of name');
+    return scoredCandidate;
+  }
+}
+
+/// This class represents the score for a particular element; how likely
+/// it is that this is the canonical element.
+class ScoredCandidate implements Comparable<ScoredCandidate> {
+  final List<String> reasons = [];
+
+  /// The canonicalization element being scored.
+  final Canonicalization element;
+  final Library library;
+
+  /// The score accumulated so far.  Higher means it is more likely that this
+  /// is the intended canonical Library.
+  double score = 0.0;
+
+  ScoredCandidate(this.element, this.library);
+
+  void alterScore(double scoreDelta, String reason) {
+    score += scoreDelta;
+    if (scoreDelta != 0) {
+      reasons.add(
+          "${reason} (${scoreDelta >= 0 ? '+' : ''}${scoreDelta.toStringAsPrecision(4)})");
+    }
+  }
+
+  @override
+  int compareTo(ScoredCandidate other) {
+    //assert(element == other.element);
+    return score.compareTo(other.score);
+  }
+
+  @override
+  String toString() =>
+      "${library.name}: ${score.toStringAsPrecision(4)} - ${reasons.join(', ')}";
+}
diff --git a/lib/src/model/categorization.dart b/lib/src/model/categorization.dart
new file mode 100644
index 0000000..0f8d9d8
--- /dev/null
+++ b/lib/src/model/categorization.dart
@@ -0,0 +1,134 @@
+// Copyright (c) 2019, 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.
+
+
+import 'package:dartdoc/src/model/model.dart';
+
+final categoryRegexp = RegExp(
+    r'[ ]*{@(api|category|subCategory|image|samples) (.+?)}[ ]*\n?',
+    multiLine: true);
+
+/// Mixin implementing dartdoc categorization for ModelElements.
+abstract class Categorization implements ModelElement {
+  @override
+  String buildDocumentationAddition(String rawDocs) =>
+      _stripAndSetDartdocCategories(rawDocs ??= '');
+
+  /// Parse {@category ...} and related information in API comments, stripping
+  /// out that information from the given comments and returning the stripped
+  /// version.
+  String _stripAndSetDartdocCategories(String rawDocs) {
+    Set<String> _categorySet = Set();
+    Set<String> _subCategorySet = Set();
+    _hasCategorization = false;
+
+    rawDocs = rawDocs.replaceAllMapped(categoryRegexp, (match) {
+      _hasCategorization = true;
+      switch (match[1]) {
+        case 'category':
+        case 'api':
+          _categorySet.add(match[2].trim());
+          break;
+        case 'subCategory':
+          _subCategorySet.add(match[2].trim());
+          break;
+        case 'image':
+          _image = match[2].trim();
+          break;
+        case 'samples':
+          _samples = match[2].trim();
+          break;
+      }
+      return '';
+    });
+
+    if (_categorySet.isEmpty) {
+      // All objects are in the default category if not specified.
+      _categorySet.add(null);
+    }
+    if (_subCategorySet.isEmpty) {
+      // All objects are in the default subcategory if not specified.
+      _subCategorySet.add(null);
+    }
+    _categoryNames = _categorySet.toList()..sort();
+    _subCategoryNames = _subCategorySet.toList()..sort();
+    _image ??= '';
+    _samples ??= '';
+    return rawDocs;
+  }
+
+  bool get hasSubCategoryNames =>
+      subCategoryNames.length > 1 || subCategoryNames.first != null;
+  List<String> _subCategoryNames;
+
+  /// Either a set of strings containing all declared subcategories for this symbol,
+  /// or a set containing Null if none were declared.
+  List<String> get subCategoryNames {
+    // TODO(jcollins-g): avoid side-effect dependency
+    if (_subCategoryNames == null) documentationLocal;
+    return _subCategoryNames;
+  }
+
+  @override
+  bool get hasCategoryNames =>
+      categoryNames.length > 1 || categoryNames.first != null;
+  List<String> _categoryNames;
+
+  /// Either a set of strings containing all declared categories for this symbol,
+  /// or a set containing Null if none were declared.
+  List<String> get categoryNames {
+    // TODO(jcollins-g): avoid side-effect dependency
+    if (_categoryNames == null) documentationLocal;
+    return _categoryNames;
+  }
+
+  bool get hasImage => image.isNotEmpty;
+  String _image;
+
+  /// Either a URI to a defined image, or the empty string if none
+  /// was declared.
+  String get image {
+    // TODO(jcollins-g): avoid side-effect dependency
+    if (_image == null) documentationLocal;
+    return _image;
+  }
+
+  bool get hasSamples => samples.isNotEmpty;
+  String _samples;
+
+  /// Either a URI to documentation with samples, or the empty string if none
+  /// was declared.
+  String get samples {
+    // TODO(jcollins-g): avoid side-effect dependency
+    if (_samples == null) documentationLocal;
+    return _samples;
+  }
+
+  bool _hasCategorization;
+
+  Iterable<Category> _categories;
+
+  Iterable<Category> get categories {
+    if (_categories == null) {
+      _categories = categoryNames
+          .map((n) => package.nameToCategory[n])
+          .where((c) => c != null)
+          .toList()
+        ..sort();
+    }
+    return _categories;
+  }
+
+  Iterable<Category> get displayedCategories {
+    if (config.showUndocumentedCategories) return categories;
+    return categories.where((c) => c.isDocumented);
+  }
+
+  /// True if categories, subcategories, a documentation icon, or samples were
+  /// declared.
+  bool get hasCategorization {
+    if (_hasCategorization == null) documentationLocal;
+    return _hasCategorization;
+  }
+}
diff --git a/lib/src/model/category.dart b/lib/src/model/category.dart
new file mode 100644
index 0000000..d080ac3
--- /dev/null
+++ b/lib/src/model/category.dart
@@ -0,0 +1,208 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:io';
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/package_meta.dart';
+import 'package:dartdoc/src/warnings.dart';
+
+
+/// A category is a subcategory of a package, containing libraries tagged
+/// with a @category identifier.
+class Category extends Nameable
+    with
+        Warnable,
+        Locatable,
+        Canonicalization,
+        MarkdownFileDocumentation,
+        LibraryContainer,
+        TopLevelContainer,
+        Indexable
+    implements Documentable {
+  /// All libraries in [libraries] must come from [package].
+  @override
+  Package package;
+  final String _name;
+  @override
+  DartdocOptionContext config;
+  final Set<Categorization> _allItems = Set();
+
+  final List<Class> _classes = [];
+  final List<Extension> _extensions = [];
+  final List<Enum> _enums = [];
+  final List<Mixin> _mixins = [];
+  final List<Class> _exceptions = [];
+  final List<TopLevelVariable> _constants = [];
+  final List<TopLevelVariable> _properties = [];
+  final List<ModelFunction> _functions = [];
+  final List<Typedef> _typedefs = [];
+
+  Category(this._name, this.package, this.config);
+
+  void addItem(Categorization c) {
+    if (_allItems.contains(c)) return;
+    _allItems.add(c);
+    if (c is Library) {
+      libraries.add(c);
+    } else if (c is Mixin) {
+      _mixins.add(c);
+    } else if (c is Enum) {
+      _enums.add(c);
+    } else if (c is Class) {
+      if (c.isErrorOrException) {
+        _exceptions.add(c);
+      } else {
+        _classes.add(c);
+      }
+    } else if (c is TopLevelVariable) {
+      if (c.isConst) {
+        _constants.add(c);
+      } else {
+        _properties.add(c);
+      }
+    } else if (c is ModelFunction) {
+      _functions.add(c);
+    } else if (c is Typedef) {
+      _typedefs.add(c);
+    } else if (c is Extension) {
+      _extensions.add(c);
+    } else {
+      throw UnimplementedError("Unrecognized element");
+    }
+  }
+
+  @override
+  // TODO(jcollins-g): make [Category] a [Warnable]?
+  Warnable get enclosingElement => null;
+
+  @override
+  Element get element => null;
+
+  @override
+  String get name => categoryDefinition?.displayName ?? _name;
+
+  @override
+  String get sortKey => _name;
+
+  @override
+  List<String> get containerOrder => config.categoryOrder;
+
+  @override
+  String get enclosingName => package.name;
+
+  @override
+  PackageGraph get packageGraph => package.packageGraph;
+
+  @override
+  Library get canonicalLibrary => null;
+
+  @override
+  List<Locatable> get documentationFrom => [this];
+
+  @override
+  DocumentLocation get documentedWhere => package.documentedWhere;
+
+  bool _isDocumented;
+
+  @override
+  bool get isDocumented {
+    if (_isDocumented == null) {
+      _isDocumented = documentedWhere != DocumentLocation.missing &&
+          documentationFile != null;
+    }
+    return _isDocumented;
+  }
+
+  @override
+  String get fullyQualifiedName => name;
+
+  @override
+  String get href =>
+      isCanonical ? '${package.baseHref}topics/${name}-topic.html' : null;
+
+  String get linkedName {
+    String unbrokenCategoryName = name.replaceAll(' ', '&nbsp;');
+    if (isDocumented) {
+      return '<a href="$href">$unbrokenCategoryName</a>';
+    } else {
+      return unbrokenCategoryName;
+    }
+  }
+
+  String _categoryNumberClass;
+
+  /// The position in the container order for this category.
+  String get categoryNumberClass {
+    if (_categoryNumberClass == null) {
+      _categoryNumberClass = "cp-${package.categories.indexOf(this)}";
+    }
+    return _categoryNumberClass;
+  }
+
+  /// Category name used in template as part of the class.
+  String get spanClass => name.split(' ').join('-').toLowerCase();
+
+  CategoryDefinition get categoryDefinition =>
+      config.categories.categoryDefinitions[sortKey];
+
+  @override
+  bool get isCanonical => categoryDefinition != null;
+
+  @override
+  String get kind => 'Topic';
+
+  FileContents _documentationFile;
+
+  @override
+  FileContents get documentationFile {
+    if (_documentationFile == null) {
+      if (categoryDefinition?.documentationMarkdown != null) {
+        _documentationFile =
+            FileContents(File(categoryDefinition.documentationMarkdown));
+      }
+    }
+    return _documentationFile;
+  }
+
+  @override
+  void warn(PackageWarning kind,
+      {String message,
+      Iterable<Locatable> referredFrom,
+      Iterable<String> extendedDebug}) {
+    packageGraph.warnOnElement(this, kind,
+        message: message,
+        referredFrom: referredFrom,
+        extendedDebug: extendedDebug);
+  }
+
+  @override
+  Iterable<Class> get classes => _classes;
+
+  @override
+  Iterable<TopLevelVariable> get constants => _constants;
+
+  @override
+  Iterable<Enum> get enums => _enums;
+
+  @override
+  Iterable<Class> get exceptions => _exceptions;
+
+  @override
+  Iterable<Extension> get extensions => _extensions;
+
+  @override
+  Iterable<ModelFunction> get functions => _functions;
+
+  @override
+  Iterable<Mixin> get mixins => _mixins;
+
+  @override
+  Iterable<TopLevelVariable> get properties => _properties;
+
+  @override
+  Iterable<Typedef> get typedefs => _typedefs;
+}
diff --git a/lib/src/model/class.dart b/lib/src/model/class.dart
new file mode 100644
index 0000000..69ba5b9
--- /dev/null
+++ b/lib/src/model/class.dart
@@ -0,0 +1,594 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/model_utils.dart' as model_utils;
+import 'package:quiver/iterables.dart' as quiver;
+
+class Class extends Container
+    with TypeParameters, Categorization
+    implements EnclosedElement {
+  List<DefinedElementType> mixins;
+  DefinedElementType supertype;
+  List<DefinedElementType> _interfaces;
+  List<Constructor> _constructors;
+  List<Operator> _inheritedOperators;
+  List<Method> _inheritedMethods;
+  List<Field> _inheritedProperties;
+
+  Class(ClassElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph) {
+    packageGraph.specialClasses.addSpecial(this);
+    mixins = _cls.mixins
+        .map((f) {
+          DefinedElementType t = ElementType.from(f, library, packageGraph);
+          return t;
+        })
+        .where((mixin) => mixin != null)
+        .toList(growable: false);
+
+    if (_cls.supertype != null && _cls.supertype.element.supertype != null) {
+      supertype = ElementType.from(_cls.supertype, library, packageGraph);
+    }
+
+    _interfaces = _cls.interfaces
+        .map((f) =>
+            ElementType.from(f, library, packageGraph) as DefinedElementType)
+        .toList(growable: false);
+  }
+
+  Constructor _defaultConstructor;
+
+  Constructor get defaultConstructor {
+    if (_defaultConstructor == null) {
+      _defaultConstructor = constructors
+          .firstWhere((c) => c.isDefaultConstructor, orElse: () => null);
+    }
+    return _defaultConstructor;
+  }
+
+  bool get hasPotentiallyApplicableExtensions =>
+      potentiallyApplicableExtensions.isNotEmpty;
+
+  List<Extension> _potentiallyApplicableExtensions;
+
+  Iterable<Extension> get potentiallyApplicableExtensions {
+    if (_potentiallyApplicableExtensions == null) {
+      _potentiallyApplicableExtensions = model_utils
+          .filterNonDocumented(packageGraph.extensions)
+          .where((e) => e.couldApplyTo(this))
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _potentiallyApplicableExtensions;
+  }
+
+  Iterable<Method> get allInstanceMethods =>
+      quiver.concat([instanceMethods, inheritedMethods]);
+
+  @override
+  Iterable<Method> get allPublicInstanceMethods =>
+      model_utils.filterNonPublic(allInstanceMethods);
+
+  bool get allPublicInstanceMethodsInherited =>
+      instanceMethods.every((f) => f.isInherited);
+
+  @override
+  Iterable<Field> get allInstanceFields =>
+      quiver.concat([instanceProperties, inheritedProperties]);
+
+  bool get allPublicInstancePropertiesInherited =>
+      allPublicInstanceProperties.every((f) => f.isInherited);
+
+  @override
+  Iterable<Operator> get allOperators =>
+      quiver.concat([operators, inheritedOperators]);
+
+  bool get allPublicOperatorsInherited =>
+      allPublicOperators.every((f) => f.isInherited);
+
+  Map<Element, ModelElement> _allElements;
+
+  Map<Element, ModelElement> get allElements {
+    if (_allElements == null) {
+      _allElements = Map();
+      for (ModelElement me in allModelElements) {
+        assert(!_allElements.containsKey(me.element));
+        _allElements[me.element] = me;
+      }
+    }
+    return _allElements;
+  }
+
+  Map<String, List<ModelElement>> _allModelElementsByNamePart;
+
+  /// Helper for [_MarkdownCommentReference._getResultsForClass].
+  Map<String, List<ModelElement>> get allModelElementsByNamePart {
+    if (_allModelElementsByNamePart == null) {
+      _allModelElementsByNamePart = {};
+      for (ModelElement me in allModelElements) {
+        _allModelElementsByNamePart.update(
+            me.namePart, (List<ModelElement> v) => v..add(me),
+            ifAbsent: () => <ModelElement>[me]);
+      }
+    }
+    return _allModelElementsByNamePart;
+  }
+
+  /// This class might be canonical for elements it does not contain.
+  /// See [Inheritable.canonicalEnclosingContainer].
+  bool contains(Element element) => allElements.containsKey(element);
+
+  Map<String, List<ModelElement>> _membersByName;
+
+  /// Given a ModelElement that is a member of some other class, return
+  /// a member of this class that has the same name and return type.
+  ///
+  /// This enables object substitution for canonicalization, such as Interceptor
+  /// for Object.
+  ModelElement memberByExample(ModelElement example) {
+    if (_membersByName == null) {
+      _membersByName = Map();
+      for (ModelElement me in allModelElements) {
+        if (!_membersByName.containsKey(me.name)) {
+          _membersByName[me.name] = List();
+        }
+        _membersByName[me.name].add(me);
+      }
+    }
+    ModelElement member;
+    Iterable<ModelElement> possibleMembers = _membersByName[example.name]
+        .where((e) => e.runtimeType == example.runtimeType);
+    if (example.runtimeType == Accessor) {
+      possibleMembers = possibleMembers.where(
+          (e) => (example as Accessor).isGetter == (e as Accessor).isGetter);
+    }
+    member = possibleMembers.first;
+    assert(possibleMembers.length == 1);
+    return member;
+  }
+
+  List<ModelElement> _allModelElements;
+
+  List<ModelElement> get allModelElements {
+    if (_allModelElements == null) {
+      _allModelElements = List.from(
+          quiver.concat([
+            allInstanceMethods,
+            allInstanceFields,
+            allAccessors,
+            allOperators,
+            constants,
+            constructors,
+            staticMethods,
+            staticProperties,
+            typeParameters,
+          ]),
+          growable: false);
+    }
+    return _allModelElements;
+  }
+
+  List<ModelElement> _allCanonicalModelElements;
+
+  List<ModelElement> get allCanonicalModelElements {
+    return (_allCanonicalModelElements ??=
+        allModelElements.where((e) => e.isCanonical).toList());
+  }
+
+  List<Constructor> get constructors {
+    if (_constructors != null) return _constructors;
+
+    _constructors = _cls.constructors.map((e) {
+      return ModelElement.from(e, library, packageGraph) as Constructor;
+    }).toList(growable: true)
+      ..sort(byName);
+
+    return _constructors;
+  }
+
+  Iterable<Constructor> get publicConstructors =>
+      model_utils.filterNonPublic(constructors);
+
+  /// Returns the library that encloses this element.
+  @override
+  ModelElement get enclosingElement => library;
+
+  @override
+  String get fileName => "${name}-class.html";
+
+  String get fullkind {
+    if (isAbstract) return 'abstract $kind';
+    return kind;
+  }
+
+  bool get hasPublicConstructors => publicConstructors.isNotEmpty;
+
+  bool get hasPublicImplementors => publicImplementors.isNotEmpty;
+
+  bool get hasInstanceProperties => instanceProperties.isNotEmpty;
+
+  bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
+
+  @override
+  bool get hasPublicMethods =>
+      publicInstanceMethods.isNotEmpty || publicInheritedMethods.isNotEmpty;
+
+  bool get hasPublicMixins => publicMixins.isNotEmpty;
+
+  bool get hasModifiers =>
+      hasPublicMixins ||
+      hasAnnotations ||
+      hasPublicInterfaces ||
+      hasPublicSuperChainReversed ||
+      hasPublicImplementors ||
+      hasPotentiallyApplicableExtensions;
+
+  @override
+  bool get hasPublicOperators =>
+      publicOperators.isNotEmpty || publicInheritedOperators.isNotEmpty;
+
+  @override
+  bool get hasPublicProperties =>
+      publicInheritedProperties.isNotEmpty ||
+      publicInstanceProperties.isNotEmpty;
+
+  @override
+  bool get hasPublicStaticMethods => publicStaticMethods.isNotEmpty;
+
+  bool get hasPublicSuperChainReversed => publicSuperChainReversed.isNotEmpty;
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${library.dirName}/$fileName';
+  }
+
+  /// Returns all the implementors of this class.
+  Iterable<Class> get publicImplementors {
+    return model_utils.filterNonPublic(model_utils.findCanonicalFor(
+        packageGraph.implementors[href] != null
+            ? packageGraph.implementors[href]
+            : []));
+  }
+
+  List<Method> get inheritedMethods {
+    if (_inheritedMethods == null) {
+      _inheritedMethods = <Method>[];
+      Set<String> methodNames = methods.map((m) => m.element.name).toSet();
+
+      Set<ExecutableElement> inheritedMethodElements =
+          _inheritedElements.where((e) {
+        return (e is MethodElement &&
+            !e.isOperator &&
+            e is! PropertyAccessorElement &&
+            !methodNames.contains(e.name));
+      }).toSet();
+
+      for (ExecutableElement e in inheritedMethodElements) {
+        Method m = ModelElement.from(e, library, packageGraph,
+            enclosingContainer: this);
+        _inheritedMethods.add(m);
+      }
+      _inheritedMethods.sort(byName);
+    }
+    return _inheritedMethods;
+  }
+
+  Iterable get publicInheritedMethods =>
+      model_utils.filterNonPublic(inheritedMethods);
+
+  bool get hasPublicInheritedMethods => publicInheritedMethods.isNotEmpty;
+
+  List<Operator> get inheritedOperators {
+    if (_inheritedOperators == null) {
+      _inheritedOperators = [];
+      Set<String> operatorNames = operators.map((o) => o.element.name).toSet();
+
+      Set<ExecutableElement> inheritedOperatorElements =
+          _inheritedElements.where((e) {
+        return (e is MethodElement &&
+            e.isOperator &&
+            !operatorNames.contains(e.name));
+      }).toSet();
+      for (ExecutableElement e in inheritedOperatorElements) {
+        Operator o = ModelElement.from(e, library, packageGraph,
+            enclosingContainer: this);
+        _inheritedOperators.add(o);
+      }
+      _inheritedOperators.sort(byName);
+    }
+    return _inheritedOperators;
+  }
+
+  Iterable<Operator> get publicInheritedOperators =>
+      model_utils.filterNonPublic(inheritedOperators);
+
+  List<Field> get inheritedProperties {
+    if (_inheritedProperties == null) {
+      _inheritedProperties = allFields.where((f) => f.isInherited).toList()
+        ..sort(byName);
+    }
+    return _inheritedProperties;
+  }
+
+  Iterable<Field> get publicInheritedProperties =>
+      model_utils.filterNonPublic(inheritedProperties);
+
+  Iterable<Method> get publicInstanceMethods => instanceMethods;
+
+  List<DefinedElementType> get interfaces => _interfaces;
+
+  Iterable<DefinedElementType> get publicInterfaces =>
+      model_utils.filterNonPublic(interfaces);
+
+  bool get isAbstract => _cls.isAbstract;
+
+  @override
+  bool get isCanonical => super.isCanonical && isPublic;
+
+  bool get isErrorOrException {
+    bool _doCheck(ClassElement element) {
+      return (element.library.isDartCore &&
+          (element.name == 'Exception' || element.name == 'Error'));
+    }
+
+    // if this class is itself Error or Exception, return true
+    if (_doCheck(_cls)) return true;
+
+    return _cls.allSupertypes.map((t) => t.element).any(_doCheck);
+  }
+
+  /// Returns true if [other] is a parent class for this class.
+  @override
+  bool isInheritingFrom(covariant Class other) =>
+      superChain.map((et) => (et.element as Class)).contains(other);
+
+  @override
+  String get kind => 'class';
+
+  Iterable<DefinedElementType> get publicMixins =>
+      model_utils.filterNonPublic(mixins);
+
+  @override
+  DefinedElementType get modelType => super.modelType;
+
+  /// Not the same as superChain as it may include mixins.
+  /// It's really not even the same as ordinary Dart inheritance, either,
+  /// because we pretend that interfaces are part of the inheritance chain
+  /// to include them in the set of things we might link to for documentation
+  /// purposes in abstract classes.
+  List<Class> _inheritanceChain;
+
+  List<Class> get inheritanceChain {
+    if (_inheritanceChain == null) {
+      _inheritanceChain = [];
+      _inheritanceChain.add(this);
+
+      /// Caching should make this recursion a little less painful.
+      for (Class c in mixins.reversed.map((e) => (e.element as Class))) {
+        _inheritanceChain.addAll(c.inheritanceChain);
+      }
+
+      for (Class c in superChain.map((e) => (e.element as Class))) {
+        _inheritanceChain.addAll(c.inheritanceChain);
+      }
+
+      /// Interfaces need to come last, because classes in the superChain might
+      /// implement them even when they aren't mentioned.
+      _inheritanceChain.addAll(
+          interfaces.expand((e) => (e.element as Class).inheritanceChain));
+    }
+    return _inheritanceChain.toList(growable: false);
+  }
+
+  List<DefinedElementType> get superChain {
+    List<DefinedElementType> typeChain = [];
+    DefinedElementType parent = supertype;
+    while (parent != null) {
+      typeChain.add(parent);
+      if (parent.type is InterfaceType) {
+        // Avoid adding [Object] to the superChain (_supertype already has this
+        // check)
+        if ((parent.type as InterfaceType)?.superclass?.superclass == null) {
+          parent = null;
+        } else {
+          parent = ElementType.from(
+              (parent.type as InterfaceType).superclass, library, packageGraph);
+        }
+      } else {
+        parent = (parent.element as Class).supertype;
+      }
+    }
+    return typeChain;
+  }
+
+  Iterable<DefinedElementType> get publicSuperChain =>
+      model_utils.filterNonPublic(superChain);
+
+  Iterable<DefinedElementType> get publicSuperChainReversed =>
+      publicSuperChain.toList().reversed;
+
+  List<ExecutableElement> __inheritedElements;
+
+  List<ExecutableElement> get _inheritedElements {
+    if (__inheritedElements == null) {
+      var classElement = element as ClassElement;
+      if (classElement.isDartCoreObject) {
+        return __inheritedElements = <ExecutableElement>[];
+      }
+
+      var inheritance = definingLibrary.inheritanceManager;
+      var classType = classElement.thisType;
+      var cmap = inheritance.getInheritedConcreteMap(classType);
+      var imap = inheritance.getInheritedMap(classType);
+
+      var combinedMap = <String, ExecutableElement>{};
+      for (var nameObj in cmap.keys) {
+        combinedMap[nameObj.name] = cmap[nameObj];
+      }
+      for (var nameObj in imap.keys) {
+        combinedMap[nameObj.name] ??= imap[nameObj];
+      }
+
+      __inheritedElements = combinedMap.values.toList();
+    }
+    return __inheritedElements;
+  }
+
+  List<Field> _fields;
+
+  /// Internal only because subclasses are allowed to override how
+  /// these are mapped to [allInheritedFields] and so forth.
+  @override
+  List<Field> get allFields {
+    if (_fields == null) {
+      _fields = [];
+      Set<PropertyAccessorElement> inheritedAccessors = Set()
+        ..addAll(_inheritedElements.whereType<PropertyAccessorElement>());
+
+      // This structure keeps track of inherited accessors, allowing lookup
+      // by field name (stripping the '=' from setters).
+      Map<String, List<PropertyAccessorElement>> accessorMap = Map();
+      for (PropertyAccessorElement accessorElement in inheritedAccessors) {
+        String name = accessorElement.name.replaceFirst('=', '');
+        accessorMap.putIfAbsent(name, () => []);
+        accessorMap[name].add(accessorElement);
+      }
+
+      // For half-inherited fields, the analyzer only links the non-inherited
+      // to the [FieldElement].  Compose our [Field] class by hand by looking up
+      // inherited accessors that may be related.
+      for (FieldElement f in _cls.fields) {
+        PropertyAccessorElement getterElement = f.getter;
+        if (getterElement == null && accessorMap.containsKey(f.name)) {
+          getterElement = accessorMap[f.name]
+              .firstWhere((e) => e.isGetter, orElse: () => null);
+        }
+        PropertyAccessorElement setterElement = f.setter;
+        if (setterElement == null && accessorMap.containsKey(f.name)) {
+          setterElement = accessorMap[f.name]
+              .firstWhere((e) => e.isSetter, orElse: () => null);
+        }
+        _addSingleField(getterElement, setterElement, inheritedAccessors, f);
+        accessorMap.remove(f.name);
+      }
+
+      // Now we only have inherited accessors who aren't associated with
+      // anything in cls._fields.
+      for (String fieldName in accessorMap.keys) {
+        List<PropertyAccessorElement> elements =
+            accessorMap[fieldName].toList();
+        PropertyAccessorElement getterElement =
+            elements.firstWhere((e) => e.isGetter, orElse: () => null);
+        PropertyAccessorElement setterElement =
+            elements.firstWhere((e) => e.isSetter, orElse: () => null);
+        _addSingleField(getterElement, setterElement, inheritedAccessors);
+      }
+
+      _fields.sort(byName);
+    }
+    return _fields;
+  }
+
+  /// Add a single Field to _fields.
+  ///
+  /// If [f] is not specified, pick the FieldElement from the PropertyAccessorElement
+  /// whose enclosing class inherits from the other (defaulting to the getter)
+  /// and construct a Field using that.
+  void _addSingleField(
+      PropertyAccessorElement getterElement,
+      PropertyAccessorElement setterElement,
+      Set<PropertyAccessorElement> inheritedAccessors,
+      [FieldElement f]) {
+    ContainerAccessor getter =
+        ContainerAccessor.from(getterElement, inheritedAccessors, this);
+    ContainerAccessor setter =
+        ContainerAccessor.from(setterElement, inheritedAccessors, this);
+    // Rebind getterElement/setterElement as ModelElement.from can resolve
+    // MultiplyInheritedExecutableElements or resolve Members.
+    getterElement = getter?.element;
+    setterElement = setter?.element;
+    assert(!(getter == null && setter == null));
+    if (f == null) {
+      // Pick an appropriate FieldElement to represent this element.
+      // Only hard when dealing with a synthetic Field.
+      if (getter != null && setter == null) {
+        f = getterElement.variable;
+      } else if (getter == null && setter != null) {
+        f = setterElement.variable;
+      } else {
+        /* getter != null && setter != null */
+        // In cases where a Field is composed of two Accessors defined in
+        // different places in the inheritance chain, there are two FieldElements
+        // for this single Field we're trying to compose.  Pick the one closest
+        // to this class on the inheritance chain.
+        if ((setter.enclosingElement)
+            .isInheritingFrom(getter.enclosingElement)) {
+          f = setterElement.variable;
+        } else {
+          f = getterElement.variable;
+        }
+      }
+    }
+    Field field;
+    if ((getter == null || getter.isInherited) &&
+        (setter == null || setter.isInherited)) {
+      // Field is 100% inherited.
+      field = ModelElement.from(f, library, packageGraph,
+          enclosingContainer: this, getter: getter, setter: setter);
+    } else {
+      // Field is <100% inherited (could be half-inherited).
+      // TODO(jcollins-g): Navigation is probably still confusing for
+      // half-inherited fields when traversing the inheritance tree.  Make
+      // this better, somehow.
+      field = ModelElement.from(f, library, packageGraph,
+          getter: getter, setter: setter);
+    }
+    _fields.add(field);
+  }
+
+  ClassElement get _cls => (element as ClassElement);
+
+  List<Method> _methods;
+
+  @override
+  List<Method> get methods {
+    if (_methods == null) {
+      _methods = _cls.methods.map((e) {
+        return ModelElement.from(e, library, packageGraph) as Method;
+      }).toList(growable: false)
+        ..sort(byName);
+    }
+    return _methods;
+  }
+
+  List<TypeParameter> _typeParameters;
+
+  // a stronger hash?
+  @override
+  List<TypeParameter> get typeParameters {
+    if (_typeParameters == null) {
+      _typeParameters = _cls.typeParameters.map((f) {
+        var lib = Library(f.enclosingElement.library, packageGraph);
+        return ModelElement.from(f, lib, packageGraph) as TypeParameter;
+      }).toList();
+    }
+    return _typeParameters;
+  }
+
+  @override
+  bool operator ==(o) =>
+      o is Class &&
+      name == o.name &&
+      o.library.name == library.name &&
+      o.library.package.name == library.package.name;
+}
diff --git a/lib/src/model/constructor.dart b/lib/src/model/constructor.dart
new file mode 100644
index 0000000..781417b
--- /dev/null
+++ b/lib/src/model/constructor.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+class Constructor extends ModelElement
+    with TypeParameters
+    implements EnclosedElement {
+  Constructor(
+      ConstructorElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph, null);
+
+  @override
+  CharacterLocation get characterLocation {
+    if (element.isSynthetic) {
+      // Make warnings for a synthetic constructor refer to somewhere reasonable
+      // since a synthetic constructor has no definition independent of the
+      // parent class.
+      return enclosingElement.characterLocation;
+    }
+    return super.characterLocation;
+  }
+
+  @override
+  // TODO(jcollins-g): Revisit this when dart-lang/sdk#31517 is implemented.
+  List<TypeParameter> get typeParameters =>
+      (enclosingElement as Class).typeParameters;
+
+  @override
+  ModelElement get enclosingElement =>
+      ModelElement.from(_constructor.enclosingElement, library, packageGraph);
+
+  String get fullKind {
+    if (isConst) return 'const $kind';
+    if (isFactory) return 'factory $kind';
+    return kind;
+  }
+
+  @override
+  String get fullyQualifiedName {
+    if (isDefaultConstructor) return super.fullyQualifiedName;
+    return '${library.name}.$name';
+  }
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$name.html';
+  }
+
+  @override
+  bool get isConst => _constructor.isConst;
+
+  bool get isDefaultConstructor => name == enclosingElement.name;
+
+  bool get isFactory => _constructor.isFactory;
+
+  @override
+  String get kind => 'constructor';
+
+  @override
+  DefinedElementType get modelType => super.modelType;
+
+  String _name;
+
+  @override
+  String get name {
+    if (_name == null) {
+      String constructorName = element.name;
+      if (constructorName.isEmpty) {
+        _name = enclosingElement.name;
+      } else {
+        _name = '${enclosingElement.name}.$constructorName';
+      }
+    }
+    return _name;
+  }
+
+  String _nameWithGenerics;
+
+  @override
+  String get nameWithGenerics {
+    if (_nameWithGenerics == null) {
+      String constructorName = element.name;
+      if (constructorName.isEmpty) {
+        _nameWithGenerics = '${enclosingElement.name}${genericParameters}';
+      } else {
+        _nameWithGenerics =
+            '${enclosingElement.name}${genericParameters}.$constructorName';
+      }
+    }
+    return _nameWithGenerics;
+  }
+
+  String get shortName {
+    if (name.contains('.')) {
+      return name.substring(_constructor.enclosingElement.name.length + 1);
+    } else {
+      return name;
+    }
+  }
+
+  ConstructorElement get _constructor => (element as ConstructorElement);
+}
diff --git a/lib/src/model/container.dart b/lib/src/model/container.dart
new file mode 100644
index 0000000..9b18110
--- /dev/null
+++ b/lib/src/model/container.dart
@@ -0,0 +1,135 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/model_utils.dart' as model_utils;
+import 'package:quiver/iterables.dart' as quiver;
+
+// Can be either a Class or Extension, used in the package graph and template data.
+// Aggregates some of the common getters.
+abstract class Container extends ModelElement {
+  List<Field> _constants;
+  List<Operator> _operators;
+  List<Method> _staticMethods;
+  List<Method> _instanceMethods;
+  List<Field> _staticFields;
+  List<Field> _instanceFields;
+
+  Container(Element element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph, null);
+
+  bool get isClass => element is ClassElement;
+
+  bool get isExtension => element is ExtensionElement;
+
+  List<Method> get methods => [];
+
+  List<Method> get instanceMethods {
+    if (_instanceMethods != null) return _instanceMethods;
+
+    _instanceMethods = methods
+        .where((m) => !m.isStatic && !m.isOperator)
+        .toList(growable: false)
+          ..sort(byName);
+    return _instanceMethods;
+  }
+
+  bool get hasPublicMethods =>
+      model_utils.filterNonPublic(instanceMethods).isNotEmpty;
+
+  Iterable<Method> get allPublicInstanceMethods =>
+      model_utils.filterNonPublic(instanceMethods);
+
+  List<Method> get staticMethods {
+    if (_staticMethods == null) {
+      _staticMethods = methods.where((m) => m.isStatic).toList(growable: false)
+        ..sort(byName);
+    }
+    return _staticMethods;
+  }
+
+  bool get hasPublicStaticMethods =>
+      model_utils.filterNonPublic(staticMethods).isNotEmpty;
+
+  Iterable<Method> get publicStaticMethods =>
+      model_utils.filterNonPublic(staticMethods);
+
+  List<Operator> get operators {
+    if (_operators == null) {
+      _operators = methods
+          .where((m) => m.isOperator)
+          .cast<Operator>()
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _operators;
+  }
+
+  Iterable<Operator> get allOperators => operators;
+
+  bool get hasPublicOperators => publicOperators.isNotEmpty;
+
+  Iterable<Operator> get allPublicOperators =>
+      model_utils.filterNonPublic(allOperators);
+
+  Iterable<Operator> get publicOperators =>
+      model_utils.filterNonPublic(operators);
+
+  List<Field> get allFields => [];
+
+  List<Field> get staticProperties {
+    if (_staticFields == null) {
+      _staticFields = allFields
+          .where((f) => f.isStatic && !f.isConst)
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _staticFields;
+  }
+
+  Iterable<Field> get publicStaticProperties =>
+      model_utils.filterNonPublic(staticProperties);
+
+  bool get hasPublicStaticProperties => publicStaticProperties.isNotEmpty;
+
+  List<Field> get instanceProperties {
+    if (_instanceFields == null) {
+      _instanceFields = allFields
+          .where((f) => !f.isStatic && !f.isInherited && !f.isConst)
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _instanceFields;
+  }
+
+  Iterable<Field> get publicInstanceProperties =>
+      model_utils.filterNonPublic(instanceProperties);
+
+  bool get hasPublicProperties => publicInstanceProperties.isNotEmpty;
+
+  Iterable<Field> get allInstanceFields => instanceProperties;
+
+  Iterable<Field> get allPublicInstanceProperties =>
+      model_utils.filterNonPublic(allInstanceFields);
+
+  bool isInheritingFrom(Container other) => false;
+
+  List<Field> get constants {
+    if (_constants == null) {
+      _constants = allFields.where((f) => f.isConst).toList(growable: false)
+        ..sort(byName);
+    }
+    return _constants;
+  }
+
+  Iterable<Field> get publicConstants => model_utils.filterNonPublic(constants);
+
+  bool get hasPublicConstants => publicConstants.isNotEmpty;
+
+  Iterable<Accessor> get allAccessors => quiver.concat([
+        allInstanceFields.expand((f) => f.allAccessors),
+        constants.map((c) => c.getter)
+      ]);
+}
diff --git a/lib/src/model/container_member.dart b/lib/src/model/container_member.dart
new file mode 100644
index 0000000..76362f3
--- /dev/null
+++ b/lib/src/model/container_member.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, 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.
+
+import 'package:dartdoc/src/model/model.dart';
+
+/// A [ModelElement] that is a [Container] member.
+mixin ContainerMember on ModelElement implements EnclosedElement {
+  /// True if this [ContainerMember] is inherited from a different class.
+  bool get isInherited;
+
+  /// True if this [ContainerMember] is overriding a superclass.
+  bool get isOverride;
+
+  /// True if this [ContainerMember] has a parameter whose type is overridden
+  /// by a subtype.
+  bool get isCovariant;
+
+  /// True if this [ContainerMember] is from an applicable [Extension].
+  /// False otherwise, including if this [ContainerMember]'s [enclosingElement]
+  /// is the extension it was declared in.
+  // TODO(jcollins-g): This semantic is a little confusing, because a declared
+  // extension member element returns false.  The rationale is an
+  // extension member is not extending itself.
+  // FIXME(jcollins-g): Remove concrete implementation after [Extendable] is
+  // implemented.
+  bool get isExtended => false;
+
+  Container _definingEnclosingContainer;
+
+  Container get definingEnclosingContainer {
+    if (_definingEnclosingContainer == null) {
+      _definingEnclosingContainer =
+          ModelElement.fromElement(element.enclosingElement, packageGraph);
+    }
+    return _definingEnclosingContainer;
+  }
+
+  @override
+  Set<String> get features {
+    Set<String> _features = super.features;
+    if (isOverride) _features.add('override');
+    if (isInherited) _features.add('inherited');
+    if (isCovariant) _features.add('covariant');
+    if (isExtended) _features.add('extended');
+    return _features;
+  }
+
+  bool _canonicalEnclosingContainerIsSet = false;
+  Container _canonicalEnclosingContainer;
+
+  Container get canonicalEnclosingContainer {
+    if (!_canonicalEnclosingContainerIsSet) {
+      _canonicalEnclosingContainer = computeCanonicalEnclosingContainer();
+      _canonicalEnclosingContainerIsSet = true;
+      assert(_canonicalEnclosingContainer == null ||
+          _canonicalEnclosingContainer.isDocumented);
+    }
+    return _canonicalEnclosingContainer;
+  }
+
+  Container computeCanonicalEnclosingContainer() {
+    // TODO(jcollins-g): move Extension specific code to [Extendable]
+    if (enclosingElement is! Extension ||
+        (enclosingElement is Extension && enclosingElement.isDocumented)) {
+      return packageGraph
+          .findCanonicalModelElementFor(enclosingElement.element);
+    }
+    return null;
+  }
+}
diff --git a/lib/src/model/documentable.dart b/lib/src/model/documentable.dart
new file mode 100644
index 0000000..52addfc
--- /dev/null
+++ b/lib/src/model/documentable.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2019, 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.
+
+import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/markdown_processor.dart';
+import 'package:dartdoc/src/package_meta.dart';
+import 'package:path/path.dart' as path;
+
+import 'model.dart';
+
+/// Bridges the gap between model elements and packages,
+/// both of which have documentation.
+abstract class Documentable extends Nameable {
+  String get documentation;
+
+  String get documentationAsHtml;
+
+  bool get hasDocumentation;
+
+  bool get hasExtendedDocumentation;
+
+  String get oneLineDoc;
+
+  PackageGraph get packageGraph;
+
+  bool get isDocumented;
+
+  DartdocOptionContext get config;
+}
+
+/// For a given package, indicate with this enum whether it should be documented
+/// [local]ly, whether we should treat the package as [missing] and any references
+/// to it made canonical to this package, or [remote], indicating that
+/// we can build hrefs to an external source.
+enum DocumentLocation {
+  local,
+  missing,
+  remote,
+}
+
+abstract class MarkdownFileDocumentation
+    implements Documentable, Canonicalization {
+  DocumentLocation get documentedWhere;
+
+  @override
+  String get documentation => documentationFile?.contents;
+
+  Documentation __documentation;
+
+  Documentation get _documentation {
+    if (__documentation != null) return __documentation;
+    __documentation = Documentation.forElement(this);
+    return __documentation;
+  }
+
+  @override
+  String get documentationAsHtml => _documentation.asHtml;
+
+  @override
+  bool get hasDocumentation =>
+      documentationFile != null && documentationFile.contents.isNotEmpty;
+
+  @override
+  bool get hasExtendedDocumentation =>
+      documentation != null && documentation.isNotEmpty;
+
+  @override
+  bool get isDocumented;
+
+  @override
+  String get oneLineDoc => __documentation.asOneLiner;
+
+  FileContents get documentationFile;
+
+  @override
+  String get location => path.toUri(documentationFile.file.path).toString();
+
+  @override
+  Set<String> get locationPieces => Set.from(<String>[location]);
+}
diff --git a/lib/src/model/dynamic.dart b/lib/src/model/dynamic.dart
new file mode 100644
index 0000000..f8a9636
--- /dev/null
+++ b/lib/src/model/dynamic.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+class Dynamic extends ModelElement {
+  Dynamic(Element element, PackageGraph packageGraph)
+      : super(element, null, packageGraph, null);
+
+  /// [dynamic] is not a real object, and so we can't document it, so there
+  /// can be nothing canonical for it.
+  @override
+  ModelElement get canonicalModelElement => null;
+
+  @override
+  ModelElement get enclosingElement => throw UnsupportedError('');
+
+  /// And similiarly, even if someone references it directly it can have
+  /// no hyperlink.
+  @override
+  String get href => null;
+
+  @override
+  String get kind => 'dynamic';
+
+  @override
+  String get linkedName => 'dynamic';
+}
diff --git a/lib/src/model/enclosed_element.dart b/lib/src/model/enclosed_element.dart
new file mode 100644
index 0000000..228be4b
--- /dev/null
+++ b/lib/src/model/enclosed_element.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, 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.
+
+import 'package:dartdoc/src/model/model.dart';
+
+/// An element that is enclosed by some other element.
+///
+/// Libraries are not enclosed.
+abstract class EnclosedElement {
+  ModelElement get enclosingElement;
+}
diff --git a/lib/src/model/enum.dart b/lib/src/model/enum.dart
new file mode 100644
index 0000000..0ff67e4
--- /dev/null
+++ b/lib/src/model/enum.dart
@@ -0,0 +1,104 @@
+// Copyright (c) 2019, 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.
+
+// TODO(jcollins-g): Consider Enum as subclass of Container?
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+class Enum extends Class {
+  Enum(ClassElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph);
+
+  List<EnumField> _instanceProperties;
+
+  @override
+  List<EnumField> get instanceProperties {
+    if (_instanceProperties == null) {
+      _instanceProperties = super
+          .instanceProperties
+          .map((Field p) => ModelElement.from(
+              p.element, p.library, p.packageGraph,
+              getter: p.getter, setter: p.setter) as EnumField)
+          .toList(growable: false);
+    }
+    return _instanceProperties;
+  }
+
+  @override
+  String get kind => 'enum';
+}
+
+/// Enum's fields are virtual, so we do a little work to create
+/// usable values for the docs.
+class EnumField extends Field {
+  int _index;
+
+  EnumField(FieldElement element, Library library, PackageGraph packageGraph,
+      Accessor getter, Accessor setter)
+      : super(element, library, packageGraph, getter, setter);
+
+  EnumField.forConstant(this._index, FieldElement element, Library library,
+      PackageGraph packageGraph, Accessor getter)
+      : super(element, library, packageGraph, getter, null);
+
+  @override
+  String get constantValueBase {
+    if (name == 'values') {
+      return 'const List&lt;<wbr><span class="type-parameter">${field.enclosingElement.name}</span>&gt;';
+    } else {
+      return 'const ${field.enclosingElement.name}($_index)';
+    }
+  }
+
+  @override
+  List<ModelElement> get documentationFrom {
+    if (name == 'values' || name == 'index') return [this];
+    return super.documentationFrom;
+  }
+
+  @override
+  String get documentation {
+    if (name == 'values') {
+      return 'A constant List of the values in this enum, in order of their declaration.';
+    } else if (name == 'index') {
+      return 'The integer index of this enum.';
+    } else {
+      return super.documentation;
+    }
+  }
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(!(canonicalLibrary == null || canonicalEnclosingContainer == null));
+    assert(canonicalLibrary == library);
+    assert(canonicalEnclosingContainer == enclosingElement);
+    return '${package.baseHref}${enclosingElement.library.dirName}/${(enclosingElement as Class).fileName}';
+  }
+
+  @override
+  String get linkedName => name;
+
+  @override
+  bool get isCanonical {
+    if (name == 'index') return false;
+    // If this is something inherited from Object, e.g. hashCode, let the
+    // normal rules apply.
+    if (_index == null) {
+      return super.isCanonical;
+    }
+    // TODO(jcollins-g): We don't actually document this as a separate entity;
+    //                   do that or change this to false and deal with the
+    //                   consequences.
+    return true;
+  }
+
+  @override
+  String get oneLineDoc => documentationAsHtml;
+
+  @override
+  Inheritable get overriddenElement => null;
+}
diff --git a/lib/src/model/extendable.dart b/lib/src/model/extendable.dart
new file mode 100644
index 0000000..bb496c6
--- /dev/null
+++ b/lib/src/model/extendable.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, 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.
+
+import 'package:dartdoc/src/model/model.dart';
+
+/// Mixin for subclasses of [ModelElement] representing Elements that can be
+/// extension methods.
+mixin Extendable on ContainerMember {
+  /// Returns this Extendable from the [Extension] in which it was declared.
+  Extendable get definingExtension => throw UnimplementedError;
+
+  @override
+  Container get canonicalEnclosingContainer => throw UnimplementedError;
+}
diff --git a/lib/src/model/extension.dart b/lib/src/model/extension.dart
new file mode 100644
index 0000000..6755a70
--- /dev/null
+++ b/lib/src/model/extension.dart
@@ -0,0 +1,155 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:collection';
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:quiver/iterables.dart' as quiver;
+
+/// Extension methods
+class Extension extends Container
+    with TypeParameters, Categorization
+    implements EnclosedElement {
+  DefinedElementType extendedType;
+
+  Extension(
+      ExtensionElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph) {
+    extendedType =
+        ElementType.from(_extension.extendedType, library, packageGraph);
+  }
+
+  bool couldApplyTo(Class c) => _couldApplyTo(c.modelType);
+
+  /// Return true if this extension could apply to [t].
+  bool _couldApplyTo(DefinedElementType t) {
+    return t.instantiatedType == extendedType.instantiatedType ||
+        (t.instantiatedType.element == extendedType.instantiatedType.element &&
+            isSubtypeOf(t)) ||
+        isBoundSupertypeTo(t);
+  }
+
+  /// The instantiated to bounds [extendedType] of this extension is a subtype of
+  /// [t].
+  bool isSubtypeOf(DefinedElementType t) => packageGraph.typeSystem
+      .isSubtypeOf(extendedType.instantiatedType, t.instantiatedType);
+
+  bool isBoundSupertypeTo(DefinedElementType t) =>
+      _isBoundSupertypeTo(t.type, HashSet());
+
+  /// Returns true if at least one supertype (including via mixins and
+  /// interfaces) is equivalent to or a subtype of [extendedType] when
+  /// instantiated to bounds.
+  bool _isBoundSupertypeTo(
+      InterfaceType superType, HashSet<InterfaceType> visited) {
+    ClassElement superClass = superType?.element;
+    if (visited.contains(superType)) return false;
+    visited.add(superType);
+    if (superClass == extendedType.type.element &&
+        (superType == extendedType.instantiatedType ||
+            packageGraph.typeSystem
+                .isSubtypeOf(superType, extendedType.instantiatedType))) {
+      return true;
+    }
+    List<InterfaceType> supertypes = [];
+    ClassElementImpl.collectAllSupertypes(supertypes, superType, null);
+    for (InterfaceType toVisit in supertypes) {
+      if (_isBoundSupertypeTo(toVisit, visited)) return true;
+    }
+    return false;
+  }
+
+  @override
+  ModelElement get enclosingElement => library;
+
+  ExtensionElement get _extension => (element as ExtensionElement);
+
+  @override
+  String get kind => 'extension';
+
+  List<Method> _methods;
+
+  @override
+  List<Method> get methods {
+    if (_methods == null) {
+      _methods = _extension.methods.map((e) {
+        return ModelElement.from(e, library, packageGraph) as Method;
+      }).toList(growable: false)
+        ..sort(byName);
+    }
+    return _methods;
+  }
+
+  List<Field> _fields;
+
+  @override
+  List<Field> get allFields {
+    if (_fields == null) {
+      _fields = _extension.fields.map((f) {
+        Accessor getter, setter;
+        if (f.getter != null) {
+          getter = ContainerAccessor(f.getter, library, packageGraph);
+        }
+        if (f.setter != null) {
+          setter = ContainerAccessor(f.setter, library, packageGraph);
+        }
+        return ModelElement.from(f, library, packageGraph,
+            getter: getter, setter: setter) as Field;
+      }).toList(growable: false)
+        ..sort(byName);
+    }
+    return _fields;
+  }
+
+  List<TypeParameter> _typeParameters;
+
+  // a stronger hash?
+  @override
+  List<TypeParameter> get typeParameters {
+    if (_typeParameters == null) {
+      _typeParameters = _extension.typeParameters.map((f) {
+        var lib = Library(f.enclosingElement.library, packageGraph);
+        return ModelElement.from(f, lib, packageGraph) as TypeParameter;
+      }).toList();
+    }
+    return _typeParameters;
+  }
+
+  @override
+  ParameterizedElementType get modelType => super.modelType;
+
+  List<ModelElement> _allModelElements;
+
+  List<ModelElement> get allModelElements {
+    if (_allModelElements == null) {
+      _allModelElements = List.from(
+          quiver.concat([
+            instanceMethods,
+            allInstanceFields,
+            allAccessors,
+            allOperators,
+            constants,
+            staticMethods,
+            staticProperties,
+            typeParameters,
+          ]),
+          growable: false);
+    }
+    return _allModelElements;
+  }
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${library.dirName}/$fileName';
+  }
+}
diff --git a/lib/src/model/field.dart b/lib/src/model/field.dart
new file mode 100644
index 0000000..fe8c27a
--- /dev/null
+++ b/lib/src/model/field.dart
@@ -0,0 +1,189 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+class Field extends ModelElement
+    with GetterSetterCombo, ContainerMember, Inheritable
+    implements EnclosedElement {
+  bool _isInherited = false;
+  Container _enclosingClass;
+  @override
+  final ContainerAccessor getter;
+  @override
+  final ContainerAccessor setter;
+
+  Field(FieldElement element, Library library, PackageGraph packageGraph,
+      this.getter, this.setter)
+      : super(element, library, packageGraph, null) {
+    assert(getter != null || setter != null);
+    if (getter != null) getter.enclosingCombo = this;
+    if (setter != null) setter.enclosingCombo = this;
+    _setModelType();
+  }
+
+  factory Field.inherited(
+      FieldElement element,
+      Class enclosingClass,
+      Library library,
+      PackageGraph packageGraph,
+      Accessor getter,
+      Accessor setter) {
+    Field newField = Field(element, library, packageGraph, getter, setter);
+    newField._isInherited = true;
+    newField._enclosingClass = enclosingClass;
+    // Can't set _isInherited to true if this is the defining element, because
+    // that would mean it isn't inherited.
+    assert(newField.enclosingElement != newField.definingEnclosingContainer);
+    return newField;
+  }
+
+  @override
+  String get documentation {
+    // Verify that hasSetter and hasGetterNoSetter are mutually exclusive,
+    // to prevent displaying more or less than one summary.
+    if (isPublic) {
+      Set<bool> assertCheck = Set()
+        ..addAll([hasPublicSetter, hasPublicGetterNoSetter]);
+      assert(assertCheck.containsAll([true, false]));
+    }
+    documentationFrom;
+    return super.documentation;
+  }
+
+  @override
+  ModelElement get enclosingElement {
+    if (_enclosingClass == null) {
+      _enclosingClass =
+          ModelElement.from(field.enclosingElement, library, packageGraph);
+    }
+    return _enclosingClass;
+  }
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalEnclosingContainer == enclosingElement);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$fileName';
+  }
+
+  @override
+  bool get isConst => field.isConst;
+
+  /// Returns true if the FieldElement is covariant, or if the first parameter
+  /// for the setter is covariant.
+  @override
+  bool get isCovariant =>
+      setter?.isCovariant == true || (field as FieldElementImpl).isCovariant;
+
+  @override
+  bool get isFinal {
+    /// isFinal returns true for the field even if it has an explicit getter
+    /// (which means we should not document it as "final").
+    if (hasExplicitGetter) return false;
+    return field.isFinal;
+  }
+
+  @override
+  bool get isInherited => _isInherited;
+
+  @override
+  String get kind => isConst ? 'constant' : 'property';
+
+  @override
+  List<String> get annotations {
+    List<String> all_annotations = List<String>();
+    all_annotations.addAll(super.annotations);
+
+    if (element is PropertyInducingElement) {
+      var pie = element as PropertyInducingElement;
+      all_annotations.addAll(annotationsFromMetadata(pie.getter?.metadata));
+      all_annotations.addAll(annotationsFromMetadata(pie.setter?.metadata));
+    }
+    return all_annotations.toList(growable: false);
+  }
+
+  @override
+  Set<String> get features {
+    Set<String> allFeatures = super.features..addAll(comboFeatures);
+    // Combo features can indicate 'inherited' and 'override' if
+    // either the getter or setter has one of those properties, but that's not
+    // really specific enough for [Field]s that have public getter/setters.
+    if (hasPublicGetter && hasPublicSetter) {
+      if (getter.isInherited && setter.isInherited) {
+        allFeatures.add('inherited');
+      } else {
+        allFeatures.remove('inherited');
+        if (getter.isInherited) allFeatures.add('inherited-getter');
+        if (setter.isInherited) allFeatures.add('inherited-setter');
+      }
+      if (getter.isOverride && setter.isOverride) {
+        allFeatures.add('override');
+      } else {
+        allFeatures.remove('override');
+        if (getter.isOverride) allFeatures.add('override-getter');
+        if (setter.isOverride) allFeatures.add('override-setter');
+      }
+    } else {
+      if (isInherited) allFeatures.add('inherited');
+      if (isOverride) allFeatures.add('override');
+    }
+    return allFeatures;
+  }
+
+  @override
+  String computeDocumentationComment() {
+    String docs = getterSetterDocumentationComment;
+    if (docs.isEmpty) return field.documentationComment;
+    return docs;
+  }
+
+  FieldElement get field => (element as FieldElement);
+
+  @override
+  String get fileName => isConst ? '$name-constant.html' : '$name.html';
+
+  String _sourceCode;
+
+  @override
+  String get sourceCode {
+    if (_sourceCode == null) {
+      // We could use a set to figure the dupes out, but that would lose ordering.
+      String fieldSourceCode = modelNode.sourceCode ?? '';
+      String getterSourceCode = getter?.sourceCode ?? '';
+      String setterSourceCode = setter?.sourceCode ?? '';
+      StringBuffer buffer = StringBuffer();
+      if (fieldSourceCode.isNotEmpty) {
+        buffer.write(fieldSourceCode);
+      }
+      if (buffer.isNotEmpty) buffer.write('\n\n');
+      if (fieldSourceCode != getterSourceCode) {
+        if (getterSourceCode != setterSourceCode) {
+          buffer.write(getterSourceCode);
+          if (buffer.isNotEmpty) buffer.write('\n\n');
+        }
+      }
+      if (fieldSourceCode != setterSourceCode) {
+        buffer.write(setterSourceCode);
+      }
+      _sourceCode = buffer.toString();
+    }
+    return _sourceCode;
+  }
+
+  void _setModelType() {
+    if (hasGetter) {
+      setModelType(getter.modelType);
+    }
+  }
+
+  @override
+  Inheritable get overriddenElement => null;
+}
diff --git a/lib/src/model/getter_setter_combo.dart b/lib/src/model/getter_setter_combo.dart
new file mode 100644
index 0000000..8684fa4
--- /dev/null
+++ b/lib/src/model/getter_setter_combo.dart
@@ -0,0 +1,231 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:convert';
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/utils.dart';
+
+/// Mixin for top-level variables and fields (aka properties)
+mixin GetterSetterCombo on ModelElement {
+  Accessor get getter;
+
+  Accessor get setter;
+
+  Iterable<Accessor> get allAccessors sync* {
+    for (Accessor a in [getter, setter]) {
+      if (a != null) yield a;
+    }
+  }
+
+  Set<String> get comboFeatures {
+    Set<String> allFeatures = Set();
+    if (hasExplicitGetter && hasPublicGetter) {
+      allFeatures.addAll(getter.features);
+    }
+    if (hasExplicitSetter && hasPublicSetter) {
+      allFeatures.addAll(setter.features);
+    }
+    if (readOnly && !isFinal && !isConst) allFeatures.add('read-only');
+    if (writeOnly) allFeatures.add('write-only');
+    if (readWrite) allFeatures.add('read / write');
+    return allFeatures;
+  }
+
+  @override
+  ModelElement enclosingElement;
+
+  bool get isInherited;
+
+  Expression get constantInitializer =>
+      (element as ConstVariableElement).constantInitializer;
+
+  String linkifyConstantValue(String original) {
+    if (constantInitializer is! InstanceCreationExpression) return original;
+    String constructorName = (constantInitializer as InstanceCreationExpression)
+        .constructorName
+        .toString();
+    Element staticElement =
+        (constantInitializer as InstanceCreationExpression).staticElement;
+    Constructor target = ModelElement.fromElement(staticElement, packageGraph);
+    Class targetClass = target.enclosingElement;
+    // TODO(jcollins-g): this logic really should be integrated into Constructor,
+    // but that's not trivial because of linkedName's usage.
+    if (targetClass.name == target.name) {
+      return original.replaceAll(constructorName, "${target.linkedName}");
+    }
+    return original.replaceAll("${targetClass.name}.${target.name}",
+        "${targetClass.linkedName}.${target.linkedName}");
+  }
+
+  String _buildConstantValueBase() {
+    String result = constantInitializer?.toString() ?? '';
+    return const HtmlEscape(HtmlEscapeMode.unknown).convert(result);
+  }
+
+  @override
+  CharacterLocation get characterLocation {
+    // Handle all synthetic possibilities.  Ordinarily, warnings for
+    // explicit setters/getters will be handled by those objects, but
+    // if a warning comes up for an enclosing synthetic field we have to
+    // put it somewhere.  So pick an accessor.
+    if (element.isSynthetic) {
+      if (hasExplicitGetter) return getter.characterLocation;
+      if (hasExplicitSetter) return setter.characterLocation;
+      assert(false, 'Field and accessors can not all be synthetic');
+    }
+    return super.characterLocation;
+  }
+
+  String get constantValue => linkifyConstantValue(constantValueBase);
+
+  String get constantValueTruncated =>
+      linkifyConstantValue(truncateString(constantValueBase, 200));
+  String _constantValueBase;
+
+  String get constantValueBase =>
+      _constantValueBase ??= _buildConstantValueBase();
+
+  bool get hasPublicGetter => hasGetter && getter.isPublic;
+
+  bool get hasPublicSetter => hasSetter && setter.isPublic;
+
+  @override
+  bool get isPublic => hasPublicGetter || hasPublicSetter;
+
+  List<ModelElement> _documentationFrom;
+
+  @override
+  List<ModelElement> get documentationFrom {
+    if (_documentationFrom == null) {
+      _documentationFrom = [];
+      if (hasPublicGetter) {
+        _documentationFrom.addAll(getter.documentationFrom);
+      } else if (hasPublicSetter) {
+        _documentationFrom.addAll(setter.documentationFrom);
+      }
+      if (_documentationFrom.isEmpty ||
+          _documentationFrom.every((e) => e.documentationComment == '')) {
+        _documentationFrom = computeDocumentationFrom;
+      }
+    }
+    return _documentationFrom;
+  }
+
+  bool get hasAccessorsWithDocs => (hasPublicGetter &&
+          !getter.isSynthetic &&
+          getter.documentation.isNotEmpty ||
+      hasPublicSetter &&
+          !setter.isSynthetic &&
+          setter.documentation.isNotEmpty);
+
+  bool get getterSetterBothAvailable => (hasPublicGetter &&
+      getter.documentation.isNotEmpty &&
+      hasPublicSetter &&
+      setter.documentation.isNotEmpty);
+
+  String _oneLineDoc;
+
+  @override
+  String get oneLineDoc {
+    if (_oneLineDoc == null) {
+      if (!hasAccessorsWithDocs) {
+        _oneLineDoc = computeOneLineDoc();
+      } else {
+        StringBuffer buffer = StringBuffer();
+        if (hasPublicGetter && getter.oneLineDoc.isNotEmpty) {
+          buffer.write('${getter.oneLineDoc}');
+        }
+        if (hasPublicSetter && setter.oneLineDoc.isNotEmpty) {
+          buffer.write('${getterSetterBothAvailable ? "" : setter.oneLineDoc}');
+        }
+        _oneLineDoc = buffer.toString();
+      }
+    }
+    return _oneLineDoc;
+  }
+
+  String get getterSetterDocumentationComment {
+    var buffer = StringBuffer();
+
+    if (hasPublicGetter && !getter.isSynthetic) {
+      assert(getter.documentationFrom.length == 1);
+      // We have to check against dropTextFrom here since documentationFrom
+      // doesn't yield the real elements for GetterSetterCombos.
+      if (!config.dropTextFrom
+          .contains(getter.documentationFrom.first.element.library.name)) {
+        String docs = getter.documentationFrom.first.documentationComment;
+        if (docs != null) buffer.write(docs);
+      }
+    }
+
+    if (hasPublicSetter && !setter.isSynthetic) {
+      assert(setter.documentationFrom.length == 1);
+      if (!config.dropTextFrom
+          .contains(setter.documentationFrom.first.element.library.name)) {
+        String docs = setter.documentationFrom.first.documentationComment;
+        if (docs != null) {
+          if (buffer.isNotEmpty) buffer.write('\n\n');
+          buffer.write(docs);
+        }
+      }
+    }
+    return buffer.toString();
+  }
+
+  String get linkedReturnType {
+    if (hasGetter) {
+      return getter.linkedReturnType;
+    } else {
+      return setter.linkedParamsNoMetadataOrNames;
+    }
+  }
+
+  @override
+  bool get canHaveParameters => hasSetter;
+
+  @override
+  List<Parameter> get parameters => setter.parameters;
+
+  @override
+  String get linkedParamsNoMetadata {
+    if (hasSetter) return setter.linkedParamsNoMetadata;
+    return null;
+  }
+
+  bool get hasExplicitGetter => hasPublicGetter && !getter.isSynthetic;
+
+  bool get hasExplicitSetter => hasPublicSetter && !setter.isSynthetic;
+
+  bool get hasGetter => getter != null;
+
+  bool get hasNoGetterSetter => !hasGetterOrSetter;
+
+  bool get hasGetterOrSetter => hasExplicitGetter || hasExplicitSetter;
+
+  bool get hasSetter => setter != null;
+
+  bool get hasPublicGetterNoSetter => (hasPublicGetter && !hasPublicSetter);
+
+  String get arrow {
+    // →
+    if (readOnly) return r'&#8594;';
+    // ←
+    if (writeOnly) return r'&#8592;';
+    // ↔
+    if (readWrite) return r'&#8596;';
+    throw UnsupportedError(
+        'GetterSetterCombo must be one of readOnly, writeOnly, or readWrite');
+  }
+
+  bool get readOnly => hasPublicGetter && !hasPublicSetter;
+
+  bool get readWrite => hasPublicGetter && hasPublicSetter;
+
+  bool get writeOnly => hasPublicSetter && !hasPublicGetter;
+}
diff --git a/lib/src/model/indexable.dart b/lib/src/model/indexable.dart
new file mode 100644
index 0000000..0e46d0d
--- /dev/null
+++ b/lib/src/model/indexable.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2019, 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.
+
+import 'package:dartdoc/src/model/model.dart';
+
+/// Something able to be indexed.
+abstract class Indexable implements Nameable {
+  String get href;
+
+  String get kind;
+
+  int get overriddenDepth => 0;
+}
diff --git a/lib/src/model/inheritable.dart b/lib/src/model/inheritable.dart
new file mode 100644
index 0000000..59423f1
--- /dev/null
+++ b/lib/src/model/inheritable.dart
@@ -0,0 +1,161 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/member.dart' show Member;
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/special_elements.dart';
+
+/// Mixin for subclasses of ModelElement representing Elements that can be
+/// inherited from one class to another.
+///
+/// We can search the inheritance chain between this instance and
+/// [definingEnclosingContainer] in [Inheritable.canonicalEnclosingContainer],
+/// for the canonical [Class] closest to where this member was defined.  We
+/// can then know that when we find [Inheritable.element] inside that [Class]'s
+/// namespace, that's the one we should treat as canonical and implementors
+/// of this class can use that knowledge to determine canonicalization.
+///
+/// We pick the class closest to the [definingEnclosingElement] so that all
+/// children of that class inheriting the same member will point to the same
+/// place in the documentation, and we pick a canonical class because that's
+/// the one in the public namespace that will be documented.
+mixin Inheritable on ContainerMember {
+  @override
+  ModelElement buildCanonicalModelElement() {
+    if (canonicalEnclosingContainer is Extension) {
+      return this;
+    }
+    if (canonicalEnclosingContainer is Class) {
+      return (canonicalEnclosingContainer as Class)
+          ?.allCanonicalModelElements
+          ?.firstWhere(
+              (m) =>
+                  m.name == name && m.isPropertyAccessor == isPropertyAccessor,
+              orElse: () => null);
+    }
+    return null;
+  }
+
+  @override
+  Container computeCanonicalEnclosingContainer() {
+    if (isInherited) {
+      Element searchElement = element;
+      searchElement = searchElement is Member
+          ? PackageGraph.getBasestElement(searchElement)
+          : searchElement;
+      // TODO(jcollins-g): generate warning if an inherited element's definition
+      // is in an intermediate non-canonical class in the inheritance chain?
+      Class previous;
+      Class previousNonSkippable;
+      Class found;
+      for (Class c in inheritance.reversed) {
+        // Filter out mixins.
+        if (c.contains(searchElement)) {
+          if ((packageGraph.inheritThrough.contains(previous) &&
+                  c != definingEnclosingContainer) ||
+              (packageGraph.inheritThrough.contains(c) &&
+                  c == definingEnclosingContainer)) {
+            return (previousNonSkippable.memberByExample(this) as Inheritable)
+                .canonicalEnclosingContainer;
+          }
+          Class canonicalC =
+              packageGraph.findCanonicalModelElementFor(c.element);
+          // TODO(jcollins-g): invert this lookup so traversal is recursive
+          // starting from the ModelElement.
+          if (canonicalC != null) {
+            assert(canonicalC.isCanonical);
+            assert(canonicalC.contains(searchElement));
+            found = canonicalC;
+            break;
+          }
+        }
+        previous = c;
+        if (!packageGraph.inheritThrough.contains(c)) {
+          previousNonSkippable = c;
+        }
+      }
+      // This is still OK because we're never supposed to cloak public
+      // classes.
+      if (definingEnclosingContainer.isCanonical &&
+          definingEnclosingContainer.isPublic) {
+        assert(definingEnclosingContainer == found);
+        return found;
+      }
+    }
+    return super.computeCanonicalEnclosingContainer();
+  }
+
+  List<Class> get inheritance {
+    List<Class> inheritance = [];
+    inheritance.addAll((enclosingElement as Class).inheritanceChain);
+    Class object = packageGraph.specialClasses[SpecialClass.object];
+    if (!inheritance.contains(definingEnclosingContainer) &&
+        definingEnclosingContainer != null) {
+      assert(definingEnclosingContainer == object);
+    }
+    // Unless the code explicitly extends dart-core's Object, we won't get
+    // an entry here.  So add it.
+    if (inheritance.last != object && object != null) {
+      inheritance.add(object);
+    }
+    assert(inheritance.where((e) => e == object).length == 1);
+    return inheritance;
+  }
+
+  Inheritable get overriddenElement;
+
+  bool _isOverride;
+
+  @override
+  bool get isOverride {
+    if (_isOverride == null) {
+      // The canonical version of the enclosing element -- not canonicalEnclosingElement,
+      // as that is the element enclosing the canonical version of this element,
+      // two different things.  Defaults to the enclosing element.
+      //
+      // We use canonical elements here where possible to deal with reexports
+      // as seen in Flutter.
+      if (enclosingElement is Extension) {
+        _isOverride = false;
+        return _isOverride;
+      }
+      Class enclosingCanonical = enclosingElement.canonicalModelElement;
+      // The container in which this element was defined, canonical if available.
+      Container definingCanonical =
+          definingEnclosingContainer.canonicalModelElement ??
+              definingEnclosingContainer;
+      // The canonical version of the element we're overriding, if available.
+      ModelElement overriddenCanonical =
+          overriddenElement?.canonicalModelElement ?? overriddenElement;
+
+      // We have to have an overridden element for it to be possible for this
+      // element to be an override.
+      _isOverride = overriddenElement != null &&
+          // The defining class and the enclosing class for this element
+          // must be the same (element is defined here).
+          enclosingCanonical == definingCanonical &&
+          // If the overridden element isn't public, we shouldn't be an
+          // override in most cases.  Approximation until #1623 is fixed.
+          overriddenCanonical.isPublic;
+      assert(!(_isOverride && isInherited));
+    }
+    return _isOverride;
+  }
+
+  int _overriddenDepth;
+
+  @override
+  int get overriddenDepth {
+    if (_overriddenDepth == null) {
+      _overriddenDepth = 0;
+      Inheritable e = this;
+      while (e.overriddenElement != null) {
+        _overriddenDepth += 1;
+        e = e.overriddenElement;
+      }
+    }
+    return _overriddenDepth;
+  }
+}
diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart
new file mode 100644
index 0000000..aa54ce0
--- /dev/null
+++ b/lib/src/model/library.dart
@@ -0,0 +1,661 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/visitor.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/package_meta.dart' show PackageMeta;
+import 'package:dartdoc/src/warnings.dart';
+import 'package:path/path.dart' as path;
+import 'package:quiver/iterables.dart' as quiver;
+
+/// 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 {
+  List<TopLevelVariable> _variables;
+  List<Element> _exportedAndLocalElements;
+  String _name;
+
+  factory Library(LibraryElement element, PackageGraph packageGraph) {
+    return packageGraph.findButDoNotCreateLibraryFor(element);
+  }
+
+  Library.fromLibraryResult(ResolvedLibraryResult libraryResult,
+      PackageGraph packageGraph, this._package)
+      : super(libraryResult.element, null, packageGraph, null) {
+    if (element == null) throw ArgumentError.notNull('element');
+
+    // Initialize [packageGraph]'s cache of ModelNodes for relevant
+    // elements in this library.
+    Map<String, CompilationUnit> _compilationUnitMap = Map();
+    _compilationUnitMap.addEntries(libraryResult.units
+        .map((ResolvedUnitResult u) => MapEntry(u.path, u.unit)));
+    _HashableChildLibraryElementVisitor((Element e) =>
+            packageGraph.populateModelNodeFor(e, _compilationUnitMap))
+        .visitElement(element);
+
+    // Initialize the list of elements defined in this library and
+    // exported via its export directives.
+    Set<Element> exportedAndLocalElements =
+        _libraryElement.exportNamespace.definedNames.values.toSet();
+    // TODO(jcollins-g): Consider switch to [_libraryElement.topLevelElements].
+    exportedAndLocalElements
+        .addAll(getDefinedElements(_libraryElement.definingCompilationUnit));
+    for (CompilationUnitElement cu in _libraryElement.parts) {
+      exportedAndLocalElements.addAll(getDefinedElements(cu));
+    }
+    _exportedAndLocalElements = exportedAndLocalElements.toList();
+
+    _package.allLibraries.add(this);
+  }
+
+  static Iterable<Element> getDefinedElements(
+      CompilationUnitElement compilationUnit) {
+    return quiver.concat([
+      compilationUnit.accessors,
+      compilationUnit.enums,
+      compilationUnit.extensions,
+      compilationUnit.functions,
+      compilationUnit.functionTypeAliases,
+      compilationUnit.mixins,
+      compilationUnit.topLevelVariables,
+      compilationUnit.types,
+    ]);
+  }
+
+  List<String> _allOriginalModelElementNames;
+
+  bool get isInSdk => _libraryElement.isInSdk;
+
+  final Package _package;
+
+  @override
+  Package get package {
+    // Everything must be in a package.  TODO(jcollins-g): Support other things
+    // that look like packages.
+    assert(_package != null);
+    return _package;
+  }
+
+  /// [allModelElements] resolved to their original names.
+  ///
+  /// A collection of [ModelElement.fullyQualifiedName]s for [ModelElement]s
+  /// documented with this library, but these ModelElements and names correspond
+  /// to the defining library where each originally came from with respect
+  /// to inheritance and reexporting.  Most useful for error reporting.
+  Iterable<String> get allOriginalModelElementNames {
+    if (_allOriginalModelElementNames == null) {
+      _allOriginalModelElementNames = allModelElements.map((e) {
+        Accessor getter;
+        Accessor setter;
+        if (e is GetterSetterCombo) {
+          if (e.hasGetter) {
+            getter = ModelElement.fromElement(e.getter.element, packageGraph);
+          }
+          if (e.hasSetter) {
+            setter = ModelElement.fromElement(e.setter.element, packageGraph);
+          }
+        }
+        return ModelElement.from(
+                e.element,
+                packageGraph.findButDoNotCreateLibraryFor(e.element),
+                packageGraph,
+                getter: getter,
+                setter: setter)
+            .fullyQualifiedName;
+      }).toList();
+    }
+    return _allOriginalModelElementNames;
+  }
+
+  @override
+  CharacterLocation get characterLocation {
+    if (element.nameOffset == -1) {
+      assert(isAnonymous,
+          'Only anonymous libraries are allowed to have no declared location');
+      return CharacterLocation(1, 1);
+    }
+    return super.characterLocation;
+  }
+
+  @override
+  CompilationUnitElement get compilationUnitElement =>
+      (element as LibraryElement).definingCompilationUnit;
+
+  @override
+  Iterable<Class> get classes => allClasses.where((c) => !c.isErrorOrException);
+
+  List<Extension> _extensions;
+
+  @override
+  Iterable<Extension> get extensions {
+    if (_extensions == null) {
+      _extensions = _exportedAndLocalElements
+          .whereType<ExtensionElement>()
+          .map((e) => ModelElement.from(e, this, packageGraph) as Extension)
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _extensions;
+  }
+
+  SdkLibrary get sdkLib {
+    if (packageGraph.sdkLibrarySources.containsKey(element.librarySource)) {
+      return packageGraph.sdkLibrarySources[element.librarySource];
+    }
+    return null;
+  }
+
+  @override
+  bool get isPublic {
+    if (!super.isPublic) return false;
+    if (sdkLib != null && (sdkLib.isInternal || !sdkLib.isDocumented)) {
+      return false;
+    }
+    if (config.isLibraryExcluded(name) ||
+        config.isLibraryExcluded(element.librarySource.uri.toString())) {
+      return false;
+    }
+    return true;
+  }
+
+  List<TopLevelVariable> _constants;
+
+  @override
+  Iterable<TopLevelVariable> get constants {
+    if (_constants == null) {
+      // _getVariables() is already sorted.
+      _constants =
+          _getVariables().where((v) => v.isConst).toList(growable: false);
+    }
+    return _constants;
+  }
+
+  Set<Library> _packageImportedExportedLibraries;
+
+  /// Returns all libraries either imported by or exported by any public library
+  /// this library's package.  (Not [PackageGraph], but sharing a package name).
+  ///
+  /// Note: will still contain non-public libraries because those can be
+  /// imported or exported.
+  // TODO(jcollins-g): move this to [Package] once it really knows about
+  // more than one package.
+  Set<Library> get packageImportedExportedLibraries {
+    if (_packageImportedExportedLibraries == null) {
+      _packageImportedExportedLibraries = Set();
+      packageGraph.publicLibraries
+          .where((l) => l.packageName == packageName)
+          .forEach((l) {
+        _packageImportedExportedLibraries.addAll(l.importedExportedLibraries);
+      });
+    }
+    return _packageImportedExportedLibraries;
+  }
+
+  Set<Library> _importedExportedLibraries;
+
+  /// Returns all libraries either imported by or exported by this library,
+  /// recursively.
+  Set<Library> get importedExportedLibraries {
+    if (_importedExportedLibraries == null) {
+      _importedExportedLibraries = Set();
+      Set<LibraryElement> importedExportedLibraryElements = Set();
+      importedExportedLibraryElements
+          .addAll((element as LibraryElement).importedLibraries);
+      importedExportedLibraryElements
+          .addAll((element as LibraryElement).exportedLibraries);
+      for (LibraryElement l in importedExportedLibraryElements) {
+        Library lib = ModelElement.from(l, library, packageGraph);
+        _importedExportedLibraries.add(lib);
+        _importedExportedLibraries.addAll(lib.importedExportedLibraries);
+      }
+    }
+    return _importedExportedLibraries;
+  }
+
+  Map<String, Set<Library>> _prefixToLibrary;
+
+  /// Map of import prefixes ('import "foo" as prefix;') to [Library].
+  Map<String, Set<Library>> get prefixToLibrary {
+    if (_prefixToLibrary == null) {
+      _prefixToLibrary = {};
+      // It is possible to have overlapping prefixes.
+      for (ImportElement i in (element as LibraryElement).imports) {
+        // Ignore invalid imports.
+        if (i.prefix?.name != null && i.importedLibrary != null) {
+          _prefixToLibrary.putIfAbsent(i.prefix?.name, () => Set());
+          _prefixToLibrary[i.prefix?.name]
+              .add(ModelElement.from(i.importedLibrary, library, packageGraph));
+        }
+      }
+    }
+    return _prefixToLibrary;
+  }
+
+  String _dirName;
+
+  String get dirName {
+    if (_dirName == null) {
+      _dirName = name;
+      if (isAnonymous) {
+        _dirName = nameFromPath;
+      }
+      _dirName = _dirName.replaceAll(':', '-').replaceAll('/', '_');
+    }
+    return _dirName;
+  }
+
+  Set<String> _canonicalFor;
+
+  Set<String> get canonicalFor {
+    if (_canonicalFor == null) {
+      // TODO(jcollins-g): restructure to avoid using side effects.
+      buildDocumentationAddition(documentationComment);
+    }
+    return _canonicalFor;
+  }
+
+  /// Hide canonicalFor from doc while leaving a note to ourselves to
+  /// help with ambiguous canonicalization determination.
+  ///
+  /// Example:
+  ///   {@canonicalFor libname.ClassName}
+  @override
+  String buildDocumentationAddition(String rawDocs) {
+    rawDocs = super.buildDocumentationAddition(rawDocs);
+    Set<String> newCanonicalFor = Set();
+    Set<String> notFoundInAllModelElements = Set();
+    final canonicalRegExp = RegExp(r'{@canonicalFor\s([^}]+)}');
+    rawDocs = rawDocs.replaceAllMapped(canonicalRegExp, (Match match) {
+      newCanonicalFor.add(match.group(1));
+      notFoundInAllModelElements.add(match.group(1));
+      return '';
+    });
+    if (notFoundInAllModelElements.isNotEmpty) {
+      notFoundInAllModelElements.removeAll(allOriginalModelElementNames);
+    }
+    for (String notFound in notFoundInAllModelElements) {
+      warn(PackageWarning.ignoredCanonicalFor, message: notFound);
+    }
+    // TODO(jcollins-g): warn if a macro/tool _does_ generate an unexpected
+    // canonicalFor?
+    if (_canonicalFor == null) {
+      _canonicalFor = newCanonicalFor;
+    }
+    return rawDocs;
+  }
+
+  /// Libraries are not enclosed by anything.
+  @override
+  ModelElement get enclosingElement => null;
+
+  List<Enum> _enums;
+
+  @override
+  List<Enum> get enums {
+    if (_enums == null) {
+      _enums = _exportedAndLocalElements
+          .whereType<ClassElement>()
+          .where((element) => element.isEnum)
+          .map((e) => ModelElement.from(e, this, packageGraph) as Enum)
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _enums;
+  }
+
+  List<Mixin> _mixins;
+
+  @override
+  List<Mixin> get mixins {
+    if (_mixins == null) {
+      /// Can not be [MixinElementImpl] because [ClassHandle]s are sometimes
+      /// returned from _exportedElements.
+      _mixins = _exportedAndLocalElements
+          .whereType<ClassElement>()
+          .where((ClassElement c) => c.isMixin)
+          .map((e) => ModelElement.from(e, this, packageGraph) as Mixin)
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _mixins;
+  }
+
+  List<Class> _exceptions;
+
+  @override
+  List<Class> get exceptions {
+    if (_exceptions == null) {
+      _exceptions =
+          allClasses.where((c) => c.isErrorOrException).toList(growable: false);
+    }
+    return _exceptions;
+  }
+
+  @override
+  String get fileName => '$dirName-library.html';
+
+  List<ModelFunction> _functions;
+
+  @override
+  List<ModelFunction> get functions {
+    if (_functions == null) {
+      _functions =
+          _exportedAndLocalElements.whereType<FunctionElement>().map((e) {
+        return ModelElement.from(e, this, packageGraph) as ModelFunction;
+      }).toList(growable: false)
+            ..sort(byName);
+    }
+    return _functions;
+  }
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    return '${package.baseHref}${library.dirName}/$fileName';
+  }
+
+  InheritanceManager3 _inheritanceManager;
+
+  InheritanceManager3 get inheritanceManager {
+    if (_inheritanceManager == null) {
+      var typeSystem = element.context.typeSystem;
+      _inheritanceManager = InheritanceManager3(typeSystem);
+    }
+    return _inheritanceManager;
+  }
+
+  bool get isAnonymous => element.name == null || element.name.isEmpty;
+
+  @override
+  String get kind => 'library';
+
+  @override
+  Library get library => this;
+
+  @override
+  String get name {
+    if (_name == null) {
+      _name = getLibraryName(element);
+    }
+    return _name;
+  }
+
+  String _nameFromPath;
+
+  /// Generate a name for this library based on its location.
+  ///
+  /// nameFromPath provides filename collision-proofing for anonymous libraries
+  /// by incorporating more from the location of the anonymous library into
+  /// the name calculation.  Simple cases (such as an anonymous library in
+  /// 'lib') are the same, but this will include slashes and possibly colons
+  /// for anonymous libraries in subdirectories or other packages.
+  String get nameFromPath {
+    if (_nameFromPath == null) {
+      _nameFromPath = getNameFromPath(element, packageGraph.driver, package);
+    }
+    return _nameFromPath;
+  }
+
+  /// The real package, as opposed to the package we are documenting it with,
+  /// [PackageGraph.name]
+  String get packageName => packageMeta?.name ?? '';
+
+  /// The real packageMeta, as opposed to the package we are documenting with.
+  PackageMeta _packageMeta;
+
+  PackageMeta get packageMeta {
+    if (_packageMeta == null) {
+      _packageMeta = PackageMeta.fromElement(element, config);
+    }
+    return _packageMeta;
+  }
+
+  List<TopLevelVariable> _properties;
+
+  /// All variables ("properties") except constants.
+  @override
+  Iterable<TopLevelVariable> get properties {
+    if (_properties == null) {
+      _properties =
+          _getVariables().where((v) => !v.isConst).toList(growable: false);
+    }
+    return _properties;
+  }
+
+  List<Typedef> _typedefs;
+
+  @override
+  List<Typedef> get typedefs {
+    if (_typedefs == null) {
+      _typedefs = _exportedAndLocalElements
+          .whereType<FunctionTypeAliasElement>()
+          .map((e) => ModelElement.from(e, this, packageGraph) as Typedef)
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _typedefs;
+  }
+
+  List<Class> _classes;
+
+  List<Class> get allClasses {
+    if (_classes == null) {
+      _classes = _exportedAndLocalElements
+          .whereType<ClassElement>()
+          .where((e) => !e.isMixin && !e.isEnum)
+          .map((e) => ModelElement.from(e, this, packageGraph) as Class)
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _classes;
+  }
+
+  LibraryElement get _libraryElement => (element as LibraryElement);
+
+  Class getClassByName(String name) {
+    return allClasses.firstWhere((it) => it.name == name, orElse: () => null);
+  }
+
+  List<TopLevelVariable> _getVariables() {
+    if (_variables == null) {
+      Set<TopLevelVariableElement> elements = _exportedAndLocalElements
+          .whereType<TopLevelVariableElement>()
+          .toSet();
+      elements.addAll(_exportedAndLocalElements
+          .whereType<PropertyAccessorElement>()
+          .map((a) => a.variable));
+      _variables = [];
+      for (TopLevelVariableElement element in elements) {
+        Accessor getter;
+        if (element.getter != null) {
+          getter = ModelElement.from(element.getter, this, packageGraph);
+        }
+        Accessor setter;
+        if (element.setter != null) {
+          setter = ModelElement.from(element.setter, this, packageGraph);
+        }
+        ModelElement me = ModelElement.from(element, this, packageGraph,
+            getter: getter, setter: setter);
+        _variables.add(me);
+      }
+      _variables.sort(byName);
+    }
+    return _variables;
+  }
+
+  /// Reverses URIs if needed to get a package URI.
+  /// Not the same as [PackageGraph.name] because there we always strip all
+  /// path components; this function only strips the package prefix if the
+  /// library is part of the default package or if it is being documented
+  /// remotely.
+  static String getNameFromPath(
+      LibraryElement element, AnalysisDriver driver, Package package) {
+    String name;
+    if (element.source.uri.toString().startsWith('dart:')) {
+      name = element.source.uri.toString();
+    } else {
+      name = driver.sourceFactory.restoreUri(element.source).toString();
+    }
+    PackageMeta hidePackage;
+    if (package.documentedWhere == DocumentLocation.remote) {
+      hidePackage = package.packageMeta;
+    } else {
+      hidePackage = package.packageGraph.packageMeta;
+    }
+    // restoreUri must not result in another file URI.
+    assert(!name.startsWith('file:'));
+
+    String defaultPackagePrefix = 'package:$hidePackage/';
+    if (name.startsWith(defaultPackagePrefix)) {
+      name = name.substring(defaultPackagePrefix.length, name.length);
+    }
+    if (name.endsWith('.dart')) {
+      name = name.substring(0, name.length - '.dart'.length);
+    }
+    assert(!name.startsWith('file:'));
+    return name;
+  }
+
+  static String getLibraryName(LibraryElement element) {
+    var source = element.source;
+
+    if (source.uri.isScheme('dart')) {
+      return '${source.uri}';
+    }
+
+    var name = element.name;
+    if (name != null && name.isNotEmpty) {
+      return name;
+    }
+
+    name = path.basename(source.fullName);
+    if (name.endsWith('.dart')) {
+      name = name.substring(0, name.length - '.dart'.length);
+    }
+    return name;
+  }
+
+  Map<String, Set<ModelElement>> _modelElementsNameMap;
+
+  /// Map of [fullyQualifiedNameWithoutLibrary] to all matching [ModelElement]s
+  /// in this library.  Used for code reference lookups.
+  Map<String, Set<ModelElement>> get modelElementsNameMap {
+    if (_modelElementsNameMap == null) {
+      _modelElementsNameMap = Map<String, Set<ModelElement>>();
+      allModelElements.forEach((ModelElement modelElement) {
+        _modelElementsNameMap.putIfAbsent(
+            modelElement.fullyQualifiedNameWithoutLibrary, () => Set());
+        _modelElementsNameMap[modelElement.fullyQualifiedNameWithoutLibrary]
+            .add(modelElement);
+      });
+    }
+    return _modelElementsNameMap;
+  }
+
+  Map<Element, Set<ModelElement>> _modelElementsMap;
+
+  Map<Element, Set<ModelElement>> get modelElementsMap {
+    if (_modelElementsMap == null) {
+      Iterable<ModelElement> results = quiver.concat([
+        library.constants,
+        library.functions,
+        library.properties,
+        library.typedefs,
+        library.extensions.expand((e) {
+          return quiver.concat([
+            [e],
+            e.allModelElements
+          ]);
+        }),
+        library.allClasses.expand((c) {
+          return quiver.concat([
+            [c],
+            c.allModelElements
+          ]);
+        }),
+        library.enums.expand((e) {
+          return quiver.concat([
+            [e],
+            e.allModelElements
+          ]);
+        }),
+        library.mixins.expand((m) {
+          return quiver.concat([
+            [m],
+            m.allModelElements
+          ]);
+        }),
+      ]);
+      _modelElementsMap = Map<Element, Set<ModelElement>>();
+      results.forEach((modelElement) {
+        _modelElementsMap.putIfAbsent(modelElement.element, () => Set());
+        _modelElementsMap[modelElement.element].add(modelElement);
+      });
+      _modelElementsMap.putIfAbsent(element, () => Set());
+      _modelElementsMap[element].add(this);
+    }
+    return _modelElementsMap;
+  }
+
+  List<ModelElement> _allModelElements;
+
+  Iterable<ModelElement> get allModelElements {
+    if (_allModelElements == null) {
+      _allModelElements = [];
+      for (Set<ModelElement> modelElements in modelElementsMap.values) {
+        _allModelElements.addAll(modelElements);
+      }
+    }
+    return _allModelElements;
+  }
+
+  List<ModelElement> _allCanonicalModelElements;
+
+  Iterable<ModelElement> get allCanonicalModelElements {
+    return (_allCanonicalModelElements ??=
+        allModelElements.where((e) => e.isCanonical).toList());
+  }
+}
diff --git a/lib/src/model/library_container.dart b/lib/src/model/library_container.dart
new file mode 100644
index 0000000..73930ec
--- /dev/null
+++ b/lib/src/model/library_container.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2019, 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.
+
+import 'package:collection/collection.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/model_utils.dart' as model_utils;
+
+/// A set of libraries, initialized after construction by accessing [libraries].
+/// Do not cache return values of any methods or members excepting [libraries]
+/// and [name] before finishing initialization of a [LibraryContainer].
+abstract class LibraryContainer
+    implements Nameable, Comparable<LibraryContainer> {
+  final List<Library> libraries = [];
+
+  PackageGraph get packageGraph;
+
+  Iterable<Library> get publicLibraries =>
+      model_utils.filterNonPublic(libraries);
+
+  bool get hasPublicLibraries => publicLibraries.isNotEmpty;
+
+  /// The name of the container or object that this LibraryContainer is a part
+  /// of.  Used for sorting in [containerOrder].
+  String get enclosingName;
+
+  /// Order by which this container should be sorted.
+  List<String> get containerOrder;
+
+  /// Sorting key.  [containerOrder] should contain these.
+  String get sortKey => name;
+
+  /// Does this container represent the SDK?  This can be false for containers
+  /// that only represent a part of the SDK.
+  bool get isSdk => false;
+
+  /// Returns:
+  /// -1 if this container is listed in [containerOrder].
+  /// 0 if this container is named the same as the [enclosingName].
+  /// 1 if this container represents the SDK.
+  /// 2 if this group has a name that contains the name [enclosingName].
+  /// 3 otherwise.
+  int get _group {
+    if (containerOrder.contains(sortKey)) return -1;
+    if (equalsIgnoreAsciiCase(sortKey, enclosingName)) return 0;
+    if (isSdk) return 1;
+    if (sortKey.toLowerCase().contains(enclosingName.toLowerCase())) return 2;
+    return 3;
+  }
+
+  @override
+  int compareTo(LibraryContainer other) {
+    if (_group == other._group) {
+      if (_group == -1) {
+        return Comparable.compare(containerOrder.indexOf(sortKey),
+            containerOrder.indexOf(other.sortKey));
+      } else {
+        return sortKey.toLowerCase().compareTo(other.sortKey.toLowerCase());
+      }
+    }
+    return Comparable.compare(_group, other._group);
+  }
+}
diff --git a/lib/src/model/locatable.dart b/lib/src/model/locatable.dart
new file mode 100644
index 0000000..517c3d1
--- /dev/null
+++ b/lib/src/model/locatable.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, 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.
+
+/// Something that can be located for warning purposes.
+abstract class Locatable {
+  List<Locatable> get documentationFrom;
+
+  /// True if documentationFrom contains only one item, [this].
+  bool get documentationIsLocal =>
+      documentationFrom.length == 1 && identical(documentationFrom.first, this);
+
+  String get fullyQualifiedName;
+
+  String get href;
+
+  /// A string indicating the URI of this Locatable, usually derived from
+  /// [Element.location].
+  String get location;
+}
+
+final RegExp locationSplitter = RegExp(r'(package:|[\\/;.])');
diff --git a/lib/src/model/method.dart b/lib/src/model/method.dart
new file mode 100644
index 0000000..1156ae2
--- /dev/null
+++ b/lib/src/model/method.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/element/member.dart' show Member;
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+class Method extends ModelElement
+    with ContainerMember, Inheritable, TypeParameters
+    implements EnclosedElement {
+  bool _isInherited = false;
+  Container _enclosingContainer;
+  @override
+  List<TypeParameter> typeParameters = [];
+
+  Method(MethodElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph, null) {
+    _calcTypeParameters();
+  }
+
+  Method.inherited(MethodElement element, this._enclosingContainer,
+      Library library, PackageGraph packageGraph,
+      {Member originalMember})
+      : super(element, library, packageGraph, originalMember) {
+    _isInherited = true;
+    _calcTypeParameters();
+  }
+
+  void _calcTypeParameters() {
+    typeParameters = _method.typeParameters.map((f) {
+      return ModelElement.from(f, library, packageGraph) as TypeParameter;
+    }).toList();
+  }
+
+  @override
+  CharacterLocation get characterLocation {
+    if (enclosingElement is Enum && name == 'toString') {
+      // The toString() method on Enums is special, treated as not having
+      // a definition location by the analyzer yet not being inherited, either.
+      // Just directly override our location with the Enum definition --
+      // this is OK because Enums can not inherit from each other nor
+      // have their definitions split between files.
+      return enclosingElement.characterLocation;
+    }
+    return super.characterLocation;
+  }
+
+  @override
+  ModelElement get enclosingElement {
+    if (_enclosingContainer == null) {
+      _enclosingContainer =
+          ModelElement.from(_method.enclosingElement, library, packageGraph);
+    }
+    return _enclosingContainer;
+  }
+
+  String get fullkind {
+    if (_method.isAbstract) return 'abstract $kind';
+    return kind;
+  }
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(!(canonicalLibrary == null || canonicalEnclosingContainer == null));
+    assert(canonicalLibrary == library);
+    assert(canonicalEnclosingContainer == enclosingElement);
+    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/${fileName}';
+  }
+
+  @override
+  bool get isInherited => _isInherited;
+
+  bool get isOperator => false;
+
+  @override
+  Set<String> get features {
+    Set<String> allFeatures = super.features;
+    if (isInherited) allFeatures.add('inherited');
+    return allFeatures;
+  }
+
+  @override
+  bool get isStatic => _method.isStatic;
+
+  @override
+  String get kind => 'method';
+
+  String get linkedReturnType => modelType.createLinkedReturnTypeName();
+
+  @override
+  DefinedElementType get modelType => super.modelType;
+
+  @override
+  Method get overriddenElement {
+    if (_enclosingContainer is Extension) {
+      return null;
+    }
+    ClassElement parent = element.enclosingElement;
+    for (InterfaceType t in parent.allSupertypes) {
+      Element e = t.getMethod(element.name);
+      if (e != null) {
+        assert(e.enclosingElement is ClassElement);
+        return ModelElement.fromElement(e, packageGraph);
+      }
+    }
+    return null;
+  }
+
+  MethodElement get _method => (element as MethodElement);
+
+  /// Methods can not be covariant; always returns false.
+  @override
+  bool get isCovariant => false;
+}
diff --git a/lib/src/model/mixin.dart b/lib/src/model/mixin.dart
new file mode 100644
index 0000000..c9fe4f7
--- /dev/null
+++ b/lib/src/model/mixin.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/model_utils.dart' as model_utils;
+import 'package:dartdoc/src/special_elements.dart';
+
+/// 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);
+
+  @override
+  bool get isAbstract => false;
+
+  List<Class> _inheritanceChain;
+
+  @override
+  List<Class> get inheritanceChain {
+    if (_inheritanceChain == null) {
+      _inheritanceChain = [];
+      _inheritanceChain.add(this);
+
+      // Mix-in interfaces come before other interfaces.
+      _inheritanceChain.addAll(superclassConstraints
+          .expand((ParameterizedElementType i) =>
+              (i.element as Class).inheritanceChain)
+          .where((Class c) =>
+              c != packageGraph.specialClasses[SpecialClass.object]));
+
+      // Interfaces need to come last, because classes in the superChain might
+      // implement them even when they aren't mentioned.
+      _inheritanceChain.addAll(
+          interfaces.expand((e) => (e.element as Class).inheritanceChain));
+    }
+    return _inheritanceChain.toList(growable: false);
+  }
+
+  List<ParameterizedElementType> _superclassConstraints;
+
+  /// Returns a list of superclass constraints for this mixin.
+  Iterable<ParameterizedElementType> get superclassConstraints {
+    if (_superclassConstraints == null) {
+      _superclassConstraints = (element as ClassElement)
+          .superclassConstraints
+          .map<ParameterizedElementType>(
+              (InterfaceType i) => ElementType.from(i, library, packageGraph))
+          .toList();
+    }
+    return _superclassConstraints;
+  }
+
+  bool get hasPublicSuperclassConstraints =>
+      publicSuperclassConstraints.isNotEmpty;
+
+  Iterable<ParameterizedElementType> get publicSuperclassConstraints =>
+      model_utils.filterNonPublic(superclassConstraints);
+
+  @override
+  bool get hasModifiers => super.hasModifiers || hasPublicSuperclassConstraints;
+
+  @override
+  String get fileName => "${name}-mixin.html";
+
+  @override
+  String get kind => 'mixin';
+}
diff --git a/lib/src/model/model.dart b/lib/src/model/model.dart
new file mode 100644
index 0000000..d691c68
--- /dev/null
+++ b/lib/src/model/model.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2019, 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.
+
+export 'accessor.dart';
+export 'canonicalization.dart';
+export 'categorization.dart';
+export 'category.dart';
+export 'class.dart';
+export 'constructor.dart';
+export 'container.dart';
+export 'container_member.dart';
+export 'documentable.dart';
+export 'dynamic.dart';
+export 'enclosed_element.dart';
+export 'enum.dart';
+export 'extendable.dart';
+export 'extension.dart';
+export 'field.dart';
+export 'getter_setter_combo.dart';
+export 'indexable.dart';
+export 'inheritable.dart';
+export 'library.dart';
+export 'library_container.dart';
+export 'locatable.dart';
+export 'method.dart';
+export 'mixin.dart';
+export 'model_element.dart';
+export 'model_function.dart';
+export 'model_node.dart';
+export 'nameable.dart';
+export 'operator.dart';
+export 'package.dart';
+export 'package_builder.dart';
+export 'package_graph.dart';
+export 'parameter.dart';
+export 'privacy.dart';
+export 'source_code_mixin.dart';
+export 'top_level_container.dart';
+export 'top_level_variable.dart';
+export 'type_parameter.dart';
+export 'typedef.dart';
diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart
new file mode 100644
index 0000000..3ef9b30
--- /dev/null
+++ b/lib/src/model/model_element.dart
@@ -0,0 +1,1777 @@
+// Copyright (c) 2014, 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.
+
+/// The models used to represent Dart code.
+library dartdoc.models;
+
+import 'dart:async';
+import 'dart:collection' show UnmodifiableListView;
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/member.dart'
+    show ExecutableMember, Member, ParameterMember;
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/generated/source_io.dart';
+import 'package:args/args.dart';
+import 'package:collection/collection.dart';
+import 'package:crypto/crypto.dart';
+import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/logging.dart';
+import 'package:dartdoc/src/markdown_processor.dart' show Documentation;
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/model_utils.dart' as utils;
+import 'package:dartdoc/src/source_linker.dart';
+import 'package:dartdoc/src/tuple.dart';
+import 'package:dartdoc/src/utils.dart';
+import 'package:dartdoc/src/warnings.dart';
+import 'package:path/path.dart' as path;
+
+/// Items mapped less than zero will sort before custom annotations.
+/// Items mapped above zero are sorted after custom annotations.
+/// Items mapped to zero will sort alphabetically among custom annotations.
+/// Custom annotations are assumed to be any annotation or feature not in this
+/// map.
+const Map<String, int> featureOrder = {
+  'read-only': 1,
+  'write-only': 1,
+  'read / write': 1,
+  'covariant': 2,
+  'final': 2,
+  'inherited': 3,
+  'inherited-getter': 3,
+  'inherited-setter': 3,
+  'override': 3,
+  'override-getter': 3,
+  'override-setter': 3,
+  'extended': 3,
+};
+
+int byFeatureOrdering(String a, String b) {
+  int scoreA = 0;
+  int scoreB = 0;
+
+  if (featureOrder.containsKey(a)) scoreA = featureOrder[a];
+  if (featureOrder.containsKey(b)) scoreB = featureOrder[b];
+
+  if (scoreA < scoreB) return -1;
+  if (scoreA > scoreB) return 1;
+  return compareAsciiLowerCaseNatural(a, b);
+}
+
+/// This doc may need to be processed in case it has a template or html
+/// fragment.
+final needsPrecacheRegExp = RegExp(r'{@(template|tool|inject-html)');
+
+final templateRegExp = RegExp(
+    r'[ ]*{@template\s+(.+?)}([\s\S]+?){@endtemplate}[ ]*\n?',
+    multiLine: true);
+final htmlRegExp = RegExp(
+    r'[ ]*{@inject-html\s*}([\s\S]+?){@end-inject-html}[ ]*\n?',
+    multiLine: true);
+final htmlInjectRegExp = RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');
+
+// Matches all tool directives (even some invalid ones). This is so
+// we can give good error messages if the directive is malformed, instead of
+// just silently emitting it as-is.
+final basicToolRegExp = RegExp(
+    r'[ ]*{@tool\s+([^}]+)}\n?([\s\S]+?)\n?{@end-tool}[ ]*\n?',
+    multiLine: true);
+
+/// Regexp to take care of splitting arguments, and handling the quotes
+/// around arguments, if any.
+///
+/// Match group 1 is the "foo=" (or "--foo=") part of the option, if any.
+/// Match group 2 contains the quote character used (which is discarded).
+/// Match group 3 is a quoted arg, if any, without the quotes.
+/// Match group 4 is the unquoted arg, if any.
+final RegExp argMatcher = RegExp(r'([a-zA-Z\-_0-9]+=)?' // option name
+    r'(?:' // Start a new non-capture group for the two possibilities.
+    r'''(["'])((?:\\{2})*|(?:.*?[^\\](?:\\{2})*))\2|''' // with quotes.
+    r'([^ ]+))'); // without quotes.
+
+final macroRegExp = RegExp(r'{@macro\s+([^}]+)}');
+
+// TODO(jcollins-g): Implement resolution per ECMA-408 4th edition, page 39 #22.
+/// Resolves this very rare case incorrectly by picking the closest element in
+/// the inheritance and interface chains from the analyzer.
+ModelElement resolveMultiplyInheritedElement(
+    MultiplyInheritedExecutableElement e,
+    Library library,
+    PackageGraph packageGraph,
+    Class enclosingClass) {
+  Iterable<Inheritable> inheritables = e.inheritedElements
+      .map((ee) => ModelElement.fromElement(ee, packageGraph) as Inheritable);
+  Inheritable foundInheritable;
+  int lowIndex = enclosingClass.inheritanceChain.length;
+  for (var inheritable in inheritables) {
+    int index =
+        enclosingClass.inheritanceChain.indexOf(inheritable.enclosingElement);
+    if (index < lowIndex) {
+      foundInheritable = inheritable;
+      lowIndex = index;
+    }
+  }
+  return ModelElement.from(foundInheritable.element, library, packageGraph,
+      enclosingContainer: enclosingClass);
+}
+
+/// This class is the foundation of Dartdoc's model for source code.
+/// All ModelElements are contained within a [PackageGraph], and laid out in a
+/// structure that mirrors the availability of identifiers in the various
+/// namespaces within that package.  For example, multiple [Class] objects
+/// for a particular identifier ([ModelElement.element]) may show up in
+/// different [Library]s as the identifier is reexported.
+///
+/// However, ModelElements have an additional concept vital to generating
+/// documentation: canonicalization.
+///
+/// A ModelElement is canonical if it is the element in the namespace where that
+/// element 'comes from' in the public interface to this [PackageGraph].  That often
+/// means the [ModelElement.library] is contained in [PackageGraph.libraries], but
+/// there are many exceptions and ambiguities the code tries to address here.
+///
+/// Non-canonical elements should refer to their canonical counterparts, making
+/// it easy to calculate links via [ModelElement.href] without having to
+/// know in a particular namespace which elements are canonical or not.
+/// A number of [PackageGraph] methods, such as [PackageGraph.findCanonicalModelElementFor]
+/// can help with this.
+///
+/// When documenting, Dartdoc should only write out files corresponding to
+/// canonical instances of ModelElement ([ModelElement.isCanonical]).  This
+/// helps prevent subtle bugs as generated output for a non-canonical
+/// ModelElement will reference itself as part of the "wrong" [Library]
+/// from the public interface perspective.
+abstract class ModelElement extends Canonicalization
+    with Privacy, Warnable, Locatable, Nameable, SourceCodeMixin, Indexable
+    implements Comparable, Documentable {
+  final Element _element;
+
+  // TODO(jcollins-g): This really wants a "member that has a type" class.
+  final Member _originalMember;
+  final Library _library;
+
+  ElementType _modelType;
+  String _rawDocs;
+  Documentation __documentation;
+  UnmodifiableListView<Parameter> _parameters;
+  String _linkedName;
+
+  String _fullyQualifiedName;
+  String _fullyQualifiedNameWithoutLibrary;
+
+  // TODO(jcollins-g): make _originalMember optional after dart-lang/sdk#15101
+  // is fixed.
+  ModelElement(
+      this._element, this._library, this._packageGraph, this._originalMember);
+
+  factory ModelElement.fromElement(Element e, PackageGraph p) {
+    Library lib = p.findButDoNotCreateLibraryFor(e);
+    Accessor getter;
+    Accessor setter;
+    if (e is PropertyInducingElement) {
+      getter = e.getter != null ? ModelElement.from(e.getter, lib, p) : null;
+      setter = e.setter != null ? ModelElement.from(e.setter, lib, p) : null;
+    }
+    return ModelElement.from(e, lib, p, getter: getter, setter: setter);
+  }
+
+  // TODO(jcollins-g): this way of using the optional parameter is messy,
+  // clean that up.
+  // TODO(jcollins-g): Refactor this into class-specific factories that
+  // call this one.
+  // TODO(jcollins-g): Enforce construction restraint.
+  // TODO(jcollins-g): Allow e to be null and drop extraneous null checks.
+  // TODO(jcollins-g): Auto-vivify element's defining library for library
+  // parameter when given a null.
+  /// Do not construct any ModelElements unless they are from this constructor.
+  /// Specify enclosingContainer if and only if this is to be an inherited or
+  /// extended object.
+  factory ModelElement.from(
+      Element e, Library library, PackageGraph packageGraph,
+      {Container enclosingContainer, Accessor getter, Accessor setter}) {
+    assert(packageGraph != null && e != null);
+    assert(library != null ||
+        e is ParameterElement ||
+        e is TypeParameterElement ||
+        e is GenericFunctionTypeElementImpl ||
+        e.kind == ElementKind.DYNAMIC);
+
+    Member originalMember;
+    // TODO(jcollins-g): Refactor object model to instantiate 'ModelMembers'
+    //                   for members?
+    if (e is Member) {
+      var basest = PackageGraph.getBasestElement(e);
+      originalMember = e;
+      e = basest;
+    }
+    Tuple3<Element, Library, Container> key =
+        Tuple3(e, library, enclosingContainer);
+    ModelElement newModelElement;
+    if (e.kind != ElementKind.DYNAMIC &&
+        packageGraph.allConstructedModelElements.containsKey(key)) {
+      newModelElement = packageGraph.allConstructedModelElements[key];
+      assert(newModelElement.element is! MultiplyInheritedExecutableElement);
+    } else {
+      if (e.kind == ElementKind.DYNAMIC) {
+        newModelElement = Dynamic(e, packageGraph);
+      }
+      if (e is MultiplyInheritedExecutableElement) {
+        newModelElement = resolveMultiplyInheritedElement(
+            e, library, packageGraph, enclosingContainer);
+      } else {
+        if (e is LibraryElement) {
+          newModelElement = Library(e, packageGraph);
+        }
+        // Also handles enums
+        if (e is ClassElement) {
+          if (e.isMixin) {
+            newModelElement = Mixin(e, library, packageGraph);
+          } else if (e.isEnum) {
+            newModelElement = Enum(e, library, packageGraph);
+          } else {
+            newModelElement = Class(e, library, packageGraph);
+          }
+        }
+        if (e is ExtensionElement) {
+          newModelElement = Extension(e, library, packageGraph);
+        }
+        if (e is FunctionElement) {
+          newModelElement = ModelFunction(e, library, packageGraph);
+        } else if (e is GenericFunctionTypeElement) {
+          // TODO(scheglov) "e" cannot be both GenericFunctionTypeElement,
+          // and FunctionTypeAliasElement or GenericTypeAliasElement.
+          if (e is FunctionTypeAliasElement) {
+            assert(e.name != '');
+            newModelElement = ModelFunctionTypedef(e, library, packageGraph);
+          } else {
+            if (e.enclosingElement is GenericTypeAliasElement) {
+              assert(e.enclosingElement.name != '');
+              newModelElement = ModelFunctionTypedef(e, library, packageGraph);
+            } else {
+              // Allowing null here is allowed as a workaround for
+              // dart-lang/sdk#32005.
+              assert(e.name == '' || e.name == null);
+              newModelElement = ModelFunctionAnonymous(e, packageGraph);
+            }
+          }
+        }
+        if (e is FunctionTypeAliasElement) {
+          newModelElement = Typedef(e, library, packageGraph);
+        }
+        if (e is FieldElement) {
+          if (enclosingContainer == null) {
+            if (e.isEnumConstant) {
+              int index =
+                  e.computeConstantValue().getField(e.name).toIntValue();
+              newModelElement = EnumField.forConstant(
+                  index, e, library, packageGraph, getter);
+              // ignore: unnecessary_cast
+            } else if (e.enclosingElement is ExtensionElement) {
+              newModelElement = Field(e, library, packageGraph, getter, setter);
+            } else if (e.enclosingElement is ClassElement &&
+                (e.enclosingElement as ClassElement).isEnum) {
+              newModelElement =
+                  EnumField(e, library, packageGraph, getter, setter);
+            } else {
+              newModelElement = Field(e, library, packageGraph, getter, setter);
+            }
+          } else {
+            // EnumFields can't be inherited, so this case is simpler.
+            newModelElement = Field.inherited(
+                e, enclosingContainer, library, packageGraph, getter, setter);
+          }
+        }
+        if (e is ConstructorElement) {
+          newModelElement = Constructor(e, library, packageGraph);
+        }
+        if (e is MethodElement && e.isOperator) {
+          if (enclosingContainer == null) {
+            newModelElement = Operator(e, library, packageGraph);
+          } else {
+            newModelElement = Operator.inherited(
+                e, enclosingContainer, library, packageGraph,
+                originalMember: originalMember);
+          }
+        }
+        if (e is MethodElement && !e.isOperator) {
+          if (enclosingContainer == null) {
+            newModelElement = Method(e, library, packageGraph);
+          } else {
+            newModelElement = Method.inherited(
+                e, enclosingContainer, library, packageGraph,
+                originalMember: originalMember);
+          }
+        }
+        if (e is TopLevelVariableElement) {
+          assert(getter != null || setter != null);
+          newModelElement =
+              TopLevelVariable(e, library, packageGraph, getter, setter);
+        }
+        if (e is PropertyAccessorElement) {
+          // TODO(jcollins-g): why test for ClassElement in enclosingElement?
+          if (e.enclosingElement is ClassElement ||
+              e is MultiplyInheritedExecutableElement) {
+            if (enclosingContainer == null) {
+              newModelElement = ContainerAccessor(e, library, packageGraph);
+            } else {
+              newModelElement = ContainerAccessor.inherited(
+                  e, library, packageGraph, enclosingContainer,
+                  originalMember: originalMember);
+            }
+          } else {
+            newModelElement = Accessor(e, library, packageGraph, null);
+          }
+        }
+        if (e is TypeParameterElement) {
+          newModelElement = TypeParameter(e, library, packageGraph);
+        }
+        if (e is ParameterElement) {
+          newModelElement = Parameter(e, library, packageGraph,
+              originalMember: originalMember);
+        }
+      }
+    }
+
+    if (newModelElement == null) throw "Unknown type ${e.runtimeType}";
+    if (enclosingContainer != null) assert(newModelElement is Inheritable);
+    // TODO(jcollins-g): Reenable Parameter caching when dart-lang/sdk#30146
+    //                   is fixed?
+    if (library != null && newModelElement is! Parameter) {
+      library.packageGraph.allConstructedModelElements[key] = newModelElement;
+      if (newModelElement is Inheritable) {
+        Tuple2<Element, Library> iKey = Tuple2(e, library);
+        library.packageGraph.allInheritableElements
+            .putIfAbsent(iKey, () => Set());
+        library.packageGraph.allInheritableElements[iKey].add(newModelElement);
+      }
+    }
+    if (newModelElement is GetterSetterCombo) {
+      assert(getter == null || newModelElement?.getter?.enclosingCombo != null);
+      assert(setter == null || newModelElement?.setter?.enclosingCombo != null);
+    }
+
+    assert(newModelElement.element is! MultiplyInheritedExecutableElement);
+    return newModelElement;
+  }
+
+  /// Stub for mustache4dart, or it will search enclosing elements to find
+  /// names for members.
+  bool get hasCategoryNames => false;
+
+  Set<Library> get exportedInLibraries {
+    return library
+        .packageGraph.libraryElementReexportedBy[this.element.library];
+  }
+
+  ModelNode _modelNode;
+
+  @override
+  ModelNode get modelNode =>
+      _modelNode ??= packageGraph.getModelNodeFor(element);
+
+  List<String> get annotations => annotationsFromMetadata(element.metadata);
+
+  /// Returns linked annotations from a given metadata set, with escaping.
+  List<String> annotationsFromMetadata(List<ElementAnnotation> md) {
+    List<String> annotationStrings = [];
+    if (md == null) return annotationStrings;
+    for (ElementAnnotation a in md) {
+      String annotation = (const HtmlEscape()).convert(a.toSource());
+      Element annotationElement = a.element;
+
+      ClassElement annotationClassElement;
+      if (annotationElement is ExecutableElement) {
+        annotationElement =
+            (annotationElement as ExecutableElement).returnType.element;
+      }
+      if (annotationElement is ClassElement) {
+        annotationClassElement = annotationElement;
+      }
+      ModelElement annotationModelElement =
+          packageGraph.findCanonicalModelElementFor(annotationElement);
+      // annotationElement can be null if the element can't be resolved.
+      Class annotationClass = packageGraph
+          .findCanonicalModelElementFor(annotationClassElement) as Class;
+      if (annotationClass == null &&
+          annotationElement != null &&
+          annotationClassElement != null) {
+        annotationClass =
+            ModelElement.fromElement(annotationClassElement, packageGraph)
+                as Class;
+      }
+      // Some annotations are intended to be invisible (@pragma)
+      if (annotationClass == null ||
+          !packageGraph.invisibleAnnotations.contains(annotationClass)) {
+        if (annotationModelElement != null) {
+          annotation = annotation.replaceFirst(
+              annotationModelElement.name, annotationModelElement.linkedName);
+        }
+        annotationStrings.add(annotation);
+      }
+    }
+    return annotationStrings;
+  }
+
+  bool _isPublic;
+
+  @override
+  bool get isPublic {
+    if (_isPublic == null) {
+      if (name == '') {
+        _isPublic = false;
+      } else if (this is! Library && (library == null || !library.isPublic)) {
+        _isPublic = false;
+      } else if (enclosingElement is Class &&
+          !(enclosingElement as Class).isPublic) {
+        _isPublic = false;
+      } else if (enclosingElement is Extension &&
+          !(enclosingElement as Extension).isPublic) {
+        _isPublic = false;
+      } else {
+        String docComment = documentationComment;
+        if (docComment == null) {
+          _isPublic = utils.hasPublicName(element);
+        } else {
+          _isPublic = utils.hasPublicName(element) &&
+              !(docComment.contains('@nodoc') ||
+                  docComment.contains('<nodoc>'));
+        }
+      }
+    }
+    return _isPublic;
+  }
+
+  List<ModelCommentReference> _commentRefs;
+
+  @override
+  List<ModelCommentReference> get commentRefs {
+    if (_commentRefs == null) {
+      _commentRefs = [];
+      for (ModelElement from in documentationFrom) {
+        List<ModelElement> checkReferences = [from];
+        if (from is Accessor) {
+          checkReferences.add(from.enclosingCombo);
+        }
+        for (ModelElement e in checkReferences) {
+          _commentRefs.addAll(e.modelNode.commentRefs ?? []);
+        }
+      }
+    }
+    return _commentRefs;
+  }
+
+  DartdocOptionContext _config;
+
+  @override
+  DartdocOptionContext get config {
+    if (_config == null) {
+      _config =
+          DartdocOptionContext.fromContextElement(packageGraph.config, element);
+    }
+    return _config;
+  }
+
+  @override
+  Set<String> get locationPieces {
+    return Set.from(element.location
+        .toString()
+        .split(locationSplitter)
+        .where((s) => s.isNotEmpty));
+  }
+
+  Set<String> get features {
+    Set<String> allFeatures = Set<String>();
+    allFeatures.addAll(annotations);
+
+    // Replace the @override annotation with a feature that explicitly
+    // indicates whether an override has occurred.
+    allFeatures.remove('@override');
+
+    // Drop the plain "deprecated" annotation, that's indicated via
+    // strikethroughs. Custom @Deprecated() will still appear.
+    allFeatures.remove('@deprecated');
+    // const and static are not needed here because const/static elements get
+    // their own sections in the doc.
+    if (isFinal) allFeatures.add('final');
+    return allFeatures;
+  }
+
+  String get featuresAsString {
+    List<String> allFeatures = features.toList()..sort(byFeatureOrdering);
+    return allFeatures.join(', ');
+  }
+
+  bool get canHaveParameters =>
+      element is ExecutableElement ||
+      element is FunctionTypedElement ||
+      element is FunctionTypeAliasElement;
+
+  ModelElement buildCanonicalModelElement() {
+    Container preferredClass;
+    if (enclosingElement is Class || enclosingElement is Extension) {
+      preferredClass = enclosingElement;
+    }
+    return packageGraph.findCanonicalModelElementFor(element,
+        preferredClass: preferredClass);
+  }
+
+  // Returns the canonical ModelElement for this ModelElement, or null
+  // if there isn't one.
+  ModelElement _canonicalModelElement;
+
+  ModelElement get canonicalModelElement =>
+      _canonicalModelElement ??= buildCanonicalModelElement();
+
+  List<ModelElement> _documentationFrom;
+
+  // TODO(jcollins-g): untangle when mixins can call super
+  @override
+  List<ModelElement> get documentationFrom {
+    if (_documentationFrom == null) {
+      _documentationFrom = computeDocumentationFrom;
+    }
+    return _documentationFrom;
+  }
+
+  bool get hasSourceHref => sourceHref.isNotEmpty;
+  String _sourceHref;
+
+  String get sourceHref {
+    _sourceHref ??= SourceLinker.fromElement(this).href();
+    return _sourceHref;
+  }
+
+  /// Returns the ModelElement(s) from which we will get documentation.
+  /// Can be more than one if this is a Field composing documentation from
+  /// multiple Accessors.
+  ///
+  /// This getter will walk up the inheritance hierarchy
+  /// to find docs, if the current class doesn't have docs
+  /// for this element.
+  List<ModelElement> get computeDocumentationFrom {
+    List<ModelElement> docFrom;
+
+    if (documentationComment == null &&
+        canOverride() &&
+        this is Inheritable &&
+        (this as Inheritable).overriddenElement != null) {
+      docFrom = (this as Inheritable).overriddenElement.documentationFrom;
+    } else if (this is Inheritable && (this as Inheritable).isInherited) {
+      Inheritable thisInheritable = (this as Inheritable);
+      ModelElement fromThis = ModelElement.fromElement(
+          element, thisInheritable.definingEnclosingContainer.packageGraph);
+      docFrom = fromThis.documentationFrom;
+    } else {
+      docFrom = [this];
+    }
+    return docFrom;
+  }
+
+  String _buildDocumentationLocal() => _buildDocumentationBaseSync();
+
+  /// Override this to add more features to the documentation builder in a
+  /// subclass.
+  String buildDocumentationAddition(String docs) => docs ??= '';
+
+  /// Separate from _buildDocumentationLocal for overriding.
+  String _buildDocumentationBaseSync() {
+    assert(_rawDocs == null,
+        'reentrant calls to _buildDocumentation* not allowed');
+    // Do not use the sync method if we need to evaluate tools or templates.
+    assert(!isCanonical ||
+        !needsPrecacheRegExp.hasMatch(documentationComment ?? ''));
+    if (config.dropTextFrom.contains(element.library.name)) {
+      _rawDocs = '';
+    } else {
+      _rawDocs = documentationComment ?? '';
+      _rawDocs = stripComments(_rawDocs) ?? '';
+      _rawDocs = _injectExamples(_rawDocs);
+      _rawDocs = _injectYouTube(_rawDocs);
+      _rawDocs = _injectAnimations(_rawDocs);
+      _rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
+    }
+    _rawDocs = buildDocumentationAddition(_rawDocs);
+    return _rawDocs;
+  }
+
+  /// Separate from _buildDocumentationLocal for overriding.  Can only be
+  /// used as part of [PackageGraph.setUpPackageGraph].
+  Future<String> _buildDocumentationBase() async {
+    assert(_rawDocs == null,
+        'reentrant calls to _buildDocumentation* not allowed');
+    // Do not use the sync method if we need to evaluate tools or templates.
+    if (config.dropTextFrom.contains(element.library.name)) {
+      _rawDocs = '';
+    } else {
+      _rawDocs = documentationComment ?? '';
+      _rawDocs = stripComments(_rawDocs) ?? '';
+      // Must evaluate tools first, in case they insert any other directives.
+      _rawDocs = await _evaluateTools(_rawDocs);
+      _rawDocs = _injectExamples(_rawDocs);
+      _rawDocs = _injectYouTube(_rawDocs);
+      _rawDocs = _injectAnimations(_rawDocs);
+      _rawDocs = _stripMacroTemplatesAndAddToIndex(_rawDocs);
+      _rawDocs = _stripHtmlAndAddToIndex(_rawDocs);
+    }
+    _rawDocs = buildDocumentationAddition(_rawDocs);
+    return _rawDocs;
+  }
+
+  /// Returns the documentation for this literal element unless
+  /// [config.dropTextFrom] indicates it should not be returned.  Macro
+  /// definitions are stripped, but macros themselves are not injected.  This
+  /// is a two stage process to avoid ordering problems.
+  String _documentationLocal;
+
+  String get documentationLocal =>
+      _documentationLocal ??= _buildDocumentationLocal();
+
+  /// Returns the docs, stripped of their leading comments syntax.
+  @override
+  String get documentation {
+    return _injectMacros(
+        documentationFrom.map((e) => e.documentationLocal).join('<p>'));
+  }
+
+  Library get definingLibrary =>
+      packageGraph.findButDoNotCreateLibraryFor(element);
+
+  Library _canonicalLibrary;
+
+  // _canonicalLibrary can be null so we can't check against null to see whether
+  // we tried to compute it before.
+  bool _canonicalLibraryIsSet = false;
+
+  @override
+  Library get canonicalLibrary {
+    if (!_canonicalLibraryIsSet) {
+      // This is not accurate if we are constructing the Package.
+      assert(packageGraph.allLibrariesAdded);
+      // Since we're may be looking for a library, find the [Element] immediately
+      // contained by a [CompilationUnitElement] in the tree.
+      Element topLevelElement = element;
+      while (topLevelElement != null &&
+          topLevelElement.enclosingElement is! LibraryElement &&
+          topLevelElement.enclosingElement is! CompilationUnitElement &&
+          topLevelElement.enclosingElement != null) {
+        topLevelElement = topLevelElement.enclosingElement;
+      }
+
+      // Privately named elements can never have a canonical library, so
+      // just shortcut them out.
+      if (!utils.hasPublicName(element)) {
+        _canonicalLibrary = null;
+      } else if (!packageGraph.localPublicLibraries.contains(definingLibrary)) {
+        List<Library> candidateLibraries = definingLibrary.exportedInLibraries
+            ?.where((l) =>
+                l.isPublic &&
+                l.package.documentedWhere != DocumentLocation.missing)
+            ?.toList();
+
+        if (candidateLibraries != null) {
+          candidateLibraries = candidateLibraries.where((l) {
+            Element lookup = (l.element as LibraryElement)
+                .exportNamespace
+                .definedNames[topLevelElement?.name];
+            if (lookup is PropertyAccessorElement) {
+              lookup = (lookup as PropertyAccessorElement).variable;
+            }
+            if (topLevelElement == lookup) return true;
+            return false;
+          }).toList();
+
+          // Avoid claiming canonicalization for elements outside of this element's
+          // defining package.
+          // TODO(jcollins-g): Make the else block unconditional.
+          if (candidateLibraries.isNotEmpty &&
+              !candidateLibraries
+                  .any((l) => l.package == definingLibrary.package)) {
+            warn(PackageWarning.reexportedPrivateApiAcrossPackages,
+                message: definingLibrary.package.fullyQualifiedName,
+                referredFrom: candidateLibraries);
+          } else {
+            candidateLibraries
+                .removeWhere((l) => l.package != definingLibrary.package);
+          }
+
+          // Start with our top-level element.
+          ModelElement warnable =
+              ModelElement.fromElement(topLevelElement, packageGraph);
+          if (candidateLibraries.length > 1) {
+            // Heuristic scoring to determine which library a human likely
+            // considers this element to be primarily 'from', and therefore,
+            // canonical.  Still warn if the heuristic isn't that confident.
+            List<ScoredCandidate> scoredCandidates =
+                warnable.scoreCanonicalCandidates(candidateLibraries);
+            candidateLibraries =
+                scoredCandidates.map((s) => s.library).toList();
+            double secondHighestScore =
+                scoredCandidates[scoredCandidates.length - 2].score;
+            double highestScore = scoredCandidates.last.score;
+            double confidence = highestScore - secondHighestScore;
+            String message =
+                "${candidateLibraries.map((l) => l.name)} -> ${candidateLibraries.last.name} (confidence ${confidence.toStringAsPrecision(4)})";
+            List<String> debugLines = [];
+            debugLines.addAll(scoredCandidates.map((s) => '${s.toString()}'));
+
+            if (confidence < config.ambiguousReexportScorerMinConfidence) {
+              warnable.warn(PackageWarning.ambiguousReexport,
+                  message: message, extendedDebug: debugLines);
+            }
+          }
+          if (candidateLibraries.isNotEmpty) {
+            _canonicalLibrary = candidateLibraries.last;
+          }
+        }
+      } else {
+        _canonicalLibrary = definingLibrary;
+      }
+      // Only pretend when not linking to remote packages.
+      if (this is Inheritable && !config.linkToRemote) {
+        if ((this as Inheritable).isInherited &&
+            _canonicalLibrary == null &&
+            packageGraph.publicLibraries.contains(library)) {
+          // In the event we've inherited a field from an object that isn't directly reexported,
+          // we may need to pretend we are canonical for this.
+          _canonicalLibrary = library;
+        }
+      }
+      _canonicalLibraryIsSet = true;
+    }
+    assert(_canonicalLibrary == null ||
+        packageGraph.publicLibraries.contains(_canonicalLibrary));
+    return _canonicalLibrary;
+  }
+
+  @override
+  bool get isCanonical {
+    if (library == canonicalLibrary) {
+      if (this is Inheritable) {
+        Inheritable i = (this as Inheritable);
+        // If we're the defining element, or if the defining element is not
+        // in the set of libraries being documented, then this element
+        // should be treated as canonical (given library == canonicalLibrary).
+        if (i.enclosingElement == i.canonicalEnclosingContainer) {
+          return true;
+        } else {
+          return false;
+        }
+      }
+      // If there's no inheritance to deal with, we're done.
+      return true;
+    }
+    return false;
+  }
+
+  String _htmlDocumentation;
+
+  @override
+  String get documentationAsHtml {
+    if (_htmlDocumentation != null) return _htmlDocumentation;
+    _htmlDocumentation = _injectHtmlFragments(_documentation.asHtml);
+    return _htmlDocumentation;
+  }
+
+  @override
+  Element get element => _element;
+
+  @override
+  String get location {
+    // Call nothing from here that can emit warnings or you'll cause stack overflows.
+    if (characterLocation != null) {
+      return "(${path.toUri(sourceFileName)}:${characterLocation.toString()})";
+    }
+    return "(${path.toUri(sourceFileName)})";
+  }
+
+  /// Returns a link to extended documentation, or the empty string if that
+  /// does not exist.
+  String get extendedDocLink {
+    if (hasExtendedDocumentation) {
+      return '<a href="${href}">[...]</a>';
+    }
+    return '';
+  }
+
+  String get fileName => "${name}.html";
+
+  /// Returns the fully qualified name.
+  ///
+  /// For example: libraryName.className.methodName
+  @override
+  String get fullyQualifiedName {
+    return (_fullyQualifiedName ??= _buildFullyQualifiedName());
+  }
+
+  String get fullyQualifiedNameWithoutLibrary {
+    // Remember, periods are legal in library names.
+    if (_fullyQualifiedNameWithoutLibrary == null) {
+      _fullyQualifiedNameWithoutLibrary =
+          fullyQualifiedName.replaceFirst("${library.fullyQualifiedName}.", '');
+    }
+    return _fullyQualifiedNameWithoutLibrary;
+  }
+
+  String get sourceFileName => element.source.fullName;
+
+  CharacterLocation _characterLocation;
+  bool _characterLocationIsSet = false;
+
+  @override
+  CharacterLocation get characterLocation {
+    if (!_characterLocationIsSet) {
+      LineInfo lineInfo = compilationUnitElement.lineInfo;
+      _characterLocationIsSet = true;
+      assert(element.nameOffset >= 0,
+          'Invalid location data for element: $fullyQualifiedName');
+      assert(lineInfo != null,
+          'No lineInfo data available for element: $fullyQualifiedName');
+      if (element.nameOffset >= 0) {
+        _characterLocation = lineInfo?.getLocation(element.nameOffset);
+      }
+    }
+    return _characterLocation;
+  }
+
+  CompilationUnitElement get compilationUnitElement =>
+      element.getAncestor((e) => e is CompilationUnitElement);
+
+  bool get hasAnnotations => annotations.isNotEmpty;
+
+  @override
+  bool get hasDocumentation =>
+      documentation != null && documentation.isNotEmpty;
+
+  @override
+  bool get hasExtendedDocumentation =>
+      href != null && _documentation.hasExtendedDocs;
+
+  bool get hasParameters => parameters.isNotEmpty;
+
+  /// If canonicalLibrary (or canonicalEnclosingElement, for Inheritable
+  /// subclasses) is null, href should be null.
+  @override
+  String get href;
+
+  String get htmlId => name;
+
+  bool get isAsynchronous =>
+      isExecutable && (element as ExecutableElement).isAsynchronous;
+
+  bool get isConst => false;
+
+  bool get isDeprecated {
+    // If element.metadata is empty, it might be because this is a property
+    // where the metadata belongs to the individual getter/setter
+    if (element.metadata.isEmpty && element is PropertyInducingElement) {
+      var pie = element as PropertyInducingElement;
+
+      // The getter or the setter might be null – so the stored value may be
+      // `true`, `false`, or `null`
+      var getterDeprecated = pie.getter?.metadata?.any((a) => a.isDeprecated);
+      var setterDeprecated = pie.setter?.metadata?.any((a) => a.isDeprecated);
+
+      var deprecatedValues =
+          [getterDeprecated, setterDeprecated].where((a) => a != null).toList();
+
+      // At least one of these should be non-null. Otherwise things are weird
+      assert(deprecatedValues.isNotEmpty);
+
+      // If there are both a setter and getter, only show the property as
+      // deprecated if both are deprecated.
+      return deprecatedValues.every((d) => d);
+    }
+    return element.metadata.any((a) => a.isDeprecated);
+  }
+
+  @override
+  bool get isDocumented => isCanonical && isPublic;
+
+  bool get isExecutable => element is ExecutableElement;
+
+  bool get isFinal => false;
+
+  bool get isLocalElement => element is LocalElement;
+
+  bool get isPropertyAccessor => element is PropertyAccessorElement;
+
+  bool get isPropertyInducer => element is PropertyInducingElement;
+
+  bool get isStatic {
+    if (isPropertyInducer) {
+      return (element as PropertyInducingElement).isStatic;
+    }
+    return false;
+  }
+
+  /// A human-friendly name for the kind of element this is.
+  @override
+  String get kind;
+
+  @override
+  Library get library => _library;
+
+  String get linkedName {
+    if (_linkedName == null) {
+      _linkedName = _calculateLinkedName();
+    }
+    return _linkedName;
+  }
+
+  String get linkedParams => utils.linkedParams(parameters);
+
+  String get linkedParamsLines => utils.linkedParams(parameters).trim();
+
+  String get linkedParamsNoMetadata =>
+      utils.linkedParams(parameters, showMetadata: false);
+
+  String get linkedParamsNoMetadataOrNames {
+    return utils.linkedParams(parameters,
+        showMetadata: false, showNames: false);
+  }
+
+  ElementType get modelType {
+    if (_modelType == null) {
+      // TODO(jcollins-g): Need an interface for a "member with a type" (or changed object model).
+      if (_originalMember != null &&
+          (_originalMember is ExecutableMember ||
+              _originalMember is ParameterMember)) {
+        if (_originalMember is ExecutableMember) {
+          _modelType = ElementType.from(
+              (_originalMember as ExecutableMember).type,
+              library,
+              packageGraph);
+        } else {
+          // ParameterMember
+          _modelType = ElementType.from(
+              (_originalMember as ParameterMember).type, library, packageGraph);
+        }
+      } else if (element is ExecutableElement ||
+          element is FunctionTypedElement ||
+          element is ParameterElement ||
+          element is TypeDefiningElement ||
+          element is PropertyInducingElement) {
+        _modelType =
+            ElementType.from((element as dynamic).type, library, packageGraph);
+      }
+    }
+    return _modelType;
+  }
+
+  void setModelType(ElementType type) {
+    _modelType = type;
+  }
+
+  @override
+  String get name => element.name;
+
+  // TODO(jcollins-g): refactor once dartdoc will only run in a VM where mixins
+  // calling super is allowed (SDK constraint >= 2.1.0).
+  String computeOneLineDoc() =>
+      '${_documentation.asOneLiner}${extendedDocLink.isEmpty ? "" : " $extendedDocLink"}';
+  String _oneLineDoc;
+
+  @override
+  String get oneLineDoc {
+    if (_oneLineDoc == null) {
+      _oneLineDoc = computeOneLineDoc();
+    }
+    return _oneLineDoc;
+  }
+
+  Member get originalMember => _originalMember;
+
+  final PackageGraph _packageGraph;
+
+  @override
+  PackageGraph get packageGraph => _packageGraph;
+
+  @override
+  Package get package => library.package;
+
+  bool get isPublicAndPackageDocumented =>
+      isPublic && library.packageGraph.packageDocumentedFor(this);
+
+  List<Parameter> _allParameters;
+
+  // TODO(jcollins-g): This is in the wrong place.  Move parts to GetterSetterCombo,
+  // elsewhere as appropriate?
+  List<Parameter> get allParameters {
+    if (_allParameters == null) {
+      final Set<Parameter> recursedParameters = Set();
+      final Set<Parameter> newParameters = Set();
+      if (this is GetterSetterCombo &&
+          (this as GetterSetterCombo).setter != null) {
+        newParameters.addAll((this as GetterSetterCombo).setter.parameters);
+      } else {
+        if (canHaveParameters) newParameters.addAll(parameters);
+      }
+      while (newParameters.isNotEmpty) {
+        recursedParameters.addAll(newParameters);
+        newParameters.clear();
+        for (Parameter p in recursedParameters) {
+          var l = p.modelType.parameters
+              .where((pm) => !recursedParameters.contains(pm));
+          newParameters.addAll(l);
+        }
+      }
+      _allParameters = recursedParameters.toList();
+    }
+    return _allParameters;
+  }
+
+  List<Parameter> get parameters {
+    if (!canHaveParameters) {
+      throw StateError("$element cannot have parameters");
+    }
+
+    if (_parameters == null) {
+      List<ParameterElement> params;
+
+      if (element is ExecutableElement) {
+        if (_originalMember != null) {
+          assert(_originalMember is ExecutableMember);
+          params = (_originalMember as ExecutableMember).parameters;
+        } else {
+          params = (element as ExecutableElement).parameters;
+        }
+      }
+      if (params == null && element is FunctionTypedElement) {
+        if (_originalMember != null) {
+          params = (_originalMember as dynamic).parameters;
+        } else {
+          params = (element as FunctionTypedElement).parameters;
+        }
+      }
+      if (params == null && element is FunctionTypeAliasElement) {
+        params = (element as FunctionTypeAliasElement).function.parameters;
+      }
+
+      _parameters = UnmodifiableListView<Parameter>(params
+          .map((p) => ModelElement.from(p, library, packageGraph) as Parameter)
+          .toList());
+    }
+    return _parameters;
+  }
+
+  @override
+  void warn(PackageWarning kind,
+      {String message,
+      Iterable<Locatable> referredFrom,
+      Iterable<String> extendedDebug}) {
+    packageGraph.warnOnElement(this, kind,
+        message: message,
+        referredFrom: referredFrom,
+        extendedDebug: extendedDebug);
+  }
+
+  String computeDocumentationComment() => element.documentationComment;
+
+  bool _documentationCommentComputed = false;
+  String _documentationComment;
+
+  String get documentationComment {
+    if (_documentationCommentComputed == false) {
+      _documentationComment = computeDocumentationComment();
+      _documentationCommentComputed = true;
+    }
+    return _documentationComment;
+  }
+
+  /// Unconditionally precache local documentation.
+  ///
+  /// Use only in factory for [PackageGraph].
+  Future precacheLocalDocs() async {
+    _documentationLocal = await _buildDocumentationBase();
+  }
+
+  Documentation get _documentation {
+    if (__documentation != null) return __documentation;
+    __documentation = Documentation.forElement(this);
+    return __documentation;
+  }
+
+  bool canOverride() =>
+      element is ClassMemberElement || element is PropertyAccessorElement;
+
+  @override
+  int compareTo(dynamic other) {
+    if (other is ModelElement) {
+      return name.toLowerCase().compareTo(other.name.toLowerCase());
+    } else {
+      return 0;
+    }
+  }
+
+  @override
+  String toString() => '$runtimeType $name';
+
+  String _buildFullyQualifiedName([ModelElement e, String fqName]) {
+    e ??= this;
+    fqName ??= e.name;
+
+    if (e is! EnclosedElement || e.enclosingElement == null) {
+      return fqName;
+    }
+
+    return _buildFullyQualifiedName(
+        e.enclosingElement, '${e.enclosingElement.name}.$fqName');
+  }
+
+  String _calculateLinkedName() {
+    // If we're calling this with an empty name, we probably have the wrong
+    // element associated with a ModelElement or there's an analysis bug.
+    assert(name.isNotEmpty ||
+        this.element?.kind == ElementKind.DYNAMIC ||
+        this is ModelFunction);
+
+    if (href == null) {
+      if (isPublicAndPackageDocumented) {
+        warn(PackageWarning.noCanonicalFound);
+      }
+      return htmlEscape.convert(name);
+    }
+
+    var classContent = isDeprecated ? ' class="deprecated"' : '';
+    return '<a${classContent} href="${href}">$name</a>';
+  }
+
+  /// Replace &#123;@example ...&#125; in API comments with the content of named file.
+  ///
+  /// Syntax:
+  ///
+  ///     &#123;@example PATH [region=NAME] [lang=NAME]&#125;
+  ///
+  /// If PATH is `dir/file.ext` and region is `r` then we'll look for the file
+  /// named `dir/file-r.ext.md`, relative to the project root directory of the
+  /// project for which the docs are being generated.
+  ///
+  /// Examples: (escaped in this comment to show literal values in dartdoc's
+  ///            dartdoc)
+  ///
+  ///     &#123;@example examples/angular/quickstart/web/main.dart&#125;
+  ///     &#123;@example abc/def/xyz_component.dart region=template lang=html&#125;
+  ///
+  String _injectExamples(String rawdocs) {
+    final dirPath = package.packageMeta.dir.path;
+    RegExp exampleRE = RegExp(r'{@example\s+([^}]+)}');
+    return rawdocs.replaceAllMapped(exampleRE, (match) {
+      var args = _getExampleArgs(match[1]);
+      if (args == null) {
+        // Already warned about an invalid parameter if this happens.
+        return '';
+      }
+      var lang =
+          args['lang'] ?? path.extension(args['src']).replaceFirst('.', '');
+
+      var replacement = match[0]; // default to fully matched string.
+
+      var fragmentFile = File(path.join(dirPath, args['file']));
+      if (fragmentFile.existsSync()) {
+        replacement = fragmentFile.readAsStringSync();
+        if (lang.isNotEmpty) {
+          replacement = replacement.replaceFirst('```', '```$lang');
+        }
+      } else {
+        // TODO(jcollins-g): move this to Package.warn system
+        var filePath =
+            this.element.source.fullName.substring(dirPath.length + 1);
+
+        logWarning(
+            'warning: ${filePath}: @example file not found, ${fragmentFile.path}');
+      }
+      return replacement;
+    });
+  }
+
+  static Future<String> _replaceAllMappedAsync(
+      String string, Pattern exp, Future<String> replace(Match match)) async {
+    StringBuffer replaced = StringBuffer();
+    int currentIndex = 0;
+    for (Match match in exp.allMatches(string)) {
+      String prefix = match.input.substring(currentIndex, match.start);
+      currentIndex = match.end;
+      replaced..write(prefix)..write(await replace(match));
+    }
+    replaced.write(string.substring(currentIndex));
+    return replaced.toString();
+  }
+
+  /// Replace &#123;@tool ...&#125&#123;@end-tool&#125; in API comments with the
+  /// output of an external tool.
+  ///
+  /// Looks for tools invocations, looks up their bound executables in the
+  /// options, and executes them with the source comment material as input,
+  /// returning the output of the tool. If a named tool isn't configured in the
+  /// options file, then it will not be executed, and dartdoc will quit with an
+  /// error.
+  ///
+  /// Tool command line arguments are passed to the tool, with the token
+  /// `$INPUT` replaced with the absolute path to a temporary file containing
+  /// the content for the tool to read and produce output from. If the tool
+  /// doesn't need any input, then no `$INPUT` is needed.
+  ///
+  /// Nested tool directives will not be evaluated, but tools may generate other
+  /// directives in their output and those will be evaluated.
+  ///
+  /// Syntax:
+  ///
+  ///     &#123;@tool TOOL [Tool arguments]&#125;
+  ///     Content to send to tool.
+  ///     &#123;@end-tool&#125;
+  ///
+  /// Examples:
+  ///
+  /// In `dart_options.yaml`:
+  ///
+  /// ```yaml
+  /// dartdoc:
+  ///   tools:
+  ///     # Prefixes the given input with "## "
+  ///     # Path is relative to project root.
+  ///     prefix: "bin/prefix.dart"
+  ///     # Prints the date
+  ///     date: "/bin/date"
+  /// ```
+  ///
+  /// In code:
+  ///
+  /// _This:_
+  ///
+  ///     &#123;@tool prefix $INPUT&#125;
+  ///     Content to send to tool.
+  ///     &#123;@end-tool&#125;
+  ///     &#123;@tool date --iso-8601=minutes --utc&#125;
+  ///     &#123;@end-tool&#125;
+  ///
+  /// _Produces:_
+  ///
+  /// ## Content to send to tool.
+  /// 2018-09-18T21:15+00:00
+  Future<String> _evaluateTools(String rawDocs) async {
+    if (config.allowTools) {
+      int invocationIndex = 0;
+      return await _replaceAllMappedAsync(rawDocs, basicToolRegExp,
+          (basicMatch) async {
+        List<String> args = _splitUpQuotedArgs(basicMatch[1]).toList();
+        // Tool name must come first.
+        if (args.isEmpty) {
+          warn(PackageWarning.toolError,
+              message:
+                  'Must specify a tool to execute for the @tool directive.');
+          return Future.value('');
+        }
+        // Count the number of invocations of tools in this dartdoc block,
+        // so that tools can differentiate different blocks from each other.
+        invocationIndex++;
+        return await config.tools.runner.run(
+            args,
+            (String message) async =>
+                warn(PackageWarning.toolError, message: message),
+            content: basicMatch[2],
+            environment: {
+              'SOURCE_LINE': characterLocation?.lineNumber.toString(),
+              'SOURCE_COLUMN': characterLocation?.columnNumber.toString(),
+              'SOURCE_PATH': (sourceFileName == null ||
+                      package?.packagePath == null)
+                  ? null
+                  : path.relative(sourceFileName, from: package.packagePath),
+              'PACKAGE_PATH': package?.packagePath,
+              'PACKAGE_NAME': package?.name,
+              'LIBRARY_NAME': library?.fullyQualifiedName,
+              'ELEMENT_NAME': fullyQualifiedNameWithoutLibrary,
+              'INVOCATION_INDEX': invocationIndex.toString(),
+              'PACKAGE_INVOCATION_INDEX':
+                  (package.toolInvocationIndex++).toString(),
+            }..removeWhere((key, value) => value == null));
+      });
+    } else {
+      return rawDocs;
+    }
+  }
+
+  /// Replace &#123;@youtube ...&#125; in API comments with some HTML to embed
+  /// a YouTube video.
+  ///
+  /// Syntax:
+  ///
+  ///     &#123;@youtube WIDTH HEIGHT URL&#125;
+  ///
+  /// Example:
+  ///
+  ///     &#123;@youtube 560 315 https://www.youtube.com/watch?v=oHg5SJYRHA0&#125;
+  ///
+  /// Which will embed a YouTube player into the page that plays the specified
+  /// video.
+  ///
+  /// The width and height must be positive integers specifying the dimensions
+  /// of the video in pixels. The height and width are used to calculate the
+  /// aspect ratio of the video; the video is always rendered to take up all
+  /// available horizontal space to accommodate different screen sizes on
+  /// desktop and mobile.
+  ///
+  /// The video URL must have the following format:
+  /// https://www.youtube.com/watch?v=oHg5SJYRHA0. This format can usually be
+  /// found in the address bar of the browser when viewing a YouTube video.
+  String _injectYouTube(String rawDocs) {
+    // Matches all youtube directives (even some invalid ones). This is so
+    // we can give good error messages if the directive is malformed, instead of
+    // just silently emitting it as-is.
+    final RegExp basicAnimationRegExp = RegExp(r'''{@youtube\s+([^}]+)}''');
+
+    // Matches YouTube IDs from supported YouTube URLs.
+    final RegExp validYouTubeUrlRegExp =
+        RegExp('https://www\.youtube\.com/watch\\?v=([^&]+)\$');
+
+    return rawDocs.replaceAllMapped(basicAnimationRegExp, (basicMatch) {
+      final ArgParser parser = ArgParser();
+      final ArgResults args = _parseArgs(basicMatch[1], parser, 'youtube');
+      if (args == null) {
+        // Already warned about an invalid parameter if this happens.
+        return '';
+      }
+      final List<String> positionalArgs = args.rest.sublist(0);
+      if (positionalArgs.length != 3) {
+        warn(PackageWarning.invalidParameter,
+            message: 'Invalid @youtube directive, "${basicMatch[0]}"\n'
+                'YouTube directives must be of the form "{@youtube WIDTH '
+                'HEIGHT URL}"');
+        return '';
+      }
+
+      final int width = int.tryParse(positionalArgs[0]);
+      if (width == null || width <= 0) {
+        warn(PackageWarning.invalidParameter,
+            message: 'A @youtube directive has an invalid width, '
+                '"${positionalArgs[0]}". The width must be a positive integer.');
+      }
+
+      final int height = int.tryParse(positionalArgs[1]);
+      if (height == null || height <= 0) {
+        warn(PackageWarning.invalidParameter,
+            message: 'A @youtube directive has an invalid height, '
+                '"${positionalArgs[1]}". The height must be a positive integer.');
+      }
+
+      final Match url = validYouTubeUrlRegExp.firstMatch(positionalArgs[2]);
+      if (url == null) {
+        warn(PackageWarning.invalidParameter,
+            message: 'A @youtube directive has an invalid URL: '
+                '"${positionalArgs[2]}". Supported YouTube URLs have the '
+                'follwing format: https://www.youtube.com/watch?v=oHg5SJYRHA0.');
+        return '';
+      }
+      final String youTubeId = url.group(url.groupCount);
+      final String aspectRatio = (height / width * 100).toStringAsFixed(2);
+
+      // Blank lines before and after, and no indenting at the beginning and end
+      // is needed so that Markdown doesn't confuse this with code, so be
+      // careful of whitespace here.
+      return '''
+
+<p style="position: relative;
+          padding-top: $aspectRatio%;">
+  <iframe src="https://www.youtube.com/embed/$youTubeId?rel=0"
+          frameborder="0"
+          allow="accelerometer;
+                 autoplay;
+                 encrypted-media;
+                 gyroscope;
+                 picture-in-picture"
+          allowfullscreen
+          style="position: absolute;
+                 top: 0;
+                 left: 0;
+                 width: 100%;
+                 height: 100%;">
+  </iframe>
+</p>
+
+'''; // String must end at beginning of line, or following inline text will be
+      // indented.
+    });
+  }
+
+  /// Replace &#123;@animation ...&#125; in API comments with some HTML to manage an
+  /// MPEG 4 video as an animation.
+  ///
+  /// Syntax:
+  ///
+  ///     &#123;@animation WIDTH HEIGHT URL [id=ID]&#125;
+  ///
+  /// Example:
+  ///
+  ///     &#123;@animation 300 300 https://example.com/path/to/video.mp4 id="my_video"&#125;
+  ///
+  /// Which will render the HTML necessary for embedding a simple click-to-play
+  /// HTML5 video player with no controls that has an HTML id of "my_video".
+  ///
+  /// The optional ID should be a unique id that is a valid JavaScript
+  /// identifier, and will be used as the id for the video tag. If no ID is
+  /// supplied, then a unique identifier (starting with "animation_") will be
+  /// generated.
+  ///
+  /// The width and height must be integers specifying the dimensions of the
+  /// video file in pixels.
+  String _injectAnimations(String rawDocs) {
+    // Matches all animation directives (even some invalid ones). This is so
+    // we can give good error messages if the directive is malformed, instead of
+    // just silently emitting it as-is.
+    final RegExp basicAnimationRegExp = RegExp(r'''{@animation\s+([^}]+)}''');
+
+    // Matches valid javascript identifiers.
+    final RegExp validIdRegExp = RegExp(r'^[a-zA-Z_]\w*$');
+
+    // Make sure we have a set to keep track of used IDs for this href.
+    package.usedAnimationIdsByHref[href] ??= {};
+
+    String getUniqueId(String base) {
+      int animationIdCount = 1;
+      String id = '$base$animationIdCount';
+      // We check for duplicate IDs so that we make sure not to collide with
+      // user-supplied ids on the same page.
+      while (package.usedAnimationIdsByHref[href].contains(id)) {
+        animationIdCount++;
+        id = '$base$animationIdCount';
+      }
+      return id;
+    }
+
+    return rawDocs.replaceAllMapped(basicAnimationRegExp, (basicMatch) {
+      final ArgParser parser = ArgParser();
+      parser.addOption('id');
+      final ArgResults args = _parseArgs(basicMatch[1], parser, 'animation');
+      if (args == null) {
+        // Already warned about an invalid parameter if this happens.
+        return '';
+      }
+      final List<String> positionalArgs = args.rest.sublist(0);
+      String uniqueId;
+      bool wasDeprecated = false;
+      if (positionalArgs.length == 4) {
+        // Supports the original form of the animation tag for backward
+        // compatibility.
+        uniqueId = positionalArgs.removeAt(0);
+        wasDeprecated = true;
+      } else if (positionalArgs.length == 3) {
+        uniqueId = args['id'] ?? getUniqueId('animation_');
+      } else {
+        warn(PackageWarning.invalidParameter,
+            message: 'Invalid @animation directive, "${basicMatch[0]}"\n'
+                'Animation directives must be of the form "{@animation WIDTH '
+                'HEIGHT URL [id=ID]}"');
+        return '';
+      }
+
+      if (!validIdRegExp.hasMatch(uniqueId)) {
+        warn(PackageWarning.invalidParameter,
+            message: 'An animation has an invalid identifier, "$uniqueId". The '
+                'identifier can only contain letters, numbers and underscores, '
+                'and must not begin with a number.');
+        return '';
+      }
+      if (package.usedAnimationIdsByHref[href].contains(uniqueId)) {
+        warn(PackageWarning.invalidParameter,
+            message: 'An animation has a non-unique identifier, "$uniqueId". '
+                'Animation identifiers must be unique.');
+        return '';
+      }
+      package.usedAnimationIdsByHref[href].add(uniqueId);
+
+      int width;
+      try {
+        width = int.parse(positionalArgs[0]);
+      } on FormatException {
+        warn(PackageWarning.invalidParameter,
+            message: 'An animation has an invalid width ($uniqueId), '
+                '"${positionalArgs[0]}". The width must be an integer.');
+        return '';
+      }
+
+      int height;
+      try {
+        height = int.parse(positionalArgs[1]);
+      } on FormatException {
+        warn(PackageWarning.invalidParameter,
+            message: 'An animation has an invalid height ($uniqueId), '
+                '"${positionalArgs[1]}". The height must be an integer.');
+        return '';
+      }
+
+      Uri movieUrl;
+      try {
+        movieUrl = Uri.parse(positionalArgs[2]);
+      } on FormatException catch (e) {
+        warn(PackageWarning.invalidParameter,
+            message: 'An animation URL could not be parsed ($uniqueId): '
+                '${positionalArgs[2]}\n$e');
+        return '';
+      }
+      final String overlayId = '${uniqueId}_play_button_';
+
+      // Only warn about deprecation if some other warning didn't occur.
+      if (wasDeprecated) {
+        warn(PackageWarning.deprecated,
+            message:
+                'Deprecated form of @animation directive, "${basicMatch[0]}"\n'
+                'Animation directives are now of the form "{@animation '
+                'WIDTH HEIGHT URL [id=ID]}" (id is an optional '
+                'parameter)');
+      }
+
+      // Blank lines before and after, and no indenting at the beginning and end
+      // is needed so that Markdown doesn't confuse this with code, so be
+      // careful of whitespace here.
+      return '''
+
+<div style="position: relative;">
+  <div id="${overlayId}"
+       onclick="var $uniqueId = document.getElementById('$uniqueId');
+                if ($uniqueId.paused) {
+                  $uniqueId.play();
+                  this.style.display = 'none';
+                } else {
+                  $uniqueId.pause();
+                  this.style.display = 'block';
+                }"
+       style="position:absolute;
+              width:${width}px;
+              height:${height}px;
+              z-index:100000;
+              background-position: center;
+              background-repeat: no-repeat;
+              background-image: url(static-assets/play_button.svg);">
+  </div>
+  <video id="$uniqueId"
+         style="width:${width}px; height:${height}px;"
+         onclick="var $overlayId = document.getElementById('$overlayId');
+                  if (this.paused) {
+                    this.play();
+                    $overlayId.style.display = 'none';
+                  } else {
+                    this.pause();
+                    $overlayId.style.display = 'block';
+                  }" loop>
+    <source src="$movieUrl" type="video/mp4"/>
+  </video>
+</div>
+
+'''; // String must end at beginning of line, or following inline text will be
+      // indented.
+    });
+  }
+
+  /// Replace &lt;<dartdoc-html>[digest]</dartdoc-html>&gt; in API comments with
+  /// the contents of the HTML fragment earlier defined by the
+  /// &#123;@inject-html&#125; directive. The [digest] is a SHA1 of the contents
+  /// of the HTML fragment, automatically generated upon parsing the
+  /// &#123;@inject-html&#125; directive.
+  ///
+  /// This markup is generated and inserted by [_stripHtmlAndAddToIndex] when it
+  /// removes the HTML fragment in preparation for markdown processing. It isn't
+  /// meant to be used at a user level.
+  ///
+  /// Example:
+  ///
+  /// You place the fragment in a dartdoc comment:
+  ///
+  ///     Some comments
+  ///     &#123;@inject-html&#125;
+  ///     &lt;p&gt;[HTML contents!]&lt;/p&gt;
+  ///     &#123;@endtemplate&#125;
+  ///     More comments
+  ///
+  /// and [_stripHtmlAndAddToIndex] will replace your HTML fragment with this:
+  ///
+  ///     Some comments
+  ///     &lt;dartdoc-html&gt;4cc02f877240bf69855b4c7291aba8a16e5acce0&lt;/dartdoc-html&gt;
+  ///     More comments
+  ///
+  /// Which will render in the final HTML output as:
+  ///
+  ///     Some comments
+  ///     &lt;p&gt;[HTML contents!]&lt;/p&gt;
+  ///     More comments
+  ///
+  /// And the HTML fragment will not have been processed or changed by Markdown,
+  /// but just injected verbatim.
+  String _injectHtmlFragments(String rawDocs) {
+    if (!config.injectHtml) return rawDocs;
+
+    return rawDocs.replaceAllMapped(htmlInjectRegExp, (match) {
+      String fragment = packageGraph.getHtmlFragment(match[1]);
+      if (fragment == null) {
+        warn(PackageWarning.unknownHtmlFragment, message: match[1]);
+      }
+      return fragment;
+    });
+  }
+
+  /// Replace &#123;@macro ...&#125; in API comments with the contents of the macro
+  ///
+  /// Syntax:
+  ///
+  ///     &#123;@macro NAME&#125;
+  ///
+  /// Example:
+  ///
+  /// You define the template in any comment for a documentable entity like:
+  ///
+  ///     &#123;@template foo&#125;
+  ///     Foo contents!
+  ///     &#123;@endtemplate&#125;
+  ///
+  /// and them somewhere use it like this:
+  ///
+  ///     Some comments
+  ///     &#123;@macro foo&#125;
+  ///     More comments
+  ///
+  /// Which will render
+  ///
+  ///     Some comments
+  ///     Foo contents!
+  ///     More comments
+  ///
+  String _injectMacros(String rawDocs) {
+    return rawDocs.replaceAllMapped(macroRegExp, (match) {
+      String macro = packageGraph.getMacro(match[1]);
+      if (macro == null) {
+        warn(PackageWarning.unknownMacro, message: match[1]);
+      }
+      return macro;
+    });
+  }
+
+  /// Parse and remove &#123;@template ...&#125; in API comments and store them
+  /// in the index on the package.
+  ///
+  /// Syntax:
+  ///
+  ///     &#123;@template NAME&#125;
+  ///     The contents of the macro
+  ///     &#123;@endtemplate&#125;
+  ///
+  String _stripMacroTemplatesAndAddToIndex(String rawDocs) {
+    return rawDocs.replaceAllMapped(templateRegExp, (match) {
+      packageGraph.addMacro(match[1].trim(), match[2].trim());
+      return "{@macro ${match[1].trim()}}";
+    });
+  }
+
+  /// Parse and remove &#123;@inject-html ...&#125; in API comments and store
+  /// them in the index on the package, replacing them with a SHA1 hash of the
+  /// contents, where the HTML will be re-injected after Markdown processing of
+  /// the rest of the text is complete.
+  ///
+  /// Syntax:
+  ///
+  ///     &#123;@inject-html&#125;
+  ///     <p>The HTML to inject.</p>
+  ///     &#123;@end-inject-html&#125;
+  ///
+  String _stripHtmlAndAddToIndex(String rawDocs) {
+    if (!config.injectHtml) return rawDocs;
+    return rawDocs.replaceAllMapped(htmlRegExp, (match) {
+      String fragment = match[1];
+      String digest = sha1.convert(fragment.codeUnits).toString();
+      packageGraph.addHtmlFragment(digest, fragment);
+      // The newlines are so that Markdown will pass this through without
+      // touching it.
+      return '\n<dartdoc-html>$digest</dartdoc-html>\n';
+    });
+  }
+
+  /// Helper to process arguments given as a (possibly quoted) string.
+  ///
+  /// First, this will split the given [argsAsString] into separate arguments,
+  /// taking any quoting (either ' or " are accepted) into account, including
+  /// handling backslash-escaped quotes.
+  ///
+  /// Then, it will prepend "--" to any args that start with an identifier
+  /// followed by an equals sign, allowing the argument parser to treat any
+  /// "foo=bar" argument as "--foo=bar". It does handle quoted args like
+  /// "foo='bar baz'" too, returning just bar (without quotes) for the foo
+  /// value.
+  Iterable<String> _splitUpQuotedArgs(String argsAsString,
+      {bool convertToArgs = false}) {
+    final Iterable<Match> matches = argMatcher.allMatches(argsAsString);
+    // Remove quotes around args, and if convertToArgs is true, then for any
+    // args that look like assignments (start with valid option names followed
+    // by an equals sign), add a "--" in front so that they parse as options.
+    return matches.map<String>((Match match) {
+      var option = '';
+      if (convertToArgs && match[1] != null && !match[1].startsWith('-')) {
+        option = '--';
+      }
+      if (match[2] != null) {
+        // This arg has quotes, so strip them.
+        return '$option${match[1] ?? ''}${match[3] ?? ''}${match[4] ?? ''}';
+      }
+      return '$option${match[0]}';
+    });
+  }
+
+  /// Helper to process arguments given as a (possibly quoted) string.
+  ///
+  /// First, this will split the given [argsAsString] into separate arguments
+  /// with [_splitUpQuotedArgs] it then parses the resulting argument list
+  /// normally with [argParser] and returns the result.
+  ArgResults _parseArgs(
+      String argsAsString, ArgParser argParser, String directiveName) {
+    var args = _splitUpQuotedArgs(argsAsString, convertToArgs: true);
+    try {
+      return argParser.parse(args);
+    } on ArgParserException catch (e) {
+      warn(PackageWarning.invalidParameter,
+          message: 'The {@$directiveName ...} directive was called with '
+              'invalid parameters. $e');
+      return null;
+    }
+  }
+
+  /// Helper for _injectExamples used to process @example arguments.
+  /// Returns a map of arguments. The first unnamed argument will have key 'src'.
+  /// The computed file path, constructed from 'src' and 'region' will have key
+  /// 'file'.
+  Map<String, String> _getExampleArgs(String argsAsString) {
+    ArgParser parser = ArgParser();
+    parser.addOption('lang');
+    parser.addOption('region');
+    ArgResults results = _parseArgs(argsAsString, parser, 'example');
+    if (results == null) {
+      return null;
+    }
+
+    // Extract PATH and fix the path separators.
+    final String src = results.rest.isEmpty
+        ? ''
+        : results.rest.first.replaceAll('/', Platform.pathSeparator);
+    final Map<String, String> args = <String, String>{
+      'src': src,
+      'lang': results['lang'],
+      'region': results['region'] ?? '',
+    };
+
+    // Compute 'file' from region and src.
+    final fragExtension = '.md';
+    var file = src + fragExtension;
+    var region = args['region'] ?? '';
+    if (region.isNotEmpty) {
+      var dir = path.dirname(src);
+      var basename = path.basenameWithoutExtension(src);
+      var ext = path.extension(src);
+      file = path.join(dir, '$basename-$region$ext$fragExtension');
+    }
+    args['file'] = config.examplePathPrefix == null
+        ? file
+        : path.join(config.examplePathPrefix, file);
+    return args;
+  }
+}
diff --git a/lib/src/model/model_function.dart b/lib/src/model/model_function.dart
new file mode 100644
index 0000000..3da5a20
--- /dev/null
+++ b/lib/src/model/model_function.dart
@@ -0,0 +1,119 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+/// A [ModelElement] for a [FunctionElement] that isn't part of a type definition.
+class ModelFunction extends ModelFunctionTyped with Categorization {
+  ModelFunction(
+      FunctionElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph);
+
+  @override
+  bool get isStatic {
+    return _func.isStatic;
+  }
+
+  @override
+  String get name => element.name ?? '';
+
+  @override
+  FunctionElement get _func => (element as FunctionElement);
+}
+
+/// A [ModelElement] for a [FunctionTypedElement] that is an
+/// explicit typedef.
+///
+/// Distinct from ModelFunctionTypedef in that it doesn't
+/// have a name, but we document it as "Function" to match how these are
+/// written in declarations.
+class ModelFunctionAnonymous extends ModelFunctionTyped {
+  ModelFunctionAnonymous(
+      FunctionTypedElement element, PackageGraph packageGraph)
+      : super(element, null, packageGraph);
+
+  @override
+  ModelElement get enclosingElement {
+    // These are not considered to be a part of libraries, so we can simply
+    // blindly instantiate a ModelElement for their enclosing element.
+    return ModelElement.fromElement(element.enclosingElement, packageGraph);
+  }
+
+  @override
+  String get name => 'Function';
+
+  @override
+  String get linkedName => 'Function';
+
+  @override
+  bool get isPublic => false;
+}
+
+/// A [ModelElement] for a [FunctionTypedElement] that is part of an
+/// explicit typedef.
+class ModelFunctionTypedef extends ModelFunctionTyped {
+  ModelFunctionTypedef(
+      FunctionTypedElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph);
+
+  @override
+  String get name {
+    Element e = element;
+    while (e != null) {
+      if (e is FunctionTypeAliasElement || e is GenericTypeAliasElement) {
+        return e.name;
+      }
+      e = e.enclosingElement;
+    }
+    assert(false);
+    return super.name;
+  }
+}
+
+class ModelFunctionTyped extends ModelElement
+    with TypeParameters
+    implements EnclosedElement {
+  @override
+  List<TypeParameter> typeParameters = [];
+
+  ModelFunctionTyped(
+      FunctionTypedElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph, null) {
+    _calcTypeParameters();
+  }
+
+  void _calcTypeParameters() {
+    typeParameters = _func.typeParameters.map((f) {
+      return ModelElement.from(f, library, packageGraph) as TypeParameter;
+    }).toList();
+  }
+
+  @override
+  ModelElement get enclosingElement => library;
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${library.dirName}/$fileName';
+  }
+
+  @override
+  String get kind => 'function';
+
+  String get linkedReturnType => modelType.createLinkedReturnTypeName();
+
+  // Food for mustache. TODO(jcollins-g): what about enclosing elements?
+  bool get isInherited => false;
+
+  @override
+  DefinedElementType get modelType => super.modelType;
+
+  FunctionTypedElement get _func => (element as FunctionTypedElement);
+}
diff --git a/lib/src/model/model_node.dart b/lib/src/model/model_node.dart
new file mode 100644
index 0000000..ff488f9
--- /dev/null
+++ b/lib/src/model/model_node.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:convert';
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/model_utils.dart' as model_utils;
+
+/// A stripped down [CommentReference] containing only that information needed
+/// for Dartdoc.  Drops link to the [CommentReference] after construction.
+class ModelCommentReference {
+  final String name;
+  final Element staticElement;
+
+  ModelCommentReference(CommentReference ref)
+      : name = ref.identifier.name,
+        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 Element element;
+
+  final int _sourceOffset;
+  final int _sourceEnd;
+
+  ModelNode(AstNode sourceNode, this.element)
+      : _sourceOffset = sourceNode?.offset,
+        _sourceEnd = sourceNode?.end,
+        commentRefs = _commentRefsFor(sourceNode);
+
+  static List<ModelCommentReference> _commentRefsFor(AstNode node) {
+    if (node is AnnotatedNode &&
+        node?.documentationComment?.references != null) {
+      return node.documentationComment.references
+          .map((c) => ModelCommentReference(c))
+          .toList(growable: false);
+    }
+    return null;
+  }
+
+  String _sourceCode;
+
+  String get sourceCode {
+    if (_sourceCode == null) {
+      if (_sourceOffset != null) {
+        String contents = model_utils.getFileContentsFor(element);
+        // Find the start of the line, so that we can line up all the indents.
+        int i = _sourceOffset;
+        while (i > 0) {
+          i -= 1;
+          if (contents[i] == '\n' || contents[i] == '\r') {
+            i += 1;
+            break;
+          }
+        }
+
+        // Trim the common indent from the source snippet.
+        var start = _sourceOffset - (_sourceOffset - i);
+        String source = contents.substring(start, _sourceEnd);
+
+        source = const HtmlEscape().convert(source);
+        source = model_utils.stripIndentFromSource(source);
+        source = model_utils.stripDartdocCommentsFromSource(source);
+
+        _sourceCode = source.trim();
+      } else {
+        _sourceCode = '';
+      }
+    }
+    return _sourceCode;
+  }
+}
diff --git a/lib/src/model/nameable.dart b/lib/src/model/nameable.dart
new file mode 100644
index 0000000..cd73e15
--- /dev/null
+++ b/lib/src/model/nameable.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2019, 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.
+
+import 'package:collection/collection.dart';
+
+import 'locatable.dart';
+
+/// Something that has a name.
+abstract class Nameable {
+  String get name;
+
+  String get fullyQualifiedName => name;
+
+  Set<String> _namePieces;
+
+  Set<String> get namePieces {
+    if (_namePieces == null) {
+      _namePieces = Set()
+        ..addAll(name.split(locationSplitter).where((s) => s.isNotEmpty));
+    }
+    return _namePieces;
+  }
+
+  String _namePart;
+
+  /// Utility getter/cache for [_MarkdownCommentReference._getResultsForClass].
+  String get namePart {
+    // TODO(jcollins-g): This should really be the same as 'name', but isn't
+    // because of accessors and operators.
+    if (_namePart == null) {
+      _namePart = fullyQualifiedName.split('.').last;
+    }
+    return _namePart;
+  }
+
+  @override
+  String toString() => name;
+}
+
+int byName(Nameable a, Nameable b) =>
+    compareAsciiLowerCaseNatural(a.name, b.name);
diff --git a/lib/src/model/operator.dart b/lib/src/model/operator.dart
new file mode 100644
index 0000000..ee10f01
--- /dev/null
+++ b/lib/src/model/operator.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/member.dart' show Member;
+import 'package:dartdoc/src/model/model.dart';
+
+class Operator extends Method {
+  static const Map<String, String> friendlyNames = {
+    "[]": "get",
+    "[]=": "put",
+    "~": "bitwise_negate",
+    "==": "equals",
+    "-": "minus",
+    "+": "plus",
+    "*": "multiply",
+    "/": "divide",
+    "<": "less",
+    ">": "greater",
+    ">=": "greater_equal",
+    "<=": "less_equal",
+    "<<": "shift_left",
+    ">>": "shift_right",
+    "^": "bitwise_exclusive_or",
+    "unary-": "unary_minus",
+    "|": "bitwise_or",
+    "&": "bitwise_and",
+    "~/": "truncate_divide",
+    "%": "modulo"
+  };
+
+  Operator(MethodElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph);
+
+  Operator.inherited(MethodElement element, Class enclosingClass,
+      Library library, PackageGraph packageGraph, {Member originalMember})
+      : super.inherited(element, enclosingClass, library, packageGraph,
+            originalMember: originalMember);
+
+  @override
+  String get fileName {
+    var actualName = super.name;
+    if (friendlyNames.containsKey(actualName)) {
+      return "operator_${friendlyNames[actualName]}.html";
+    } else {
+      return '$actualName.html';
+    }
+  }
+
+  @override
+  String get fullyQualifiedName =>
+      '${library.name}.${enclosingElement.name}.${super.name}';
+
+  @override
+  bool get isOperator => true;
+
+  @override
+  String get name {
+    return 'operator ${super.name}';
+  }
+}
diff --git a/lib/src/model/package.dart b/lib/src/model/package.dart
new file mode 100644
index 0000000..4cc18d6
--- /dev/null
+++ b/lib/src/model/package.dart
@@ -0,0 +1,326 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:io';
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/markdown_processor.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/package_meta.dart';
+import 'package:dartdoc/src/warnings.dart';
+import 'package:path/path.dart' as path;
+import 'package:pub_semver/pub_semver.dart';
+
+final RegExp substituteNameVersion = RegExp(r'%([bnv])%');
+
+/// A [LibraryContainer] that contains [Library] objects related to a particular
+/// package.
+class Package extends LibraryContainer
+    with Nameable, Locatable, Canonicalization, Warnable
+    implements Privacy, Documentable {
+  String _name;
+  PackageGraph _packageGraph;
+
+  final Map<String, Category> _nameToCategory = {};
+
+  // Creates a package, if necessary, and adds it to the [packageGraph].
+  factory Package.fromPackageMeta(
+      PackageMeta packageMeta, PackageGraph packageGraph) {
+    String packageName = packageMeta.name;
+
+    bool expectNonLocal = false;
+
+    if (!packageGraph.packageMap.containsKey(packageName) &&
+        packageGraph.allLibrariesAdded) expectNonLocal = true;
+    packageGraph.packageMap.putIfAbsent(
+        packageName, () => 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
+    // before allLibrariesAdded is true.
+    assert(
+        !(expectNonLocal &&
+            packageGraph.packageMap[packageName].documentedWhere ==
+                DocumentLocation.local),
+        'Found more libraries to document after allLibrariesAdded was set to true');
+    return packageGraph.packageMap[packageName];
+  }
+
+  Package._(this._name, this._packageGraph, this._packageMeta);
+
+  @override
+  bool get isCanonical => true;
+
+  @override
+  Library get canonicalLibrary => null;
+
+  /// Number of times we have invoked a tool for this package.
+  int toolInvocationIndex = 0;
+
+  // The animation IDs that have already been used, indexed by the [href] of the
+  // object that contains them.
+  Map<String, Set<String>> usedAnimationIdsByHref = {};
+
+  /// Pieces of the location, split to remove 'package:' and slashes.
+  @override
+  Set<String> get locationPieces => Set();
+
+  /// Holds all libraries added to this package.  May include non-documented
+  /// libraries, but is not guaranteed to include a complete list of
+  /// non-documented libraries unless they are all referenced by documented ones.
+  /// Not sorted.
+  final Set<Library> allLibraries = Set();
+
+  bool get hasHomepage =>
+      packageMeta.homepage != null && packageMeta.homepage.isNotEmpty;
+
+  String get homepage => packageMeta.homepage;
+
+  String get kind => (isSdk) ? 'SDK' : 'package';
+
+  @override
+  List<Locatable> get documentationFrom => [this];
+
+  /// Return true if the code has defined non-default categories for libraries
+  /// in this package.
+  bool get hasCategories => categories.isNotEmpty;
+
+  LibraryContainer get defaultCategory => nameToCategory[null];
+
+  String _documentationAsHtml;
+
+  @override
+  String get documentationAsHtml {
+    if (_documentationAsHtml != null) return _documentationAsHtml;
+    _documentationAsHtml = Documentation.forElement(this).asHtml;
+
+    return _documentationAsHtml;
+  }
+
+  @override
+  String get documentation {
+    return hasDocumentationFile ? documentationFile.contents : null;
+  }
+
+  @override
+  bool get hasDocumentation =>
+      documentationFile != null && documentationFile.contents.isNotEmpty;
+
+  @override
+  bool get hasExtendedDocumentation => documentation.isNotEmpty;
+
+  // TODO: Clients should use [documentationFile] so they can act differently on
+  // plain text or markdown.
+  bool get hasDocumentationFile => documentationFile != null;
+
+  FileContents get documentationFile => packageMeta.getReadmeContents();
+
+  @override
+  String get oneLineDoc => '';
+
+  @override
+  bool get isDocumented =>
+      isFirstPackage || documentedWhere != DocumentLocation.missing;
+
+  @override
+  Warnable get enclosingElement => null;
+
+  bool _isPublic;
+
+  @override
+  bool get isPublic {
+    if (_isPublic == null) _isPublic = libraries.any((l) => l.isPublic);
+    return _isPublic;
+  }
+
+  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) {
+      if (isPublic) {
+        return DocumentLocation.local;
+      } else {
+        // Possible if excludes result in a "documented" package not having
+        // any actual documentation.
+        return DocumentLocation.missing;
+      }
+    } else {
+      if (config.linkToRemote && config.linkToUrl.isNotEmpty && isPublic) {
+        return DocumentLocation.remote;
+      } else {
+        return DocumentLocation.missing;
+      }
+    }
+  }
+
+  @override
+  String get enclosingName => packageGraph.defaultPackageName;
+
+  @override
+  String get fullyQualifiedName => 'package:$name';
+
+  String _baseHref;
+
+  String get baseHref {
+    if (_baseHref == null) {
+      if (documentedWhere == DocumentLocation.remote) {
+        _baseHref =
+            config.linkToUrl.replaceAllMapped(substituteNameVersion, (m) {
+          switch (m.group(1)) {
+            // Return the prerelease tag of the release if a prerelease,
+            // or 'stable' otherwise. Mostly coded around
+            // the Dart SDK's use of dev/stable, but theoretically applicable
+            // elsewhere.
+            case 'b':
+              {
+                Version version = Version.parse(packageMeta.version);
+                return version.isPreRelease
+                    ? version.preRelease.first
+                    : 'stable';
+              }
+            case 'n':
+              return name;
+            // The full version string of the package.
+            case 'v':
+              return packageMeta.version;
+            default:
+              assert(false, 'Unsupported case: ${m.group(1)}');
+              return null;
+          }
+        });
+        if (!_baseHref.endsWith('/')) _baseHref = '${_baseHref}/';
+      } else {
+        _baseHref = '';
+      }
+    }
+    return _baseHref;
+  }
+
+  @override
+  String get href => '${baseHref}index.html';
+
+  @override
+  String get location => path.toUri(packageMeta.resolvedDir).toString();
+
+  @override
+  String get name => _name;
+
+  @override
+  Package get package => this;
+
+  @override
+  PackageGraph get packageGraph => _packageGraph;
+
+  // Workaround for mustache4dart issue where templates do not recognize
+  // inherited properties as being in-context.
+  @override
+  Iterable<Library> get publicLibraries {
+    assert(libraries.every((l) => l.packageMeta == _packageMeta));
+    return super.publicLibraries;
+  }
+
+  /// A map of category name to the category itself.
+  Map<String, Category> get nameToCategory {
+    if (_nameToCategory.isEmpty) {
+      Category categoryFor(String category) {
+        _nameToCategory.putIfAbsent(
+            category, () => Category(category, this, config));
+        return _nameToCategory[category];
+      }
+
+      _nameToCategory[null] = Category(null, this, config);
+      for (Categorization c in libraries.expand(
+          (l) => l.allCanonicalModelElements.whereType<Categorization>())) {
+        for (String category in c.categoryNames) {
+          categoryFor(category).addItem(c);
+        }
+      }
+    }
+    return _nameToCategory;
+  }
+
+  List<Category> _categories;
+
+  List<Category> get categories {
+    if (_categories == null) {
+      _categories = nameToCategory.values.where((c) => c.name != null).toList()
+        ..sort();
+    }
+    return _categories;
+  }
+
+  Iterable<LibraryContainer> get categoriesWithPublicLibraries =>
+      categories.where((c) => c.publicLibraries.isNotEmpty);
+
+  Iterable<Category> get documentedCategories =>
+      categories.where((c) => c.isDocumented);
+
+  bool get hasDocumentedCategories => documentedCategories.isNotEmpty;
+
+  DartdocOptionContext _config;
+
+  @override
+  DartdocOptionContext get config {
+    if (_config == null) {
+      _config = DartdocOptionContext.fromContext(
+          packageGraph.config, Directory(packagePath));
+    }
+    return _config;
+  }
+
+  /// Is this the package at the top of the list?  We display the first
+  /// package specially (with "Libraries" rather than the package name).
+  bool get isFirstPackage =>
+      packageGraph.localPackages.isNotEmpty &&
+      identical(packageGraph.localPackages.first, this);
+
+  @override
+  bool get isSdk => packageMeta.isSdk;
+
+  String _packagePath;
+
+  String get packagePath {
+    if (_packagePath == null) {
+      _packagePath = path.canonicalize(packageMeta.dir.path);
+    }
+    return _packagePath;
+  }
+
+  String get version => packageMeta.version ?? '0.0.0-unknown';
+
+  @override
+  void warn(PackageWarning kind,
+      {String message,
+      Iterable<Locatable> referredFrom,
+      Iterable<String> extendedDebug}) {
+    packageGraph.warnOnElement(this, kind,
+        message: message,
+        referredFrom: referredFrom,
+        extendedDebug: extendedDebug);
+  }
+
+  final PackageMeta _packageMeta;
+
+  PackageMeta get packageMeta => _packageMeta;
+
+  @override
+  Element get element => null;
+
+  @override
+  List<String> get containerOrder => config.packageOrder;
+}
diff --git a/lib/src/model/package_builder.dart b/lib/src/model/package_builder.dart
new file mode 100644
index 0000000..02c6048
--- /dev/null
+++ b/lib/src/model/package_builder.dart
@@ -0,0 +1,462 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/file_system/file_system.dart' as file_system;
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/src/context/builder.dart';
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/dart/analysis/performance_logger.dart';
+import 'package:analyzer/src/dart/sdk/sdk.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/java_io.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/generated/source_io.dart';
+import 'package:analyzer/src/source/package_map_resolver.dart';
+import 'package:analyzer/src/source/sdk_ext.dart';
+import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/io_utils.dart';
+import 'package:dartdoc/src/logging.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/package_meta.dart' show PackageMeta;
+import 'package:dartdoc/src/special_elements.dart';
+import 'package:package_config/discovery.dart' as package_config;
+import 'package:path/path.dart' as path;
+import 'package:quiver/iterables.dart' as quiver;
+
+/// Everything you need to instantiate a PackageGraph object for documenting.
+class PackageBuilder {
+  final DartdocOptionContext config;
+
+  PackageBuilder(this.config);
+
+  Future<PackageGraph> buildPackageGraph() async {
+    if (config.topLevelPackageMeta.needsPubGet) {
+      config.topLevelPackageMeta.runPubGet();
+    }
+
+    PackageGraph newGraph = PackageGraph.UninitializedPackageGraph(
+        config,
+        driver,
+        await driver.currentSession.typeSystem,
+        sdk,
+        hasEmbedderSdkFiles);
+    await getLibraries(newGraph);
+    await newGraph.initializePackageGraph();
+    return newGraph;
+  }
+
+  DartSdk _sdk;
+
+  DartSdk get sdk {
+    if (_sdk == null) {
+      _sdk = FolderBasedDartSdk(PhysicalResourceProvider.INSTANCE,
+          PhysicalResourceProvider.INSTANCE.getFolder(config.sdkDir));
+    }
+    return _sdk;
+  }
+
+  EmbedderSdk _embedderSdk;
+
+  EmbedderSdk get embedderSdk {
+    if (_embedderSdk == null && !config.topLevelPackageMeta.isSdk) {
+      _embedderSdk = EmbedderSdk(PhysicalResourceProvider.INSTANCE,
+          EmbedderYamlLocator(packageMap).embedderYamls);
+    }
+    return _embedderSdk;
+  }
+
+  static Map<String, List<file_system.Folder>> _calculatePackageMap(
+      file_system.Folder dir) {
+    Map<String, List<file_system.Folder>> map = Map();
+    var info = package_config.findPackagesFromFile(dir.toUri());
+
+    for (String name in info.packages) {
+      Uri uri = info.asMap()[name];
+      String packagePath = path.normalize(path.fromUri(uri));
+      file_system.Resource resource =
+          PhysicalResourceProvider.INSTANCE.getResource(packagePath);
+      if (resource is file_system.Folder) {
+        map[name] = [resource];
+      }
+    }
+
+    return map;
+  }
+
+  Map<String, List<file_system.Folder>> _packageMap;
+
+  Map<String, List<file_system.Folder>> get packageMap {
+    if (_packageMap == null) {
+      file_system.Folder cwd =
+          PhysicalResourceProvider.INSTANCE.getResource(config.inputDir);
+      _packageMap = _calculatePackageMap(cwd);
+    }
+    return _packageMap;
+  }
+
+  DartUriResolver _embedderResolver;
+
+  DartUriResolver get embedderResolver {
+    if (_embedderResolver == null) {
+      _embedderResolver = DartUriResolver(embedderSdk);
+    }
+    return _embedderResolver;
+  }
+
+  SourceFactory get sourceFactory {
+    List<UriResolver> resolvers = [];
+    resolvers.add(SdkExtUriResolver(packageMap));
+    final UriResolver packageResolver =
+        PackageMapUriResolver(PhysicalResourceProvider.INSTANCE, packageMap);
+    UriResolver sdkResolver;
+    if (embedderSdk == null || embedderSdk.urlMappings.isEmpty) {
+      // The embedder uri resolver has no mappings. Use the default Dart SDK
+      // uri resolver.
+      sdkResolver = DartUriResolver(sdk);
+    } else {
+      // The embedder uri resolver has mappings, use it instead of the default
+      // Dart SDK uri resolver.
+      sdkResolver = embedderResolver;
+    }
+
+    /// [AnalysisDriver] seems to require package resolvers that
+    /// never resolve to embedded SDK files, and the resolvers list must still
+    /// contain a DartUriResolver.  This hack won't be necessary once analyzer
+    /// has a clean public API.
+    resolvers.add(PackageWithoutSdkResolver(packageResolver, sdkResolver));
+    resolvers.add(sdkResolver);
+    resolvers.add(
+        file_system.ResourceUriResolver(PhysicalResourceProvider.INSTANCE));
+
+    assert(
+        resolvers.any((UriResolver resolver) => resolver is DartUriResolver));
+    SourceFactory sourceFactory = SourceFactory(resolvers);
+    return sourceFactory;
+  }
+
+  AnalysisDriver _driver;
+
+  AnalysisDriver get driver {
+    if (_driver == null) {
+      PerformanceLog log = PerformanceLog(null);
+      AnalysisDriverScheduler scheduler = AnalysisDriverScheduler(log);
+      AnalysisOptionsImpl options = AnalysisOptionsImpl();
+
+      // TODO(jcollins-g): pass in an ExperimentStatus instead?
+      options.enabledExperiments = config.enableExperiment
+        ..add('extension-methods');
+
+      // TODO(jcollins-g): Make use of currently not existing API for managing
+      //                   many AnalysisDrivers
+      // TODO(jcollins-g): make use of DartProject isApi()
+      _driver = AnalysisDriver(
+          scheduler,
+          log,
+          PhysicalResourceProvider.INSTANCE,
+          MemoryByteStore(),
+          FileContentOverlay(),
+          null,
+          sourceFactory,
+          options);
+      driver.results.listen((_) {});
+      driver.exceptions.listen((_) {});
+      scheduler.start();
+    }
+    return _driver;
+  }
+
+  /// Return an Iterable with the sdk files we should parse.
+  /// Filter can be String or RegExp (technically, anything valid for
+  /// [String.contains])
+  Iterable<String> getSdkFilesToDocument() sync* {
+    for (var sdkLib in sdk.sdkLibraries) {
+      Source source = sdk.mapDartUri(sdkLib.shortName);
+      yield source.fullName;
+    }
+  }
+
+  /// Parse a single library at [filePath] using the current analysis driver.
+  /// If [filePath] is not a library, returns null.
+  Future<ResolvedLibraryResult> processLibrary(String filePath) async {
+    String name = filePath;
+
+    if (name.startsWith(directoryCurrentPath)) {
+      name = name.substring(directoryCurrentPath.length);
+      if (name.startsWith(Platform.pathSeparator)) name = name.substring(1);
+    }
+    JavaFile javaFile = JavaFile(filePath).getAbsoluteFile();
+    Source source = FileBasedSource(javaFile);
+
+    // TODO(jcollins-g): remove the manual reversal using embedderSdk when we
+    // upgrade to analyzer-0.30 (where DartUriResolver implements
+    // restoreAbsolute)
+    Uri uri = embedderSdk?.fromFileUri(source.uri)?.uri;
+    if (uri != null) {
+      source = FileBasedSource(javaFile, uri);
+    } else {
+      uri = driver.sourceFactory.restoreUri(source);
+      if (uri != null) {
+        source = FileBasedSource(javaFile, uri);
+      }
+    }
+    var sourceKind = await driver.getSourceKind(filePath);
+    // Allow dart source files with inappropriate suffixes (#1897).  Those
+    // do not show up as SourceKind.LIBRARY.
+    if (sourceKind != SourceKind.PART) {
+      // Loading libraryElements from part files works, but is painfully slow
+      // and creates many duplicates.
+      return await driver.currentSession.getResolvedLibrary(source.fullName);
+    }
+    return null;
+  }
+
+  Set<PackageMeta> _packageMetasForFiles(Iterable<String> files) {
+    Set<PackageMeta> metas = Set();
+    for (String filename in files) {
+      metas.add(PackageMeta.fromFilename(filename));
+    }
+    return metas;
+  }
+
+  /// 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 = Set();
+    Set<PackageMeta> current;
+    do {
+      lastPass = _packageMetasForFiles(files);
+
+      // 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.
+      await driver.discoverAvailableFiles();
+      files.addAll(driver.knownFiles);
+      files.addAll(_includeExternalsFrom(driver.knownFiles));
+      current = _packageMetasForFiles(files);
+      // To get canonicalization correct for non-locally documented packages
+      // (so we can generate the right hyperlinks), it's vital that we
+      // add all libraries in dependent packages.  So if the analyzer
+      // discovers some files in a package we haven't seen yet, add files
+      // for that package.
+      for (PackageMeta meta in current.difference(lastPass)) {
+        if (meta.isSdk) {
+          files.addAll(getSdkFilesToDocument());
+        } else {
+          files.addAll(
+              findFilesToDocumentInPackage(meta.dir.path, false, false));
+        }
+      }
+    } while (!lastPass.containsAll(current));
+  }
+
+  /// Given a package name, explore the directory and pull out all top level
+  /// library files in the "lib" directory to document.
+  Iterable<String> findFilesToDocumentInPackage(
+      String basePackageDir, bool autoIncludeDependencies,
+      [bool filterExcludes = true]) sync* {
+    final String sep = path.separator;
+
+    Set<String> packageDirs = Set()..add(basePackageDir);
+
+    if (autoIncludeDependencies) {
+      Map<String, Uri> info = package_config
+          .findPackagesFromFile(
+              Uri.file(path.join(basePackageDir, 'pubspec.yaml')))
+          .asMap();
+      for (String packageName in info.keys) {
+        if (!filterExcludes || !config.exclude.contains(packageName)) {
+          packageDirs.add(path.dirname(info[packageName].toFilePath()));
+        }
+      }
+    }
+
+    for (String packageDir in packageDirs) {
+      var packageLibDir = path.join(packageDir, 'lib');
+      var packageLibSrcDir = path.join(packageLibDir, 'src');
+      // To avoid analyzing package files twice, only files with paths not
+      // containing '/packages' will be added. The only exception is if the file
+      // to analyze already has a '/package' in its path.
+      for (var lib
+          in listDir(packageDir, recursive: true, listDir: _packageDirList)) {
+        if (lib.endsWith('.dart') &&
+            (!lib.contains('${sep}packages${sep}') ||
+                packageDir.contains('${sep}packages${sep}'))) {
+          // Only include libraries within the lib dir that are not in lib/src
+          if (path.isWithin(packageLibDir, lib) &&
+              !path.isWithin(packageLibSrcDir, lib)) {
+            // Only add the file if it does not contain 'part of'
+            var contents = File(lib).readAsStringSync();
+
+            if (contents.contains(newLinePartOfRegexp) ||
+                contents.startsWith(partOfRegexp)) {
+              // NOOP: it's a part file
+            } else {
+              yield lib;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /// Calculate includeExternals based on a list of files.  Assumes each
+  /// file might be part of a [DartdocOptionContext], and loads those
+  /// objects to find any [DartdocOptionContext.includeExternal] configurations
+  /// therein.
+  Iterable<String> _includeExternalsFrom(Iterable<String> files) sync* {
+    for (String file in files) {
+      DartdocOptionContext fileContext =
+          DartdocOptionContext.fromContext(config, File(file));
+      if (fileContext.includeExternal != null) {
+        yield* fileContext.includeExternal;
+      }
+    }
+  }
+
+  Set<String> getFiles() {
+    Iterable<String> files;
+    if (config.topLevelPackageMeta.isSdk) {
+      files = getSdkFilesToDocument();
+    } else {
+      files = findFilesToDocumentInPackage(
+          config.inputDir, config.autoIncludeDependencies);
+    }
+    files = quiver.concat([files, _includeExternalsFrom(files)]);
+    return Set.from(files.map((s) => File(s).absolute.path));
+  }
+
+  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 (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 = Set();
+    await _parseLibraries(uninitializedPackageGraph.addLibraryToGraph,
+        foundLibraries, files, isLibraryIncluded);
+    if (config.include.isNotEmpty) {
+      Iterable knownLibraryNames = foundLibraries.map((l) => l.name);
+      Set notFound = Set.from(config.include)
+          .difference(Set.from(knownLibraryNames))
+          .difference(Set.from(config.exclude));
+      if (notFound.isNotEmpty) {
+        throw 'Did not find: [${notFound.join(', ')}] in '
+            'known libraries: [${knownLibraryNames.join(', ')}]';
+      }
+    }
+    // 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
+  /// it like a package and only return the `lib` dir.
+  ///
+  /// This ensures that packages don't have non-`lib` content documented.
+  static Iterable<FileSystemEntity> _packageDirList(Directory dir) sync* {
+    var entities = dir.listSync();
+
+    var pubspec = entities.firstWhere(
+        (e) => e is File && path.basename(e.path) == 'pubspec.yaml',
+        orElse: () => null);
+
+    var libDir = entities.firstWhere(
+        (e) => e is Directory && path.basename(e.path) == 'lib',
+        orElse: () => null);
+
+    if (pubspec != null && libDir != null) {
+      yield libDir;
+    } else {
+      yield* entities;
+    }
+  }
+}
+
+/// This class resolves package URIs, but only if a given SdkResolver doesn't
+/// resolve them.
+///
+/// TODO(jcollins-g): remove this hackery when a clean public API to analyzer
+/// exists, and port dartdoc to it.
+class PackageWithoutSdkResolver extends UriResolver {
+  final UriResolver _packageResolver;
+  final UriResolver _sdkResolver;
+
+  PackageWithoutSdkResolver(this._packageResolver, this._sdkResolver);
+
+  @override
+  Source resolveAbsolute(Uri uri, [Uri actualUri]) {
+    if (_sdkResolver.resolveAbsolute(uri, actualUri) == null) {
+      return _packageResolver.resolveAbsolute(uri, actualUri);
+    }
+    return null;
+  }
+
+  @override
+  Uri restoreAbsolute(Source source) {
+    Uri resolved;
+    try {
+      resolved = _sdkResolver.restoreAbsolute(source);
+    } catch (ArgumentError) {
+      // SDK resolvers really don't like being thrown package paths.
+    }
+    if (resolved == null) {
+      return _packageResolver.restoreAbsolute(source);
+    }
+    return null;
+  }
+}
diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart
new file mode 100644
index 0000000..864ea31
--- /dev/null
+++ b/lib/src/model/package_graph.dart
@@ -0,0 +1,916 @@
+// Copyright (c) 2019, 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.
+
+import 'dart:async';
+
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/analysis/session.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/dart/element/member.dart';
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/generated/source_io.dart';
+import 'package:analyzer/src/generated/type_system.dart' show Dart2TypeSystem;
+import 'package:collection/collection.dart';
+import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/model_utils.dart' as utils;
+import 'package:dartdoc/src/package_meta.dart' show PackageMeta;
+import 'package:dartdoc/src/special_elements.dart';
+import 'package:dartdoc/src/tuple.dart';
+import 'package:dartdoc/src/warnings.dart';
+
+class PackageGraph {
+  PackageGraph.UninitializedPackageGraph(
+      this.config, this.driver, this.typeSystem, this.sdk, this.hasEmbedderSdk)
+      : packageMeta = config.topLevelPackageMeta,
+        session = driver.currentSession {
+    _packageWarningCounter = PackageWarningCounter(this);
+    // Make sure the default package exists, even if it has no libraries.
+    // This can happen for packages that only contain embedder SDKs.
+    Package.fromPackageMeta(packageMeta, this);
+  }
+
+  /// 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 = PackageMeta.fromElement(element, config);
+    var lib = Library.fromLibraryResult(
+        result, this, 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.
+    specialClasses = SpecialClasses();
+    // Go through docs of every ModelElement in package to pre-build the macros
+    // index.  Uses toList() in order to get all the precaching on the stack.
+    List<Future> precacheFutures = precacheLocalDocs().toList();
+    for (Future f in precacheFutures) {
+      await f;
+    }
+    _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.
+    documentedPackages.toList().forEach((package) {
+      package.libraries.sort((a, b) => compareNatural(a.name, b.name));
+      package.libraries.forEach((library) {
+        library.allClasses.forEach(_addToImplementors);
+        _extensions.addAll(library.extensions);
+      });
+    });
+    _implementors.values.forEach((l) => l.sort());
+    allImplementorsAdded = true;
+    allExtensionsAdded = true;
+
+    // We should have found all special classes by now.
+    specialClasses.assertSpecials();
+  }
+
+  /// Generate a list of futures for any docs that actually require precaching.
+  Iterable<Future> precacheLocalDocs() sync* {
+    // Prevent reentrancy.
+    Set<ModelElement> precachedElements = Set();
+
+    Iterable<Future> precacheOneElement(ModelElement m) sync* {
+      for (ModelElement d
+          in m.documentationFrom.where((d) => d.documentationComment != null)) {
+        if (needsPrecacheRegExp.hasMatch(d.documentationComment) &&
+            !precachedElements.contains(d)) {
+          precachedElements.add(d);
+          yield d.precacheLocalDocs();
+          // TopLevelVariables get their documentation from getters and setters,
+          // so should be precached if either has a template.
+          if (m is TopLevelVariable) {
+            precachedElements.add(m);
+            yield m.precacheLocalDocs();
+          }
+        }
+      }
+    }
+
+    for (ModelElement m in allModelElements) {
+      // Skip if there is a canonicalModelElement somewhere else we can run this
+      // for.  Not the same as allCanonicalModelElements since we need to run
+      // for any ModelElement that might not have a canonical ModelElement,
+      // too.
+      if (m.canonicalModelElement != null && !m.isCanonical) continue;
+      yield* precacheOneElement(m);
+    }
+  }
+
+  // Many ModelElements have the same ModelNode; don't build/cache this data more
+  // than once for them.
+  final Map<Element, ModelNode> _modelNodes = Map();
+
+  void populateModelNodeFor(
+      Element element, Map<String, CompilationUnit> compilationUnitMap) {
+    _modelNodes.putIfAbsent(
+        element,
+        () =>
+            ModelNode(utils.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
+  /// is true.
+  bool allImplementorsAdded = false;
+
+  /// It is safe to cache values derived from the [_extensions] table if this
+  /// is true.
+  bool allExtensionsAdded = false;
+
+  Map<String, List<Class>> get implementors {
+    assert(allImplementorsAdded);
+    return _implementors;
+  }
+
+  Iterable<Extension> get extensions {
+    assert(allExtensionsAdded);
+    return _extensions;
+  }
+
+  Map<String, Set<ModelElement>> _findRefElementCache;
+
+  Map<String, Set<ModelElement>> get findRefElementCache {
+    if (_findRefElementCache == null) {
+      assert(packageGraph.allLibrariesAdded);
+      _findRefElementCache = Map();
+      for (final modelElement
+          in utils.filterNonDocumented(packageGraph.allLocalModelElements)) {
+        _findRefElementCache.putIfAbsent(
+            modelElement.fullyQualifiedNameWithoutLibrary, () => Set());
+        _findRefElementCache.putIfAbsent(
+            modelElement.fullyQualifiedName, () => Set());
+        _findRefElementCache[modelElement.fullyQualifiedName].add(modelElement);
+        _findRefElementCache[modelElement.fullyQualifiedNameWithoutLibrary]
+            .add(modelElement);
+      }
+    }
+    return _findRefElementCache;
+  }
+
+  // All library objects related to this package; a superset of _libraries.
+  final Map<LibraryElement, Library> allLibraries = Map();
+
+  /// Keep track of warnings
+  PackageWarningCounter _packageWarningCounter;
+
+  /// All ModelElements constructed for this package; a superset of [allModelElements].
+  final Map<Tuple3<Element, Library, Container>, ModelElement>
+      allConstructedModelElements = Map();
+
+  /// Anything that might be inheritable, place here for later lookup.
+  final Map<Tuple2<Element, Library>, Set<ModelElement>>
+      allInheritableElements = Map();
+
+  /// Map of Class.href to a list of classes implementing that class
+  final Map<String, List<Class>> _implementors = Map();
+
+  /// A list of extensions that exist in the package graph.
+  final List<Extension> _extensions = [];
+
+  /// PackageMeta for the default package.
+  final PackageMeta packageMeta;
+
+  /// Name of the default package.
+  String get defaultPackageName => packageMeta.name;
+
+  /// Dartdoc's configuration flags.
+  final DartdocOptionContext config;
+
+  Package _defaultPackage;
+
+  Package get defaultPackage {
+    if (_defaultPackage == null) {
+      _defaultPackage = Package.fromPackageMeta(packageMeta, this);
+    }
+    return _defaultPackage;
+  }
+
+  final bool hasEmbedderSdk;
+
+  bool get hasFooterVersion => !config.excludeFooterVersion;
+
+  PackageGraph get packageGraph => this;
+
+  /// Map of package name to Package.
+  final Map<String, Package> packageMap = {};
+
+  /// TODO(brianwilkerson) Replace the driver with the session.
+  final AnalysisDriver driver;
+  final AnalysisSession session;
+  final Dart2TypeSystem typeSystem;
+  final DartSdk sdk;
+
+  Map<Source, SdkLibrary> _sdkLibrarySources;
+
+  Map<Source, SdkLibrary> get sdkLibrarySources {
+    if (_sdkLibrarySources == null) {
+      _sdkLibrarySources = Map();
+      for (SdkLibrary lib in sdk?.sdkLibraries) {
+        _sdkLibrarySources[sdk.mapDartUri(lib.shortName)] = lib;
+      }
+    }
+    return _sdkLibrarySources;
+  }
+
+  final Map<String, String> _macros = {};
+  final Map<String, String> _htmlFragments = {};
+  bool allLibrariesAdded = false;
+  bool _localDocumentationBuilt = false;
+
+  /// Returns true if there's at least one library documented in the package
+  /// that has the same package path as the library for the given element.
+  /// Usable as a cross-check for dartdoc's canonicalization to generate
+  /// warnings for ModelElement.isPublicAndPackageDocumented.
+  Set<String> _allRootDirs;
+
+  bool packageDocumentedFor(ModelElement element) {
+    if (_allRootDirs == null) {
+      _allRootDirs = Set()
+        ..addAll(publicLibraries.map((l) => l.packageMeta?.resolvedDir));
+    }
+    return (_allRootDirs.contains(element.library.packageMeta?.resolvedDir));
+  }
+
+  PackageWarningCounter get packageWarningCounter => _packageWarningCounter;
+
+  final Set<Tuple3<Element, PackageWarning, String>> _warnAlreadySeen = Set();
+
+  void warnOnElement(Warnable warnable, PackageWarning kind,
+      {String message,
+      Iterable<Locatable> referredFrom,
+      Iterable<String> extendedDebug}) {
+    var newEntry = Tuple3(warnable?.element, kind, message);
+    if (_warnAlreadySeen.contains(newEntry)) {
+      return;
+    }
+    // Warnings can cause other warnings.  Queue them up via the stack but
+    // don't allow warnings we're already working on to get in there.
+    _warnAlreadySeen.add(newEntry);
+    _warnOnElement(warnable, kind,
+        message: message,
+        referredFrom: referredFrom,
+        extendedDebug: extendedDebug);
+    _warnAlreadySeen.remove(newEntry);
+  }
+
+  void _warnOnElement(Warnable warnable, PackageWarning kind,
+      {String message,
+      Iterable<Locatable> referredFrom,
+      Iterable<String> extendedDebug}) {
+    if (warnable != null) {
+      // This sort of warning is only applicable to top level elements.
+      if (kind == PackageWarning.ambiguousReexport) {
+        while (warnable.enclosingElement is! Library &&
+            warnable.enclosingElement != null) {
+          warnable = warnable.enclosingElement;
+        }
+      }
+    } else {
+      // If we don't have an element, we need a message to disambiguate.
+      assert(message != null);
+    }
+    if (_packageWarningCounter.hasWarning(warnable, kind, message)) {
+      return;
+    }
+    // Some kinds of warnings it is OK to drop if we're not documenting them.
+    // TODO(jcollins-g): drop this and use new flag system instead.
+    if (warnable != null &&
+        skipWarningIfNotDocumentedFor.contains(kind) &&
+        !warnable.isDocumented) {
+      return;
+    }
+    // Elements that are part of the Dart SDK can have colons in their FQNs.
+    // This confuses IntelliJ and makes it so it can't link to the location
+    // of the error in the console window, so separate out the library from
+    // the path.
+    // TODO(jcollins-g): What about messages that may include colons?  Substituting
+    //                   them out doesn't work as well there since it might confuse
+    //                   the user, yet we still want IntelliJ to link properly.
+    final warnableName = _safeWarnableName(warnable);
+
+    String warnablePrefix = 'from';
+    String referredFromPrefix = 'referred to by';
+    String warningMessage;
+    switch (kind) {
+      case PackageWarning.noCanonicalFound:
+        // Fix these warnings by adding libraries with --include, or by using
+        // --auto-include-dependencies.
+        // TODO(jcollins-g): pipeline references through linkedName for error
+        //                   messages and warn for non-public canonicalization
+        //                   errors.
+        warningMessage =
+            "no canonical library found for ${warnableName}, not linking";
+        break;
+      case PackageWarning.ambiguousReexport:
+        // Fix these warnings by adding the original library exporting the
+        // symbol with --include, by using --auto-include-dependencies,
+        // or by using --exclude to hide one of the libraries involved
+        warningMessage =
+            "ambiguous reexport of ${warnableName}, canonicalization candidates: ${message}";
+        break;
+      case PackageWarning.noLibraryLevelDocs:
+        warningMessage =
+            "${warnable.fullyQualifiedName} has no library level documentation comments";
+        break;
+      case PackageWarning.ambiguousDocReference:
+        warningMessage = "ambiguous doc reference ${message}";
+        break;
+      case PackageWarning.ignoredCanonicalFor:
+        warningMessage =
+            "library says it is {@canonicalFor ${message}} but ${message} can't be canonical there";
+        break;
+      case PackageWarning.packageOrderGivesMissingPackageName:
+        warningMessage =
+            "--package-order gives invalid package name: '${message}'";
+        break;
+      case PackageWarning.reexportedPrivateApiAcrossPackages:
+        warningMessage =
+            "private API of ${message} is reexported by libraries in other packages: ";
+        break;
+      case PackageWarning.notImplemented:
+        warningMessage = message;
+        break;
+      case PackageWarning.unresolvedDocReference:
+        warningMessage = "unresolved doc reference [${message}]";
+        referredFromPrefix = 'in documentation inherited from';
+        break;
+      case PackageWarning.unknownMacro:
+        warningMessage = "undefined macro [${message}]";
+        break;
+      case PackageWarning.unknownHtmlFragment:
+        warningMessage = "undefined HTML fragment identifier [${message}]";
+        break;
+      case PackageWarning.brokenLink:
+        warningMessage = 'dartdoc generated a broken link to: ${message}';
+        warnablePrefix = 'to element';
+        referredFromPrefix = 'linked to from';
+        break;
+      case PackageWarning.orphanedFile:
+        warningMessage = 'dartdoc generated a file orphan: ${message}';
+        break;
+      case PackageWarning.unknownFile:
+        warningMessage =
+            'dartdoc detected an unknown file in the doc tree: ${message}';
+        break;
+      case PackageWarning.missingFromSearchIndex:
+        warningMessage =
+            'dartdoc generated a file not in the search index: ${message}';
+        break;
+      case PackageWarning.typeAsHtml:
+        // The message for this warning can contain many punctuation and other symbols,
+        // so bracket with a triple quote for defense.
+        warningMessage = 'generic type handled as HTML: """${message}"""';
+        break;
+      case PackageWarning.invalidParameter:
+        warningMessage = 'invalid parameter to dartdoc directive: ${message}';
+        break;
+      case PackageWarning.toolError:
+        warningMessage = 'tool execution failed: ${message}';
+        break;
+      case PackageWarning.deprecated:
+        warningMessage = 'deprecated dartdoc usage: ${message}';
+        break;
+      case PackageWarning.unresolvedExport:
+        warningMessage = 'unresolved export uri: ${message}';
+        break;
+    }
+
+    List<String> messageParts = [warningMessage];
+    if (warnable != null) {
+      messageParts.add("$warnablePrefix $warnableName: ${warnable.location}");
+    }
+    if (referredFrom != null) {
+      for (Locatable referral in referredFrom) {
+        if (referral != warnable) {
+          var referredFromStrings = _safeWarnableName(referral);
+          messageParts.add(
+              "$referredFromPrefix $referredFromStrings: ${referral.location}");
+        }
+      }
+    }
+    if (config.verboseWarnings && extendedDebug != null) {
+      messageParts.addAll(extendedDebug.map((s) => "    $s"));
+    }
+    String fullMessage;
+    if (messageParts.length <= 2) {
+      fullMessage = messageParts.join(', ');
+    } else {
+      fullMessage = messageParts.join('\n    ');
+    }
+
+    packageWarningCounter.addWarning(warnable, kind, message, fullMessage);
+  }
+
+  String _safeWarnableName(Locatable locatable) {
+    if (locatable == null) {
+      return '<unknown>';
+    }
+
+    return locatable.fullyQualifiedName.replaceFirst(':', '-');
+  }
+
+  List<Package> get packages => packageMap.values.toList();
+
+  List<Package> _publicPackages;
+
+  List<Package> get publicPackages {
+    if (_publicPackages == null) {
+      assert(allLibrariesAdded);
+      // Help the user if they pass us a package that doesn't exist.
+      for (String packageName in config.packageOrder) {
+        if (!packages.map((p) => p.name).contains(packageName)) {
+          warnOnElement(
+              null, PackageWarning.packageOrderGivesMissingPackageName,
+              message:
+                  "${packageName}, packages: ${packages.map((p) => p.name).join(',')}");
+        }
+      }
+      _publicPackages = packages.where((p) => p.isPublic).toList()..sort();
+    }
+    return _publicPackages;
+  }
+
+  /// Local packages are to be documented locally vs. remote or not at all.
+  List<Package> get localPackages =>
+      publicPackages.where((p) => p.isLocal).toList();
+
+  /// Documented packages are documented somewhere (local or remote).
+  Iterable<Package> get documentedPackages =>
+      packages.where((p) => p.documentedWhere != DocumentLocation.missing);
+
+  Map<LibraryElement, Set<Library>> _libraryElementReexportedBy = Map();
+
+  /// Prevent cycles from breaking our stack.
+  Set<Tuple2<Library, LibraryElement>> _reexportsTagged = Set();
+
+  void _tagReexportsFor(
+      final Library topLevelLibrary, final LibraryElement libraryElement,
+      [ExportElement lastExportedElement]) {
+    Tuple2<Library, LibraryElement> key =
+        Tuple2(topLevelLibrary, libraryElement);
+    if (_reexportsTagged.contains(key)) {
+      return;
+    }
+    _reexportsTagged.add(key);
+    if (libraryElement == null) {
+      // The first call to _tagReexportFor should not have a null libraryElement.
+      assert(lastExportedElement != null);
+      warnOnElement(
+          findButDoNotCreateLibraryFor(lastExportedElement.enclosingElement),
+          PackageWarning.unresolvedExport,
+          message: '"${lastExportedElement.uri}"',
+          referredFrom: <Locatable>[topLevelLibrary]);
+      return;
+    }
+    _libraryElementReexportedBy.putIfAbsent(libraryElement, () => Set());
+    _libraryElementReexportedBy[libraryElement].add(topLevelLibrary);
+    for (ExportElement exportedElement in libraryElement.exports) {
+      _tagReexportsFor(
+          topLevelLibrary, exportedElement.exportedLibrary, exportedElement);
+    }
+  }
+
+  int _lastSizeOfAllLibraries = 0;
+
+  Map<LibraryElement, Set<Library>> get libraryElementReexportedBy {
+    // Table must be reset if we're still in the middle of adding libraries.
+    if (allLibraries.keys.length != _lastSizeOfAllLibraries) {
+      _lastSizeOfAllLibraries = allLibraries.keys.length;
+      _libraryElementReexportedBy = Map<LibraryElement, Set<Library>>();
+      _reexportsTagged = Set();
+      for (Library library in publicLibraries) {
+        _tagReexportsFor(library, library.element);
+      }
+    }
+    return _libraryElementReexportedBy;
+  }
+
+  /// A lookup index for hrefs to allow warnings to indicate where a broken
+  /// link or orphaned file may have come from.  Not cached because
+  /// [ModelElement]s can be created at any time and we're basing this
+  /// on more than just [allLocalModelElements] to make the error messages
+  /// comprehensive.
+  Map<String, Set<ModelElement>> get allHrefs {
+    Map<String, Set<ModelElement>> hrefMap = Map();
+    // TODO(jcollins-g ): handle calculating hrefs causing new elements better
+    //                    than toList().
+    for (ModelElement modelElement
+        in allConstructedModelElements.values.toList()) {
+      // Technically speaking we should be able to use canonical model elements
+      // only here, but since the warnings that depend on this debug
+      // canonicalization problems, don't limit ourselves in case an href is
+      // generated for something non-canonical.
+      if (modelElement is Dynamic) continue;
+      // TODO: see [Accessor.enclosingCombo]
+      if (modelElement is Accessor) continue;
+      if (modelElement.href == null) continue;
+      hrefMap.putIfAbsent(modelElement.href, () => Set());
+      hrefMap[modelElement.href].add(modelElement);
+    }
+    for (Package package in packageMap.values) {
+      for (Library library in package.libraries) {
+        if (library.href == null) continue;
+        hrefMap.putIfAbsent(library.href, () => Set());
+        hrefMap[library.href].add(library);
+      }
+    }
+    return hrefMap;
+  }
+
+  void _addToImplementors(Class c) {
+    assert(!allImplementorsAdded);
+    _implementors.putIfAbsent(c.href, () => []);
+    void _checkAndAddClass(Class key, Class implClass) {
+      _implementors.putIfAbsent(key.href, () => []);
+      List list = _implementors[key.href];
+
+      if (!list.any((l) => l.element == c.element)) {
+        list.add(implClass);
+      }
+    }
+
+    if (c.mixins.isNotEmpty) {
+      c.mixins.forEach((t) {
+        _checkAndAddClass(t.element, c);
+      });
+    }
+    if (c.supertype != null) {
+      _checkAndAddClass(c.supertype.element, c);
+    }
+    if (c.interfaces.isNotEmpty) {
+      c.interfaces.forEach((t) {
+        _checkAndAddClass(t.element, c);
+      });
+    }
+  }
+
+  List<Library> get libraries =>
+      packages.expand((p) => p.libraries).toList()..sort();
+
+  List<Library> _publicLibraries;
+
+  Iterable<Library> get publicLibraries {
+    if (_publicLibraries == null) {
+      assert(allLibrariesAdded);
+      _publicLibraries = utils.filterNonPublic(libraries).toList();
+    }
+    return _publicLibraries;
+  }
+
+  List<Library> _localLibraries;
+
+  Iterable<Library> get localLibraries {
+    if (_localLibraries == null) {
+      assert(allLibrariesAdded);
+      _localLibraries = localPackages.expand((p) => p.libraries).toList()
+        ..sort();
+    }
+    return _localLibraries;
+  }
+
+  List<Library> _localPublicLibraries;
+
+  Iterable<Library> get localPublicLibraries {
+    if (_localPublicLibraries == null) {
+      assert(allLibrariesAdded);
+      _localPublicLibraries = utils.filterNonPublic(localLibraries).toList();
+    }
+    return _localPublicLibraries;
+  }
+
+  Set<Class> _inheritThrough;
+
+  /// Return the set of [Class]es objects should inherit through if they
+  /// show up in the inheritance chain.  Do not call before interceptorElement is
+  /// found.  Add classes here if they are similar to Interceptor in that they
+  /// are to be ignored even when they are the implementors of [Inheritable]s,
+  /// and the class these inherit from should instead claim implementation.
+  Set<Class> get inheritThrough {
+    if (_inheritThrough == null) {
+      _inheritThrough = Set();
+      _inheritThrough.add(specialClasses[SpecialClass.interceptor]);
+    }
+    return _inheritThrough;
+  }
+
+  Set<Class> _invisibleAnnotations;
+
+  /// Returns the set of [Class] objects that are similar to pragma
+  /// in that we should never count them as documentable annotations.
+  Set<Class> get invisibleAnnotations {
+    if (_invisibleAnnotations == null) {
+      _invisibleAnnotations = Set();
+      _invisibleAnnotations.add(specialClasses[SpecialClass.pragma]);
+    }
+    return _invisibleAnnotations;
+  }
+
+  @override
+  String toString() => 'PackageGraph built from ${defaultPackage.name}';
+
+  final Map<Element, Library> _canonicalLibraryFor = Map();
+
+  /// Tries to find a top level library that references this element.
+  Library findCanonicalLibraryFor(Element e) {
+    assert(allLibrariesAdded);
+    Element searchElement = e;
+    if (e is PropertyAccessorElement) {
+      searchElement = e.variable;
+    }
+    if (e is GenericFunctionTypeElement) {
+      searchElement = e.enclosingElement;
+    }
+
+    if (_canonicalLibraryFor.containsKey(e)) {
+      return _canonicalLibraryFor[e];
+    }
+    _canonicalLibraryFor[e] = null;
+    for (Library library in publicLibraries) {
+      if (library.modelElementsMap.containsKey(searchElement)) {
+        for (ModelElement modelElement
+            in library.modelElementsMap[searchElement]) {
+          if (modelElement.isCanonical) {
+            _canonicalLibraryFor[e] = library;
+            break;
+          }
+        }
+      }
+    }
+    return _canonicalLibraryFor[e];
+  }
+
+  // TODO(jcollins-g): Revise when dart-lang/sdk#29600 is fixed.
+  static Element getBasestElement(Element possibleMember) {
+    Element element = possibleMember;
+    while (element is Member) {
+      element = (element as Member).baseElement;
+    }
+    return element;
+  }
+
+  /// Tries to find a canonical ModelElement for this element.  If we know
+  /// this element is related to a particular class, pass preferredClass to
+  /// disambiguate.
+  ///
+  /// This doesn't know anything about [PackageGraph.inheritThrough] and probably
+  /// shouldn't, so using it with [Inheritable]s without special casing is
+  /// not advised.
+  ModelElement findCanonicalModelElementFor(Element e,
+      {Container preferredClass}) {
+    assert(allLibrariesAdded);
+    Library lib = findCanonicalLibraryFor(e);
+    if (preferredClass != null && preferredClass is Container) {
+      Container canonicalClass =
+          findCanonicalModelElementFor(preferredClass.element);
+      if (canonicalClass != null) preferredClass = canonicalClass;
+    }
+    if (lib == null && preferredClass != null) {
+      lib = findCanonicalLibraryFor(preferredClass.element);
+    }
+    ModelElement modelElement;
+    // For elements defined in extensions, they are canonical.
+    if (e?.enclosingElement is ExtensionElement) {
+      lib ??= Library(e.enclosingElement.library, packageGraph);
+      // (TODO:keertip) Find a better way to exclude members of extensions
+      //  when libraries are specified using the "--include" flag
+      if (lib?.isDocumented == true) {
+        return ModelElement.from(e, lib, packageGraph);
+      }
+    }
+    // TODO(jcollins-g): Special cases are pretty large here.  Refactor to split
+    // out into helpers.
+    // TODO(jcollins-g): The data structures should be changed to eliminate guesswork
+    // with member elements.
+    if (e is ClassMemberElement || e is PropertyAccessorElement) {
+      if (e is Member) e = getBasestElement(e);
+      Set<ModelElement> candidates = Set();
+      Tuple2<Element, Library> iKey = Tuple2(e, lib);
+      Tuple4<Element, Library, Class, ModelElement> key =
+          Tuple4(e, lib, null, null);
+      Tuple4<Element, Library, Class, ModelElement> keyWithClass =
+          Tuple4(e, lib, preferredClass, null);
+      if (allConstructedModelElements.containsKey(key)) {
+        candidates.add(allConstructedModelElements[key]);
+      }
+      if (allConstructedModelElements.containsKey(keyWithClass)) {
+        candidates.add(allConstructedModelElements[keyWithClass]);
+      }
+      if (candidates.isEmpty && allInheritableElements.containsKey(iKey)) {
+        candidates
+            .addAll(allInheritableElements[iKey].where((me) => me.isCanonical));
+      }
+      Class canonicalClass = findCanonicalModelElementFor(e.enclosingElement);
+      if (canonicalClass != null) {
+        candidates.addAll(canonicalClass.allCanonicalModelElements.where((m) {
+          if (m.element == e) return true;
+          return false;
+        }));
+      }
+      Set<ModelElement> matches = Set()
+        ..addAll(candidates.where((me) => me.isCanonical));
+
+      // It's possible to find accessors but no combos.  Be sure that if we
+      // have Accessors, we find their combos too.
+      if (matches.any((me) => me is Accessor)) {
+        List<GetterSetterCombo> combos =
+            matches.whereType<Accessor>().map((a) => a.enclosingCombo).toList();
+        matches.addAll(combos);
+        assert(combos.every((c) => c.isCanonical));
+      }
+
+      // This is for situations where multiple classes may actually be canonical
+      // for an inherited element whose defining Class is not canonical.
+      if (matches.length > 1 &&
+          preferredClass != null &&
+          preferredClass is Class) {
+        // Search for matches inside our superchain.
+        List<Class> superChain = preferredClass.superChain
+            .map((et) => et.element)
+            .cast<Class>()
+            .toList();
+        superChain.add(preferredClass);
+        matches.removeWhere((me) =>
+            !superChain.contains((me as EnclosedElement).enclosingElement));
+        // Assumed all matches are EnclosedElement because we've been told about a
+        // preferredClass.
+        Set<Class> enclosingElements = Set()
+          ..addAll(matches
+              .map((me) => (me as EnclosedElement).enclosingElement as Class));
+        for (Class c in superChain.reversed) {
+          if (enclosingElements.contains(c)) {
+            matches.removeWhere(
+                (me) => (me as EnclosedElement).enclosingElement != c);
+          }
+          if (matches.length <= 1) break;
+        }
+      }
+
+      // Prefer a GetterSetterCombo to Accessors.
+      if (matches.any((me) => me is GetterSetterCombo)) {
+        matches.removeWhere((me) => me is Accessor);
+      }
+
+      assert(matches.length <= 1);
+      if (matches.isNotEmpty) {
+        modelElement = matches.first;
+      }
+    } else {
+      if (lib != null) {
+        Accessor getter;
+        Accessor setter;
+        if (e is PropertyInducingElement) {
+          if (e.getter != null) {
+            getter = ModelElement.from(e.getter, lib, packageGraph);
+          }
+          if (e.setter != null) {
+            setter = ModelElement.from(e.setter, lib, packageGraph);
+          }
+        }
+        modelElement = ModelElement.from(e, lib, packageGraph,
+            getter: getter, setter: setter);
+      }
+      assert(modelElement is! Inheritable);
+      if (modelElement != null && !modelElement.isCanonical) {
+        modelElement = null;
+      }
+    }
+    // Prefer Fields.
+    if (e is PropertyAccessorElement && modelElement is Accessor) {
+      modelElement = (modelElement as Accessor).enclosingCombo;
+    }
+    return modelElement;
+  }
+
+  /// 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).
+  Library findButDoNotCreateLibraryFor(Element e) {
+    // This is just a cache to avoid creating lots of libraries over and over.
+    if (allLibraries.containsKey(e.library)) {
+      return allLibraries[e.library];
+    }
+    return null;
+  }
+
+  /// 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).
+  Library findOrCreateLibraryFor(ResolvedLibraryResult result) {
+    // This is just a cache to avoid creating lots of libraries over and over.
+    if (allLibraries.containsKey(result.element.library)) {
+      return allLibraries[result.element.library];
+    }
+    // can be null if e is for dynamic
+    if (result.element.library == null) {
+      return null;
+    }
+    Library foundLibrary = Library.fromLibraryResult(
+        result,
+        this,
+        Package.fromPackageMeta(
+            PackageMeta.fromElement(result.element.library, config),
+            packageGraph));
+    allLibraries[result.element.library] = foundLibrary;
+    return foundLibrary;
+  }
+
+  List<ModelElement> _allModelElements;
+
+  Iterable<ModelElement> get allModelElements {
+    assert(allLibrariesAdded);
+    if (_allModelElements == null) {
+      _allModelElements = [];
+      Set<Package> packagesToDo = packages.toSet();
+      Set<Package> completedPackages = Set();
+      while (packagesToDo.length > completedPackages.length) {
+        packagesToDo.difference(completedPackages).forEach((Package p) {
+          Set<Library> librariesToDo = p.allLibraries.toSet();
+          Set<Library> completedLibraries = Set();
+          while (librariesToDo.length > completedLibraries.length) {
+            librariesToDo
+                .difference(completedLibraries)
+                .forEach((Library library) {
+              _allModelElements.addAll(library.allModelElements);
+              completedLibraries.add(library);
+            });
+            librariesToDo.addAll(p.allLibraries);
+          }
+          completedPackages.add(p);
+        });
+        packagesToDo.addAll(packages);
+      }
+    }
+    return _allModelElements;
+  }
+
+  List<ModelElement> _allLocalModelElements;
+
+  Iterable<ModelElement> get allLocalModelElements {
+    assert(allLibrariesAdded);
+    if (_allLocalModelElements == null) {
+      _allLocalModelElements = [];
+      this.localLibraries.forEach((library) {
+        _allLocalModelElements.addAll(library.allModelElements);
+      });
+    }
+    return _allLocalModelElements;
+  }
+
+  List<ModelElement> _allCanonicalModelElements;
+
+  Iterable<ModelElement> get allCanonicalModelElements {
+    return (_allCanonicalModelElements ??=
+        allLocalModelElements.where((e) => e.isCanonical).toList());
+  }
+
+  String getMacro(String name) {
+    assert(_localDocumentationBuilt);
+    return _macros[name];
+  }
+
+  void addMacro(String name, String content) {
+    assert(!_localDocumentationBuilt);
+    _macros[name] = content;
+  }
+
+  String getHtmlFragment(String name) {
+    assert(_localDocumentationBuilt);
+    return _htmlFragments[name];
+  }
+
+  void addHtmlFragment(String name, String content) {
+    assert(!_localDocumentationBuilt);
+    _htmlFragments[name] = content;
+  }
+}
diff --git a/lib/src/model/parameter.dart b/lib/src/model/parameter.dart
new file mode 100644
index 0000000..a1a0286
--- /dev/null
+++ b/lib/src/model/parameter.dart
@@ -0,0 +1,75 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/member.dart' show Member;
+import 'package:dartdoc/src/model/model.dart';
+
+class Parameter extends ModelElement implements EnclosedElement {
+  Parameter(
+      ParameterElement element, Library library, PackageGraph packageGraph,
+      {Member originalMember})
+      : super(element, library, packageGraph, originalMember);
+
+  String get defaultValue {
+    if (!hasDefaultValue) return null;
+    return _parameter.defaultValueCode;
+  }
+
+  @override
+  ModelElement get enclosingElement => (_parameter.enclosingElement != null)
+      ? ModelElement.from(_parameter.enclosingElement, library, packageGraph)
+      : null;
+
+  bool get hasDefaultValue {
+    return _parameter.defaultValueCode != null &&
+        _parameter.defaultValueCode.isNotEmpty;
+  }
+
+  @override
+  String get href {
+    throw StateError('href not implemented for parameters');
+  }
+
+  @override
+  String get htmlId {
+    if (_parameter.enclosingElement != null) {
+      String enclosingName = _parameter.enclosingElement.name;
+      if (_parameter.enclosingElement is GenericFunctionTypeElement) {
+        // TODO(jcollins-g): Drop when GenericFunctionTypeElement populates name.
+        // Also, allowing null here is allowed as a workaround for
+        // dart-lang/sdk#32005.
+        for (Element e = _parameter.enclosingElement;
+            e.enclosingElement != null;
+            e = e.enclosingElement) {
+          enclosingName = e.name;
+          if (enclosingName != null && enclosingName.isNotEmpty) break;
+        }
+      }
+      return '${enclosingName}-param-${name}';
+    } else {
+      return 'param-${name}';
+    }
+  }
+
+  @override
+  int get hashCode => element == null ? 0 : element.hashCode;
+
+  @override
+  bool operator ==(Object object) =>
+      object is Parameter && (_parameter.type == object._parameter.type);
+
+  bool get isCovariant => _parameter.isCovariant;
+
+  bool get isOptional => _parameter.isOptional;
+
+  bool get isOptionalNamed => _parameter.isNamed;
+
+  bool get isOptionalPositional => _parameter.isOptionalPositional;
+
+  @override
+  String get kind => 'parameter';
+
+  ParameterElement get _parameter => element as ParameterElement;
+}
diff --git a/lib/src/model/privacy.dart b/lib/src/model/privacy.dart
new file mode 100644
index 0000000..9961eb0
--- /dev/null
+++ b/lib/src/model/privacy.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, 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.
+
+/// Classes implementing this have a public/private distinction.
+abstract class Privacy {
+  bool get isPublic;
+}
diff --git a/lib/src/model/source_code_mixin.dart b/lib/src/model/source_code_mixin.dart
new file mode 100644
index 0000000..1c09f98
--- /dev/null
+++ b/lib/src/model/source_code_mixin.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+abstract class SourceCodeMixin implements Documentable {
+  ModelNode get modelNode;
+
+  CharacterLocation get characterLocation;
+
+  Element get element;
+
+  bool get hasSourceCode => config.includeSource && sourceCode.isNotEmpty;
+
+  Library get library;
+
+  String get sourceCode => modelNode == null ? '' : modelNode.sourceCode;
+}
diff --git a/lib/src/model/top_level_container.dart b/lib/src/model/top_level_container.dart
new file mode 100644
index 0000000..b5fc82f
--- /dev/null
+++ b/lib/src/model/top_level_container.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2019, 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.
+
+import 'package:dartdoc/src/model/model.dart';
+import 'package:dartdoc/src/model_utils.dart' as model_utils;
+
+/// A set of [Class]es, [Enum]s, [TopLevelVariable]s, [ModelFunction]s,
+/// [Property]s, and [Typedef]s, possibly initialized after construction by
+/// accessing private member variables.  Do not call any methods or members
+/// excepting [name] and the private Lists below before finishing initialization
+/// of a [TopLevelContainer].
+abstract class TopLevelContainer implements Nameable {
+  Iterable<Class> get classes;
+
+  Iterable<Extension> get extensions;
+
+  Iterable<Enum> get enums;
+
+  Iterable<Mixin> get mixins;
+
+  Iterable<Class> get exceptions;
+
+  Iterable<TopLevelVariable> get constants;
+
+  Iterable<TopLevelVariable> get properties;
+
+  Iterable<ModelFunction> get functions;
+
+  Iterable<Typedef> get typedefs;
+
+  bool get hasPublicClasses => publicClasses.isNotEmpty;
+
+  bool get hasPublicExtensions => publicExtensions.isNotEmpty;
+
+  bool get hasPublicConstants => publicConstants.isNotEmpty;
+
+  bool get hasPublicEnums => publicEnums.isNotEmpty;
+
+  bool get hasPublicExceptions => publicExceptions.isNotEmpty;
+
+  bool get hasPublicFunctions => publicFunctions.isNotEmpty;
+
+  bool get hasPublicMixins => publicMixins.isNotEmpty;
+
+  bool get hasPublicProperties => publicProperties.isNotEmpty;
+
+  bool get hasPublicTypedefs => publicTypedefs.isNotEmpty;
+
+  Iterable<Class> get publicClasses => model_utils.filterNonPublic(classes);
+
+  Iterable<Extension> get publicExtensions =>
+      model_utils.filterNonPublic(extensions);
+
+  Iterable<TopLevelVariable> get publicConstants =>
+      model_utils.filterNonPublic(constants);
+
+  Iterable<Enum> get publicEnums => model_utils.filterNonPublic(enums);
+
+  Iterable<Class> get publicExceptions =>
+      model_utils.filterNonPublic(exceptions);
+
+  Iterable<ModelFunction> get publicFunctions =>
+      model_utils.filterNonPublic(functions);
+
+  Iterable<Mixin> get publicMixins => model_utils.filterNonPublic(mixins);
+
+  Iterable<TopLevelVariable> get publicProperties =>
+      model_utils.filterNonPublic(properties);
+
+  Iterable<Typedef> get publicTypedefs => model_utils.filterNonPublic(typedefs);
+}
diff --git a/lib/src/model/top_level_variable.dart b/lib/src/model/top_level_variable.dart
new file mode 100644
index 0000000..2d93a8c
--- /dev/null
+++ b/lib/src/model/top_level_variable.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+/// Top-level variables. But also picks up getters and setters?
+class TopLevelVariable extends ModelElement
+    with Canonicalization, GetterSetterCombo, SourceCodeMixin, Categorization
+    implements EnclosedElement {
+  @override
+  final Accessor getter;
+  @override
+  final Accessor setter;
+
+  TopLevelVariable(TopLevelVariableElement element, Library library,
+      PackageGraph packageGraph, this.getter, this.setter)
+      : super(element, library, packageGraph, null) {
+    if (getter != null) {
+      getter.enclosingCombo = this;
+    }
+    if (setter != null) {
+      setter.enclosingCombo = this;
+    }
+  }
+
+  @override
+  bool get isInherited => false;
+
+  @override
+  String get documentation {
+    // Verify that hasSetter and hasGetterNoSetter are mutually exclusive,
+    // to prevent displaying more or less than one summary.
+    if (isPublic) {
+      Set<bool> assertCheck = Set()
+        ..addAll([hasPublicSetter, hasPublicGetterNoSetter]);
+      assert(assertCheck.containsAll([true, false]));
+    }
+    return super.documentation;
+  }
+
+  @override
+  ModelElement get enclosingElement => library;
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${library.dirName}/$fileName';
+  }
+
+  @override
+  bool get isConst => _variable.isConst;
+
+  @override
+  bool get isFinal {
+    /// isFinal returns true for the variable even if it has an explicit getter
+    /// (which means we should not document it as "final").
+    if (hasExplicitGetter) return false;
+    return _variable.isFinal;
+  }
+
+  @override
+  String get kind => isConst ? 'top-level constant' : 'top-level property';
+
+  @override
+  Set<String> get features => super.features..addAll(comboFeatures);
+
+  @override
+  String computeDocumentationComment() {
+    String docs = getterSetterDocumentationComment;
+    if (docs.isEmpty) return _variable.documentationComment;
+    return docs;
+  }
+
+  @override
+  String get fileName => isConst ? '$name-constant.html' : '$name.html';
+
+  @override
+  DefinedElementType get modelType => super.modelType;
+
+  TopLevelVariableElement get _variable => (element as TopLevelVariableElement);
+}
diff --git a/lib/src/model/type_parameter.dart b/lib/src/model/type_parameter.dart
new file mode 100644
index 0000000..fc5d0ae
--- /dev/null
+++ b/lib/src/model/type_parameter.dart
@@ -0,0 +1,100 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+class TypeParameter extends ModelElement {
+  TypeParameter(
+      TypeParameterElement element, Library library, PackageGraph packageGraph)
+      : super(element, library, packageGraph, null);
+
+  @override
+  ModelElement get enclosingElement => (element.enclosingElement != null)
+      ? ModelElement.from(element.enclosingElement, library, packageGraph)
+      : null;
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${enclosingElement.library.dirName}/${enclosingElement.name}/$name';
+  }
+
+  @override
+  String get kind => 'type parameter';
+
+  ElementType _boundType;
+
+  ElementType get boundType {
+    if (_boundType == null) {
+      var bound = _typeParameter.bound;
+      if (bound != null) {
+        _boundType = ElementType.from(bound, library, packageGraph);
+      }
+    }
+    return _boundType;
+  }
+
+  String _name;
+
+  @override
+  String get name {
+    if (_name == null) {
+      _name = _typeParameter.bound != null
+          ? '${_typeParameter.name} extends ${boundType.nameWithGenerics}'
+          : _typeParameter.name;
+    }
+    return _name;
+  }
+
+  String _linkedName;
+
+  @override
+  String get linkedName {
+    if (_linkedName == null) {
+      _linkedName = _typeParameter.bound != null
+          ? '${_typeParameter.name} extends ${boundType.linkedName}'
+          : _typeParameter.name;
+    }
+    return _linkedName;
+  }
+
+  TypeParameterElement get _typeParameter => element as TypeParameterElement;
+}
+
+abstract class TypeParameters implements ModelElement {
+  String get nameWithGenerics => '$name$genericParameters';
+
+  String get nameWithLinkedGenerics => '$name$linkedGenericParameters';
+
+  bool get hasGenericParameters => typeParameters.isNotEmpty;
+
+  String get genericParameters {
+    if (typeParameters.isEmpty) return '';
+
+    var joined = typeParameters
+        .map((t) => t.name)
+        .join('</span>, <span class="type-parameter">');
+    return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
+  }
+
+  String get linkedGenericParameters {
+    if (typeParameters.isEmpty) return '';
+
+    var joined = typeParameters
+        .map((t) => t.linkedName)
+        .join('</span>, <span class="type-parameter">');
+    return '<span class="signature">&lt;<wbr><span class="type-parameter">${joined}</span>&gt;</span>';
+  }
+
+  @override
+  DefinedElementType get modelType;
+
+  List<TypeParameter> get typeParameters;
+}
diff --git a/lib/src/model/typedef.dart b/lib/src/model/typedef.dart
new file mode 100644
index 0000000..15a168e
--- /dev/null
+++ b/lib/src/model/typedef.dart
@@ -0,0 +1,65 @@
+// Copyright (c) 2019, 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.
+
+import 'package:analyzer/dart/element/element.dart';
+import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/model.dart';
+
+class Typedef extends ModelElement
+    with SourceCodeMixin, TypeParameters, Categorization
+    implements EnclosedElement {
+  Typedef(FunctionTypeAliasElement element, Library library,
+      PackageGraph packageGraph)
+      : super(element, library, packageGraph, null);
+
+  @override
+  ModelElement get enclosingElement => library;
+
+  @override
+  String get nameWithGenerics => '$name${super.genericParameters}';
+
+  @override
+  String get genericParameters {
+    if (element is GenericTypeAliasElement) {
+      List<TypeParameterElement> genericTypeParameters =
+          (element as GenericTypeAliasElement).function.typeParameters;
+      if (genericTypeParameters.isNotEmpty) {
+        var joined = genericTypeParameters
+            .map((t) => t.name)
+            .join('</span>, <span class="type-parameter">');
+        return '&lt;<wbr><span class="type-parameter">${joined}</span>&gt;';
+      }
+    } // else, all types are resolved.
+    return '';
+  }
+
+  @override
+  String get href {
+    if (!identical(canonicalModelElement, this)) {
+      return canonicalModelElement?.href;
+    }
+    assert(canonicalLibrary != null);
+    assert(canonicalLibrary == library);
+    return '${package.baseHref}${library.dirName}/$fileName';
+  }
+
+  // Food for mustache.
+  bool get isInherited => false;
+
+  @override
+  String get kind => 'typedef';
+
+  String get linkedReturnType => modelType.createLinkedReturnTypeName();
+
+  @override
+  DefinedElementType get modelType => super.modelType;
+
+  FunctionTypeAliasElement get _typedef =>
+      (element as FunctionTypeAliasElement);
+
+  @override
+  List<TypeParameter> get typeParameters => _typedef.typeParameters.map((f) {
+        return ModelElement.from(f, library, packageGraph) as TypeParameter;
+      }).toList();
+}
diff --git a/lib/src/model_utils.dart b/lib/src/model_utils.dart
index 18fbc35..35c1d1d 100644
--- a/lib/src/model_utils.dart
+++ b/lib/src/model_utils.dart
@@ -11,7 +11,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/element_type.dart';
 
 final Map<String, String> _fileContents = <String, String>{};
diff --git a/lib/src/source_linker.dart b/lib/src/source_linker.dart
index 992e5fe..3e86e44 100644
--- a/lib/src/source_linker.dart
+++ b/lib/src/source_linker.dart
@@ -6,7 +6,7 @@
 library dartdoc.source_linker;
 
 import 'package:dartdoc/src/dartdoc_options.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:path/path.dart' as path;
 
 final uriTemplateRegexp = RegExp(r'(%[frl]%)');
diff --git a/lib/src/special_elements.dart b/lib/src/special_elements.dart
index 0dceb95..028f665 100644
--- a/lib/src/special_elements.dart
+++ b/lib/src/special_elements.dart
@@ -11,7 +11,7 @@
 
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/generated/sdk.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 
 /// Which of the [SpecialClasses] to get.
 enum SpecialClass {
diff --git a/lib/src/warnings.dart b/lib/src/warnings.dart
index a2a2280..2d59d7b 100644
--- a/lib/src/warnings.dart
+++ b/lib/src/warnings.dart
@@ -7,7 +7,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:dartdoc/src/dartdoc_options.dart';
 import 'package:dartdoc/src/logging.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/package_meta.dart';
 import 'package:dartdoc/src/tuple.dart';
 
@@ -208,22 +208,6 @@
   Package get package;
 }
 
-/// Something that can be located for warning purposes.
-abstract class Locatable {
-  List<Locatable> get documentationFrom;
-
-  /// True if documentationFrom contains only one item, [this].
-  bool get documentationIsLocal => documentationFrom.length == 1 && identical(documentationFrom.first, this);
-
-  String get fullyQualifiedName;
-
-  String get href;
-
-  /// A string indicating the URI of this Locatable, usually derived from
-  /// [Element.location].
-  String get location;
-}
-
 // The kinds of warnings that can be displayed when documenting a package.
 enum PackageWarning {
   ambiguousDocReference,
diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart
index d049533..02400fe 100644
--- a/test/dartdoc_test.dart
+++ b/test/dartdoc_test.dart
@@ -9,7 +9,7 @@
 
 import 'package:dartdoc/dartdoc.dart';
 import 'package:dartdoc/src/logging.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/warnings.dart';
 import 'package:path/path.dart' as path;
diff --git a/test/model_test.dart b/test/model_test.dart
index 29e7f24..1c103d6 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -7,7 +7,7 @@
 import 'dart:io';
 
 import 'package:dartdoc/dartdoc.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart';
 import 'package:dartdoc/src/special_elements.dart';
 import 'package:dartdoc/src/warnings.dart';
diff --git a/test/src/utils.dart b/test/src/utils.dart
index 9167896..2436e23 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -10,7 +10,7 @@
 
 import 'package:dartdoc/dartdoc.dart';
 import 'package:dartdoc/src/html/html_generator.dart';
-import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/package_meta.dart';
 import 'package:path/path.dart' as path;