Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit 70b5525

Browse files
author
Olivier Chafik
committed
Add a no_implicit_dynamic linter
1 parent c4ce27f commit 70b5525

File tree

3 files changed

+347
-0
lines changed

3 files changed

+347
-0
lines changed

lib/src/rules.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import 'package:linter/src/rules/iterable_contains_unrelated_type.dart';
3030
import 'package:linter/src/rules/library_names.dart';
3131
import 'package:linter/src/rules/library_prefixes.dart';
3232
import 'package:linter/src/rules/non_constant_identifier_names.dart';
33+
import 'package:linter/src/rules/no_implicit_dynamic.dart';
3334
import 'package:linter/src/rules/one_member_abstracts.dart';
3435
import 'package:linter/src/rules/overridden_fields.dart';
3536
import 'package:linter/src/rules/package_api_docs.dart';
@@ -53,6 +54,7 @@ import 'package:linter/src/rules/unrelated_type_equality_checks.dart';
5354
final Registry ruleRegistry = new Registry()
5455
..register(new AlwaysDeclareReturnTypes())
5556
..register(new AlwaysSpecifyTypes())
57+
..register(new NoImplicitDynamic())
5658
..register(new AnnotateOverrides())
5759
..register(new AvoidAs())
5860
..register(new AvoidEmptyElse())
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
library linter.src.rules.annotate_types;
6+
7+
import 'package:analyzer/dart/ast/ast.dart';
8+
import 'package:analyzer/dart/ast/visitor.dart';
9+
import 'package:analyzer/dart/element/element.dart';
10+
import 'package:analyzer/dart/element/type.dart';
11+
import 'package:linter/src/linter.dart';
12+
import 'package:linter/src/util.dart';
13+
14+
const desc = 'Implicit use of dynamic.';
15+
16+
const details = '''
17+
**AVOID** using "implicitly dynamic" values.
18+
19+
Untyped / dynamic invocations may fail or be slower at runtime, but dynamic
20+
types often creep up unintentionally. Explicitly mark variables or return types
21+
as `dynamic` (instead of `var`) to express your intent unequivocally.
22+
23+
Note: this works best with the --strong command-line flag and after disabling
24+
both `always_specify_types` and `always_declare_return_types` lints.
25+
26+
**GOOD:**
27+
```dart
28+
String trim(String s) => s.trim();
29+
30+
main() {
31+
var s = trim(' a ').toUpperCase();
32+
33+
dynamic x;
34+
x = ... ;
35+
x.reallyNotSureThisExists();
36+
}
37+
```
38+
39+
**BAD:**
40+
```dart
41+
trim(s) => s.trim();
42+
43+
main() {
44+
var s = trim(1).toUpperCase();
45+
46+
var x;
47+
x = ... ;
48+
x.reallyNotSureThisExists();
49+
}
50+
```
51+
''';
52+
53+
class NoImplicitDynamic extends LintRule {
54+
NoImplicitDynamic()
55+
: super(
56+
name: 'no_implicit_dynamic',
57+
description: desc,
58+
details: details,
59+
group: Group.style);
60+
61+
@override
62+
AstVisitor getVisitor() => new Visitor(this);
63+
}
64+
65+
// TODO(ochafik): Handle implicit return types of method declarations (vs. overrides).
66+
class Visitor extends SimpleAstVisitor {
67+
final LintRule rule;
68+
Visitor(this.rule);
69+
70+
Element _getBestElement(Expression node) {
71+
if (node is SimpleIdentifier) return node.bestElement;
72+
if (node is PrefixedIdentifier) return node.bestElement;
73+
if (node is PropertyAccess) return node.propertyName.bestElement;
74+
return null;
75+
}
76+
77+
bool _isImplicitDynamic(Expression node) {
78+
if (node == null) return false;
79+
while (node is ParenthesizedExpression) {
80+
node = node.expression;
81+
}
82+
83+
if (node is AsExpression || node is Literal) return false;
84+
var t = node.bestType;
85+
if (!t.isDynamic && !t.isObject) return false;
86+
87+
var e = _getBestElement(node);
88+
if (e is PropertyAccessorElement) e = e.variable;
89+
if (e is VariableElement) return e.hasImplicitType;
90+
91+
if (node is ConditionalExpression) {
92+
return !node.thenExpression.bestType.isDynamic ||
93+
!node.elseExpression.bestType.isDynamic;
94+
}
95+
if (node is MethodInvocation) {
96+
return node.methodName.bestElement?.hasImplicitReturnType != false;
97+
}
98+
99+
return true;
100+
}
101+
102+
void _checkTarget(Expression target, [token]) {
103+
if (_isImplicitDynamic(target)) {
104+
// Avoid double taxation (if `x` is dynamic, only lint `x.y.z` once).
105+
Expression subTarget;
106+
if (target is PropertyAccess) subTarget = target.realTarget;
107+
else if (target is MethodInvocation) subTarget = target.realTarget;
108+
else if (target is IndexExpression) subTarget = target.realTarget;
109+
else if (target is PrefixedIdentifier) subTarget = target.prefix;
110+
111+
if (_isImplicitDynamic(subTarget)) return;
112+
113+
_reportNodeOrToken(target, token);
114+
}
115+
}
116+
117+
_reportNodeOrToken(AstNode node, token) {
118+
if (token != null) {
119+
rule.reportLintForToken(token);
120+
} else {
121+
rule.reportLint(node);
122+
}
123+
}
124+
125+
@override
126+
visitPrefixedIdentifier(PrefixedIdentifier node) {
127+
if (_isObjectProperty(node.identifier)) return;
128+
_checkTarget(node.prefix, node.period);
129+
}
130+
131+
@override
132+
visitPropertyAccess(PropertyAccess node) {
133+
if (_isObjectProperty(node.propertyName)) return;
134+
_checkTarget(node.realTarget, node.operator);
135+
}
136+
137+
bool _isObjectProperty(SimpleIdentifier node) {
138+
var name = node.name;
139+
return name == 'runtimeType' || name == 'hashCode';
140+
}
141+
142+
@override
143+
visitIndexExpression(IndexExpression node) {
144+
_checkTarget(node.realTarget, node.leftBracket);
145+
}
146+
147+
@override
148+
visitAssignmentExpression(AssignmentExpression node) {
149+
var rhs = node.rightHandSide;
150+
_checkAssignment(rhs,
151+
rhs.bestParameterElement ?? _getBestElement(node.leftHandSide));
152+
}
153+
154+
@override
155+
visitMethodInvocation(MethodInvocation node) {
156+
var methodName = node.methodName;
157+
_checkMethodInvocation(node.realTarget, methodName.bestElement, methodName.name, node.argumentList.arguments, node.operator);
158+
}
159+
160+
_checkMethodInvocation(Expression target, ExecutableElement methodElement, String methodName, List<Expression> arguments, token) {
161+
for (var arg in arguments) {
162+
_checkAssignment(arg, arg.bestParameterElement);
163+
}
164+
165+
if (methodElement != null) return;
166+
167+
if (methodName == 'toString' && arguments.isEmpty ||
168+
methodName == 'noSuchMethod' && arguments.size == 1) {
169+
return;
170+
}
171+
_checkTarget(target, token);
172+
}
173+
174+
@override
175+
visitBinaryExpression(BinaryExpression node) {
176+
_checkMethodInvocation(node.leftOperand, node.bestElement, node.operator.toString(), [node.rightOperand], node.operator);
177+
}
178+
179+
_checkAssignment(Expression arg, Element toElement) {
180+
if (!_isImplicitDynamic(arg)) return;
181+
182+
if (toElement == null) return;
183+
184+
if (_isDynamicOrObject(toElement.type)) return;
185+
186+
rule.reportLint(arg);
187+
}
188+
189+
_isDynamicOrObject(DartType t) => t.isDynamic || t.isObject;
190+
191+
@override
192+
void visitConditionalExpression(ConditionalExpression node) {
193+
_checkTarget(node.condition);
194+
}
195+
196+
@override
197+
visitDeclaredIdentifier(DeclaredIdentifier node) {
198+
if (node.type == null && node.identifier.bestType.isDynamic && node.element.type.isDynamic) {
199+
rule.reportLintForToken(node.keyword);
200+
}
201+
}
202+
203+
204+
// bool _isImplicitDynamicVariable(VariableDeclaration node) =>
205+
// node.element.hasImplicitType &&
206+
// node.name.bestType.isDynamic &&
207+
// (node.initializer == null || _isImplicitDynamic(node.initializer));
208+
//
209+
// @override
210+
// visitVariableDeclarationList(VariableDeclarationList node) {
211+
// if (node.type == null && node.variables.any(_isImplicitDynamicVariable)) {
212+
// rule.reportLintForToken(node.keyword);
213+
// }
214+
// }
215+
}

