Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit 31609ed

Browse files
committed
feat: add avoid-dynamic rule
1 parent b818ff8 commit 31609ed

File tree

8 files changed

+221
-0
lines changed

8 files changed

+221
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* feat: add static code diagnostic `avoid-dynamic`.
6+
37
## 4.10.1
48

59
* fix: restore `analyze` command as default command.

lib/src/analyzers/lint_analyzer/rules/rules_factory.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'models/rule.dart';
22
import 'rules_list/always_remove_listener/always_remove_listener_rule.dart';
3+
import 'rules_list/avoid_dynamic/avoid_dynamic_rule.dart';
34
import 'rules_list/avoid_global_state/avoid_global_state_rule.dart';
45
import 'rules_list/avoid_ignoring_return_values/avoid_ignoring_return_values_rule.dart';
56
import 'rules_list/avoid_late_keyword/avoid_late_keyword_rule.dart';
@@ -43,6 +44,7 @@ import 'rules_list/provide_correct_intl_args/provide_correct_intl_args_rule.dart
4344

4445
final _implementedRules = <String, Rule Function(Map<String, Object>)>{
4546
AlwaysRemoveListenerRule.ruleId: (config) => AlwaysRemoveListenerRule(config),
47+
AvoidDynamicRule.ruleId: (config) => AvoidDynamicRule(config),
4648
AvoidGlobalStateRule.ruleId: (config) => AvoidGlobalStateRule(config),
4749
AvoidIgnoringReturnValuesRule.ruleId: (config) =>
4850
AvoidIgnoringReturnValuesRule(config),
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// ignore_for_file: public_member_api_docs
2+
3+
import 'package:analyzer/dart/ast/ast.dart';
4+
import 'package:analyzer/dart/ast/visitor.dart';
5+
6+
import '../../../../../utils/node_utils.dart';
7+
import '../../../lint_utils.dart';
8+
import '../../../models/internal_resolved_unit_result.dart';
9+
import '../../../models/issue.dart';
10+
import '../../../models/severity.dart';
11+
import '../../models/common_rule.dart';
12+
import '../../rule_utils.dart';
13+
14+
part 'visitor.dart';
15+
16+
class AvoidDynamicRule extends CommonRule {
17+
static const String ruleId = 'avoid-dynamic';
18+
19+
static const _warning = 'Avoid using dynamic type.';
20+
21+
AvoidDynamicRule([Map<String, Object> config = const {}])
22+
: super(
23+
id: ruleId,
24+
severity: readSeverity(config, Severity.warning),
25+
excludes: readExcludes(config),
26+
);
27+
28+
@override
29+
Iterable<Issue> check(InternalResolvedUnitResult source) {
30+
final visitor = _Visitor();
31+
32+
source.unit.visitChildren(visitor);
33+
34+
return visitor.nodes
35+
.map((node) => createIssue(
36+
rule: this,
37+
location: nodeLocation(
38+
node: node,
39+
source: source,
40+
),
41+
message: _warning,
42+
))
43+
.toList(growable: false);
44+
}
45+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
part of 'avoid_dynamic_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _nodes = <AstNode>[];
5+
6+
Iterable<AstNode> get nodes => _nodes;
7+
8+
@override
9+
void visitSimpleIdentifier(SimpleIdentifier node) {
10+
final parent = node.parent;
11+
if (parent is NamedType && (parent.type?.isDynamic ?? false)) {
12+
final grandParent = node.parent?.parent;
13+
if (grandParent != null) {
14+
final grandGrandParent = grandParent.parent;
15+
if (!(grandGrandParent is NamedType &&
16+
(grandGrandParent.type?.isDartCoreMap ?? false))) {
17+
_nodes.add(grandParent);
18+
}
19+
}
20+
}
21+
}
22+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
2+
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/avoid_dynamic/avoid_dynamic_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_dynamic/examples/example.dart';
8+
9+
void main() {
10+
group('AvoidDynamicRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = AvoidDynamicRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'avoid-dynamic',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = AvoidDynamicRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [2, 6, 10, 10, 10, 12, 23, 28, 31, 38],
29+
startColumns: [3, 4, 1, 22, 33, 7, 3, 3, 3, 32],
30+
locationTexts: [
31+
'dynamic s = 1',
32+
's as dynamic',
33+
'dynamic someFunction(dynamic a, dynamic b) {\n'
34+
' // LINT\n'
35+
' if (a is dynamic) {\n'
36+
' return b;\n'
37+
' }\n'
38+
'\n'
39+
' return a + b;\n'
40+
'}',
41+
'dynamic a',
42+
'dynamic b',
43+
'a is dynamic',
44+
'final dynamic value',
45+
'dynamic get state => value;',
46+
'dynamic calculate() {\n'
47+
' return value;\n'
48+
' }',
49+
'<dynamic>',
50+
],
51+
messages: [
52+
'Avoid using dynamic type.',
53+
'Avoid using dynamic type.',
54+
'Avoid using dynamic type.',
55+
'Avoid using dynamic type.',
56+
'Avoid using dynamic type.',
57+
'Avoid using dynamic type.',
58+
'Avoid using dynamic type.',
59+
'Avoid using dynamic type.',
60+
'Avoid using dynamic type.',
61+
'Avoid using dynamic type.',
62+
],
63+
);
64+
});
65+
});
66+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
void main() {
2+
dynamic s = 1; // LINT
3+
4+
final result = someFunction('1', '2');
5+
6+
(s as dynamic).toString(); // LINT
7+
}
8+
9+
// LINT
10+
dynamic someFunction(dynamic a, dynamic b) {
11+
// LINT
12+
if (a is dynamic) {
13+
return b;
14+
}
15+
16+
return a + b;
17+
}
18+
19+
typedef Json = Map<String, dynamic>;
20+
21+
class SomeClass {
22+
// LINT
23+
final dynamic value;
24+
25+
SomeClass(this.value);
26+
27+
// LINT
28+
dynamic get state => value;
29+
30+
// LINT
31+
dynamic calculate() {
32+
return value;
33+
}
34+
}
35+
36+
abstract class BaseClass<T> {}
37+
38+
class Generic extends BaseClass<dynamic> {} // LINT
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Avoid dynamic
2+
3+
## Rule id {#rule-id}
4+
5+
avoid-dynamic
6+
7+
## Severity {#severity}
8+
9+
Warning
10+
11+
## Description {#description}
12+
13+
Warns when `dynamic` type is used as variable type in declaration, return type of a function, etc. Using `dynamic` is considered unsafe since it can easily result in runtime errors.
14+
15+
**Note:** using `dynamic` type for `Map<>` is considered fine since there is no better way to declare type of JSON payload.
16+
17+
### Example {#example}
18+
19+
Bad:
20+
21+
```dart
22+
dynamic x = 10; // LINT
23+
24+
// LINT
25+
String concat(dynamic a, dynamic b) {
26+
return a + b;
27+
}
28+
```
29+
30+
Good:
31+
32+
```dart
33+
int x = 10;
34+
35+
final x = 10;
36+
37+
String concat(String a, String b) {
38+
return a + b;
39+
}
40+
```

website/docs/rules/overview.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ Rules configuration is [described here](../getting-started/configuration#configu
1111

1212
## Common {#common}
1313

14+
- [avoid-dynamic](./common/avoid-dynamic.md)
15+
16+
Warns when `dynamic` type is used as variable type in declaration, return type of a function, etc.
17+
1418
- [avoid-global-state](./common/avoid-global-state.md)
1519

1620
Warns about usage mutable global variables.

0 commit comments

Comments
 (0)