Fix global table lookup of non-canonical elements that have canonical counterparts (#2397)

* First stage -- include all references that can be canonicalized

* Cleanups and hashmap conversion

* prefer enclosing combos (filtering accessors)

* dartfmt

* add tests
diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart
index 12a66de..ca288d9 100644
--- a/lib/src/markdown_processor.dart
+++ b/lib/src/markdown_processor.dart
@@ -459,6 +459,10 @@
         // conflicts are allowed, but an instance field is allowed to have the
         // same name as a named constructor.
         _reducePreferConstructorViaIndicators,
+        // Prefer Fields/TopLevelVariables to accessors.
+        // TODO(jcollins-g): Remove after fixing dart-lang/dartdoc#2396 or
+        // exclude Accessors from all lookup tables.
+        _reducePreferCombos,
         // Prefer the Dart analyzer's resolution of comment references.  We
         // can't start from this because of the differences in Dartdoc
         // canonicalization.
@@ -547,6 +551,14 @@
     }
   }
 
+  void _reducePreferCombos() {
+    var accessors = results.whereType<Accessor>().toList();
+    accessors.forEach((a) {
+      results.remove(a);
+      results.add(a.enclosingCombo);
+    });
+  }
+
   void _findTypeParameters() {
     if (element is TypeParameters) {
       results.addAll((element as TypeParameters).typeParameters.where((p) =>
@@ -648,7 +660,7 @@
         prefixToLibrary[codeRefChompedParts.first]?.forEach((l) => l
             .modelElementsNameMap[lookup]
             ?.map(_convertConstructors)
-            ?.forEach((m) => _addCanonicalResult(m, _getPreferredClass(m))));
+            ?.forEach((m) => _addCanonicalResult(m)));
       }
     }
   }
@@ -660,8 +672,7 @@
         if (codeRefChomped == modelElement.fullyQualifiedNameWithoutLibrary ||
             (modelElement is Library &&
                 codeRefChomped == modelElement.fullyQualifiedName)) {
-          _addCanonicalResult(
-              _convertConstructors(modelElement), preferredClass);
+          _addCanonicalResult(_convertConstructors(modelElement));
         }
       }
     }
@@ -671,7 +682,7 @@
     // Only look for partially qualified matches if we didn't find a fully qualified one.
     if (library.modelElementsNameMap.containsKey(codeRefChomped)) {
       for (final modelElement in library.modelElementsNameMap[codeRefChomped]) {
-        _addCanonicalResult(_convertConstructors(modelElement), preferredClass);
+        _addCanonicalResult(_convertConstructors(modelElement));
       }
     }
   }
@@ -684,15 +695,7 @@
         packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
       for (var modelElement
           in packageGraph.findRefElementCache[codeRefChomped]) {
-        // For fully qualified matches, the original preferredClass passed
-        // might make no sense.  Instead, use the enclosing class from the
-        // element in [packageGraph.findRefElementCache], because that element's
-        // enclosing class will be preferred from [codeRefChomped]'s perspective.
-        _addCanonicalResult(
-            _convertConstructors(modelElement),
-            modelElement.enclosingElement is Class
-                ? modelElement.enclosingElement
-                : null);
+        _addCanonicalResult(_convertConstructors(modelElement));
       }
     }
   }
@@ -785,9 +788,8 @@
   }
 
   // Add a result, but make it canonical.
