Skip to content

Commit e864efe

Browse files
authored
Import prefix support for dartdoc (#1875)
* basic tests working * dartfmt * Rebuild test package docs * update test package docs * handle some nulls * seems to work now even in mixed prefix case * rebuild stable docs * dartfmt * Rebuild test package docs * add comment
1 parent 42bd3b4 commit e864efe

File tree

307 files changed

+5577
-1383
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

307 files changed

+5577
-1383
lines changed

lib/src/markdown_processor.dart

Lines changed: 88 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ final RegExp trailingIgnoreStuff = new RegExp(r'(<.*>|\(.*\))$');
127127
final RegExp leadingIgnoreStuff =
128128
new RegExp(r'^(const|final|var)[\s]+', multiLine: true);
129129

130-
// This is explicitly intended as a reference to a constructor.
131-
final RegExp isConstructor = new RegExp(r'^new[\s]+', multiLine: true);
130+
// If found, this may be intended as a reference to a constructor.
131+
final RegExp isConstructor = new RegExp(r'(^new[\s]+|\(\)$)', multiLine: true);
132132

133133
// This is probably not really intended as a doc reference, so don't try or
134134
// warn about them.
@@ -299,12 +299,45 @@ class _MarkdownCommentReference {
299299
assert(element != null);
300300
assert(element.packageGraph.allLibrariesAdded);
301301

302-
codeRefChomped = codeRef.replaceFirst(isConstructor, '');
302+
codeRefChomped = codeRef.replaceAll(isConstructor, '');
303303
library =
304304
element is ModelElement ? (element as ModelElement).library : null;
305305
packageGraph = library.packageGraph;
306306
}
307307

308+
String __impliedDefaultConstructor;
309+
bool __impliedDefaultConstructorIsSet = false;
310+
311+
/// Returns the name of the implied default constructor if there is one, or
312+
/// null if not.
313+
///
314+
/// Default constructors are a special case in dartdoc. If we look up a name
315+
/// within a class of that class itself, the first thing we find is the
316+
/// default constructor. But we determine whether that's what they actually
317+
/// intended (vs. the enclosing class) by context -- whether they seem
318+
/// to be calling it with () or have a 'new' in front of it, or
319+
/// whether the name is repeated.
320+
///
321+
/// Similarly, referencing a class by itself might actually refer to its
322+
/// constructor based on these same heuristics.
323+
///
324+
/// With the name of the implied default constructor, other methods can
325+
/// determine whether or not the constructor and/or class we resolved to
326+
/// is actually matching the user's intent.
327+
String get _impliedDefaultConstructor {
328+
if (!__impliedDefaultConstructorIsSet) {
329+
__impliedDefaultConstructorIsSet = true;
330+
if (codeRef.contains(isConstructor) ||
331+
(codeRefChompedParts.length >= 2 &&
332+
codeRefChompedParts[codeRefChompedParts.length - 1] ==
333+
codeRefChompedParts[codeRefChompedParts.length - 2])) {
334+
// If the last two parts of the code reference are equal, this is probably a default constructor.
335+
__impliedDefaultConstructor = codeRefChompedParts.last;
336+
}
337+
}
338+
return __impliedDefaultConstructor;
339+
}
340+
308341
/// Calculate reference to a ModelElement.
309342
///
310343
/// Uses a series of calls to the _find* methods in this class to get one
@@ -318,8 +351,6 @@ class _MarkdownCommentReference {
318351
for (void Function() findMethod in [
319352
// This might be an operator. Strip the operator prefix and try again.
320353
_findWithoutOperatorPrefix,
321-
// Oh, and someone might have some type parameters or other garbage.
322-
_findWithoutTrailingIgnoreStuff,
323354
// Oh, and someone might have thrown on a 'const' or 'final' in front.
324355
_findWithoutLeadingIgnoreStuff,
325356
// Maybe this ModelElement has parameters, and this is one of them.
@@ -330,6 +361,8 @@ class _MarkdownCommentReference {
330361
_findTypeParameters,
331362
// This could be local to the class, look there first.
332363
_findWithinTryClasses,
364+
// This could be a reference to a renamed library.
365+
_findReferenceFromPrefixes,
333366
// We now need the ref element cache to keep from repeatedly searching [Package.allModelElements].
334367
// But if not, look for a fully qualified match. (That only makes sense
335368
// if the codeRef might be qualified, and contains periods.)
@@ -340,8 +373,12 @@ class _MarkdownCommentReference {
340373
_findGlobalWithinRefElementCache,
341374
// This could conceivably be a reference to an enum member. They don't show up in allModelElements.
342375
_findEnumReferences,
376+
// Oh, and someone might have some type parameters or other garbage.
377+
// After finding within classes because sometimes parentheses are used
378+
// to imply constructors.
379+
_findWithoutTrailingIgnoreStuff,
343380
// Use the analyzer to resolve a comment reference.
344-
_findAnalyzerReferences
381+
_findAnalyzerReferences,
345382
]) {
346383
findMethod();
347384
// Remove any "null" objects after each step of trying to add to results.
@@ -355,8 +392,6 @@ class _MarkdownCommentReference {
355392
// This isn't C++. References to class methods are slightly expensive
356393
// in Dart so don't build that list unless you need to.
357394
for (void Function() reduceMethod in [
358-
// If this name could refer to a class or a constructor, prefer the class.
359-
_reducePreferClass,
360395
// If a result is actually in this library, prefer that.
361396
_reducePreferResultsInSameLibrary,
362397
// If a result is accessible in this library, prefer that.
@@ -404,30 +439,6 @@ class _MarkdownCommentReference {
404439
List<String> get codeRefChompedParts =>
405440
_codeRefChompedParts ??= codeRefChomped.split('.');
406441

407-
/// Returns true if this is a constructor we should consider due to its
408-
/// name and the code reference, or if this isn't a constructor. False
409-
/// otherwise.
410-
bool _ConsiderIfConstructor(ModelElement modelElement) {
411-
// TODO(jcollins-g): Rewrite this to handle constructors in a less hacky way
412-
if (modelElement is! Constructor) return true;
413-
if (codeRef.contains(isConstructor)) return true;
414-
Constructor aConstructor = modelElement;
415-
if (codeRefParts.length > 1) {
416-
// Pick the last two parts, in case a specific library was part of the
417-
// codeRef.
418-
if (codeRefParts[codeRefParts.length - 1] ==
419-
codeRefParts[codeRefParts.length - 2]) {
420-
// Foobar.Foobar -- assume they really do mean the constructor for this class.
421-
return true;
422-
}
423-
}
424-
if (aConstructor.name != aConstructor.enclosingElement.name) {
425-
// This isn't a default constructor so treat it like any other member.
426-
return true;
427-
}
428-
return false;
429-
}
430-
431442
void _reducePreferAnalyzerResolution() {
432443
Element refElement = _getRefElementFromCommentRefs(commentRefs, codeRef);
433444
if (results.any((me) => me.element == refElement)) {
@@ -471,12 +482,6 @@ class _MarkdownCommentReference {
471482
}
472483
}
473484

474-
void _reducePreferClass() {
475-
if (results.any((r) => r is Class)) {
476-
results.removeWhere((r) => r is Constructor);
477-
}
478-
}
479-
480485
void _findTypeParameters() {
481486
if (element is TypeParameters) {
482487
results.addAll((element as TypeParameters).typeParameters.where((p) =>
@@ -540,14 +545,51 @@ class _MarkdownCommentReference {
540545
}
541546
}
542547

548+
/// Transform members of [toConvert] that are classes to their default constructor,
549+
/// if a constructor is implied. If not, do the reverse conversion for default
550+
/// constructors.
551+
ModelElement _convertConstructors(ModelElement toConvert) {
552+
if (_impliedDefaultConstructor != null) {
553+
if (toConvert is Class && toConvert.name == _impliedDefaultConstructor) {
554+
return toConvert.defaultConstructor;
555+
}
556+
return toConvert;
557+
} else {
558+
if (toConvert is Constructor &&
559+
(toConvert.enclosingElement as Class).defaultConstructor ==
560+
toConvert) {
561+
return toConvert.enclosingElement;
562+
}
563+
return toConvert;
564+
}
565+
}
566+
567+
void _findReferenceFromPrefixes() {
568+
if (element is! ModelElement) return;
569+
Map<String, Set<Library>> prefixToLibrary =
570+
(element as ModelElement).definingLibrary.prefixToLibrary;
571+
if (prefixToLibrary.containsKey(codeRefChompedParts.first)) {
572+
if (codeRefChompedParts.length == 1) {
573+
results.addAll(prefixToLibrary[codeRefChompedParts.first]);
574+
} else {
575+
String lookup = codeRefChompedParts.sublist(1).join('.');
576+
prefixToLibrary[codeRefChompedParts.first]?.forEach((l) => l
577+
.modelElementsNameMap[lookup]
578+
?.map(_convertConstructors)
579+
?.forEach((m) => _addCanonicalResult(m, _getPreferredClass(m))));
580+
}
581+
}
582+
}
583+
543584
void _findGlobalWithinRefElementCache() {
544585
if (packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
545586
for (final modelElement
546587
in packageGraph.findRefElementCache[codeRefChomped]) {
547588
if (codeRefChomped == modelElement.fullyQualifiedNameWithoutLibrary ||
548589
(modelElement is Library &&
549590
codeRefChomped == modelElement.fullyQualifiedName)) {
550-
_addCanonicalResult(modelElement, null);
591+
_addCanonicalResult(
592+
_convertConstructors(modelElement), preferredClass);
551593
}
552594
}
553595
}
@@ -557,8 +599,7 @@ class _MarkdownCommentReference {
557599
// Only look for partially qualified matches if we didn't find a fully qualified one.
558600
if (library.modelElementsNameMap.containsKey(codeRefChomped)) {
559601
for (final modelElement in library.modelElementsNameMap[codeRefChomped]) {
560-
if (!_ConsiderIfConstructor(modelElement)) continue;
561-
_addCanonicalResult(modelElement, preferredClass);
602+
_addCanonicalResult(_convertConstructors(modelElement), preferredClass);
562603
}
563604
}
564605
}
@@ -571,13 +612,12 @@ class _MarkdownCommentReference {
571612
packageGraph.findRefElementCache.containsKey(codeRefChomped)) {
572613
for (final ModelElement modelElement
573614
in packageGraph.findRefElementCache[codeRefChomped]) {
574-
if (!_ConsiderIfConstructor(modelElement)) continue;
575615
// For fully qualified matches, the original preferredClass passed
576616
// might make no sense. Instead, use the enclosing class from the
577617
// element in [packageGraph.findRefElementCache], because that element's
578618
// enclosing class will be preferred from [codeRefChomped]'s perspective.
579619
_addCanonicalResult(
580-
modelElement,
620+
_convertConstructors(modelElement),
581621
modelElement.enclosingElement is Class
582622
? modelElement.enclosingElement
583623
: null);
@@ -678,26 +718,17 @@ class _MarkdownCommentReference {
678718
/// Get any possible results for this class in the superChain. Returns
679719
/// true if we found something.
680720
void _getResultsForSuperChainElement(Class c, Class tryClass) {
681-
Iterable<ModelElement> membersToCheck;
682-
membersToCheck = (c.allModelElementsByNamePart[codeRefChomped] ?? [])
683-
.where((m) => _ConsiderIfConstructor(m));
721+
Iterable<ModelElement> membersToCheck =
722+
(c.allModelElementsByNamePart[codeRefChomped] ?? [])
723+
.map(_convertConstructors);
684724
for (final ModelElement modelElement in membersToCheck) {
685725
// [thing], a member of this class
686726
_addCanonicalResult(modelElement, tryClass);
687727
}
688728
membersToCheck = (c.allModelElementsByNamePart[codeRefChompedParts.last] ??
689729
<ModelElement>[])
690-
.where((m) => _ConsiderIfConstructor(m));
691-
if (codeRefChompedParts.first == c.name) {
692-
// [Foo...thing], a member of this class (possibly a parameter).
693-
membersToCheck.forEach((m) => _addCanonicalResult(m, tryClass));
694-
} else if (codeRefChompedParts.length > 1 &&
695-
codeRefChompedParts[codeRefChompedParts.length - 2] == c.name) {
696-
// [....Foo.thing], a member of this class partially specified.
697-
membersToCheck
698-
.whereType<Constructor>()
699-
.forEach((m) => _addCanonicalResult(m, tryClass));
700-
}
730+
.map(_convertConstructors);
731+
membersToCheck.forEach((m) => _addCanonicalResult(m, tryClass));
701732
results.remove(null);
702733
if (results.isNotEmpty) return;
703734
if (c.fullyQualifiedNameWithoutLibrary == codeRefChomped) {

lib/src/model.dart

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,15 @@ class Class extends ModelElement
631631
.toList(growable: false);
632632
}
633633

634+
Constructor _defaultConstructor;
635+
Constructor get defaultConstructor {
636+
if (_defaultConstructor == null) {
637+
_defaultConstructor = constructors
638+
.firstWhere((c) => c.isDefaultConstructor, orElse: () => null);
639+
}
640+
return _defaultConstructor;
641+
}
642+
634643
Iterable<Method> get allInstanceMethods =>
635644
quiverIterables.concat([instanceMethods, inheritedMethods]);
636645

@@ -1227,7 +1236,10 @@ class Constructor extends ModelElement
12271236
}
12281237

12291238
@override
1230-
String get fullyQualifiedName => '${library.name}.$name';
1239+
String get fullyQualifiedName {
1240+
if (isDefaultConstructor) return super.fullyQualifiedName;
1241+
return '${library.name}.$name';
1242+
}
12311243

12321244
@override
12331245
String get href {
@@ -1241,6 +1253,8 @@ class Constructor extends ModelElement
12411253
@override
12421254
bool get isConst => _constructor.isConst;
12431255

1256+
bool get isDefaultConstructor => name == enclosingElement.name;
1257+
12441258
bool get isFactory => _constructor.isFactory;
12451259

12461260
@override
@@ -2245,6 +2259,24 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
22452259
return _importedExportedLibraries;
22462260
}
22472261

2262+
Map<String, Set<Library>> _prefixToLibrary;
2263+
2264+
/// Map of import prefixes ('import "foo" as prefix;') to [Library].
2265+
Map<String, Set<Library>> get prefixToLibrary {
2266+
if (_prefixToLibrary == null) {
2267+
_prefixToLibrary = {};
2268+
// It is possible to have overlapping prefixes.
2269+
for (ImportElement i in (element as LibraryElement).imports) {
2270+
if (i.prefix?.name != null) {
2271+
_prefixToLibrary.putIfAbsent(i.prefix?.name, () => new Set());
2272+
_prefixToLibrary[i.prefix?.name].add(
2273+
new ModelElement.from(i.importedLibrary, library, packageGraph));
2274+
}
2275+
}
2276+
}
2277+
return _prefixToLibrary;
2278+
}
2279+
22482280
String _dirName;
22492281
String get dirName {
22502282
if (_dirName == null) {

test/dartdoc_test.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ void main() {
252252
Package p = packageGraph.defaultPackage;
253253
expect(p.name, 'test_package');
254254
expect(p.hasDocumentationFile, isTrue);
255-
expect(packageGraph.defaultPackage.publicLibraries, hasLength(10));
255+
// Total number of public libraries in test_package.
256+
expect(packageGraph.defaultPackage.publicLibraries, hasLength(12));
256257
expect(packageGraph.localPackages.length, equals(1));
257258
});
258259

@@ -307,7 +308,7 @@ void main() {
307308
PackageGraph p = results.packageGraph;
308309
expect(p.defaultPackage.name, 'test_package');
309310
expect(p.defaultPackage.hasDocumentationFile, isTrue);
310-
expect(p.localPublicLibraries, hasLength(9));
311+
expect(p.localPublicLibraries, hasLength(11));
311312
expect(p.localPublicLibraries.map((lib) => lib.name).contains('fake'),
312313
isFalse);
313314
});

0 commit comments

Comments
 (0)