Repository: dart-lang/dartdoc (dart)

Issue:
In "implementers" list, include `extension type` implementers along with classes and mixins
See `TypeData` - https://api.dart.dev/main/4ddc4a9e8d1a13aa50bac280669e4334e39a0b57/dart-typed_data/TypedData-class.html

You see all of the implementers.

See `JSTypedArray` - https://api.dart.dev/main/4ddc4a9e8d1a13aa50bac280669e4334e39a0b57/dart-js_interop/JSTypedArray-extension-type.html

```dart
/// The Javascript `Int8Array`.
@JS('Int8Array')
extension type JSInt8Array._(JSInt8ArrayRepType _jsInt8Array)
    implements JSTypedArray {}
```

I'd expect to see `implementers`, too!

Pull request:
We already display the extension types that "implement" a class; this adds an "Implementers" section to the extension type page.

Also, change "implementor" to "implementer" in every case I could find.

Fixes https://github.com/dart-lang/dartdoc/issues/3656

---

- [x] I’ve reviewed the contributor guide and applied the relevant portions to this PR.

<details>
  <summary>Contribution guidelines:</summary><br>

- See our [contributor guide](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md) for general expectations for PRs.
- Larger or significant changes should be discussed in an issue before creating a PR.
- Contributions to our repos should follow the [Dart style guide](https://dart.dev/guides/language/effective-dart) and use `dart format`.
- Most changes should add an entry to the changelog and may need to [rev the pubspec package version](https://github.com/dart-lang/sdk/wiki/External-Package-Maintenance#making-a-change).
- Changes to packages require [corresponding tests](https://github.com/dart-lang/.github/blob/main/CONTRIBUTING.md#Testing).

Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.
</details>

Patch:
diff --git a/lib/src/generator/templates.aot_renderers_for_html.dart b/lib/src/generator/templates.aot_renderers_for_html.dart
index e57ebbd9b8..203c8e3756 100644
--- a/lib/src/generator/templates.aot_renderers_for_html.dart
+++ b/lib/src/generator/templates.aot_renderers_for_html.dart
@@ -322,12 +322,12 @@ String renderClass(ClassTemplateData context0) {
     buffer.write('\n        ');
     buffer.write(_renderClass_partial_mixed_in_types_7(context2));
     buffer.writeln();
-    if (context2.hasPublicImplementors) {
+    if (context2.hasPublicImplementers) {
       buffer.writeln();
       buffer.write('''
           <dt>Implementers</dt>
           <dd><ul class="comma-separated clazz-relationships">''');
-      var context3 = context2.publicImplementorsSorted;
+      var context3 = context2.publicImplementersSorted;
       for (var context4 in context3) {
         buffer.writeln();
         buffer.write('''
@@ -865,6 +865,24 @@ String renderExtensionType<T extends ExtensionType>(
         ''');
   buffer.write(_renderExtensionType_partial_interfaces_5(context2));
   buffer.writeln();
+  if (context2.hasPublicImplementers) {
+    buffer.writeln();
+    buffer.write('''
+          <dt>Implementers</dt>
+          <dd><ul class="comma-separated clazz-relationships">''');
+    var context4 = context2.publicImplementersSorted;
+    for (var context5 in context4) {
+      buffer.writeln();
+      buffer.write('''
+              <li>''');
+      buffer.write(context5.linkedName);
+      buffer.write('''</li>''');
+    }
+    buffer.writeln();
+    buffer.write('''
+          </ul></dd>''');
+  }
+  buffer.writeln();
   buffer.write('''
       </dl>
       ''');
@@ -883,10 +901,10 @@ String renderExtensionType<T extends ExtensionType>(
         <h2>Properties</h2>
 
         <dl class="properties">''');
-    var context4 = context2.publicInstanceFieldsSorted;
-    for (var context5 in context4) {
+    var context6 = context2.publicInstanceFieldsSorted;
+    for (var context7 in context6) {
       buffer.write('\n            ');
-      buffer.write(_renderExtensionType_partial_property_8(context5));
+      buffer.write(_renderExtensionType_partial_property_8(context7));
     }
     buffer.writeln();
     buffer.write('''
@@ -1528,13 +1546,13 @@ String renderMixin(MixinTemplateData context0) {
     buffer.write('\n        ');
     buffer.write(_renderMixin_partial_interfaces_6(context2));
     buffer.writeln();
-    if (context2.hasPublicImplementors) {
+    if (context2.hasPublicImplementers) {
       buffer.writeln();
       buffer.write('''
           <dt>Mixin Applications</dt>
           <dd>
             <ul class="comma-separated mixin-relationships">''');
-      var context5 = context2.publicImplementorsSorted;
+      var context5 = context2.publicImplementersSorted;
       for (var context6 in context5) {
         buffer.writeln();
         buffer.write('''
diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart
index 4d784f34bc..ffa4ebe3c9 100644
--- a/lib/src/generator/templates.runtime_renderers.dart
+++ b/lib/src/generator/templates.runtime_renderers.dart
@@ -15225,12 +15225,12 @@ class _Renderer_TypeImplementing extends RendererBase<TypeImplementing> {
                       self.renderSimpleVariable(c, remainingNames, 'bool'),
                   getBool: (CT_ c) => c.hasModifiers,
                 ),
-                'hasPublicImplementors': Property(
-                  getValue: (CT_ c) => c.hasPublicImplementors,
+                'hasPublicImplementers': Property(
+                  getValue: (CT_ c) => c.hasPublicImplementers,
                   renderVariable: (CT_ c, Property<CT_> self,
                           List<String> remainingNames) =>
                       self.renderSimpleVariable(c, remainingNames, 'bool'),
-                  getBool: (CT_ c) => c.hasPublicImplementors,
+                  getBool: (CT_ c) => c.hasPublicImplementers,
                 ),
                 'hasPublicInterfaces': Property(
                   getValue: (CT_ c) => c.hasPublicInterfaces,
@@ -15265,28 +15265,28 @@ class _Renderer_TypeImplementing extends RendererBase<TypeImplementing> {
                         parent: r));
                   },
                 ),
-                'publicImplementors': Property(
-                  getValue: (CT_ c) => c.publicImplementors,
+                'publicImplementers': Property(
+                  getValue: (CT_ c) => c.publicImplementers,
                   renderVariable: (CT_ c, Property<CT_> self,
                           List<String> remainingNames) =>
                       self.renderSimpleVariable(
                           c, remainingNames, 'Iterable<InheritingContainer>'),
                   renderIterable: (CT_ c, RendererBase<CT_> r,
                       List<MustachioNode> ast, StringSink sink) {
-                    return c.publicImplementors.map((e) =>
+                    return c.publicImplementers.map((e) =>
                         _render_InheritingContainer(e, ast, r.template, sink,
                             parent: r));
                   },
                 ),
-                'publicImplementorsSorted': Property(
-                  getValue: (CT_ c) => c.publicImplementorsSorted,
+                'publicImplementersSorted': Property(
+                  getValue: (CT_ c) => c.publicImplementersSorted,
                   renderVariable: (CT_ c, Property<CT_> self,
                           List<String> remainingNames) =>
                       self.renderSimpleVariable(
                           c, remainingNames, 'List<InheritingContainer>'),
                   renderIterable: (CT_ c, RendererBase<CT_> r,
                       List<MustachioNode> ast, StringSink sink) {
-                    return c.publicImplementorsSorted.map((e) =>
+                    return c.publicImplementersSorted.map((e) =>
                         _render_InheritingContainer(e, ast, r.template, sink,
                             parent: r));
                   },
@@ -16846,7 +16846,7 @@ const _invisibleGetters = {
     'allConstructedModelElements',
     'allExtensionsAdded',
     'allHrefs',
-    'allImplementorsAdded',
+    'allImplementersAdded',
     'allInheritableElements',
     'allLibraries',
     'allLibrariesAdded',
@@ -16862,7 +16862,7 @@ const _invisibleGetters = {
     'hasEmbedderSdk',
     'hasFooterVersion',
     'hashCode',
-    'implementors',
+    'implementers',
     'inheritThrough',
     'inheritanceManager',
     'libraries',
diff --git a/lib/src/model/inheriting_container.dart b/lib/src/model/inheriting_container.dart
index fca8dd0038..5ca54ac93a 100644
--- a/lib/src/model/inheriting_container.dart
+++ b/lib/src/model/inheriting_container.dart
@@ -503,14 +503,14 @@ mixin TypeImplementing on InheritingContainer {
       getTypeFor(interface, library) as DefinedElementType
   ];
 
-  late final List<InheritingContainer> publicImplementorsSorted =
-      publicImplementors.toList(growable: false)..sort(byName);
+  late final List<InheritingContainer> publicImplementersSorted =
+      publicImplementers.toList(growable: false)..sort(byName);
 
   @override
   bool get hasModifiers =>
-      super.hasModifiers || hasPublicInterfaces || hasPublicImplementors;
+      super.hasModifiers || hasPublicInterfaces || hasPublicImplementers;
 
-  bool get hasPublicImplementors => publicImplementors.isNotEmpty;
+  bool get hasPublicImplementers => publicImplementers.isNotEmpty;
 
   bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;
 
@@ -522,34 +522,34 @@ mixin TypeImplementing on InheritingContainer {
           interface.modelElement as InheritingContainer,
       ];
 
-  /// All the "immediate" public implementors of this [TypeImplementing].
+  /// All the "immediate" public implementers of this [TypeImplementing].
   ///
   /// For a [Mixin], this is actually the mixin applications using the [Mixin].
   ///
-  /// If this [InheritingContainer] has a private implementor, then that is
-  /// counted as a proxy for any public implementors of that private container.
-  Iterable<InheritingContainer> get publicImplementors {
+  /// If this [InheritingContainer] has a private implementer, then that is
+  /// counted as a proxy for any public implementers of that private container.
+  Iterable<InheritingContainer> get publicImplementers {
     var result = <InheritingContainer>{};
     var seen = <InheritingContainer>{};
 
-    // Recursively adds [implementor] if public, or the implementors of
-    // [implementor] if not.
-    void addToResult(InheritingContainer implementor) {
-      if (seen.contains(implementor)) return;
-      seen.add(implementor);
-      if (implementor.isPublicAndPackageDocumented) {
-        result.add(implementor);
+    // Recursively adds [implementer] if public, or the implementers of
+    // [implementer] if not.
+    void addToResult(InheritingContainer implementer) {
+      if (seen.contains(implementer)) return;
+      seen.add(implementer);
+      if (implementer.isPublicAndPackageDocumented) {
+        result.add(implementer);
       } else {
-        var implementors = packageGraph.implementors[implementor];
-        if (implementors != null) {
-          model_utils.findCanonicalFor(implementors).forEach(addToResult);
+        var implementers = packageGraph.implementers[implementer];
+        if (implementers != null) {
+          model_utils.findCanonicalFor(implementers).forEach(addToResult);
         }
       }
     }
 
-    var immediateImplementors = packageGraph.implementors[this];
-    if (immediateImplementors != null) {
-      model_utils.findCanonicalFor(immediateImplementors).forEach(addToResult);
+    var immediateImplementers = packageGraph.implementers[this];
+    if (immediateImplementers != null) {
+      model_utils.findCanonicalFor(immediateImplementers).forEach(addToResult);
     }
     return result;
   }
diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart
index e6168f1f7b..102cb2e397 100644
--- a/lib/src/model/package_graph.dart
+++ b/lib/src/model/package_graph.dart
@@ -145,7 +145,7 @@ class PackageGraph with CommentReferable, Nameable {
         package.warn(PackageWarning.noDocumentableLibrariesInPackage);
       }
     }
-    allImplementorsAdded = true;
+    allImplementersAdded = true;
     allExtensionsAdded = true;
 
     // We should have found all special classes by now.
@@ -280,17 +280,17 @@ class PackageGraph with CommentReferable, Nameable {
 
   ModelNode? getModelNodeFor(Element element) => _modelNodes[element];
 
-  /// It is safe to cache values derived from the [_implementors] table if this
+  /// It is safe to cache values derived from the [_implementers] table if this
   /// is true.
-  bool allImplementorsAdded = false;
+  bool allImplementersAdded = false;
 
   /// It is safe to cache values derived from the [_extensions] table if this
   /// is true.
   bool allExtensionsAdded = false;
 
-  Map<InheritingContainer, List<InheritingContainer>> get implementors {
-    assert(allImplementorsAdded);
-    return _implementors;
+  Map<InheritingContainer, List<InheritingContainer>> get implementers {
+    assert(allImplementersAdded);
+    return _implementers;
   }
 
   Iterable<Extension> get documentedExtensions =>
@@ -323,8 +323,12 @@ class PackageGraph with CommentReferable, Nameable {
   final Map<InheritableElementsKey, Set<ModelElement>> allInheritableElements =
       {};
 
-  /// A mapping of the list of classes which implement each class.
-  final Map<InheritingContainer, List<InheritingContainer>> _implementors =
+  /// A mapping of the list of classes, enums, mixins, and extension types which
+  /// "implement" each class, mixin, and extension type.
+  ///
+  /// For the purposes of the "Implementers" section in the output, this
+  /// includes elements that "implement" or "extend" another element.
+  final Map<InheritingContainer, List<InheritingContainer>> _implementers =
       LinkedHashMap<InheritingContainer, List<InheritingContainer>>(
           equals: (InheritingContainer a, InheritingContainer b) =>
               a.definingContainer == b.definingContainer,
@@ -575,7 +579,7 @@ class PackageGraph with CommentReferable, Nameable {
   }
 
   void _addToImplementers(Iterable<InheritingContainer> containers) {
-    assert(!allImplementorsAdded);
+    assert(!allImplementersAdded);
 
     // Private containers may not be included in documentation, but may still be
     // necessary links in the implementation chain. They are added here as they
@@ -589,7 +593,7 @@ class PackageGraph with CommentReferable, Nameable {
       }
       implemented = implemented.canonicalModelElement as InheritingContainer? ??
           implemented;
-      var list = _implementors.putIfAbsent(implemented, () => []);
+      var list = _implementers.putIfAbsent(implemented, () => []);
       // TODO(srawlins): This would be more efficient if we created a
       // SplayTreeSet keyed off of `.element`.
       if (!list.any((l) => l.element == implementer.element)) {
diff --git a/lib/templates/class.html b/lib/templates/class.html
index 4431de77f9..8dfe36f107 100644
--- a/lib/templates/class.html
+++ b/lib/templates/class.html
@@ -19,14 +19,14 @@
         {{ >interfaces }}
         {{ >mixed_in_types }}
 
-        {{ #hasPublicImplementors }}
+        {{ #hasPublicImplementers }}
           <dt>Implementers</dt>
           <dd><ul class="comma-separated clazz-relationships">
-            {{ #publicImplementorsSorted }}
+            {{ #publicImplementersSorted }}
               <li>{{{ linkedName }}}</li>
-            {{ /publicImplementorsSorted }}
+            {{ /publicImplementersSorted }}
           </ul></dd>
-        {{ /hasPublicImplementors }}
+        {{ /hasPublicImplementers }}
 
         {{ #hasPotentiallyApplicableExtensions }}
           <dt>Available Extensions</dt>
diff --git a/lib/templates/extension_type.html b/lib/templates/extension_type.html
index 4e65170e22..121bd6b971 100644
--- a/lib/templates/extension_type.html
+++ b/lib/templates/extension_type.html
@@ -22,6 +22,15 @@
           </ul>
         </dd>
         {{ >interfaces }}
+
+        {{ #hasPublicImplementers }}
+          <dt>Implementers</dt>
+          <dd><ul class="comma-separated clazz-relationships">
+            {{ #publicImplementersSorted }}
+              <li>{{{ linkedName }}}</li>
+            {{ /publicImplementersSorted }}
+          </ul></dd>
+        {{ /hasPublicImplementers }}
       </dl>
       {{ >container_annotations }}
     </section>
diff --git a/lib/templates/mixin.html b/lib/templates/mixin.html
index 5fb8614faf..e9c1e29f1b 100644
--- a/lib/templates/mixin.html
+++ b/lib/templates/mixin.html
@@ -27,16 +27,16 @@
         {{ >super_chain }}
         {{ >interfaces }}
 
-        {{ #hasPublicImplementors }}
+        {{ #hasPublicImplementers }}
           <dt>Mixin Applications</dt>
           <dd>
             <ul class="comma-separated mixin-relationships">
-              {{ #publicImplementorsSorted }}
+              {{ #publicImplementersSorted }}
               <li>{{{ linkedName }}}</li>
-              {{ /publicImplementorsSorted }}
+              {{ /publicImplementersSorted }}
             </ul>
           </dd>
-        {{ /hasPublicImplementors }}
+        {{ /hasPublicImplementers }}
 
         {{ >annotations }}
       </dl>


Test patch:
diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart
index c1772cfe0a..60112e61ea 100644
--- a/test/end2end/model_test.dart
+++ b/test/end2end/model_test.dart
@@ -1695,9 +1695,9 @@ void main() async {
               .publicInterfaces.first.modelElement,
           equals(ImplementBase));
 
-      expect(ImplementBase.publicImplementors,
+      expect(ImplementBase.publicImplementers,
           contains(ImplementerOfDeclaredPrivateClasses));
-      expect(ImplementBase.publicImplementors, contains(ImplementerOfThings));
+      expect(ImplementBase.publicImplementers, contains(ImplementerOfThings));
     });
 
     test('Overrides from intermediate abstract classes are picked up correctly',
@@ -1801,9 +1801,9 @@ void main() async {
       expect(ThingToImplementInMixin.hasModifiers, isTrue);
       expect(MixInImplementation.hasModifiers, isTrue);
       expect(MixedInImplementation.hasModifiers, isTrue);
-      expect(ThingToImplementInMixin.publicImplementors,
+      expect(ThingToImplementInMixin.publicImplementers,
           orderedEquals([MixInImplementation]));
-      expect(MixInImplementation.publicImplementors,
+      expect(MixInImplementation.publicImplementers,
           orderedEquals([MixedInImplementation]));
       expect(
           MixedInImplementation.allFields
@@ -4695,16 +4695,16 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
     setUpAll(() {
       apple = exLibrary.classes.firstWhere((c) => c.name == 'Apple');
       b = exLibrary.classes.firstWhere((c) => c.name == 'B');
-      implA = apple.publicImplementors.toList();
+      implA = apple.publicImplementers.toList();
       implC = exLibrary.classes
           .firstWhere((c) => c.name == 'Cat')
-          .publicImplementors
+          .publicImplementers
           .toList();
     });
 
     test('private classes do not break the implementor chain', () {
       var Super1 = fakeLibrary.classes.singleWhere((c) => c.name == 'Super1');
-      var publicImplementors = Super1.publicImplementors.map((i) => i.name);
+      var publicImplementors = Super1.publicImplementers.map((i) => i.name);
       expect(publicImplementors, hasLength(3));
       // A direct implementor.
       expect(publicImplementors, contains('Super4'));
@@ -4720,7 +4720,7 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
       var GenericSuperProperty = fakeLibrary.classes
           .singleWhere((c) => c.name == 'GenericSuperProperty');
       var publicImplementors =
-          GenericSuperProperty.publicImplementors.map((i) => i.name);
+          GenericSuperProperty.publicImplementers.map((i) => i.name);
       expect(publicImplementors, hasLength(1));
       // A direct implementor.
       expect(publicImplementors, contains('GenericSuperValue'));
@@ -4728,7 +4728,7 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
       var GenericSuperValue =
           fakeLibrary.classes.singleWhere((c) => c.name == 'GenericSuperValue');
       publicImplementors =
-          GenericSuperValue.publicImplementors.map((i) => i.name);
+          GenericSuperValue.publicImplementers.map((i) => i.name);
       expect(publicImplementors, hasLength(1));
       // A direct implementor.
       expect(publicImplementors, contains('GenericSuperNum'));
@@ -4736,7 +4736,7 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
       var GenericSuperNum =
           fakeLibrary.classes.singleWhere((c) => c.name == 'GenericSuperNum');
       publicImplementors =
-          GenericSuperNum.publicImplementors.map((i) => i.name);
+          GenericSuperNum.publicImplementers.map((i) => i.name);
       expect(publicImplementors, hasLength(1));
       expect(publicImplementors, contains('GenericSuperInt'));
     });
@@ -4746,7 +4746,7 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
     });
 
     test('apple has some implementors', () {
-      expect(apple.hasPublicImplementors, isTrue);
+      expect(apple.hasPublicImplementers, isTrue);
       expect(implA, isNotNull);
       expect(implA, hasLength(1));
       expect(implA[0].name, equals('B'));
@@ -4763,7 +4763,7 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
     test('B does not have implementors', () {
       expect(b, isNotNull);
       expect(b.name, equals('B'));
-      expect(b.publicImplementors, hasLength(0));
+      expect(b.publicImplementers, hasLength(0));
     });
   });
 
diff --git a/test/templates/extension_type_test.dart b/test/templates/extension_type_test.dart
index d9cb148589..266e22c88f 100644
--- a/test/templates/extension_type_test.dart
+++ b/test/templates/extension_type_test.dart
@@ -112,6 +112,21 @@ extension type Two(List<int> it) implements One<int> {}
     ]);
   }
 
+  void test_implementers() async {
+    await createPackageWithLibrary('''
+extension type One<E>(List<E> it) {}
+extension type Two(List<int> it) implements One<int> {}
+''');
+    var htmlLines = readLines(['lib', 'One-extension-type.html']);
+
+    htmlLines.expectMainContentContainsAllInOrder([
+      matches('<dt>Implementers</dt>'),
+      matches('<dd><ul class="comma-separated clazz-relationships">'),
+      matches('<li><a href="../lib/Two-extension-type.html">Two</a></li>'),
+      matches('</ul></dd>'),
+    ]);
+  }
+
   void test_constructors() async {
     await createPackageWithLibrary('''
 extension type One(int it) {