Skip to content

Commit a6b2eb9

Browse files
authored
Merge pull request #2772 from jcollins-g/constructor-tearoffs+typeparams
Allow embedded type parameters in references
2 parents 9f81df5 + 173132e commit a6b2eb9

File tree

4 files changed

+113
-5
lines changed

4 files changed

+113
-5
lines changed

lib/src/comment_references/parser.dart

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class CommentReferenceParser {
9393
/// ```text
9494
/// <rawCommentReference> ::= <prefix>?<commentReference><suffix>?
9595
///
96-
/// <commentReference> ::= (<packageName> '.')? (<libraryName> '.')? <dartdocIdentifier> ('.' <identifier>)*
96+
/// <commentReference> ::= (<packageName> '.')? (<libraryName> '.')? <dartdocIdentifier> <typeArguments> ('.' <identifier> <typeArguments>)*
9797
/// ```
9898
List<CommentReferenceNode> _parseRawCommentReference() {
9999
var children = <CommentReferenceNode>[];
@@ -121,6 +121,17 @@ class CommentReferenceParser {
121121
} else if (identifierResult.type ==
122122
_IdentifierResultType.parsedIdentifier) {
123123
children.add(identifierResult.node);
124+
var typeVariablesResult = _parseTypeVariables();
125+
if (typeVariablesResult.type == _TypeVariablesResultType.endOfFile) {
126+
break;
127+
} else if (typeVariablesResult.type ==
128+
_TypeVariablesResultType.notTypeVariables) {
129+
// Do nothing, _index has not moved.
130+
;
131+
} else if (typeVariablesResult.type ==
132+
_TypeVariablesResultType.parsedTypeVariables) {
133+
children.add(typeVariablesResult.node);
134+
}
124135
}
125136
if (_atEnd || _thisChar != $dot) {
126137
break;
@@ -236,6 +247,22 @@ class CommentReferenceParser {
236247
IdentifierNode(codeRef.substring(startIndex, _index)));
237248
}
238249

250+
/// Parse a list of type variables (arguments or parameters).
251+
///
252+
/// Dartdoc isolates these where present and potentially valid, but we don't
253+
/// break them down.
254+
_TypeVariablesParseResult _parseTypeVariables() {
255+
if (_atEnd) {
256+
return _TypeVariablesParseResult.endOfFile;
257+
}
258+
var startIndex = _index;
259+
if (_matchBraces($lt, $gt)) {
260+
return _TypeVariablesParseResult.ok(
261+
TypeVariablesNode(codeRef.substring(startIndex + 1, _index - 1)));
262+
}
263+
return _TypeVariablesParseResult.notIdentifier;
264+
}
265+
239266
static const _callableHintSuffix = '()';
240267

241268
/// ```text
@@ -267,7 +294,7 @@ class CommentReferenceParser {
267294
if ((_thisChar == $exclamation || _thisChar == $question) && _nextAtEnd) {
268295
return _SuffixParseResult.junk;
269296
}
270-
if (_matchBraces($lparen, $rparen) || _matchBraces($lt, $gt)) {
297+
if (_matchBraces($lparen, $rparen)) {
271298
return _SuffixParseResult.junk;
272299
}
273300

@@ -331,8 +358,10 @@ class CommentReferenceParser {
331358
while (!_atEnd) {
332359
if (_thisChar == startChar) braceCount++;
333360
if (_thisChar == endChar) braceCount--;
334-
++_index;
335-
if (braceCount == 0) return true;
361+
_index++;
362+
if (braceCount == 0) {
363+
return true;
364+
}
336365
}
337366
_index = startIndex;
338367
return false;
@@ -392,6 +421,32 @@ class _IdentifierParseResult {
392421
_IdentifierParseResult._(_IdentifierResultType.notIdentifier, null);
393422
}
394423

424+
enum _TypeVariablesResultType {
425+
endOfFile, // Found end of file instead of the beginning of a list of type
426+
// variables.
427+
notTypeVariables, // Found something, but it isn't type variables.
428+
parsedTypeVariables, // Found type variables.
429+
}
430+
431+
class _TypeVariablesParseResult {
432+
final _TypeVariablesResultType type;
433+
434+
final TypeVariablesNode node;
435+
436+
const _TypeVariablesParseResult._(this.type, this.node);
437+
438+
factory _TypeVariablesParseResult.ok(TypeVariablesNode node) =>
439+
_TypeVariablesParseResult._(
440+
_TypeVariablesResultType.parsedTypeVariables, node);
441+
442+
static const _TypeVariablesParseResult endOfFile =
443+
_TypeVariablesParseResult._(_TypeVariablesResultType.endOfFile, null);
444+
445+
static const _TypeVariablesParseResult notIdentifier =
446+
_TypeVariablesParseResult._(
447+
_TypeVariablesResultType.notTypeVariables, null);
448+
}
449+
395450
enum _SuffixResultType {
396451
junk, // Found known types of junk it is OK to ignore.
397452
missing, // There is no suffix here. Same as EOF as this is a suffix.
@@ -456,3 +511,19 @@ class IdentifierNode extends CommentReferenceNode {
456511
@override
457512
String toString() => 'Identifier["$text"]';
458513
}
514+
515+
/// Represents one or more type variables, may be
516+
/// comma separated.
517+
class TypeVariablesNode extends CommentReferenceNode {
518+
@override
519+
520+
/// Note that this will contain commas, spaces, and other text, as
521+
/// generally type variables are a form of junk that comment references
522+
/// should ignore.
523+
final String text;
524+
525+
TypeVariablesNode(this.text);
526+
527+
@override
528+
String toString() => 'TypeVariablesNode["$text"]';
529+
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ environment:
77
sdk: '>=2.11.99 <3.0.0'
88

99
dependencies:
10-
analyzer: ^2.1.0
10+
analyzer: ^2.2.0
1111
args: ^2.0.0
1212
charcode: ^1.2.0
1313
collection: ^1.2.0

test/comment_referable/parser_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ void main() {
8181
['ThisThingy', '[]', 'parameter']);
8282
});
8383

84+
test('Check that embedded types within tearoff-like constructs parse', () {
85+
expectParseEquivalent('this<stuff>.isValid', ['this', 'isValid']);
86+
expectParseEquivalent(
87+
'this<stuff, is, also>.isValid', ['this', 'isValid']);
88+
expectParseEquivalent('this<stuff<that<is, real>, complicated>>.isValid',
89+
['this', 'isValid']);
90+
expectParseError('this<stuff.isntValid');
91+
expectParseError('this<stuff>, notBeingValid>.isntValid');
92+
expectParseError('(AndThisMayBeValidDart.butIt).isntValidInReferences');
93+
expectParseError('<NotActually>.valid');
94+
});
95+
8496
test('Basic negative tests', () {
8597
expectParseError(r'.');
8698
expectParseError(r'');

test/end2end/model_special_cases_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,31 @@ void main() {
203203
expect(referenceLookup(constructorTearoffs, 'Ft.new'),
204204
equals(MatchingLinkResult(Fnew)));
205205
});
206+
207+
test('we can use (ignored) type parameters in references', () {
208+
expect(referenceLookup(E, 'D<String>.new'),
209+
equals(MatchingLinkResult(Dnew)));
210+
expect(referenceLookup(constructorTearoffs, 'F<T>.new'),
211+
equals(MatchingLinkResult(Fnew)));
212+
expect(
213+
referenceLookup(
214+
constructorTearoffs, 'F<InvalidThings, DoNotMatterHere>.new'),
215+
equals(MatchingLinkResult(Fnew)));
216+
});
217+
218+
test('negative tests', () {
219+
// Mixins do not have constructors.
220+
expect(referenceLookup(constructorTearoffs, 'M.new'),
221+
equals(MatchingLinkResult(null)));
222+
// These things aren't expressions, parentheses are still illegal.
223+
expect(referenceLookup(constructorTearoffs, '(C).new'),
224+
equals(MatchingLinkResult(null)));
225+
226+
// A bare new will still not work to reference constructors.
227+
// TODO(jcollins-g): reconsider this if we remove "new" as a hint.
228+
expect(referenceLookup(A, 'new'), equals(MatchingLinkResult(null)));
229+
expect(referenceLookup(At, 'new'), equals(MatchingLinkResult(null)));
230+
});
206231
}, skip: !_constructorTearoffsAllowed.allows(utils.platformVersion));
207232
});
208233

0 commit comments

Comments
 (0)