Add a list of potentially applicable extensions to every class page (#2053)

* Fix discovery

* Add tests

* Applicable extensions

* Review comments

* Add 'AvailableExtensions' to class pages

* remove some unfinished bits, dartfmt

* Works

* Cleaned up

* dartfmt

* Review comments
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 4be99f7..a02b983 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -22,6 +22,7 @@
         InstanceCreationExpression;
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/dart/element/type_system.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';
@@ -35,12 +36,14 @@
 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/element/type.dart' show InterfaceTypeImpl;
 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';
@@ -793,6 +796,20 @@
     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);
+    }
+    return _potentiallyApplicableExtensions;
+  }
+
   Iterable<Method> get allInstanceMethods =>
       quiver.concat([instanceMethods, inheritedMethods]);
 
@@ -951,7 +968,8 @@
       hasAnnotations ||
       hasPublicInterfaces ||
       hasPublicSuperChainReversed ||
-      hasPublicImplementors;
+      hasPublicImplementors ||
+      hasPotentiallyApplicableExtensions;
 
   @override
   bool get hasPublicOperators =>
@@ -1326,6 +1344,28 @@
         ElementType.from(_extension.extendedType, library, packageGraph);
   }
 
+  /// Returns [true] if there is an instantiation of [c] to which this extension
+  /// could be applied.
+  bool couldApplyTo(Class c) =>
+      _couldApplyTo(extendedType.type, c.element, packageGraph.typeSystem);
+
+  static bool _couldApplyTo(
+      DartType extendedType, ClassElement element, Dart2TypeSystem typeSystem) {
+    InterfaceTypeImpl classInstantiated =
+        typeSystem.instantiateToBounds(element.thisType);
+    classInstantiated = element.instantiate(
+        typeArguments: classInstantiated.typeArguments.map((a) {
+          if (a.isDynamic) {
+            return typeSystem.typeProvider.neverType;
+          }
+          return a;
+        }).toList(),
+        nullabilitySuffix: classInstantiated.nullabilitySuffix);
+
+    return (classInstantiated.element == extendedType.element) ||
+        typeSystem.isSubtypeOf(classInstantiated, extendedType);
+  }
+
   @override
   ModelElement get enclosingElement => library;
 
@@ -2371,11 +2411,8 @@
 
     // Initialize the list of elements defined in this library and
     // exported via its export directives.
-    Set<Element> exportedAndLocalElements = _libraryElement
-        .exportNamespace
-        .definedNames
-        .values
-        .toSet();
+    Set<Element> exportedAndLocalElements =
+        _libraryElement.exportNamespace.definedNames.values.toSet();
     // TODO(jcollins-g): Consider switch to [_libraryElement.topLevelElements].
     exportedAndLocalElements
         .addAll(getDefinedElements(_libraryElement.definingCompilationUnit));
@@ -2403,6 +2440,8 @@
 
   List<String> _allOriginalModelElementNames;
 
+  bool get isInSdk => _libraryElement.isInSdk;
+
   final Package _package;
 
   @override
@@ -4967,7 +5006,7 @@
 
 class PackageGraph {
   PackageGraph.UninitializedPackageGraph(
-      this.config, this.driver, this.sdk, this.hasEmbedderSdk)
+      this.config, this.driver, this.typeSystem, this.sdk, this.hasEmbedderSdk)
       : packageMeta = config.topLevelPackageMeta,
         session = driver.currentSession {
     _packageWarningCounter = PackageWarningCounter(this);
@@ -5023,10 +5062,14 @@
       package._libraries.sort((a, b) => compareNatural(a.name, b.name));
       package._libraries.forEach((library) {
         library.allClasses.forEach(_addToImplementors);
+        // TODO(jcollins-g): Use a better data structure.
+        _extensions.addAll(library.extensions);
       });
     });
     _implementors.values.forEach((l) => l.sort());
     allImplementorsAdded = true;
+    _extensions.sort(byName);
+    allExtensionsAdded = true;
 
     // We should have found all special classes by now.
     specialClasses.assertSpecials();
@@ -5080,15 +5123,24 @@
 
   SpecialClasses specialClasses;
 
-  /// It is safe to cache values derived from the _implementors table if this
+  /// 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 {
@@ -5126,6 +5178,10 @@
   /// 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.
