Skip to content

Commit 2e64c2b

Browse files
authored
Correct links to remote, canonical libraries (#3912)
Fixes #3891 In this example, the **file** package exports elements from `dart:io`. So when `--link-to-remote` is used, the canonical library for those exported elements should be `api.dart.dev`. Also some cleaning: * Move `_searchForCanonicalLibrary` from ModelElement to `canonicalization.dart`, keeping more canonicalization logic together. * This allows us to make `Canonicalization` private. * Make some comments in ModelElement doc-comments.
1 parent db81d94 commit 2e64c2b

File tree

5 files changed

+85
-59
lines changed

5 files changed

+85
-59
lines changed

lib/src/model/canonicalization.dart

+60-3
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,78 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:analyzer/dart/element/element.dart';
56
import 'package:dartdoc/src/model/model.dart';
67
import 'package:dartdoc/src/warnings.dart';
78

9+
/// Searches [PackageGraph.libraryExports] for a public, documented library
10+
/// which exports this [ModelElement], ideally in its library's package.
11+
Library? canonicalLibraryCandidate(ModelElement modelElement) {
12+
var thisAndExported =
13+
modelElement.packageGraph.libraryExports[modelElement.library.element];
14+
if (thisAndExported == null) {
15+
return null;
16+
}
17+
18+
// Since we're looking for a library, find the [Element] immediately
19+
// contained by a [CompilationUnitElement] in the tree.
20+
var topLevelElement = modelElement.element;
21+
while (topLevelElement.enclosingElement3 is! LibraryElement &&
22+
topLevelElement.enclosingElement3 is! CompilationUnitElement &&
23+
topLevelElement.enclosingElement3 != null) {
24+
topLevelElement = topLevelElement.enclosingElement3!;
25+
}
26+
var topLevelElementName = topLevelElement.name;
27+
if (topLevelElementName == null) {
28+
// Any member of an unnamed extension is not public, and has no
29+
// canonical library.
30+
return null;
31+
}
32+
33+
final candidateLibraries = thisAndExported.where((l) {
34+
if (!l.isPublic) return false;
35+
if (l.package.documentedWhere == DocumentLocation.missing) return false;
36+
if (modelElement is Library) return true;
37+
var lookup = l.element.exportNamespace.definedNames[topLevelElementName];
38+
return topLevelElement ==
39+
(lookup is PropertyAccessorElement ? lookup.variable2 : lookup);
40+
}).toList(growable: true);
41+
42+
if (candidateLibraries.isEmpty) {
43+
return null;
44+
}
45+
if (candidateLibraries.length == 1) {
46+
return candidateLibraries.single;
47+
}
48+
49+
var remoteLibraries = candidateLibraries
50+
.where((l) => l.package.documentedWhere == DocumentLocation.remote);
51+
if (remoteLibraries.length == 1) {
52+
// If one or more local libraries export code from a remotely documented
53+
// library (and we're linking to remote libraries), then just use the remote
54+
// library.
55+
return remoteLibraries.single;
56+
}
57+
58+
var topLevelModelElement =
59+
ModelElement.forElement(topLevelElement, modelElement.packageGraph);
60+
61+
return _Canonicalization(topLevelModelElement)
62+
.canonicalLibraryCandidate(candidateLibraries);
63+
}
64+
865
/// Canonicalization support in Dartdoc.
966
///
1067
/// This provides heuristic scoring to determine which library a human likely
1168
/// considers this element to be primarily 'from', and therefore, canonical.
1269
/// Still warn if the heuristic isn't very confident.
13-
final class Canonicalization {
70+
final class _Canonicalization {
1471
final ModelElement _element;
1572

16-
Canonicalization(this._element);
73+
_Canonicalization(this._element);
1774

1875
/// Calculates a candidate for the canonical library of [_element], among [libraries].
19-
Library calculateCanonicalCandidate(Iterable<Library> libraries) {
76+
Library canonicalLibraryCandidate(Iterable<Library> libraries) {
2077
var locationPieces = _element.element.location
2178
.toString()
2279
.split(_locationSplitter)

lib/src/model/model_element.dart

+6-50
Original file line numberDiff line numberDiff line change
@@ -466,14 +466,14 @@ abstract class ModelElement
466466
.join();
467467
}
468468

469-
// True if this is a function, or if it is an type alias to a function.
469+
/// Whether this is a function, or if it is an type alias to a function.
470470
bool get isCallable =>
471471
element is FunctionTypedElement ||
472472
(element is TypeAliasElement &&
473473
(element as TypeAliasElement).aliasedType is FunctionType);
474474

475-
// The canonical ModelElement for this ModelElement,
476-
// or null if there isn't one.
475+
/// The canonical ModelElement for this ModelElement, or null if there isn't
476+
/// one.
477477
late final ModelElement? canonicalModelElement = () {
478478
final enclosingElement = this.enclosingElement;
479479
var preferredClass = switch (enclosingElement) {
@@ -514,8 +514,9 @@ abstract class ModelElement
514514

515515
var definingLibraryIsLocalPublic =
516516
packageGraph.localPublicLibraries.contains(library);
517-
var possibleCanonicalLibrary =
518-
definingLibraryIsLocalPublic ? library : _searchForCanonicalLibrary();
517+
var possibleCanonicalLibrary = definingLibraryIsLocalPublic
518+
? library
519+
: canonicalLibraryCandidate(this);
519520

520521
if (possibleCanonicalLibrary != null) return possibleCanonicalLibrary;
521522

@@ -532,51 +533,6 @@ abstract class ModelElement
532533
return null;
533534
}();
534535

535-
/// Searches [PackageGraph.libraryExports] for a public, documented library
536-
/// which exports this [ModelElement], ideally in [library]'s package.
537-
Library? _searchForCanonicalLibrary() {
538-
var thisAndExported = packageGraph.libraryExports[library.element];
539-
if (thisAndExported == null) {
540-
return null;
541-
}
542-
543-
// Since we're looking for a library, find the [Element] immediately
544-
// contained by a [CompilationUnitElement] in the tree.
545-
var topLevelElement = element;
546-
while (topLevelElement.enclosingElement3 is! LibraryElement &&
547-
topLevelElement.enclosingElement3 is! CompilationUnitElement &&
548-
topLevelElement.enclosingElement3 != null) {
549-
topLevelElement = topLevelElement.enclosingElement3!;
550-
}
551-
var topLevelElementName = topLevelElement.name;
552-
if (topLevelElementName == null) {
553-
// Any member of an unnamed extension is not public, and has no
554-
// canonical library.
555-
return null;
556-
}
557-
558-
final candidateLibraries = thisAndExported.where((l) {
559-
if (!l.isPublic) return false;
560-
if (l.package.documentedWhere == DocumentLocation.missing) return false;
561-
if (this is Library) return true;
562-
var lookup = l.element.exportNamespace.definedNames[topLevelElementName];
563-
return topLevelElement ==
564-
(lookup is PropertyAccessorElement ? lookup.variable2 : lookup);
565-
}).toList(growable: true);
566-
567-
if (candidateLibraries.isEmpty) {
568-
return null;
569-
}
570-
if (candidateLibraries.length == 1) {
571-
return candidateLibraries.single;
572-
}
573-
574-
var topLevelModelElement =
575-
ModelElement.forElement(topLevelElement, packageGraph);
576-
return Canonicalization(topLevelModelElement)
577-
.calculateCanonicalCandidate(candidateLibraries);
578-
}
579-
580536
@override
581537
bool get isCanonical {
582538
if (!isPublic) return false;

lib/src/model/package_graph.dart

+6-5
Original file line numberDiff line numberDiff line change
@@ -751,8 +751,9 @@ class PackageGraph with CommentReferable, Nameable {
751751
return buffer.toString();
752752
}
753753

754-
/// Tries to find a canonical [ModelElement] for this [modelElement]. If we
755-
/// know this element is related to a particular class, pass a
754+
/// Tries to find a canonical [ModelElement] for [modelElement].
755+
///
756+
/// If we know the element is related to a particular class, pass a
756757
/// [preferredClass] to disambiguate.
757758
///
758759
/// This doesn't know anything about [PackageGraph.inheritThrough] and
@@ -795,10 +796,10 @@ class PackageGraph with CommentReferable, Nameable {
795796
ModelElement? canonicalModelElement;
796797
if (declaration != null &&
797798
(element is ClassMemberElement || element is PropertyAccessorElement)) {
798-
modelElement = getModelForElement(declaration);
799-
element = modelElement.element;
799+
var declarationModelElement = getModelForElement(declaration);
800+
element = declarationModelElement.element;
800801
canonicalModelElement = _findCanonicalModelElementForAmbiguous(
801-
modelElement, library,
802+
declarationModelElement, library,
802803
preferredClass: preferredClass as InheritingContainer?);
803804
} else {
804805
if (library != null) {

test/dartdoc_test_base.dart

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ abstract class DartdocTestBase {
3939
String get dartAsyncUrlPrefix =>
4040
'https://api.dart.dev/stable/3.2.0/dart-async';
4141

42-
String get dartCoreUrlPrefix => 'https://api.dart.dev/stable/3.2.0/dart-core';
42+
String get dartSdkUrlPrefix => 'https://api.dart.dev/stable/3.2.0';
43+
44+
String get dartCoreUrlPrefix => '$dartSdkUrlPrefix/dart-core';
4345

4446
String get sdkConstraint => '>=3.7.0 <4.0.0';
4547

test/libraries_test.dart

+10
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ class LibrariesTest extends DartdocTestBase {
194194
expect(library.href, '${placeholder}libraries');
195195
}
196196

197+
void test_library_containsClassWithSameNameAsDartSdk() async {
198+
var library = await bootPackageWithLibrary(
199+
'export "dart:io";',
200+
libraryFilePath: 'lib/library.dart',
201+
);
202+
203+
expect(library.classes.named('FileSystemEntity').linkedName,
204+
'<a href="$dartSdkUrlPrefix/dart-io/FileSystemEntity-class.html">FileSystemEntity</a>');
205+
}
206+
197207
void test_publicLibrary_unnamed() async {
198208
var library =
199209
(await bootPackageFromFiles([d.file('lib/lib1.dart', 'library;')]))

0 commit comments

Comments
 (0)