Skip to content

Commit

Permalink
Implement package global and graph global lookups
Browse files Browse the repository at this point in the history
  • Loading branch information
jcollins-g committed Jun 15, 2021
1 parent bbfad75 commit 13ddc73
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 50 deletions.
21 changes: 15 additions & 6 deletions lib/src/comment_references/parser.dart
Expand Up @@ -142,7 +142,6 @@ class CommentReferenceParser {
}

static const _constructorHintPrefix = 'new';

static const _ignorePrefixes = ['const', 'final', 'var'];

/// Implement parsing a prefix to a comment reference.
Expand All @@ -153,17 +152,19 @@ class CommentReferenceParser {
///
/// <constructorPrefixHint> ::= 'new '
///
/// <leadingJunk> ::= ('const' | 'final' | 'var' | 'operator')(' '+)
/// <leadingJunk> ::= ('const' | 'final' | 'var')(' '+)
/// ```
_PrefixParseResult _parsePrefix() {
if (_atEnd) {
return _PrefixParseResult.endOfFile;
}
if (_tryMatchLiteral(_constructorHintPrefix)) {
if (_tryMatchLiteral(_constructorHintPrefix,
requireTrailingNonidentifier: true)) {
return _PrefixParseResult.ok(
ConstructorHintStartNode(_constructorHintPrefix));
}
if (_ignorePrefixes.any((p) => _tryMatchLiteral(p))) {
if (_ignorePrefixes
.any((p) => _tryMatchLiteral(p, requireTrailingNonidentifier: true))) {
return _PrefixParseResult.junk;
}

Expand Down Expand Up @@ -278,10 +279,12 @@ class CommentReferenceParser {

/// Advances [_index] on match, preserves on non-match.
bool _tryMatchLiteral(String characters,
{bool acceptTrailingWhitespace = true}) {
{bool acceptTrailingWhitespace = true,
bool requireTrailingNonidentifier = false}) {
assert(acceptTrailingWhitespace != null);
if (characters.length + _index > _referenceLength) return false;
for (var startIndex = _index;
int startIndex;
for (startIndex = _index;
_index - startIndex < characters.length;
_index++) {
if (codeRef.codeUnitAt(_index) !=
Expand All @@ -290,6 +293,12 @@ class CommentReferenceParser {
return false;
}
}
if (requireTrailingNonidentifier) {
if (_atEnd || !_nonIdentifierChars.contains(_thisChar)) {
_index = startIndex;
return false;
}
}
if (acceptTrailingWhitespace) _walkPastWhitespace();
return true;
}
Expand Down
27 changes: 21 additions & 6 deletions lib/src/markdown_processor.dart
Expand Up @@ -191,23 +191,34 @@ class MatchingLinkResult {

bool isEquivalentTo(MatchingLinkResult other) {
if (this == other) return true;
if (modelElement?.canonicalModelElement ==
other.modelElement?.canonicalModelElement) return true;
var compareThis = modelElement;
var compareOther = other.modelElement;

if (compareThis is Accessor) {
compareThis = (compareThis as Accessor).enclosingCombo;
}

if (compareOther is Accessor) {
compareOther = (compareOther as Accessor).enclosingCombo;
}

if (compareThis?.canonicalModelElement ==
compareOther?.canonicalModelElement) return true;
// The old implementation just throws away Parameter matches to avoid
// problems with warning unnecessarily at higher levels of the code.
// I'd like to fix this at a different layer with the new lookup, so treat
// this as equivalent to a null type.
if (other.modelElement is Parameter && modelElement == null) {
if (compareOther is Parameter && compareThis == null) {
return true;
}
if (modelElement is Parameter && other.modelElement == null) {
if (compareThis is Parameter && compareOther == null) {
return true;
}
// Same with TypeParameter.
if (other.modelElement is TypeParameter && modelElement == null) {
if (compareOther is TypeParameter && compareThis == null) {
return true;
}
if (modelElement is TypeParameter && other.modelElement == null) {
if (compareThis is TypeParameter && compareOther == null) {
return true;
}
return false;
Expand Down Expand Up @@ -290,6 +301,10 @@ bool _rejectDefaultConstructors(CommentReferable referable) {
referable.name == referable.enclosingElement.name) {
return false;
}
// Avoid accidentally preferring arguments of the default constructor.
if (referable is ModelElement && referable.enclosingElement is Constructor) {
return false;
}
return true;
}

Expand Down
18 changes: 15 additions & 3 deletions lib/src/model/library.dart
Expand Up @@ -662,9 +662,21 @@ class Library extends ModelElement with Categorization, TopLevelContainer {
Map<String, CommentReferable> _referenceChildren;
@override
Map<String, CommentReferable> get referenceChildren {
return _referenceChildren ??= Map.fromEntries(
element.exportNamespace.definedNames.entries.map((entry) => MapEntry(
entry.key, ModelElement.fromElement(entry.value, packageGraph))));
if (_referenceChildren == null) {
_referenceChildren = Map.fromEntries(
element.exportNamespace.definedNames.entries.map((entry) => MapEntry(
entry.key, ModelElement.fromElement(entry.value, packageGraph))));
// TODO(jcollins-g): warn and get rid of this case where it shows up.
// If a user is hiding parts of a prefix import, the user should not
// refer to hidden members via the prefix, because that can be
// ambiguous. dart-lang/dartdoc#2683.
for (var prefixEntry in prefixToLibrary.entries) {
if (!_referenceChildren.containsKey(prefixEntry.key)) {
_referenceChildren[prefixEntry.key] = prefixEntry.value.first;
}
}
}
return _referenceChildren;
}

@override
Expand Down
1 change: 1 addition & 0 deletions lib/src/model/package.dart
Expand Up @@ -414,6 +414,7 @@ class Package extends LibraryContainer
// lookups like this.
for (var lib in publicLibrariesSorted) {
for (var referableEntry in lib.referenceChildren.entries) {
// Avoiding tearoffs for performance reasons.
if (!_referenceChildren.containsKey(referableEntry.key)) {
_referenceChildren[referableEntry.key] = referableEntry.value;
}
Expand Down
51 changes: 32 additions & 19 deletions lib/src/model/package_graph.dart
Expand Up @@ -1030,26 +1030,39 @@ class PackageGraph with CommentReferable, Nameable {
Map<String, CommentReferable> get referenceChildren {
if (_referenceChildren == null) {
_referenceChildren = {};

// Packages are the top priority.
_referenceChildren.addEntries(packages.map((p) => MapEntry(p.name, p)));
// TODO(jcollins-g): deprecate and start warning for anything needing these
// elements.
var librariesWithCanonicals =
packages.expand((p) => referenceChildren.entries.where((e) {
var v = e.value;
return v is ModelElement
? v.canonicalModelElement != null
: true;
}));
var topLevelsWithCanonicals = librariesWithCanonicals
.expand((p) => referenceChildren.entries.where((e) {
var v = e.value;
return v is ModelElement
? v.canonicalModelElement != null
: true;
}));
_referenceChildren.addEntries(librariesWithCanonicals);
_referenceChildren.addEntries(topLevelsWithCanonicals);

// Libraries are next.
// TODO(jcollins-g): Warn about directly referencing libraries out of
// scope?
for (var p in documentedPackages) {
for (var lib in p.publicLibrariesSorted) {
if (!_referenceChildren.containsKey(lib.name)) {
_referenceChildren[lib.name] = lib;
}
}
}
// TODO(jcollins-g): Warn about directly referencing top level items
// out of scope?
for (var p in documentedPackages) {
for (var lib in p.publicLibrariesSorted) {
for (var me in [
...lib.publicConstants,
...lib.publicFunctions,
...lib.publicProperties,
...lib.publicTypedefs,
...lib.publicExtensions,
...lib.publicClasses,
...lib.publicEnums,
...lib.publicMixins
]) {
if (!_referenceChildren.containsKey(me.name)) {
_referenceChildren[me.name] = me;
}
}
}
}
}
return _referenceChildren;
}
Expand Down
34 changes: 25 additions & 9 deletions test/comment_referable/parser_test.dart
Expand Up @@ -7,33 +7,36 @@ import 'package:test/test.dart';

void main() {
void expectParseEquivalent(String codeRef, List<String> parts,
{bool constructorHint = false}) {
{bool constructorHint = false, bool callableHint = false}) {
var result = CommentReferenceParser(codeRef).parse();
var hasHint = result.isNotEmpty &&
(result.first is ConstructorHintStartNode ||
result.last is CallableHintEndNode);
var hasConstructorHint =
result.isNotEmpty && result.first is ConstructorHintStartNode;
var hasCallableHint =
result.isNotEmpty && result.last is CallableHintEndNode;
var stringParts = result.whereType<IdentifierNode>().map((i) => i.text);
expect(stringParts, equals(parts));
expect(hasHint, equals(constructorHint));
expect(hasConstructorHint, equals(constructorHint));
expect(hasCallableHint, equals(callableHint));
}

void expectParsePassthrough(String codeRef) =>
expectParseEquivalent(codeRef, [codeRef]);

void expectParseError(String codeRef) {
expect(CommentReferenceParser(codeRef).parse(), isEmpty);
expect(CommentReferenceParser(codeRef).parse().whereType<IdentifierNode>(),
isEmpty);
}

group('Basic comment reference parsing', () {
test('Check that basic references parse', () {
expectParseEquivalent('valid', ['valid']);
expectParseEquivalent('new valid', ['valid'], constructorHint: true);
expectParseEquivalent('valid()', ['valid'], constructorHint: true);
expectParseEquivalent('const valid()', ['valid'], constructorHint: true);
expectParseEquivalent('valid()', ['valid'], callableHint: true);
expectParseEquivalent('const valid()', ['valid'], callableHint: true);
expectParseEquivalent('final valid', ['valid']);
expectParseEquivalent('this.is.valid', ['this', 'is', 'valid']);
expectParseEquivalent('this.is.valid()', ['this', 'is', 'valid'],
constructorHint: true);
callableHint: true);
expectParseEquivalent('new this.is.valid', ['this', 'is', 'valid'],
constructorHint: true);
expectParseEquivalent('const this.is.valid', ['this', 'is', 'valid']);
Expand All @@ -46,6 +49,19 @@ void main() {
expectParseEquivalent('this.is.valid(things)', ['this', 'is', 'valid']);
});

test('Check that cases dependent on prefix resolution not firing parse',
() {
expectParsePassthrough('constant');
expectParsePassthrough('newThingy');
expectParsePassthrough('operatorThingy');
expectParseEquivalent('operator+', ['+']);
expectParseError('const()');
// TODO(jcollins-g): might need to revisit these two with constructor
// tearoffs?
expectParsePassthrough('new');
expectParseError('new()');
});

test('Check that operator references parse', () {
expectParsePassthrough('[]');
expectParsePassthrough('<=');
Expand Down
32 changes: 26 additions & 6 deletions test/end2end/model_test.dart
Expand Up @@ -2137,12 +2137,17 @@ void main() {
nameWithSingleUnderscore,
theOnlyThingInTheLibrary;
Constructor aNonDefaultConstructor, defaultConstructor;
Class Apple, BaseClass, baseForDocComments, ExtraSpecialList, string;
Class Apple,
BaseClass,
baseForDocComments,
ExtraSpecialList,
string,
metaUseResult;
Method doAwesomeStuff, anotherMethod;
// ignore: unused_local_variable
Operator bracketOperator, bracketOperatorOtherClass;
Parameter doAwesomeStuffParam;
Field forInheriting, action, initializeMe;
Field forInheriting, action, initializeMe, somethingShadowy;

setUpAll(() async {
nameWithTwoUnderscores = fakeLibrary.constants
Expand All @@ -2153,6 +2158,10 @@ void main() {
.firstWhere((e) => e.name == 'dart:core')
.allClasses
.firstWhere((c) => c.name == 'String');
metaUseResult = packageGraph.allLibraries.values
.firstWhere((e) => e.name == 'meta')
.allClasses
.firstWhere((c) => c.name == 'UseResult');
baseForDocComments =
fakeLibrary.classes.firstWhere((c) => c.name == 'BaseForDocComments');
aNonDefaultConstructor = baseForDocComments.constructors.firstWhere(
Expand All @@ -2161,6 +2170,8 @@ void main() {
.firstWhere((c) => c.name == 'BaseForDocComments');
initializeMe = baseForDocComments.allFields
.firstWhere((f) => f.name == 'initializeMe');
somethingShadowy = baseForDocComments.allFields
.firstWhere((f) => f.name == 'somethingShadowy');
doAwesomeStuff = baseForDocComments.instanceMethods
.firstWhere((m) => m.name == 'doAwesomeStuff');
anotherMethod = baseForDocComments.instanceMethods
Expand Down Expand Up @@ -2248,12 +2259,23 @@ void main() {
equals(MatchingLinkResult(aNonDefaultConstructor)));
});

test('Deprecated lookup styles still function', () {
// dart-lang/dartdoc#2683
expect(bothLookup(baseForDocComments, 'aPrefix.UseResult'),
equals(MatchingLinkResult(metaUseResult)));
});

test('Verify basic linking inside class', () {
expect(
bothLookup(
baseForDocComments, 'BaseForDocComments.BaseForDocComments'),
equals(MatchingLinkResult(defaultConstructor)));

// We don't want the parameter on the default constructor, here.
expect(
bothLookup(baseForDocComments, 'BaseForDocComments.somethingShadowy'),
equals(MatchingLinkResult(somethingShadowy)));

expect(bothLookup(doAwesomeStuff, 'aNonDefaultConstructor'),
equals(MatchingLinkResult(aNonDefaultConstructor)));

Expand Down Expand Up @@ -2311,8 +2333,7 @@ void main() {
equals(MatchingLinkResult(theOnlyThingInTheLibrary)));

// A name that exists in this package but is not imported.
// TODO(jcollins-g): package-wide lookups are not yet implemented with the new lookup code.
expect(originalLookup(doAwesomeStuff, 'doesStuff'),
expect(bothLookup(doAwesomeStuff, 'doesStuff'),
equals(MatchingLinkResult(doesStuff)));

// A name of a class from an import of a library that exported that name.
Expand All @@ -2339,8 +2360,7 @@ void main() {
equals(MatchingLinkResult(forInheriting)));

// Reference to an inherited member in another library via class name.
// TODO(jcollins-g): reference to non-imported symbols isn't implemented yet in new lookup.
expect(originalLookup(doAwesomeStuff, 'ExtendedBaseReexported.action'),
expect(bothLookup(doAwesomeStuff, 'ExtendedBaseReexported.action'),
equals(MatchingLinkResult(action)));
});
});
Expand Down
5 changes: 4 additions & 1 deletion testing/test_package/lib/fake.dart
Expand Up @@ -55,6 +55,7 @@ import 'dart:collection';
// ignore: uri_does_not_exist
//import 'dart:json' as invalidPrefix;
import 'package:meta/meta.dart' show Required;
import 'package:meta/meta.dart' as aPrefix show Immutable;
import 'csspub.dart' as css;
import 'csspub.dart' as renamedLib2;
import 'package:test_package/src//import_unusual.dart';
Expand Down Expand Up @@ -962,7 +963,9 @@ class BaseForDocComments {

final bool initializeMe;

BaseForDocComments(this.initializeMe);
int somethingShadowy;

BaseForDocComments(this.initializeMe, [bool somethingShadowy]);

BaseForDocComments.aNonDefaultConstructor(this.initializeMe);

Expand Down

0 comments on commit 13ddc73

Please sign in to comment.