test/rules/no_implicit_dynamic.dart

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
takeInt(int i) {}
2+
takeDynamic(dynamic i) {}
3+
takeObject(Object i) {}
4+
5+
conditionals(implicitBool, bool explicitBool) {
6+
explicitBool ? 1 : 2;
7+
8+
implicitBool //LINT
9+
? 1 : 2;
10+
11+
takeInt(explicitBool ? 1 : "2"); //LINT
12+
}
13+
14+
methodCalls() {
15+
var implicitDynamic;
16+
dynamic explicitDynamic;
17+
18+
takeDynamic(1);
19+
takeObject(1);
20+
takeInt(1);
21+
22+
takeDynamic(implicitDynamic);
23+
takeObject(implicitDynamic);
24+
takeInt(implicitDynamic); //LINT
25+
26+
takeDynamic(explicitDynamic);
27+
takeObject(explicitDynamic);
28+
takeInt(explicitDynamic);
29+
}
30+
31+
class Foo {
32+
var implicitDynamic;
33+
dynamic explicitDynamic;
34+
int i;
35+
}
36+
37+
assignments() {
38+
Foo newFoo() => new Foo();
39+
int i;
40+
var f = newFoo();
41+
42+
// Exercice prefixed identifiers path:
43+
f.i = f.i;
44+
f.i = f.implicitDynamic; //LINT
45+
f.i = f.explicitDynamic;
46+
47+
// Exercice property access path:
48+
f.i = newFoo().i;
49+
f.i = newFoo().implicitDynamic; //LINT
50+
f.i = newFoo().explicitDynamic;
51+
52+
i = f.i;
53+
i = f.implicitDynamic; //LINT
54+
i = f.explicitDynamic;
55+
}
56+
57+
vars(implicitDynamic, dynamic explicitDynamic) {
58+
implicitDynamic.foo; //LINT
59+
implicitDynamic?.foo; //LINT
60+
implicitDynamic.foo(); //LINT
61+
implicitDynamic?.foo(); //LINT
62+
implicitDynamic['foo']; //LINT
63+
implicitDynamic.toString();
64+
implicitDynamic.runtimeType;
65+
implicitDynamic.hashCode;
66+
67+
explicitDynamic.foo;
68+
explicitDynamic.foo();
69+
explicitDynamic['foo'];
70+
explicitDynamic.toString();
71+
explicitDynamic.runtimeType;
72+
explicitDynamic.hashCode;
73+
}
74+
75+
operators(implicitDynamic, dynamic explicitDynamic) {
76+
implicitDynamic + 1; //LINT
77+
implicitDynamic * 1; //LINT
78+
79+
explicitDynamic + 1;
80+
explicitDynamic + null;
81+
82+
// int.operator+ expects an int parameter:
83+
1 + implicitDynamic; //LINT
84+
1 + explicitDynamic;
85+
}
86+
87+
cascades() {
88+
var implicitDynamic;
89+
implicitDynamic
90+
..foo //LINT
91+
..foo() //LINT
92+
..['foo'] //LINT
93+
..toString()
94+
..runtimeType
95+
..hashCode;
96+
}
97+
98+
dynamicMethods() {
99+
trim(s) =>
100+
s.trim(); //LINT
101+
var s = trim(1)
102+
.toUpperCase(); //LINT
103+
104+
implicit() {}
105+
implicit()
106+
.x //LINT
107+
.y;
108+
109+
dynamic explicit() {}
110+
explicit().x;
111+
}
112+
113+
chaining() {
114+
var x;
115+
// Only report the first implicit dynamic in a chain:
116+
x
117+
.y //LINT
118+
.z
119+
.w;
120+
121+
dynamic y;
122+
// Calling an explicit dynamic is okay...
123+
y.z;
124+
y.z();
125+
y['z'];
126+
// ... but returns an implicit dynamic.
127+
y.z.w; //LINT
128+
y.z().w; //LINT
129+
y['z'].w; //LINT
130+
}

0 commit comments

Comments
 (0)