Skip to content

Commit e8be4d8

Browse files
authored
add library and package disambiguation in new lookup code (#2700)
* Beginnings of grandparent override scoping * intermediate * implement basic overrides, constructors still broken * intermediate * this seems to work much better * dartfmt * This now seems to work with tests * rebuild && dartfmt * fix up tests a bit * add comment * fix package / library cases * move test to correct group * remove assert * starting to work on test problems * Fix some errors in the tests
1 parent f7dd024 commit e8be4d8

File tree

9 files changed

+95
-41
lines changed

9 files changed

+95
-41
lines changed

lib/src/comment_references/parser.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ class CommentReferenceParser {
158158
if (_atEnd) {
159159
return _PrefixParseResult.endOfFile;
160160
}
161+
_walkPastWhitespace();
161162
if (_tryMatchLiteral(_constructorHintPrefix,
162163
requireTrailingNonidentifier: true)) {
163164
return _PrefixParseResult.ok(

lib/src/generator/templates.runtime_renderers.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2581,6 +2581,25 @@ class _Renderer_CommentReferable extends RendererBase<CommentReferable> {
25812581
parent: r, getters: _invisibleGetters['Element']);
25822582
},
25832583
),
2584+
'href': Property(
2585+
getValue: (CT_ c) => c.href,
2586+
renderVariable:
2587+
(CT_ c, Property<CT_> self, List<String> remainingNames) {
2588+
if (remainingNames.isEmpty) {
2589+
return self.getValue(c).toString();
2590+
}
2591+
var name = remainingNames.first;
2592+
var nextProperty =
2593+
_Renderer_String.propertyMap().getValue(name);
2594+
return nextProperty.renderVariable(self.getValue(c),
2595+
nextProperty, [...remainingNames.skip(1)]);
2596+
},
2597+
isNullValue: (CT_ c) => c.href == null,
2598+
renderValue: (CT_ c, RendererBase<CT_> r,
2599+
List<MustachioNode> ast, StringSink sink) {
2600+
_render_String(c.href, ast, r.template, sink, parent: r);
2601+
},
2602+
),
25842603
'library': Property(
25852604
getValue: (CT_ c) => c.library,
25862605
renderVariable:
@@ -14592,6 +14611,7 @@ const _invisibleGetters = {
1459214611
},
1459314612
'CommentReferable': {
1459414613
'scope',
14614+
'href',
1459514615
'referenceChildren',
1459614616
'referenceParents',
1459714617
'referenceGrandparentOverrides',

lib/src/markdown_processor.dart

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -174,25 +174,24 @@ final List<md.BlockSyntax> _markdownBlockSyntaxes = [
174174
final RegExp _hideSchemes = RegExp('^(http|https)://');
175175

176176
class MatchingLinkResult {
177-
final ModelElement modelElement;
177+
final CommentReferable commentReferable;
178178
final bool warn;
179179

180-
MatchingLinkResult(this.modelElement, {this.warn = true});
180+
MatchingLinkResult(this.commentReferable, {this.warn = true});
181181

182182
@override
183183
bool operator ==(Object other) {
184184
return other is MatchingLinkResult &&
185-
modelElement == other.modelElement &&
185+
commentReferable == other.commentReferable &&
186186
warn == other.warn;
187187
}
188188

189189
@override
190-
int get hashCode => hash2(modelElement, warn);
190+
int get hashCode => hash2(commentReferable, warn);
191191

192192
bool isEquivalentTo(MatchingLinkResult other) {
193-
if (this == other) return true;
194-
var compareThis = modelElement;
195-
var compareOther = other.modelElement;
193+
var compareThis = commentReferable;
194+
var compareOther = other.commentReferable;
196195

197196
if (compareThis is Accessor) {
198197
compareThis = (compareThis as Accessor).enclosingCombo;
@@ -202,8 +201,16 @@ class MatchingLinkResult {
202201
compareOther = (compareOther as Accessor).enclosingCombo;
203202
}
204203

205-
if (compareThis?.canonicalModelElement ==
206-
compareOther?.canonicalModelElement) return true;
204+
if (compareThis is ModelElement &&
205+
compareThis.canonicalModelElement != null) {
206+
compareThis = (compareThis as ModelElement).canonicalModelElement;
207+
}
208+
if (compareOther is ModelElement &&
209+
compareOther.canonicalModelElement != null) {
210+
compareOther = (compareOther as ModelElement).canonicalModelElement;
211+
}
212+
if (compareThis == compareOther) return true;
213+
207214
// The old implementation just throws away Parameter matches to avoid
208215
// problems with warning unnecessarily at higher levels of the code.
209216
// I'd like to fix this at a different layer with the new lookup, so treat
@@ -221,12 +228,13 @@ class MatchingLinkResult {
221228
if (compareThis is TypeParameter && compareOther == null) {
222229
return true;
223230
}
231+
224232
return false;
225233
}
226234

227235
@override
228236
String toString() {
229-
return 'element: [${modelElement is Constructor ? 'new ' : ''}${modelElement?.fullyQualifiedName}] warn: $warn';
237+
return 'element: [${commentReferable is Constructor ? 'new ' : ''}${commentReferable?.fullyQualifiedName}] warn: $warn';
230238
}
231239
}
232240

@@ -354,12 +362,6 @@ MatchingLinkResult _getMatchingLinkElementCommentReferable(
354362
var lookupResult =
355363
warnable.referenceBy(commentReference.referenceBy, filter: filter);
356364

357-
// TODO(jcollins-g): Referring to packages or other non-[ModelElement]s
358-
// might be needed here. Determine if that's the case.
359-
if (!(lookupResult is ModelElement)) {
360-
lookupResult = null;
361-
}
362-
363365
// TODO(jcollins-g): Consider prioritizing analyzer resolution before custom.
364366
return MatchingLinkResult(lookupResult);
365367
}
@@ -1001,11 +1003,11 @@ const _referenceLookupWarnings = {
10011003
md.Node _makeLinkNode(String codeRef, Warnable warnable) {
10021004
var result = getMatchingLinkElement(warnable, codeRef);
10031005
var textContent = htmlEscape.convert(codeRef);
1004-
var linkedElement = result.modelElement;
1006+
var linkedElement = result.commentReferable;
10051007
if (linkedElement != null) {
10061008
if (linkedElement.href != null) {
10071009
var anchor = md.Element.text('a', textContent);
1008-
if (linkedElement.isDeprecated) {
1010+
if (linkedElement is ModelElement && linkedElement.isDeprecated) {
10091011
anchor.attributes['class'] = 'deprecated';
10101012
}
10111013
anchor.attributes['href'] = linkedElement.href;
@@ -1041,11 +1043,11 @@ MatchingLinkResult getMatchingLinkElement(Warnable warnable, String codeRef,
10411043
if (doComparison) {
10421044
resultNew = _getMatchingLinkElementCommentReferable(codeRef, warnable);
10431045
resultOld = _getMatchingLinkElementLegacy(codeRef, warnable);
1044-
if (resultNew.modelElement != null) {
1046+
if (resultNew.commentReferable != null) {
10451047
markdownStats.resolvedNewLookupReferences++;
10461048
}
10471049
result = experimentalReferenceLookup ? resultNew : resultOld;
1048-
if (resultOld.modelElement != null) {
1050+
if (resultOld.commentReferable != null) {
10491051
markdownStats.resolvedOldLookupReferences++;
10501052
}
10511053
} else {
@@ -1059,12 +1061,13 @@ MatchingLinkResult getMatchingLinkElement(Warnable warnable, String codeRef,
10591061
if (resultOld.isEquivalentTo(resultNew)) {
10601062
markdownStats.resolvedEquivalentlyReferences++;
10611063
} else {
1062-
if (resultNew.modelElement == null && resultOld.modelElement != null) {
1064+
if (resultNew.commentReferable == null &&
1065+
resultOld.commentReferable != null) {
10631066
warnable.warn(PackageWarning.referenceLookupMissingWithNew,
10641067
message: '[$codeRef] => ' + resultOld.toString(),
10651068
referredFrom: warnable.documentationFrom);
1066-
} else if (resultNew.modelElement != null &&
1067-
resultOld.modelElement == null) {
1069+
} else if (resultNew.commentReferable != null &&
1070+
resultOld.commentReferable == null) {
10681071
warnable.warn(PackageWarning.referenceLookupFoundWithNew,
10691072
message: '[$codeRef] => ' + resultNew.toString(),
10701073
referredFrom: warnable.documentationFrom);
@@ -1077,7 +1080,7 @@ MatchingLinkResult getMatchingLinkElement(Warnable warnable, String codeRef,
10771080
}
10781081
}
10791082
markdownStats.totalReferences++;
1080-
if (result.modelElement != null) markdownStats.resolvedReferences++;
1083+
if (result.commentReferable != null) markdownStats.resolvedReferences++;
10811084
return result;
10821085
}
10831086

@@ -1095,16 +1098,21 @@ final RegExp allAfterLastNewline = RegExp(r'\n.*$', multiLine: true);
10951098
// https://github.com/dart-lang/dartdoc/issues/1250#issuecomment-269257942
10961099
void showWarningsForGenericsOutsideSquareBracketsBlocks(
10971100
String text, Warnable element) {
1098-
for (var position in findFreeHangingGenericsPositions(text)) {
1099-
var priorContext =
1100-
'${text.substring(max(position - maxPriorContext, 0), position)}';
1101-
var postContext =
1102-
'${text.substring(position, min(position + maxPostContext, text.length))}';
1103-
priorContext = priorContext.replaceAll(allBeforeFirstNewline, '');
1104-
postContext = postContext.replaceAll(allAfterLastNewline, '');
1105-
var errorMessage = '$priorContext$postContext';
1106-
// TODO(jcollins-g): allow for more specific error location inside comments
1107-
element.warn(PackageWarning.typeAsHtml, message: errorMessage);
1101+
// Skip this if not warned for performance and for dart-lang/sdk#46419.
1102+
if (element.config.packageWarningOptions
1103+
.warningModes[PackageWarning.typeAsHtml] !=
1104+
PackageWarningMode.ignore) {
1105+
for (var position in findFreeHangingGenericsPositions(text)) {
1106+
var priorContext =
1107+
'${text.substring(max(position - maxPriorContext, 0), position)}';
1108+
var postContext =
1109+
'${text.substring(position, min(position + maxPostContext, text.length))}';
1110+
priorContext = priorContext.replaceAll(allBeforeFirstNewline, '');
1111+
postContext = postContext.replaceAll(allAfterLastNewline, '');
1112+
var errorMessage = '$priorContext$postContext';
1113+
// TODO(jcollins-g): allow for more specific error location inside comments
1114+
element.warn(PackageWarning.typeAsHtml, message: errorMessage);
1115+
}
11081116
}
11091117
}
11101118

lib/src/model/comment_referable.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ mixin CommentReferable implements Nameable {
4242
/// lookups via [referenceChildren]. Can be cached.
4343
Scope get scope => null;
4444

45+
String get href => null;
46+
4547
/// Look up a comment reference by its component parts. If [tryParents] is
4648
/// true, try looking up the same reference in any parents of [this].
4749
/// Will skip over results that do not pass a given [filter] and keep

lib/src/model/package_graph.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,8 @@ class PackageGraph with CommentReferable, Nameable {
10311031
if (_referenceChildren == null) {
10321032
_referenceChildren = {};
10331033
// Packages are the top priority.
1034-
_referenceChildren.addEntries(packages.map((p) => MapEntry(p.name, p)));
1034+
_referenceChildren
1035+
.addEntries(packages.map((p) => MapEntry('package:${p.name}', p)));
10351036

10361037
// Libraries are next.
10371038
// TODO(jcollins-g): Warn about directly referencing libraries out of

test/comment_referable/parser_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ void main() {
4747
expectParseEquivalent('this.is.valid<>', ['this', 'is', 'valid']);
4848
expectParseEquivalent('this.is.valid<stuff>', ['this', 'is', 'valid']);
4949
expectParseEquivalent('this.is.valid(things)', ['this', 'is', 'valid']);
50+
expectParseEquivalent('\nthis.is.valid', ['this', 'is', 'valid']);
5051
});
5152

5253
test('Check that cases dependent on prefix resolution not firing parse',

test/end2end/model_test.dart

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,10 +2129,10 @@ void main() {
21292129
/// as the original lookup code returns canonicalized results and the
21302130
/// new lookup code is only guaranteed to return equivalent results.
21312131
MatchingLinkResult definingLinkResult(MatchingLinkResult originalResult) {
2132-
if (originalResult.modelElement != null) {
2132+
if (originalResult.commentReferable?.element != null) {
21332133
return MatchingLinkResult(
2134-
ModelElement.fromElement(originalResult.modelElement.element,
2135-
originalResult.modelElement.packageGraph),
2134+
ModelElement.fromElement(originalResult.commentReferable.element,
2135+
originalResult.commentReferable.packageGraph),
21362136
warn: originalResult.warn);
21372137
}
21382138
return originalResult;
@@ -2228,7 +2228,10 @@ void main() {
22282228
});
22292229

22302230
test('Linking for inherited field from reexport context', () {
2231-
expect(bothLookup(aField, 'anotherNotReexportedVariable'),
2231+
// While this can work in the old code at times, it is using the
2232+
// analyzer and the new test harness can't leverage the analyzer
2233+
// when testing the old lookup code.
2234+
expect(newLookup(aField, 'anotherNotReexportedVariable'),
22322235
equals(MatchingLinkResult(anotherNotReexportedVariable)));
22332236
});
22342237

@@ -2250,6 +2253,8 @@ void main() {
22502253
});
22512254

22522255
group('Ordinary namespace cases', () {
2256+
Package DartPackage;
2257+
Library Dart;
22532258
ModelFunction doesStuff, function1, topLevelFunction;
22542259
TopLevelVariable incorrectDocReference,
22552260
incorrectDocReferenceFromEx,
@@ -2276,6 +2281,9 @@ void main() {
22762281
aConstructorShadowedField;
22772282

22782283
setUpAll(() async {
2284+
Dart = packageGraph.allLibraries.values
2285+
.firstWhere((l) => l.name == 'Dart');
2286+
DartPackage = packageGraph.packages.firstWhere((p) => p.name == 'Dart');
22792287
nameWithTwoUnderscores = fakeLibrary.constants
22802288
.firstWhere((v) => v.name == 'NAME_WITH_TWO_UNDERSCORES');
22812289
nameWithSingleUnderscore = fakeLibrary.constants
@@ -2348,6 +2356,14 @@ void main() {
23482356
.firstWhere((f) => f.name == 'aConstructorShadowed');
23492357
});
23502358

2359+
test('Referring to libraries and packages with the same name is fine',
2360+
() {
2361+
expect(bothLookup(Apple, 'Dart'), equals(MatchingLinkResult(Dart)));
2362+
// New feature: allow disambiguation if you really want to specify a package.
2363+
expect(newLookup(Apple, 'package:Dart'),
2364+
equals(MatchingLinkResult(DartPackage)));
2365+
});
2366+
23512367
test('Verify basic linking inside a constructor', () {
23522368
// Field formal parameters worked sometimes by accident in the old code,
23532369
// but should work reliably now.
@@ -2378,7 +2394,10 @@ void main() {
23782394

23792395
test('Deprecated lookup styles still function', () {
23802396
// dart-lang/dartdoc#2683
2381-
expect(bothLookup(baseForDocComments, 'aPrefix.UseResult'),
2397+
// We can't check the old code this way as this is one of the few
2398+
// cases in the old code where it relies on the analyzer's resolution of
2399+
// the doc comment -- the test harness can't do that for the old code.
2400+
expect(newLookup(baseForDocComments, 'aPrefix.UseResult'),
23822401
equals(MatchingLinkResult(metaUseResult)));
23832402
});
23842403

test/src/utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import 'package:pub_semver/pub_semver.dart';
1919
/// The number of public libraries in testing/test_package, minus 2 for
2020
/// the excluded libraries listed in the initializers for _testPackageGraphMemo
2121
/// and minus 1 for the <nodoc> tag in the 'excluded' library.
22-
const int kTestPackagePublicLibraries = 23;
22+
const int kTestPackagePublicLibraries = 24;
2323

2424
final _resourceProvider = pubPackageMetaProvider.resourceProvider;
2525
final _pathContext = _resourceProvider.pathContext;

testing/test_package/lib/dart.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/// A bold name for a library to test comment reference resolution.
2+
library Dart;

0 commit comments

Comments
 (0)