Skip to content

Commit 75b3702

Browse files
authored
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
1 parent 9528968 commit 75b3702

File tree

7 files changed

+167
-21
lines changed

7 files changed

+167
-21
lines changed

lib/src/model.dart

+70-9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'package:analyzer/dart/ast/ast.dart'
2222
InstanceCreationExpression;
2323
import 'package:analyzer/dart/element/element.dart';
2424
import 'package:analyzer/dart/element/type.dart';
25+
import 'package:analyzer/dart/element/type_system.dart';
2526
import 'package:analyzer/dart/element/visitor.dart';
2627
import 'package:analyzer/file_system/file_system.dart' as file_system;
2728
import 'package:analyzer/file_system/physical_file_system.dart';
@@ -35,12 +36,14 @@ import 'package:analyzer/src/dart/element/element.dart';
3536
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
3637
import 'package:analyzer/src/dart/element/member.dart'
3738
show ExecutableMember, Member, ParameterMember;
39+
import 'package:analyzer/src/dart/element/type.dart' show InterfaceTypeImpl;
3840
import 'package:analyzer/src/dart/sdk/sdk.dart';
3941
import 'package:analyzer/src/generated/engine.dart';
4042
import 'package:analyzer/src/generated/java_io.dart';
4143
import 'package:analyzer/src/generated/sdk.dart';
4244
import 'package:analyzer/src/generated/source.dart';
4345
import 'package:analyzer/src/generated/source_io.dart';
46+
import 'package:analyzer/src/generated/type_system.dart' show Dart2TypeSystem;
4447
import 'package:analyzer/src/source/package_map_resolver.dart';
4548
import 'package:analyzer/src/source/sdk_ext.dart';
4649
import 'package:args/args.dart';
@@ -793,6 +796,20 @@ class Class extends Container
793796
return _defaultConstructor;
794797
}
795798

799+
bool get hasPotentiallyApplicableExtensions =>
800+
potentiallyApplicableExtensions.isNotEmpty;
801+
802+
List<Extension> _potentiallyApplicableExtensions;
803+
Iterable<Extension> get potentiallyApplicableExtensions {
804+
if (_potentiallyApplicableExtensions == null) {
805+
_potentiallyApplicableExtensions = utils
806+
.filterNonDocumented(packageGraph.extensions)
807+
.where((e) => e.couldApplyTo(this))
808+
.toList(growable: false);
809+
}
810+
return _potentiallyApplicableExtensions;
811+
}
812+
796813
Iterable<Method> get allInstanceMethods =>
797814
quiver.concat([instanceMethods, inheritedMethods]);
798815

@@ -951,7 +968,8 @@ class Class extends Container
951968
hasAnnotations ||
952969
hasPublicInterfaces ||
953970
hasPublicSuperChainReversed ||
954-
hasPublicImplementors;
971+
hasPublicImplementors ||
972+
hasPotentiallyApplicableExtensions;
955973

956974
@override
957975
bool get hasPublicOperators =>
@@ -1326,6 +1344,28 @@ class Extension extends Container
13261344
ElementType.from(_extension.extendedType, library, packageGraph);
13271345
}
13281346

1347+
/// Returns [true] if there is an instantiation of [c] to which this extension
1348+
/// could be applied.
1349+
bool couldApplyTo(Class c) =>
1350+
_couldApplyTo(extendedType.type, c.element, packageGraph.typeSystem);
1351+
1352+
static bool _couldApplyTo(
1353+
DartType extendedType, ClassElement element, Dart2TypeSystem typeSystem) {
1354+
InterfaceTypeImpl classInstantiated =
1355+
typeSystem.instantiateToBounds(element.thisType);
1356+
classInstantiated = element.instantiate(
1357+
typeArguments: classInstantiated.typeArguments.map((a) {
1358+
if (a.isDynamic) {
1359+
return typeSystem.typeProvider.neverType;
1360+
}
1361+
return a;
1362+
}).toList(),
1363+
nullabilitySuffix: classInstantiated.nullabilitySuffix);
1364+
1365+
return (classInstantiated.element == extendedType.element) ||
1366+
typeSystem.isSubtypeOf(classInstantiated, extendedType);
1367+
}
1368+
13291369
@override
13301370
ModelElement get enclosingElement => library;
13311371