+  // TODO(jcollins-g): Consider implementing a smarter structure for this.
+  final List<Extension> _extensions = List();
+
   /// PackageMeta for the default package.
   final PackageMeta packageMeta;
 
@@ -5156,6 +5212,7 @@
   /// TODO(brianwilkerson) Replace the driver with the session.
   final AnalysisDriver driver;
   final AnalysisSession session;
+  final TypeSystem typeSystem;
   final DartSdk sdk;
 
   Map<Source, SdkLibrary> _sdkLibrarySources;
@@ -6818,7 +6875,11 @@
     }
 
     PackageGraph newGraph = PackageGraph.UninitializedPackageGraph(
-        config, driver, sdk, hasEmbedderSdkFiles);
+        config,
+        driver,
+        await driver.currentSession.typeSystem,
+        sdk,
+        hasEmbedderSdkFiles);
     await getLibraries(newGraph);
     await newGraph.initializePackageGraph();
     return newGraph;
diff --git a/lib/templates/class.html b/lib/templates/class.html
index a9adc45..ae308d6 100644
--- a/lib/templates/class.html
+++ b/lib/templates/class.html
@@ -57,6 +57,15 @@
         </ul></dd>
         {{/hasPublicImplementors}}
 
+        {{#hasPotentiallyApplicableExtensions}}
+        <dt>Available Extensions</dt>
+        <dd><ul class="comma-separated clazz-relationships">
+          {{#potentiallyApplicableExtensions}}
+          <li>{{{linkedName}}}</li>
+          {{/potentiallyApplicableExtensions}}
+        </ul></dd>
+        {{/hasPotentiallyApplicableExtensions}}
+
         {{#hasAnnotations}}
         <dt>Annotations</dt>
         <dd><ul class="annotation-list clazz-relationships">
diff --git a/lib/templates/extension.html b/lib/templates/extension.html
index 9fdee1c..e722cc7 100644
--- a/lib/templates/extension.html
+++ b/lib/templates/extension.html
@@ -13,16 +13,18 @@
 
     {{#extension}}
     {{>documentation}}
-
-    <dt>on</dt>
-    <dd>
-        <ul class="comma-separated clazz-relationships">
+    <section>
+        <dl class="dl-horizontal">
+        <dt>on</dt>
+        <dd>
+            <ul class="comma-separated clazz-relationships">
             {{#extendedType}}
             <li>{{{linkedName}}}</li>
             {{/extendedType}}
-        </ul>
-    </dd>
-
+            </ul>
+        </dd>
+        </dl>
+    </section>
 
     {{#hasPublicProperties}}
     <section class="summary offset-anchor" id="instance-properties">
diff --git a/pubspec.yaml b/pubspec.yaml
index fbe82be..bc50ce3 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -5,7 +5,7 @@
 description: A documentation generator for Dart.
 homepage: https://github.com/dart-lang/dartdoc
 environment:
-  sdk: '>=2.1.0 <3.0.0'
+  sdk: '>=2.2.0 <3.0.0'
 
 dependencies:
   analyzer: ^0.39.0
diff --git a/test/model_test.dart b/test/model_test.dart
index 42317ce..43a8876 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -2125,10 +2125,17 @@
   });
 
   group('Extension', () {
-    Extension ext, fancyList;
+    Extension arm, leg, ext, fancyList, uphill;
     Extension documentOnceReexportOne, documentOnceReexportTwo;
     Library reexportOneLib, reexportTwoLib;
-    Class extensionReferencer;
+    Class apple,
+        anotherExtended,
+        baseTest,
+        bigAnotherExtended,
+        extensionReferencer,
+        megaTron,
+        superMegaTron,
+        string;
     Method doSomeStuff, doStuff, s;
     List<Extension> extensions;
 
@@ -2141,7 +2148,11 @@
           .firstWhere((e) => e.name == 'DocumentThisExtensionOnce');
       documentOnceReexportTwo = reexportTwoLib.extensions
           .firstWhere((e) => e.name == 'DocumentThisExtensionOnce');
-
+      string = packageGraph.allLibraries.values
+          .firstWhere((e) => e.name == 'dart:core')
+          .allClasses
+          .firstWhere((c) => c.name == 'String');
+      apple = exLibrary.classes.firstWhere((e) => e.name == 'Apple');
       ext = exLibrary.extensions.firstWhere((e) => e.name == 'AppleExtension');
       extensionReferencer =
           exLibrary.classes.firstWhere((c) => c.name == 'ExtensionReferencer');
@@ -2155,6 +2166,17 @@
           .instanceMethods
           .firstWhere((m) => m.name == 'doStuff');
       extensions = exLibrary.publicExtensions.toList();
+      baseTest = fakeLibrary.classes.firstWhere((e) => e.name == 'BaseTest');
+      bigAnotherExtended =
+          fakeLibrary.classes.firstWhere((e) => e.name == 'BigAnotherExtended');
+      anotherExtended =
+          fakeLibrary.classes.firstWhere((e) => e.name == 'AnotherExtended');
+      arm = fakeLibrary.extensions.firstWhere((e) => e.name == 'Arm');
+      leg = fakeLibrary.extensions.firstWhere((e) => e.name == 'Leg');
+      uphill = fakeLibrary.extensions.firstWhere((e) => e.name == 'Uphill');
+      megaTron = fakeLibrary.classes.firstWhere((e) => e.name == 'Megatron');
+      superMegaTron =
+          fakeLibrary.classes.firstWhere((e) => e.name == 'SuperMegaTron');
     });
 
     test('basic canonicalization for extensions', () {
@@ -2164,9 +2186,28 @@
       expect(documentOnceReexportTwo.isCanonical, isTrue);
     });
 
+    test('classes know about applicableExtensions', () {
+      expect(apple.potentiallyApplicableExtensions, orderedEquals([ext]));
+      expect(string.potentiallyApplicableExtensions,
+          isNot(contains(documentOnceReexportOne)));
+      expect(string.potentiallyApplicableExtensions,
+          contains(documentOnceReexportTwo));
+      expect(baseTest.potentiallyApplicableExtensions, isEmpty);
+      expect(anotherExtended.potentiallyApplicableExtensions,
+          orderedEquals([uphill]));
+      expect(bigAnotherExtended.potentiallyApplicableExtensions,
+          orderedEquals([uphill]));
+    });
+
+    test('type parameters and bounds work with applicableExtensions', () {
+      expect(
+          superMegaTron.potentiallyApplicableExtensions, orderedEquals([leg]));
+      expect(
+          megaTron.potentiallyApplicableExtensions, orderedEquals([arm, leg]));
+    });
+
     // TODO(jcollins-g): implement feature and update tests
     test('documentation links do not crash in base cases', () {
-
       packageGraph.packageWarningCounter.hasWarning(
           doStuff,
           PackageWarning.notImplemented,
diff --git a/testing/test_package/lib/fake.dart b/testing/test_package/lib/fake.dart
index 5e828e7..332b86b 100644
--- a/testing/test_package/lib/fake.dart
+++ b/testing/test_package/lib/fake.dart
@@ -990,6 +990,38 @@
   }
 }
 
+//
+// Test classes for extension discovery.
+//
+
+extension Arm on Megatron<int> {
+  bool get hasLeftArm => true;
+}
+
+extension Leg on Megatron<String> {
+  bool get hasRightLeg => true;
+}
+
+class Megatron<T> {}
+
+class SuperMegaTron<T extends String> extends Megatron<String> {}
+
+extension Uphill on AnotherExtended<SubclassBaseTest> {
+  bool get hasDirection => false;
+}
+
+class SubclassBaseTest extends BaseTest {}
+
+class BaseTest {}
+
+class AnotherExtended<T extends BaseTest> extends BaseTest {}
+
+class BigAnotherExtended extends AnotherExtended<SubclassBaseTest> {}
+
+//
+//
+//
+
 /// Test an edge case for cases where inherited ExecutableElements can come
 /// both from private classes and public interfaces.  The test makes sure the
 /// class still takes precedence (#1561).
diff --git a/tool/grind.dart b/tool/grind.dart
index 2f05c76..eb229f5 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -486,6 +486,7 @@
         'examples',
         '--include-source',
         '--json',
+        '--link-to-remote',
         '--pretty-index-json',
       ]..addAll(extraDartdocParameters),
       workingDirectory: testPackage.absolute.path);