Skip to content

Commit 48f4706

Browse files
authored
Implement this, operator, and parameter resolution on Containers (#2673)
* Start of prefix implementation... * dartfmt * fix this, operators, and parameters * Fix error in test package * rebuild * Wrong negative case * Update comment
1 parent 2d902c0 commit 48f4706

File tree

10 files changed

+212
-38
lines changed

10 files changed

+212
-38
lines changed

lib/src/comment_references/parser.dart

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,69 @@
55
import 'package:charcode/charcode.dart';
66
import 'package:meta/meta.dart';
77

8+
const _operatorKeyword = 'operator';
9+
const Map<String, String> operatorNames = {
10+
'[]': 'get',
11+
'[]=': 'put',
12+
'~': 'bitwise_negate',
13+
'==': 'equals',
14+
'-': 'minus',
15+
'+': 'plus',
16+
'*': 'multiply',
17+
'/': 'divide',
18+
'<': 'less',
19+
'>': 'greater',
20+
'>=': 'greater_equal',
21+
'<=': 'less_equal',
22+
'<<': 'shift_left',
23+
'>>': 'shift_right',
24+
'>>>': 'triple_shift',
25+
'^': 'bitwise_exclusive_or',
26+
'unary-': 'unary_minus',
27+
'|': 'bitwise_or',
28+
'&': 'bitwise_and',
29+
'~/': 'truncate_divide',
30+
'%': 'modulo'
31+
};
32+
33+
class StringTrie {
34+
final Map<int, StringTrie> children = {};
35+
36+
/// Does [this] node represent a valid entry in the trie?
37+
bool valid = false;
38+
39+
/// Greedily match on the string trie. Returns the index of the first
40+
/// non-operator character if valid, otherwise -1.
41+
int match(String toMatch, [int index = 0]) {
42+
if (index < 0 || index >= toMatch.length) return valid ? index : 1;
43+
var matchChar = toMatch.codeUnitAt(index);
44+
if (children.containsKey(matchChar)) {
45+
return children[matchChar].match(toMatch, index + 1);
46+
}
47+
return valid ? index : -1;
48+
}
49+
50+
void addWord(String toAdd) {
51+
var currentTrie = this;
52+
for (var i in toAdd.codeUnits) {
53+
currentTrie.children.putIfAbsent(i, () => StringTrie());
54+
currentTrie = currentTrie.children[i];
55+
}
56+
currentTrie.valid = true;
57+
}
58+
}
59+
60+
StringTrie _operatorParseTrie;
61+
StringTrie get operatorParseTrie {
62+
if (_operatorParseTrie == null) {
63+
_operatorParseTrie = StringTrie();
64+
for (var name in operatorNames.keys) {
65+
_operatorParseTrie.addWord(name);
66+
}
67+
}
68+
return _operatorParseTrie;
69+
}
70+
871
/// A parser for comment references.
972
// TODO(jcollins-g): align with [CommentReference] from analyzer AST.
1073
class CommentReferenceParser {
@@ -30,7 +93,7 @@ class CommentReferenceParser {
3093
/// ```text
3194
/// <rawCommentReference> ::= <prefix>?<commentReference><suffix>?
3295
///
33-
/// <commentReference> ::= (<packageName> '.')? (<libraryName> '.')? <identifier> ('.' <identifier>)*
96+
/// <commentReference> ::= (<packageName> '.')? (<libraryName> '.')? <dartdocIdentifier> ('.' <identifier>)*
3497
/// ```
3598
List<CommentReferenceNode> _parseRawCommentReference() {
3699
var children = <CommentReferenceNode>[];
@@ -90,7 +153,7 @@ class CommentReferenceParser {
90153
///
91154
/// <constructorPrefixHint> ::= 'new '
92155
///
93-
/// <leadingJunk> ::= ('const' | 'final' | 'var')(' '+)
156+
/// <leadingJunk> ::= ('const' | 'final' | 'var' | 'operator')(' '+)
94157
/// ```
95158
_PrefixParseResult _parsePrefix() {
96159
if (_atEnd) {
@@ -108,25 +171,55 @@ class CommentReferenceParser {
108171
return _PrefixParseResult.missing;
109172
}
110173

111-
static const _whitespace = [$space, $tab, $lf, $cr];
174+
static const _whitespace = {$space, $tab, $lf, $cr};
112175
static const _nonIdentifierChars = {
113176
$dot,
114-
$lt,
115177
$gt,
116178
$lparen,
179+
$lt,
117180
$rparen,
118-
$slash,
119181
$backslash,
120182
$question,
121183
$exclamation,
122184
..._whitespace,
123185
};
124186

187+
/// Advances the index forward to the end of the operator if one is
188+
/// present and returns the operator's name. Otherwise, leaves _index
189+
/// unchanged and returns null.
190+
String _tryParseOperator() {
191+
var tryIndex = _index;
192+
if (tryIndex + _operatorKeyword.length < codeRef.length &&
193+
codeRef.substring(tryIndex, tryIndex + _operatorKeyword.length) ==
194+
_operatorKeyword) {
195+
tryIndex = tryIndex + _operatorKeyword.length;
196+
while (_whitespace.contains(codeRef.codeUnitAt(tryIndex))) {
197+
tryIndex++;
198+
}
199+
}
200+
201+
var result = operatorParseTrie.match(codeRef, tryIndex);
202+
if (result == -1) {
203+
return null;
204+
}
205+
_index = result;
206+
return codeRef.substring(tryIndex, result);
207+
}
208+
209+
/// Parse a dartdoc identifier.
210+
///
211+
/// Dartdoc identifiers can include some operators.
125212
_IdentifierParseResult _parseIdentifier() {
126213
if (_atEnd) {
127214
return _IdentifierParseResult.endOfFile;
128215
}
129216
var startIndex = _index;
217+
218+
var foundOperator = _tryParseOperator();
219+
if (foundOperator != null) {
220+
return _IdentifierParseResult.ok(IdentifierNode(foundOperator));
221+
}
222+
130223
while (!_atEnd) {
131224
if (_nonIdentifierChars.contains(_thisChar)) {
132225
if (startIndex == _index) {

lib/src/generator/templates.runtime_renderers.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3347,6 +3347,13 @@ class _Renderer_Container extends RendererBase<Container> {
33473347
self.renderSimpleVariable(c, remainingNames, 'bool'),
33483348
getBool: (CT_ c) => c.hasInstanceFields == true,
33493349
),
3350+
'hasParameters': Property(
3351+
getValue: (CT_ c) => c.hasParameters,
3352+
renderVariable: (CT_ c, Property<CT_> self,
3353+
List<String> remainingNames) =>
3354+
self.renderSimpleVariable(c, remainingNames, 'bool'),
3355+
getBool: (CT_ c) => c.hasParameters == true,
3356+
),
33503357
'hasPublicConstantFields': Property(
33513358
getValue: (CT_ c) => c.hasPublicConstantFields,
33523359
renderVariable: (CT_ c, Property<CT_> self,
@@ -3722,6 +3729,18 @@ class _Renderer_Container extends RendererBase<Container> {
37223729
getters: _invisibleGetters['CommentReferable']));
37233730
},
37243731
),
3732+
'scope': Property(
3733+
getValue: (CT_ c) => c.scope,
3734+
renderVariable: (CT_ c, Property<CT_> self,
3735+
List<String> remainingNames) =>
3736+
self.renderSimpleVariable(c, remainingNames, 'Scope'),
3737+
isNullValue: (CT_ c) => c.scope == null,
3738+
renderValue:
3739+
(CT_ c, RendererBase<CT_> r, List<MustachioNode> ast) {
3740+
return renderSimple(c.scope, ast, r.template,
3741+
parent: r, getters: _invisibleGetters['Scope']);
3742+
},
3743+
),
37253744
'staticAccessors': Property(
37263745
getValue: (CT_ c) => c.staticAccessors,
37273746
renderVariable: (CT_ c, Property<CT_> self,
@@ -6415,6 +6434,13 @@ class _Renderer_GetterSetterCombo extends RendererBase<GetterSetterCombo> {
64156434
self.renderSimpleVariable(c, remainingNames, 'bool'),
64166435
getBool: (CT_ c) => c.hasNoGetterSetter == true,
64176436
),
6437+
'hasParameters': Property(
6438+
getValue: (CT_ c) => c.hasParameters,
6439+
renderVariable: (CT_ c, Property<CT_> self,
6440+
List<String> remainingNames) =>
6441+
self.renderSimpleVariable(c, remainingNames, 'bool'),
6442+
getBool: (CT_ c) => c.hasParameters == true,
6443+
),
64186444
'hasPublicGetter': Property(
64196445
getValue: (CT_ c) => c.hasPublicGetter,
64206446
renderVariable: (CT_ c, Property<CT_> self,
@@ -13679,6 +13705,13 @@ class _Renderer_TypeParameter extends RendererBase<TypeParameter> {
1367913705
parent: r);
1368013706
},
1368113707
),
13708+
'hasParameters': Property(
13709+
getValue: (CT_ c) => c.hasParameters,
13710+
renderVariable: (CT_ c, Property<CT_> self,
13711+
List<String> remainingNames) =>
13712+
self.renderSimpleVariable(c, remainingNames, 'bool'),
13713+
getBool: (CT_ c) => c.hasParameters == true,
13714+
),
1368213715
'href': Property(
1368313716
getValue: (CT_ c) => c.href,
1368413717
renderVariable:
@@ -14996,6 +15029,7 @@ const _invisibleGetters = {
1499615029
'getterSetterDocumentationComment',
1499715030
'modelType',
1499815031
'isCallable',
15032+
'hasParameters',
1499915033
'parameters',
1500015034
'linkedParamsNoMetadata',
1500115035
'hasExplicitGetter',

lib/src/model/comment_referable.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ mixin CommentReferable implements Nameable {
9292
}
9393
if (result?.enclosingElement is Container) {
9494
assert(false,
95-
'[Container] member detected, override in subclass and handle inheritance');
95+
'[Container] member detected, support not implemented for analyzer scope inside containers');
9696
return null;
9797
}
9898
return recurseChildrenAndFilter(referenceLookup, result, filter);

lib/src/model/container.dart

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:analyzer/dart/element/scope.dart';
67
import 'package:dartdoc/src/model/comment_referable.dart';
78
import 'package:dartdoc/src/model/model.dart';
89
import 'package:dartdoc/src/model_utils.dart' as model_utils;
@@ -32,6 +33,13 @@ abstract class Container extends ModelElement with TypeParameters {
3233
Container(Element element, Library library, PackageGraph packageGraph)
3334
: super(element, library, packageGraph);
3435

36+
// TODO(jcollins-g): Implement a ContainerScope that flattens supertypes?
37+
@override
38+
Scope get scope => null;
39+
40+
@override
41+
bool get hasParameters => false;
42+
3543
/// Is this a class (but not an enum)?
3644
bool get isClass =>
3745
element is ClassElement && !(element as ClassElement).isEnum;
@@ -251,9 +259,31 @@ abstract class Container extends ModelElement with TypeParameters {
251259
@override
252260
@mustCallSuper
253261
Map<String, CommentReferable> get referenceChildren {
254-
return _referenceChildren ??= Map.fromEntries(allModelElements
255-
.where((e) => e is! Accessor)
256-
.map((e) => MapEntry(e.name, e)));
262+
if (_referenceChildren == null) {
263+
_referenceChildren = {};
264+
for (var modelElement in allModelElements) {
265+
if (modelElement is Accessor) continue;
266+
if (modelElement is Operator) {
267+
// TODO(jcollins-g): once todo in [Operator.name] is fixed, remove
268+
// this special case.
269+
_referenceChildren[modelElement.element.name] = modelElement;
270+
} else {
271+
_referenceChildren[modelElement.name] = modelElement;
272+
}
273+
// Don't complain about references to parameter names, but prefer
274+
// referring to anything else.
275+
// TODO(jcollins-g): Figure out something good to do in the ecosystem
276+
// here to wean people off the habit of unscoped parameter references.
277+
if (modelElement.hasParameters) {
278+
for (var parameterElement in modelElement.parameters) {
279+
_referenceChildren.putIfAbsent(
280+
parameterElement.name, () => parameterElement);
281+
}
282+
}
283+
}
284+
_referenceChildren['this'] = this;
285+
}
286+
return _referenceChildren;
257287
}
258288

259289
@override

lib/src/model/getter_setter_combo.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ mixin GetterSetterCombo on ModelElement {
195195
@override
196196
bool get isCallable => hasSetter;
197197

198+
@override
199+
bool get hasParameters => hasSetter;
200+
198201
@override
199202
List<Parameter> get parameters => setter.parameters;
200203

lib/src/model/operator.dart

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,10 @@
44

55
import 'package:analyzer/dart/element/element.dart';
66
import 'package:analyzer/src/dart/element/member.dart' show Member;
7+
import 'package:dartdoc/src/comment_references/parser.dart';
78
import 'package:dartdoc/src/model/model.dart';
89

910
class Operator extends Method {
10-
static const Map<String, String> friendlyNames = {
11-
'[]': 'get',
12-
'[]=': 'put',
13-
'~': 'bitwise_negate',
14-
'==': 'equals',
15-
'-': 'minus',
16-
'+': 'plus',
17-
'*': 'multiply',
18-
'/': 'divide',
19-
'<': 'less',
20-
'>': 'greater',
21-
'>=': 'greater_equal',
22-
'<=': 'less_equal',
23-
'<<': 'shift_left',
24-
'>>': 'shift_right',
25-
'>>>': 'triple_shift',
26-
'^': 'bitwise_exclusive_or',
27-
'unary-': 'unary_minus',
28-
'|': 'bitwise_or',
29-
'&': 'bitwise_and',
30-
'~/': 'truncate_divide',
31-
'%': 'modulo'
32-
};
33-
3411
Operator(MethodElement element, Library library, PackageGraph packageGraph)
3512
: super(element, library, packageGraph);
3613

@@ -42,8 +19,8 @@ class Operator extends Method {
4219
@override
4320
String get fileName {
4421
var actualName = super.name;
45-
if (friendlyNames.containsKey(actualName)) {
46-
actualName = 'operator_${friendlyNames[actualName]}';
22+
if (operatorNames.containsKey(actualName)) {
23+
actualName = 'operator_${operatorNames[actualName]}';
4724
}
4825
return '$actualName.$fileType';
4926
}
@@ -57,6 +34,9 @@ class Operator extends Method {
5734

5835
@override
5936
String get name {
37+
// TODO(jcollins-g): New lookup code will no longer require this operator
38+
// prefix. Delete it and use super implementation after old lookup code
39+
// is removed.
6040
return 'operator ${super.name}';
6141
}
6242
}

lib/src/model/type_parameter.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class TypeParameter extends ModelElement {
4747
return _boundType;
4848
}
4949

50+
@override
51+
bool get hasParameters => false;
52+
5053
String _name;
5154

5255
@override

test/comment_referable/parser_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ void main() {
1717
expect(hasHint, equals(constructorHint));
1818
}
1919

20+
void expectParsePassthrough(String codeRef) =>
21+
expectParseEquivalent(codeRef, [codeRef]);
22+
2023
void expectParseError(String codeRef) {
2124
expect(CommentReferenceParser(codeRef).parse(), isEmpty);
2225
}
@@ -43,11 +46,31 @@ void main() {
4346
expectParseEquivalent('this.is.valid(things)', ['this', 'is', 'valid']);
4447
});
4548

49+
test('Check that operator references parse', () {
50+
expectParsePassthrough('[]');
51+
expectParsePassthrough('<=');
52+
expectParsePassthrough('>=');
53+
expectParsePassthrough('>');
54+
expectParsePassthrough('>>');
55+
expectParsePassthrough('>>>');
56+
expectParseEquivalent('operator []', ['[]']);
57+
expectParseEquivalent('operator []', ['[]']);
58+
expectParseEquivalent('operator[]', ['[]']);
59+
expectParseEquivalent('operator <=', ['<=']);
60+
expectParseEquivalent('operator >=', ['>=']);
61+
62+
expectParseEquivalent('ThisThingy.operator []', ['ThisThingy', '[]']);
63+
expectParseEquivalent('ThisThingy.operator [].parameter',
64+
['ThisThingy', '[]', 'parameter']);
65+
});
66+
4667
test('Basic negative tests', () {
4768
expectParseError(r'.');
4869
expectParseError(r'');
4970
expectParseError('foo(wefoi');
5071
expectParseError('<MoofMilker>');
72+
expectParseError('>%');
73+
expectParseError('>=>');
5174
});
5275
});
5376
}

0 commit comments

Comments
 (0)