4
4
5
5
import 'package:analyzer/dart/ast/ast.dart' ;
6
6
import 'package:analyzer/dart/ast/visitor.dart' ;
7
- import 'package:analyzer/dart/element/element.dart' ;
8
7
import 'package:analyzer/dart/element/type.dart' ;
9
- import 'package:analyzer/dart/element/type_provider.dart' ;
10
- // ignore: implementation_imports
11
- import 'package:analyzer/src/dart/element/type.dart' show InvalidTypeImpl;
12
8
13
9
import '../analyzer.dart' ;
14
10
import '../extensions.dart' ;
@@ -36,8 +32,8 @@ var coordinates = <int, int>{};
36
32
37
33
**EXCEPTIONS:**
38
34
39
- When a `LinkedHashSet` or `LinkedHashMap` is expected, a collection literal is
40
- not preferred (or allowed) .
35
+ There are cases with `LinkedHashSet` or `LinkedHashMap` where a literal constructor
36
+ will trigger a type error so those will be excluded from the lint .
41
37
42
38
```dart
43
39
void main() {
@@ -76,49 +72,40 @@ class PreferCollectionLiterals extends LintRule {
76
72
@override
77
73
void registerNodeProcessors (
78
74
NodeLintRegistry registry, LinterContext context) {
79
- var visitor = _Visitor (this , context.typeProvider );
75
+ var visitor = _Visitor (this );
80
76
registry.addInstanceCreationExpression (this , visitor);
81
77
registry.addMethodInvocation (this , visitor);
82
78
}
83
79
}
84
80
85
81
class _Visitor extends SimpleAstVisitor <void > {
86
82
final LintRule rule;
87
- final TypeProvider typeProvider;
88
- _Visitor (this .rule, this .typeProvider);
83
+ _Visitor (this .rule);
89
84
90
85
@override
91
86
void visitInstanceCreationExpression (InstanceCreationExpression node) {
92
87
var constructorName = node.constructorName.name? .name;
93
88
94
- if (node.constructorName.type.element is TypeAliasElement ) {
95
- // Allow the use of typedef constructors.
96
- return ;
97
- }
98
-
99
89
// Maps.
100
- if (node.isHashMap) {
101
- var approximateContextType = _approximateContextType (node);
102
- if (approximateContextType is InvalidType ) return ;
103
- if (approximateContextType.isTypeHashMap) return ;
104
- }
105
- if (node.isMap || node.isHashMap) {
90
+ if (_isMap (node) || _isHashMap (node)) {
91
+ if (_shouldSkipLinkedHashLint (node, _isTypeHashMap)) {
92
+ return ;
93
+ }
106
94
if (constructorName == null && node.argumentList.arguments.isEmpty) {
107
95
rule.reportLint (node);
108
96
}
109
97
return ;
110
98
}
111
99
112
100
// Sets.
113
- if (node.isHashSet) {
114
- var approximateContextType = _approximateContextType (node);
115
- if (approximateContextType is InvalidType ) return ;
116
- if (approximateContextType.isTypeHashSet) return ;
117
- }
118
- if (node.isSet || node.isHashSet) {
101
+ if (_isSet (node) || _isHashSet (node)) {
102
+ if (_shouldSkipLinkedHashLint (node, _isTypeHashSet)) {
103
+ return ;
104
+ }
105
+
119
106
var args = node.argumentList.arguments;
120
107
if (constructorName == null ) {
121
- // Allow ` LinkedHashSet(equals: (a, b) => false, hashCode: (o) => 13)`.
108
+ // Skip: LinkedHashSet(equals: (a, b) => false, hashCode: (o) => 13)
122
109
if (args.isEmpty) {
123
110
rule.reportLint (node);
124
111
}
@@ -135,7 +122,7 @@ class _Visitor extends SimpleAstVisitor<void> {
135
122
136
123
@override
137
124
void visitMethodInvocation (MethodInvocation node) {
138
- // Something like ` ['foo', 'bar', 'baz'].toSet()`.
125
+ // ['foo', 'bar', 'baz'].toSet();
139
126
if (node.methodName.name != 'toSet' ) {
140
127
return ;
141
128
}
@@ -144,130 +131,64 @@ class _Visitor extends SimpleAstVisitor<void> {
144
131
}
145
132
}
146
133
147
- /// A very, very rough approximation of the context type of [node] .
148
- ///
149
- /// This approximation will never be accurate for some expressions.
150
- DartType ? _approximateContextType (Expression node) {
151
- var ancestor = node.parent;
152
- var ancestorChild = node;
153
- while (ancestor != null ) {
154
- if (ancestor is ParenthesizedExpression ) {
155
- ancestorChild = ancestor;
156
- ancestor = ancestor.parent;
157
- } else if (ancestor is CascadeExpression &&
158
- ancestorChild == ancestor.target) {
159
- ancestorChild = ancestor;
160
- ancestor = ancestor.parent;
161
- } else {
162
- break ;
163
- }
164
- }
134
+ bool _isHashMap (Expression expression) =>
135
+ _isTypeHashMap (expression.staticType);
165
136
166
- switch (ancestor) {
167
- // TODO(srawlins): Handle [AwaitExpression], [BinaryExpression],
168
- // [CascadeExpression], [ConditionalExpression], [SwitchExpressionCase],
169
- // likely others. Or move everything here to an analysis phase which
170
- // has the actual context type.
171
- case ArgumentList ():
172
- // Allow `function(LinkedHashSet())` for `function(LinkedHashSet mySet)`
173
- // and `function(LinkedHashMap())` for `function(LinkedHashMap myMap)`.
174
- return node.staticParameterElement? .type ?? InvalidTypeImpl .instance;
175
- case AssignmentExpression ():
176
- // Allow `x = LinkedHashMap()`.
177
- return ancestor.staticType;
178
- case ExpressionFunctionBody (parent: var function)
179
- when function is FunctionExpression :
180
- // Allow `<int, LinkedHashSet>{}.putIfAbsent(3, () => LinkedHashSet())`
181
- // and `<int, LinkedHashMap>{}.putIfAbsent(3, () => LinkedHashMap())`.
182
- var functionType = _approximateContextType (function);
183
- return functionType is FunctionType ? functionType.returnType : null ;
184
- case NamedExpression ():
185
- // Allow `void f({required LinkedHashSet<Foo> s})`.
186
- return ancestor.staticParameterElement? .type ??
187
- InvalidTypeImpl .instance;
188
- case ReturnStatement ():
189
- return _expectedReturnType (
190
- ancestor.thisOrAncestorOfType <FunctionBody >(),
191
- );
192
- case VariableDeclaration (parent: VariableDeclarationList (: var type)):
193
- // Allow `LinkedHashSet<int> s = node` and
194
- // `LinkedHashMap<int> s = node`.
195
- return type? .type;
196
- case YieldStatement ():
197
- return _expectedReturnType (
198
- ancestor.thisOrAncestorOfType <FunctionBody >(),
199
- );
200
- }
137
+ bool _isHashSet (Expression expression) =>
138
+ _isTypeHashSet (expression.staticType);
201
139
202
- return null ;
203
- }
140
+ bool _isMap ( Expression expression) =>
141
+ expression.staticType ? .isDartCoreMap ?? false ;
204
142
205
- /// Extracts the expected type for return statements or yield statements.
206
- ///
207
- /// For example, for an asynchronous [body] in a function with a declared
208
- /// [returnType] of `Future<int>` , this returns `int` . (Note: it would be more
209
- /// accurate to use `FutureOr<int>` and an assignability check, but `int` is
210
- /// an approximation that works for now; this should probably be revisited.)
211
- DartType ? _expectedReturnableOrYieldableType (
212
- DartType ? returnType,
213
- FunctionBody body,
214
- ) {
215
- if (returnType == null || returnType is InvalidType ) return null ;
216
- if (body.isAsynchronous) {
217
- if (! body.isGenerator && returnType.isDartAsyncFuture) {
218
- var typeArgs = (returnType as InterfaceType ).typeArguments;
219
- return typeArgs.isEmpty ? null : typeArgs.first;
143
+ bool _isSet (Expression expression) =>
144
+ expression.staticType? .isDartCoreSet ?? false ;
145
+
146
+ bool _isTypeHashMap (DartType ? type) =>
147
+ type.isSameAs ('LinkedHashMap' , 'dart.collection' );
148
+
149
+ bool _isTypeHashSet (DartType ? type) =>
150
+ type.isSameAs ('LinkedHashSet' , 'dart.collection' );
151
+
152
+ bool _shouldSkipLinkedHashLint (
153
+ InstanceCreationExpression node, bool Function (DartType node) typeCheck) {
154
+ if (_isHashMap (node) || _isHashSet (node)) {
155
+ // Skip: LinkedHashSet<int> s = ...; or LinkedHashMap<int> s = ...;
156
+ var parent = node.parent;
157
+ if (parent is VariableDeclaration ) {
158
+ var parent2 = parent.parent;
159
+ if (parent2 is VariableDeclarationList ) {
160
+ var assignmentType = parent2.type? .type;
161
+ if (assignmentType != null && typeCheck (assignmentType)) {
162
+ return true ;
163
+ }
164
+ }
220
165
}
221
- if (body.isGenerator && returnType.isDartAsyncStream) {
222
- var typeArgs = (returnType as InterfaceType ).typeArguments;
223
- return typeArgs.isEmpty ? null : typeArgs.first;
166
+ // Skip: function(LinkedHashSet()); when function(LinkedHashSet mySet) or
167
+ // function(LinkedHashMap()); when function(LinkedHashMap myMap)
168
+ if (parent is ArgumentList ) {
169
+ var paramType = node.staticParameterElement? .type;
170
+ if (paramType == null || typeCheck (paramType)) {
171
+ return true ;
172
+ }
224
173
}
225
- } else {
226
- if (body.isGenerator && returnType.isDartCoreIterable) {
227
- var typeArgs = (returnType as InterfaceType ).typeArguments;
228
- return typeArgs.isEmpty ? null : typeArgs.first;
174
+
175
+ // Skip: void f({required LinkedHashSet<Foo> s})
176
+ if (parent is NamedExpression ) {
177
+ var paramType = parent.staticParameterElement? .type;
178
+ if (paramType != null && typeCheck (paramType)) {
179
+ return true ;
180
+ }
229
181
}
230
- }
231
- return returnType;
232
- }
233
182
234
- /// Attempts to calculate the expected return type of the function represented
235
- /// by [body] , accounting for an approximation of the function's context type,
236
- /// in the case of a function literal.
237
- DartType ? _expectedReturnType (FunctionBody ? body) {
238
- if (body == null ) return null ;
239
- var parent = body.parent;
240
- if (parent is FunctionExpression ) {
241
- var grandparent = parent.parent;
242
- if (grandparent is FunctionDeclaration ) {
243
- var returnType = grandparent.declaredElement? .returnType;
244
- return _expectedReturnableOrYieldableType (returnType, body);
183
+ // Skip: <int, LinkedHashSet>{}.putIfAbsent(3, () => LinkedHashSet());
184
+ // or <int, LinkedHashMap>{}.putIfAbsent(3, () => LinkedHashMap());
185
+ if (parent is ExpressionFunctionBody ) {
186
+ var expressionType = parent.expression.staticType;
187
+ if (expressionType != null && typeCheck (expressionType)) {
188
+ return true ;
189
+ }
245
190
}
246
- var functionType = _approximateContextType (parent);
247
- if (functionType is ! FunctionType ) return null ;
248
- var returnType = functionType.returnType;
249
- return _expectedReturnableOrYieldableType (returnType, body);
250
191
}
251
- if (parent is MethodDeclaration ) {
252
- var returnType = parent.declaredElement? .returnType;
253
- return _expectedReturnableOrYieldableType (returnType, body);
254
- }
255
- return null ;
192
+ return false ;
256
193
}
257
194
}
258
-
259
- extension on Expression {
260
- bool get isHashMap => staticType.isTypeHashMap;
261
-
262
- bool get isHashSet => staticType.isTypeHashSet;
263
-
264
- bool get isMap => staticType? .isDartCoreMap ?? false ;
265
-
266
- bool get isSet => staticType? .isDartCoreSet ?? false ;
267
- }
268
-
269
- extension on DartType ? {
270
- bool get isTypeHashMap => isSameAs ('LinkedHashMap' , 'dart.collection' );
271
-
272
- bool get isTypeHashSet => isSameAs ('LinkedHashSet' , 'dart.collection' );
273
- }
0 commit comments