-  void _addCanonicalResult(ModelElement modelElement, Container tryClass) {
-    results.add(packageGraph.findCanonicalModelElementFor(modelElement.element,
-        preferredClass: tryClass));
+  void _addCanonicalResult(ModelElement modelElement) {
+    results.add(modelElement.canonicalModelElement);
   }
 
   /// _getResultsForClass assumes codeRefChomped might be a member of tryClass (inherited or not)
@@ -804,7 +806,7 @@
     } else {
       // People like to use 'this' in docrefs too.
       if (codeRef == 'this') {
-        _addCanonicalResult(tryClass, null);
+        _addCanonicalResult(tryClass);
       } else {
         // TODO(jcollins-g): get rid of reimplementation of identifier resolution
         //                   or integrate into ModelElement in a simpler way.
@@ -816,7 +818,7 @@
         //                   Fortunately superChains are short, but optimize this if it matters.
         superChain.addAll(tryClass.superChain.map((t) => t.element));
         for (final c in superChain) {
-          _getResultsForSuperChainElement(c, tryClass);
+          _getResultsForSuperChainElement(c);
           if (results.isNotEmpty) break;
         }
       }
@@ -825,12 +827,12 @@
 
   /// Get any possible results for this class in the superChain.   Returns
   /// true if we found something.
-  void _getResultsForSuperChainElement(Class c, Class tryClass) {
+  void _getResultsForSuperChainElement(Class c) {
     var membersToCheck = (c.allModelElementsByNamePart[codeRefChomped] ?? [])
         .map(_convertConstructors);
     for (var modelElement in membersToCheck) {
       // [thing], a member of this class
-      _addCanonicalResult(modelElement, tryClass);
+      _addCanonicalResult(modelElement);
     }
     if (codeRefChompedParts.length < 2 ||
         codeRefChompedParts[codeRefChompedParts.length - 2] == c.name) {
@@ -838,7 +840,7 @@
           (c.allModelElementsByNamePart[codeRefChompedParts.last] ??
                   <ModelElement>[])
               .map(_convertConstructors);
-      membersToCheck.forEach((m) => _addCanonicalResult(m, tryClass));
+      membersToCheck.forEach((m) => _addCanonicalResult(m));
     }
     results.remove(null);
     if (results.isNotEmpty) return;
diff --git a/lib/src/model/class.dart b/lib/src/model/class.dart
index d63299d..49d8049 100644
--- a/lib/src/model/class.dart
+++ b/lib/src/model/class.dart
@@ -401,8 +401,7 @@
       var accessorMap = <String, List<PropertyAccessorElement>>{};
       for (var accessorElement in inheritedAccessorElements) {
         var name = accessorElement.name.replaceFirst('=', '');
-        accessorMap.putIfAbsent(name, () => []);
-        accessorMap[name].add(accessorElement);
+        accessorMap.putIfAbsent(name, () => []).add(accessorElement);
       }
 
       // For half-inherited fields, the analyzer only links the non-inherited
diff --git a/lib/src/model/library.dart b/lib/src/model/library.dart
index 2b7a0c1..2a7b962 100644
--- a/lib/src/model/library.dart
+++ b/lib/src/model/library.dart
@@ -2,6 +2,8 @@
 // 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/analysis/results.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -283,8 +285,8 @@
       for (var i in element.imports) {
         // Ignore invalid imports.
         if (i.prefix?.name != null && i.importedLibrary != null) {
-          _prefixToLibrary.putIfAbsent(i.prefix?.name, () => {});
-          _prefixToLibrary[i.prefix?.name]
+          _prefixToLibrary
+              .putIfAbsent(i.prefix?.name, () => {})
               .add(ModelElement.from(i.importedLibrary, library, packageGraph));
         }
       }
@@ -585,29 +587,29 @@
   static String getLibraryName(LibraryElement element) =>
       _getLibraryName(element);
 
-  /*late final*/ Map<String, Set<ModelElement>> _modelElementsNameMap;
+  /*late final*/ HashMap<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 {
+  HashMap<String, Set<ModelElement>> get modelElementsNameMap {
     if (_modelElementsNameMap == null) {
-      _modelElementsNameMap = <String, Set<ModelElement>>{};
+      _modelElementsNameMap = HashMap<String, Set<ModelElement>>();
       allModelElements.forEach((ModelElement modelElement) {
         // [definingLibrary] may be null if [element] has been imported or
         // exported with a non-normalized URI, like "src//a.dart".
         if (modelElement.definingLibrary == null) return;
-        _modelElementsNameMap.putIfAbsent(
-            modelElement.fullyQualifiedNameWithoutLibrary, () => {});
-        _modelElementsNameMap[modelElement.fullyQualifiedNameWithoutLibrary]
+        _modelElementsNameMap
+            .putIfAbsent(
+                modelElement.fullyQualifiedNameWithoutLibrary, () => {})
             .add(modelElement);
       });
     }
     return _modelElementsNameMap;
   }
 
-  /*late final*/ Map<Element, Set<ModelElement>> _modelElementsMap;
+  /*late final*/ HashMap<Element, Set<ModelElement>> _modelElementsMap;
 
-  Map<Element, Set<ModelElement>> get modelElementsMap {
+  HashMap<Element, Set<ModelElement>> get modelElementsMap {
     if (_modelElementsMap == null) {
       var results = quiver.concat(<Iterable<ModelElement>>[
         library.constants,
@@ -639,13 +641,13 @@
           ]);
         }),
       ]);
-      _modelElementsMap = <Element, Set<ModelElement>>{};
+      _modelElementsMap = HashMap<Element, Set<ModelElement>>();
       results.forEach((modelElement) {
-        _modelElementsMap.putIfAbsent(modelElement.element, () => {});
-        _modelElementsMap[modelElement.element].add(modelElement);
+        _modelElementsMap
+            .putIfAbsent(modelElement.element, () => {})
+            .add(modelElement);
       });
-      _modelElementsMap.putIfAbsent(element, () => {});
-      _modelElementsMap[element].add(this);
+      _modelElementsMap.putIfAbsent(element, () => {}).add(this);
     }
     return _modelElementsMap;
   }
diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart
index 4ba3de3..58c57b4 100644
--- a/lib/src/model/model_element.dart
+++ b/lib/src/model/model_element.dart
@@ -299,8 +299,9 @@
       library.packageGraph.allConstructedModelElements[key] = newModelElement;
       if (newModelElement is Inheritable) {
         var iKey = Tuple2<Element, Library>(e, library);
-        library.packageGraph.allInheritableElements.putIfAbsent(iKey, () => {});
-        library.packageGraph.allInheritableElements[iKey].add(newModelElement);
+        library.packageGraph.allInheritableElements
+            .putIfAbsent(iKey, () => {})
+            .add(newModelElement);
       }
     }
   }
