Skip to content

Frame for merging scoped lookups #2661

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/src/element_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions lib/src/generator/templates.runtime_renderers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2586,6 +2586,19 @@ class _Renderer_CommentReferable extends RendererBase<CommentReferable> {
parent: r);
},
),
'packageGraph': Property(
getValue: (CT_ c) => c.packageGraph,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'PackageGraph'),
isNullValue: (CT_ c) => c.packageGraph == null,
renderValue:
(CT_ c, RendererBase<CT_> r, List<MustachioNode> ast) {
return renderSimple(c.packageGraph, ast, r.template,
parent: r);
},
),
'referenceChildren': Property(
getValue: (CT_ c) => c.referenceChildren,
renderVariable: (CT_ c, Property<CT_> self,
Expand All @@ -2611,6 +2624,17 @@ class _Renderer_CommentReferable extends RendererBase<CommentReferable> {
(e) => renderSimple(e, ast, r.template, parent: r));
},
),
'scope': Property(
getValue: (CT_ c) => c.scope,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'Scope'),
isNullValue: (CT_ c) => c.scope == null,
renderValue:
(CT_ c, RendererBase<CT_> r, List<MustachioNode> ast) {
return renderSimple(c.scope, ast, r.template, parent: r);
},
),
});

_Renderer_CommentReferable(
Expand Down
89 changes: 75 additions & 14 deletions lib/src/model/comment_referable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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
Expand All @@ -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) {
Expand All @@ -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.
Expand All @@ -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<String, CommentReferable> get referenceChildren;

/// Iterable of immediate "parents" to try resolving component parts.
Expand Down
1 change: 1 addition & 0 deletions lib/src/model/package_graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion test/comment_referable/comment_referable_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<TopChild> children;
Expand Down