@@ -2371,11 +2411,8 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
23712411

23722412
// Initialize the list of elements defined in this library and
23732413
// exported via its export directives.
2374-
Set<Element> exportedAndLocalElements = _libraryElement
2375-
.exportNamespace
2376-
.definedNames
2377-
.values
2378-
.toSet();
2414+
Set<Element> exportedAndLocalElements =
2415+
_libraryElement.exportNamespace.definedNames.values.toSet();
23792416
// TODO(jcollins-g): Consider switch to [_libraryElement.topLevelElements].
23802417
exportedAndLocalElements
23812418
.addAll(getDefinedElements(_libraryElement.definingCompilationUnit));
@@ -2403,6 +2440,8 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
24032440

24042441
List<String> _allOriginalModelElementNames;
24052442

2443+
bool get isInSdk => _libraryElement.isInSdk;
2444+
24062445
final Package _package;
24072446

24082447
@override
@@ -4967,7 +5006,7 @@ class Operator extends Method {
49675006

49685007
class PackageGraph {
49695008
PackageGraph.UninitializedPackageGraph(
4970-
this.config, this.driver, this.sdk, this.hasEmbedderSdk)
5009+
this.config, this.driver, this.typeSystem, this.sdk, this.hasEmbedderSdk)
49715010
: packageMeta = config.topLevelPackageMeta,
49725011
session = driver.currentSession {
49735012
_packageWarningCounter = PackageWarningCounter(this);
@@ -5023,10 +5062,14 @@ class PackageGraph {
50235062
package._libraries.sort((a, b) => compareNatural(a.name, b.name));
50245063
package._libraries.forEach((library) {
50255064
library.allClasses.forEach(_addToImplementors);
5065+
// TODO(jcollins-g): Use a better data structure.
5066+
_extensions.addAll(library.extensions);
50265067
});
50275068
});
50285069
_implementors.values.forEach((l) => l.sort());
50295070
allImplementorsAdded = true;
5071+
_extensions.sort(byName);
5072+
allExtensionsAdded = true;
50305073

50315074
// We should have found all special classes by now.
50325075
specialClasses.assertSpecials();
@@ -5080,15 +5123,24 @@ class PackageGraph {
50805123

50815124
SpecialClasses specialClasses;
50825125

5083-
/// It is safe to cache values derived from the _implementors table if this
5126+
/// It is safe to cache values derived from the [_implementors] table if this
50845127
/// is true.
50855128
bool allImplementorsAdded = false;
50865129

5130+
/// It is safe to cache values derived from the [_extensions] table if this
5131+
/// is true.
5132+
bool allExtensionsAdded = false;
5133+
50875134
Map<String, List<Class>> get implementors {
50885135
assert(allImplementorsAdded);
50895136
return _implementors;
50905137
}
50915138

5139+
Iterable<Extension> get extensions {
5140+
assert(allExtensionsAdded);
5141+
return _extensions;
5142+
}
5143+
50925144
Map<String, Set<ModelElement>> _findRefElementCache;
50935145

50945146
Map<String, Set<ModelElement>> get findRefElementCache {
@@ -5126,6 +5178,10 @@ class PackageGraph {
51265178
/// Map of Class.href to a list of classes implementing that class
51275179
final Map<String, List<Class>> _implementors = Map();
51285180

5181+
/// A list of extensions that exist in the package graph.
5182+
// TODO(jcollins-g): Consider implementing a smarter structure for this.
5183+
final List<Extension> _extensions = List();
5184+
51295185
/// PackageMeta for the default package.
51305186
final PackageMeta packageMeta;
51315187

@@ -5156,6 +5212,7 @@ class PackageGraph {
51565212
/// TODO(brianwilkerson) Replace the driver with the session.
51575213
final AnalysisDriver driver;
51585214
final AnalysisSession session;
5215+
final TypeSystem typeSystem;
51595216
final DartSdk sdk;
51605217

51615218
Map<Source, SdkLibrary> _sdkLibrarySources;
@@ -6818,7 +6875,11 @@ class PackageBuilder {
68186875
}
68196876

68206877
PackageGraph newGraph = PackageGraph.UninitializedPackageGraph(
6821-
config, driver, sdk, hasEmbedderSdkFiles);
6878+
config,
6879+
driver,
6880+
await driver.currentSession.typeSystem,
6881+
sdk,
6882+
hasEmbedderSdkFiles);
68226883
await getLibraries(newGraph);
68236884
await newGraph.initializePackageGraph();
68246885
return newGraph;

lib/templates/class.html

+9
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
5757
</ul></dd>
5858
{{/hasPublicImplementors}}
5959

60+
{{#hasPotentiallyApplicableExtensions}}
61+
<dt>Available Extensions</dt>
62+
<dd><ul class="comma-separated clazz-relationships">
63+
{{#potentiallyApplicableExtensions}}
64+
<li>{{{linkedName}}}</li>
65+
{{/potentiallyApplicableExtensions}}
66+
</ul></dd>
67+
{{/hasPotentiallyApplicableExtensions}}
68+
6069
{{#hasAnnotations}}
6170
<dt>Annotations</dt>
6271
<dd><ul class="annotation-list clazz-relationships">

lib/templates/extension.html

+9-7
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@ <h5>{{parent.name}} {{parent.kind}}</h5>
1313

1414
{{#extension}}
1515
{{>documentation}}
16-
17-
<dt>on</dt>
18-
<dd>
19-
<ul class="comma-separated clazz-relationships">
16+
<section>
17+
<dl class="dl-horizontal">
18+
<dt>on</dt>
19+
<dd>
20+
<ul class="comma-separated clazz-relationships">
2021
{{#extendedType}}
2122
<li>{{{linkedName}}}</li>
2223
{{/extendedType}}
23-
</ul>
24-
</dd>
25-
24+
</ul>
25+
</dd>
26+
</dl>
27+
</section>
2628

2729
{{#hasPublicProperties}}
2830
<section class="summary offset-anchor" id="instance-properties">

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ author: Dart Team <[email protected]>
55
description: A documentation generator for Dart.
66
homepage: https://github.com/dart-lang/dartdoc
77
environment:
8-
sdk: '>=2.1.0 <3.0.0'
8+
sdk: '>=2.2.0 <3.0.0'
99

1010
dependencies:
1111
analyzer: ^0.39.0

test/model_test.dart

+45-4
Original file line numberDiff line numberDiff line change
@@ -2125,10 +2125,17 @@ void main() {
21252125
});
21262126

21272127
group('Extension', () {
2128-
Extension ext, fancyList;
2128+
Extension arm, leg, ext, fancyList, uphill;
21292129
Extension documentOnceReexportOne, documentOnceReexportTwo;
21302130
Library reexportOneLib, reexportTwoLib;
2131-
Class extensionReferencer;
2131+
Class apple,
2132+
anotherExtended,
2133+
baseTest,
2134+
bigAnotherExtended,
2135+
extensionReferencer,
2136+
megaTron,
2137+
superMegaTron,
2138+
string;
21322139
Method doSomeStuff, doStuff, s;
21332140
List<Extension> extensions;
21342141

@@ -2141,7 +2148,11 @@ void main() {
21412148
.firstWhere((e) => e.name == 'DocumentThisExtensionOnce');
21422149
documentOnceReexportTwo = reexportTwoLib.extensions
21432150
.firstWhere((e) => e.name == 'DocumentThisExtensionOnce');
2144-
2151+
string = packageGraph.allLibraries.values
2152+
.firstWhere((e) => e.name == 'dart:core')
2153+
.allClasses
2154+
.firstWhere((c) => c.name == 'String');
2155+
apple = exLibrary.classes.firstWhere((e) => e.name == 'Apple');
21452156
ext = exLibrary.extensions.firstWhere((e) => e.name == 'AppleExtension');
21462157
extensionReferencer =
21472158
exLibrary.classes.firstWhere((c) => c.name == 'ExtensionReferencer');
@@ -2155,6 +2166,17 @@ void main() {
21552166
.instanceMethods
21562167
.firstWhere((m) => m.name == 'doStuff');
21572168
extensions = exLibrary.publicExtensions.toList();
2169+
baseTest = fakeLibrary.classes.firstWhere((e) => e.name == 'BaseTest');
2170+
bigAnotherExtended =
2171+
fakeLibrary.classes.firstWhere((e) => e.name == 'BigAnotherExtended');
2172+
anotherExtended =
2173+
fakeLibrary.classes.firstWhere((e) => e.name == 'AnotherExtended');
2174+
arm = fakeLibrary.extensions.firstWhere((e) => e.name == 'Arm');
2175+
leg = fakeLibrary.extensions.firstWhere((e) => e.name == 'Leg');
2176+
uphill = fakeLibrary.extensions.firstWhere((e) => e.name == 'Uphill');
2177+
megaTron = fakeLibrary.classes.firstWhere((e) => e.name == 'Megatron');
2178+
superMegaTron =
2179+
fakeLibrary.classes.firstWhere((e) => e.name == 'SuperMegaTron');
21582180
});
21592181

21602182
test('basic canonicalization for extensions', () {
@@ -2164,9 +2186,28 @@ void main() {
21642186
expect(documentOnceReexportTwo.isCanonical, isTrue);
21652187
});
21662188

2189+
test('classes know about applicableExtensions', () {
2190+
expect(apple.potentiallyApplicableExtensions, orderedEquals([ext]));
2191+
expect(string.potentiallyApplicableExtensions,
2192+
isNot(contains(documentOnceReexportOne)));
2193+
expect(string.potentiallyApplicableExtensions,
2194+
contains(documentOnceReexportTwo));
2195+
expect(baseTest.potentiallyApplicableExtensions, isEmpty);
2196+
expect(anotherExtended.potentiallyApplicableExtensions,
2197+
orderedEquals([uphill]));
2198+
expect(bigAnotherExtended.potentiallyApplicableExtensions,
2199+
orderedEquals([uphill]));
2200+
});
2201+
2202+
test('type parameters and bounds work with applicableExtensions', () {
2203+
expect(
2204+
superMegaTron.potentiallyApplicableExtensions, orderedEquals([leg]));
2205+
expect(
2206+
megaTron.potentiallyApplicableExtensions, orderedEquals([arm, leg]));
2207+
});
2208+
21672209
// TODO(jcollins-g): implement feature and update tests
21682210
test('documentation links do not crash in base cases', () {
2169-
21702211
packageGraph.packageWarningCounter.hasWarning(
21712212
doStuff,
21722213
PackageWarning.notImplemented,

testing/test_package/lib/fake.dart

+32
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,38 @@ class ReferringClass {
990990
}
991991
}
992992

993+
//
994+
// Test classes for extension discovery.
995+
//
996+
997+
extension Arm on Megatron<int> {
998+
bool get hasLeftArm => true;
999+
}
1000+
1001+
extension Leg on Megatron<String> {
1002+
bool get hasRightLeg => true;
1003+
}
1004+
1005+
class Megatron<T> {}
1006+
1007+
class SuperMegaTron<T extends String> extends Megatron<String> {}
1008+
1009+
extension Uphill on AnotherExtended<SubclassBaseTest> {
1010+
bool get hasDirection => false;
1011+
}
1012+
1013+
class SubclassBaseTest extends BaseTest {}
1014+
1015+
class BaseTest {}
1016+
1017+
class AnotherExtended<T extends BaseTest> extends BaseTest {}
1018+
1019+
class BigAnotherExtended extends AnotherExtended<SubclassBaseTest> {}
1020+
1021+
//
1022+
//
1023+
//
1024+
9931025
/// Test an edge case for cases where inherited ExecutableElements can come
9941026
/// both from private classes and public interfaces. The test makes sure the
9951027
/// class still takes precedence (#1561).

tool/grind.dart

+1
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ Future<List<Map>> _buildTestPackageDocs(
486486
'examples',
487487
'--include-source',
488488
'--json',
489+
'--link-to-remote',
489490
'--pretty-index-json',
490491
]..addAll(extraDartdocParameters),
491492
workingDirectory: testPackage.absolute.path);

0 commit comments

Comments
 (0)