@@ -557,10 +558,9 @@
         preferredClass: preferredClass);
   }
 
+  ModelElement _canonicalModelElement;
   // Returns the canonical ModelElement for this ModelElement, or null
   // if there isn't one.
-  ModelElement _canonicalModelElement;
-
   ModelElement get canonicalModelElement =>
       _canonicalModelElement ??= buildCanonicalModelElement();
 
diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart
index 2b39258..7f4ad89 100644
--- a/lib/src/model/package_graph.dart
+++ b/lib/src/model/package_graph.dart
@@ -184,13 +184,13 @@
     return _extensions;
   }
 
-  Map<String, Set<ModelElement>> _findRefElementCache;
-  Map<String, Set<ModelElement>> get findRefElementCache {
+  HashMap<String, Set<ModelElement>> _findRefElementCache;
+  HashMap<String, Set<ModelElement>> get findRefElementCache {
     if (_findRefElementCache == null) {
       assert(packageGraph.allLibrariesAdded);
-      _findRefElementCache = {};
+      _findRefElementCache = HashMap<String, Set<ModelElement>>();
       for (final modelElement
-          in utils.filterNonDocumented(packageGraph.allLocalModelElements)) {
+          in utils.filterHasCanonical(packageGraph.allModelElements)) {
         _findRefElementCache.putIfAbsent(
             modelElement.fullyQualifiedNameWithoutLibrary, () => {});
         _findRefElementCache.putIfAbsent(
@@ -210,15 +210,15 @@
   PackageWarningCounter _packageWarningCounter;
 
   /// All ModelElements constructed for this package; a superset of [allModelElements].
-  final Map<Tuple3<Element, Library, Container>, ModelElement>
-      allConstructedModelElements = {};
+  final allConstructedModelElements =
+      HashMap<Tuple3<Element, Library, Container>, ModelElement>();
 
   /// Anything that might be inheritable, place here for later lookup.
-  final Map<Tuple2<Element, Library>, Set<ModelElement>>
-      allInheritableElements = {};
+  final allInheritableElements =
+      HashMap<Tuple2<Element, Library>, Set<ModelElement>>();
 
   /// A mapping of the list of classes which implement each class.
-  final Map<Class, List<Class>> _implementors = LinkedHashMap(
+  final _implementors = LinkedHashMap<Class, List<Class>>(
       equals: (Class a, Class b) => a.definingClass == b.definingClass,
       hashCode: (Class class_) => class_.definingClass.hashCode);
 
@@ -530,8 +530,9 @@
           referredFrom: <Locatable>[topLevelLibrary]);
       return;
     }
-    _libraryElementReexportedBy.putIfAbsent(libraryElement, () => {});
-    _libraryElementReexportedBy[libraryElement].add(topLevelLibrary);
+    _libraryElementReexportedBy
+        .putIfAbsent(libraryElement, () => {})
+        .add(topLevelLibrary);
     for (var exportedElement in libraryElement.exports) {
       _tagReexportsFor(
           topLevelLibrary, exportedElement.exportedLibrary, exportedElement);
diff --git a/lib/src/model_utils.dart b/lib/src/model_utils.dart
index 77a1432..3890d27 100644
--- a/lib/src/model_utils.dart
+++ b/lib/src/model_utils.dart
@@ -73,6 +73,11 @@
   return null;
 }
 
+Iterable<T> filterHasCanonical<T extends ModelElement>(
+    Iterable<T> maybeHasCanonicalItems) {
+  return maybeHasCanonicalItems.where((me) => me.canonicalModelElement != null);
+}
+
 /// Remove elements that aren't documented.
 Iterable<T> filterNonDocumented<T extends Documentable>(
     Iterable<T> maybeDocumentedItems) {
diff --git a/lib/src/package_config_provider.dart b/lib/src/package_config_provider.dart
index 3221aa4..845137e 100644
--- a/lib/src/package_config_provider.dart
+++ b/lib/src/package_config_provider.dart
@@ -27,8 +27,9 @@
   final _packageConfigData = <String, List<package_config.Package>>{};
 
   void addPackageToConfigFor(String location, String name, Uri root) {
-    _packageConfigData.putIfAbsent(location, () => []);
-    _packageConfigData[location].add(package_config.Package(name, root));
+    _packageConfigData
+        .putIfAbsent(location, () => [])
+        .add(package_config.Package(name, root));
   }
 
   @override
diff --git a/lib/src/warnings.dart b/lib/src/warnings.dart
index b8a5756..de96ee8 100644
--- a/lib/src/warnings.dart
+++ b/lib/src/warnings.dart
@@ -482,8 +482,7 @@
       errorCount += 1;
     }
     var warningData = Tuple2<PackageWarning, String>(kind, message);
-    countedWarnings.putIfAbsent(element?.element, () => {});
-    countedWarnings[element?.element].add(warningData);
+    countedWarnings.putIfAbsent(element?.element, () => {}).add(warningData);
     _writeWarning(kind, warningMode, config.verboseWarnings,
         element?.fullyQualifiedName, fullMessage);
   }
diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart
index 8fc70e9..9b3eda5 100644
--- a/test/end2end/model_test.dart
+++ b/test/end2end/model_test.dart
@@ -15,6 +15,7 @@
 import 'package:dartdoc/src/render/parameter_renderer.dart';
 import 'package:dartdoc/src/render/typedef_renderer.dart';
 import 'package:dartdoc/src/special_elements.dart';
+import 'package:dartdoc/src/tuple.dart';
 import 'package:dartdoc/src/warnings.dart';
 import 'package:test/test.dart';
 
@@ -882,6 +883,24 @@
         docsAsHtml = doAwesomeStuff.documentationAsHtml;
       });
 
+      test('Verify links to inherited members inside class', () {
+        expect(
+            docsAsHtml,
+            contains(
+                '<a href="${HTMLBASE_PLACEHOLDER}fake/ImplicitProperties/forInheriting.html">ClassWithUnusualProperties.forInheriting</a>'));
+        expect(
+            docsAsHtml,
+            contains(
+                '<a href="%%__HTMLBASE_dartdoc_internal__%%reexport_two/BaseReexported/action.html">ExtendedBaseReexported.action</a></p>'));
+        var doAwesomeStuffWarnings = packageGraph.packageWarningCounter
+                .countedWarnings[doAwesomeStuff.element] ??
+            [];
+        expect(
+            doAwesomeStuffWarnings,
+            isNot(anyElement((Tuple2<PackageWarning, String> e) =>
+                e.item1 == PackageWarning.ambiguousDocReference)));
+      });
+
       test('can handle renamed imports', () {
         var aFunctionUsingRenamedLib = fakeLibrary.functions
             .firstWhere((f) => f.name == 'aFunctionUsingRenamedLib');
diff --git a/testing/test_package/lib/fake.dart b/testing/test_package/lib/fake.dart
index 7aace41..6cccca1 100644
--- a/testing/test_package/lib/fake.dart
+++ b/testing/test_package/lib/fake.dart
@@ -858,6 +858,7 @@
 /// {@subCategory Things and Such}
 /// {@image https://flutter.io/images/catalog-widget-placeholder.png}
 /// {@samples https://flutter.io}
+///
 class BaseForDocComments {
   /// Takes a [value] and returns a String.
   ///
@@ -899,6 +900,10 @@
   ///
   /// Reference to something that doesn't exist containing a type parameter [ThisIsNotHereNoWay<MyType>]
   ///
+  /// Reference to an inherited member: [ClassWithUnusualProperties.forInheriting]
+  ///
+  /// Reference to an inherited member in another library via class name: [ExtendedBaseReexported.action]
+  ///
   /// Link to a nonexistent file (erroneously expects base href): [link](SubForDocComments/localMethod.html)
   ///
   /// Link to an existing file: [link](../SubForDocComments/localMethod.html)
diff --git a/testing/test_package/lib/src/somelib.dart b/testing/test_package/lib/src/somelib.dart
index 697ec1f..921d4ee 100644
--- a/testing/test_package/lib/src/somelib.dart
+++ b/testing/test_package/lib/src/somelib.dart
@@ -8,6 +8,13 @@
 
 class AUnicornClass {}
 
+class BaseReexported {
+  String action;
+}
+
+class ExtendedBaseReexported extends BaseReexported {
+}
+
 /// A private extension.
 extension _Unseen on Object {
   void doYouSeeMe() { }
@@ -21,4 +28,4 @@
 /// [_Unseen] is not seen, but [DocumentMe] is.
 extension DocumentThisExtensionOnce on String {
   String get reportOnString => '$this is wonderful';
-}
\ No newline at end of file
+}