diff --git a/lib/src/element_type.dart b/lib/src/element_type.dart index 5ca366d1f2..e5d887d311 100644 --- a/lib/src/element_type.dart +++ b/lib/src/element_type.dart @@ -16,6 +16,7 @@ import 'package:dartdoc/src/render/element_type_renderer.dart'; /// may link to a [ModelElement]. abstract class ElementType extends Privacy with CommentReferable, Nameable { final DartType _type; + @override final PackageGraph packageGraph; final ElementType returnedFrom; @override diff --git a/lib/src/generator/templates.runtime_renderers.dart b/lib/src/generator/templates.runtime_renderers.dart index da647ef2f5..bd5a970e3d 100644 --- a/lib/src/generator/templates.runtime_renderers.dart +++ b/lib/src/generator/templates.runtime_renderers.dart @@ -2586,6 +2586,19 @@ class _Renderer_CommentReferable extends RendererBase { parent: r); }, ), + 'packageGraph': Property( + getValue: (CT_ c) => c.packageGraph, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable( + c, remainingNames, 'PackageGraph'), + isNullValue: (CT_ c) => c.packageGraph == null, + renderValue: + (CT_ c, RendererBase r, List ast) { + return renderSimple(c.packageGraph, ast, r.template, + parent: r); + }, + ), 'referenceChildren': Property( getValue: (CT_ c) => c.referenceChildren, renderVariable: (CT_ c, Property self, @@ -2611,6 +2624,17 @@ class _Renderer_CommentReferable extends RendererBase { (e) => renderSimple(e, ast, r.template, parent: r)); }, ), + 'scope': Property( + getValue: (CT_ c) => c.scope, + renderVariable: (CT_ c, Property self, + List remainingNames) => + self.renderSimpleVariable(c, remainingNames, 'Scope'), + isNullValue: (CT_ c) => c.scope == null, + renderValue: + (CT_ c, RendererBase r, List ast) { + return renderSimple(c.scope, ast, r.template, parent: r); + }, + ), }); _Renderer_CommentReferable( diff --git a/lib/src/model/comment_referable.dart b/lib/src/model/comment_referable.dart index deb65fbef5..91afd2e875 100644 --- a/lib/src/model/comment_referable.dart +++ b/lib/src/model/comment_referable.dart @@ -10,17 +10,37 @@ library dartdoc.src.model.comment_reference; import 'dart:core'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/scope.dart'; import 'package:dartdoc/dartdoc.dart'; import 'package:meta/meta.dart'; class ReferenceChildrenLookup { final String lookup; final List remaining; + ReferenceChildrenLookup(this.lookup, this.remaining); + + @override + String toString() => '$lookup.${remaining.join(".")}'; +} + +extension on Scope { + /// Prefer the getter for a bundled lookup if both exist. + Element lookupPreferGetter(String id) { + var result = lookup(id); + return result.getter ?? result.setter; + } } /// Support comment reference lookups on a Nameable object. mixin CommentReferable implements Nameable { + PackageGraph packageGraph; + + /// For any [CommentReferable] where an analyzer [Scope] exists (or can + /// be constructed), implement this. This will take priority over + /// lookups via [referenceChildren]. Can be cached. + Scope get scope => null; + /// Look up a comment reference by its component parts. If [tryParents] is /// true, try looking up the same reference in any parents of [this]. /// Will skip over results that do not pass a given [filter] and keep @@ -37,20 +57,14 @@ mixin CommentReferable implements Nameable { /// Search for the reference. for (var referenceLookup in childLookups(reference)) { + if (scope != null) { + result = lookupViaScope(referenceLookup, filter); + if (result != null) break; + } if (referenceChildren.containsKey(referenceLookup.lookup)) { - result = referenceChildren[referenceLookup.lookup]; - if (referenceLookup.remaining.isNotEmpty) { - result = result?.referenceBy(referenceLookup.remaining, - tryParents: false, filter: filter); - } else if (!filter(result)) { - result = result?.referenceBy([referenceLookup.lookup], - tryParents: false, filter: filter); - } - if (!filter(result)) { - result = null; - } + result = _lookupViaReferenceChildren(referenceLookup, filter); + if (result != null) break; } - if (result != null) break; } // If we can't find it in children, try searching parents if allowed. if (result == null && tryParents) { @@ -62,6 +76,51 @@ mixin CommentReferable implements Nameable { return result; } + /// Looks up references by [scope], skipping over results that do not match + /// the given filter. + /// + /// Override if [Scope.lookup] may return a [PrefixElement] or other elements + /// not corresponding to a [CommentReferable], but you still want to have + /// an implementation of [scope]. + CommentReferable lookupViaScope(ReferenceChildrenLookup referenceLookup, + bool Function(CommentReferable) filter) { + var resultElement = scope.lookupPreferGetter(referenceLookup.lookup); + if (resultElement is PrefixElement) { + assert(false, + 'PrefixElement detected, override [lookupViaScope] in subclass'); + return null; + } + return recurseChildrenAndFilter(referenceLookup, + ModelElement.fromElement(resultElement, packageGraph), filter); + } + + CommentReferable _lookupViaReferenceChildren( + ReferenceChildrenLookup referenceLookup, + bool Function(CommentReferable) filter) => + recurseChildrenAndFilter( + referenceLookup, referenceChildren[referenceLookup.lookup], filter); + + /// Given a [result] found in an implementation of [lookupViaScope] or + /// [_lookupViaReferenceChildren], recurse through children, skipping over + /// results that do not match the filter. + CommentReferable recurseChildrenAndFilter( + ReferenceChildrenLookup referenceLookup, + CommentReferable result, + bool Function(CommentReferable) filter) { + assert(result != null); + if (referenceLookup.remaining.isNotEmpty) { + result = result.referenceBy(referenceLookup.remaining, + tryParents: false, filter: filter); + } else if (!filter(result)) { + result = result.referenceBy([referenceLookup.lookup], + tryParents: false, filter: filter); + } + if (!filter(result)) { + result = null; + } + return result; + } + /// A list of lookups that should be attempted on children based on /// [reference]. This allows us to deal with libraries that may have /// separators in them. [referenceBy] stops at the first one found. @@ -71,8 +130,10 @@ mixin CommentReferable implements Nameable { ]; /// Map of name to the elements that are a member of [this], but - /// not this model element itself. - /// Can be cached. + /// not this model element itself. Can be cached. + /// + /// There is no need to duplicate references here that can be found via + /// [scope]. Map get referenceChildren; /// Iterable of immediate "parents" to try resolving component parts. diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart index eebe66a950..a805b80fad 100644 --- a/lib/src/model/package_graph.dart +++ b/lib/src/model/package_graph.dart @@ -267,6 +267,7 @@ class PackageGraph with CommentReferable, Nameable { bool get hasFooterVersion => !config.excludeFooterVersion; + @override PackageGraph get packageGraph => this; /// Map of package name to Package. diff --git a/test/comment_referable/comment_referable_test.dart b/test/comment_referable/comment_referable_test.dart index 24c51c10e4..6b94605322 100644 --- a/test/comment_referable/comment_referable_test.dart +++ b/test/comment_referable/comment_referable_test.dart @@ -25,7 +25,7 @@ abstract class Base extends Nameable with CommentReferable { Element get element => throw UnimplementedError(); } -class Top extends Base with CommentReferable { +class Top extends Base { @override final String name; final List children;