diff --git a/lib/src/model/comment_referable.dart b/lib/src/model/comment_referable.dart index feb12902c6..578bbdbe83 100644 --- a/lib/src/model/comment_referable.dart +++ b/lib/src/model/comment_referable.dart @@ -10,7 +10,6 @@ import 'dart:core'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/scope.dart'; import 'package:collection/collection.dart'; -import 'package:dartdoc/src/model/container.dart'; import 'package:dartdoc/src/model/library.dart'; import 'package:dartdoc/src/model/model_element.dart'; import 'package:dartdoc/src/model/nameable.dart'; @@ -53,12 +52,13 @@ mixin CommentReferable implements Nameable { return tryParents ? null : this; } for (var referenceLookup in _childLookups(reference)) { - if (scope != null) { - var result = _lookupViaScope(referenceLookup, filter: filter); - if (result != null) { - return result; - } + // First attempt: Ask analyzer's `Scope.lookup` API. + var result = _lookupViaScope(referenceLookup, filter: filter); + if (result != null) { + return result; } + + // Second attempt: Look through `referenceChildren`. final referenceChildren = this.referenceChildren; final childrenResult = referenceChildren[referenceLookup.lookup]; if (childrenResult != null) { @@ -97,12 +97,33 @@ mixin CommentReferable implements Nameable { _ReferenceChildrenLookup referenceLookup, { required bool Function(CommentReferable?) filter, }) { - final resultElement = scope!.lookupPreferGetter(referenceLookup.lookup); - if (resultElement == null) return null; + Element? resultElement; + final scope = this.scope; + if (scope != null) { + resultElement = scope.lookupPreferGetter(referenceLookup.lookup); + if (resultElement == null) return null; + } + + if (this case ModelElement(:var modelNode?) when resultElement == null) { + var references = modelNode.commentData?.references; + if (references != null) { + resultElement = references[referenceLookup.lookup]?.element; + } + } + + if (resultElement == null) { + return null; + } + ModelElement result; if (resultElement is PropertyAccessorElement) { final variable = resultElement.variable2!; if (variable.isSynthetic) { + // First, cache the synthetic variable, so that the + // PropertyAccessorElement getter and/or setter are set (see + // `Field.new` regarding `enclosingCombo`). + packageGraph.getModelForElement(variable); + // Then, use the result for the PropertyAccessorElement. result = packageGraph.getModelForElement(resultElement); } else { result = packageGraph.getModelForElement(variable); @@ -110,14 +131,6 @@ mixin CommentReferable implements Nameable { } else { result = packageGraph.getModelForElement(resultElement); } - if (result.enclosingElement is Container) { - assert( - false, - '[Container] member detected, support not implemented for analyzer ' - 'scope inside containers', - ); - return null; - } return _recurseChildrenAndFilter(referenceLookup, result, filter: filter); } diff --git a/lib/src/model/extension.dart b/lib/src/model/extension.dart index 50c1a859e3..1427cef05f 100644 --- a/lib/src/model/extension.dart +++ b/lib/src/model/extension.dart @@ -75,7 +75,7 @@ class Extension extends Container { setter = ContainerAccessor(fieldSetter, library, packageGraph); } return getModelForPropertyInducingElement(field, library, - getter: getter, setter: setter) as Field; + getter: getter, setter: setter, enclosingContainer: this) as Field; }).toList(growable: false); @override diff --git a/lib/src/model/inheriting_container.dart b/lib/src/model/inheriting_container.dart index 77bf68c70b..cae5d7e7cd 100644 --- a/lib/src/model/inheriting_container.dart +++ b/lib/src/model/inheriting_container.dart @@ -566,7 +566,7 @@ abstract class InheritingContainer extends Container { (setter == null || setter.isInherited)) { // Field is 100% inherited. return getModelForPropertyInducingElement(field, library, - enclosingContainer: this, getter: getter, setter: setter) as Field; + getter: getter, setter: setter, enclosingContainer: this) as Field; } else { // Field is <100% inherited (could be half-inherited). // TODO(jcollins-g): Navigation is probably still confusing for diff --git a/lib/src/model/model_element.dart b/lib/src/model/model_element.dart index 6a15eccbc6..cc7e979f8a 100644 --- a/lib/src/model/model_element.dart +++ b/lib/src/model/model_element.dart @@ -133,8 +133,15 @@ abstract class ModelElement } ModelElement newModelElement; - if (e is FieldElement) { - if (enclosingContainer == null) { + if (e is TopLevelVariableElement) { + assert(getter != null || setter != null); + newModelElement = + TopLevelVariable(e, library, packageGraph, getter, setter); + } else if (e is FieldElement) { + if (enclosingContainer is Extension) { + newModelElement = Field(e, library, packageGraph, + getter as ContainerAccessor?, setter as ContainerAccessor?); + } else if (enclosingContainer == null) { if (e.isEnumConstant) { var constantValue = e.computeConstantValue(); if (constantValue == null) { @@ -157,7 +164,6 @@ abstract class ModelElement getter as ContainerAccessor?, setter as ContainerAccessor?); } } else { - // EnumFields can't be inherited, so this case is simpler. newModelElement = Field.inherited( e, enclosingContainer, @@ -167,16 +173,11 @@ abstract class ModelElement setter as ContainerAccessor?, ); } - } else if (e is TopLevelVariableElement) { - assert(getter != null || setter != null); - newModelElement = - TopLevelVariable(e, library, packageGraph, getter, setter); } else { throw UnimplementedError( 'Unrecognized property inducing element: $e (${e.runtimeType})'); } - if (enclosingContainer != null) assert(newModelElement is Inheritable); _cacheNewModelElement(e, newModelElement, library, enclosingContainer: enclosingContainer); @@ -193,8 +194,6 @@ abstract class ModelElement // clean that up. // TODO(jcollins-g): Enforce construction restraint. // TODO(jcollins-g): Allow e to be null and drop extraneous null checks. - // TODO(jcollins-g): Auto-vivify element's defining library for library - // parameter when given a null. factory ModelElement.for_( Element e, Library library, PackageGraph packageGraph, {Container? enclosingContainer}) { diff --git a/test/end2end/model_test.dart b/test/end2end/model_test.dart index 80689caf0f..9fbe1c7271 100644 --- a/test/end2end/model_test.dart +++ b/test/end2end/model_test.dart @@ -2317,8 +2317,7 @@ void main() async { late final Class TypeParameterThings, TypeParameterThingsExtended, TypeParameterThingsExtendedQ; - late final Extension UnboundTypeTargetExtension; - late final Field aName, aThing, doesNotCrash; + late final Field aName, aThing; late final TypeParameter ATypeParam, BTypeParam, CTypeParam, @@ -2329,11 +2328,6 @@ void main() async { late final ModelFunction aTopLevelTypeParameterFunction; setUpAll(() { - UnboundTypeTargetExtension = - fakeLibrary.extensions.named('UnboundTypeTargetExtension'); - doesNotCrash = - UnboundTypeTargetExtension.instanceFields.named('doesNotCrash'); - aTopLevelTypeParameterFunction = fakeLibrary.functions.named('aTopLevelTypeParameterFunction'); // TODO(jcollins-g): dart-lang/dartdoc#2704, HTML and type parameters @@ -2368,11 +2362,6 @@ void main() async { QTypeParam = aMethodExtendedQ.typeParameters.named('QTypeParam'); }); - test('on extension targeting an unbound type', () { - expect(referenceLookup(UnboundTypeTargetExtension, 'doesNotCrash'), - equals(MatchingLinkResult(doesNotCrash))); - }); - test('on inherited documentation', () { expect(referenceLookup(aMethodExtended, 'ATypeParam'), equals(MatchingLinkResult(ATypeParam))); diff --git a/test/extensions_test.dart b/test/extensions_test.dart index f23e7f982e..c61ff49f20 100644 --- a/test/extensions_test.dart +++ b/test/extensions_test.dart @@ -53,6 +53,38 @@ var f() {} ); } + void test_referenceToExtensionGetter() async { + var library = await bootPackageWithLibrary(''' +extension Ex on int { + bool get b => true; +} + +/// Text [Ex.b]. +var f() {} +'''); + + expect( + library.functions.named('f').documentationAsHtml, + contains('Ex.b'), + ); + } + + void test_referenceToExtensionSetter() async { + var library = await bootPackageWithLibrary(''' +extension Ex on int { + set b(int value) {} +} + +/// Text [Ex.b]. +var f() {} +'''); + + expect( + library.functions.named('f').documentationAsHtml, + contains('Ex.b'), + ); + } + // TODO(srawlins): Test everything else about extensions. } diff --git a/testing/test_package/lib/fake.dart b/testing/test_package/lib/fake.dart index fc0bda08fd..7b0097afc7 100644 --- a/testing/test_package/lib/fake.dart +++ b/testing/test_package/lib/fake.dart @@ -1076,12 +1076,6 @@ extension Leg on Megatron { bool get hasRightLeg => true; } -/// Refer to [doesNotCrash] here. -extension UnboundTypeTargetExtension on T { - /// We hope so! - bool get doesNotCrash => true; -} - class Megatron {} class SuperMegaTron extends Megatron {}