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

Commit 020abd2

Browse files
author
Konoshenko Vlad
authored
feat: add static code diagnostics avoid-unnecessary-type-assertions (#549)
* feat: add static code diagnostics `avoid-unnecessary-type-assertions` * feat: remove from overview * feat: formatted code * feat: formatted code * feat: remove as operator * Update CHANGELOG.md * Update CHANGELOG.md * doc: updated documentation * doc: updated documentation * doc: updated documentation * doc: updated documentation * doc: updated documentation * doc: updated documentation * doc: updated documentation
1 parent a9bef96 commit 020abd2

File tree

9 files changed

+394
-0
lines changed

9 files changed

+394
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
* feat: introduce file metrics
6+
* feat: add static code diagnostics `avoid-unnecessary-type-assertions`
67
* refactor: cleanup anti-patterns, metrics and rules documentation
78

89
## 4.6.0

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'rules_list/avoid_non_null_assertion/avoid_non_null_assertion.dart';
77
import 'rules_list/avoid_preserve_whitespace_false/avoid_preserve_whitespace_false.dart';
88
import 'rules_list/avoid_returning_widgets/avoid_returning_widgets.dart';
99
import 'rules_list/avoid_unnecessary_setstate/avoid_unnecessary_setstate.dart';
10+
import 'rules_list/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions.dart';
1011
import 'rules_list/avoid_unused_parameters/avoid_unused_parameters.dart';
1112
import 'rules_list/avoid_wrapping_in_padding/avoid_wrapping_in_padding.dart';
1213
import 'rules_list/binary_expression_operand_order/binary_expression_operand_order.dart';
@@ -50,6 +51,8 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
5051
AvoidReturningWidgetsRule(config),
5152
AvoidUnnecessarySetStateRule.ruleId: (config) =>
5253
AvoidUnnecessarySetStateRule(config),
54+
AvoidUnnecessaryTypeAssertions.ruleId: (config) =>
55+
AvoidUnnecessaryTypeAssertions(config),
5356
AvoidUnusedParametersRule.ruleId: (config) =>
5457
AvoidUnusedParametersRule(config),
5558
AvoidWrappingInPaddingRule.ruleId: (config) =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'package:analyzer/dart/ast/ast.dart';
2+
import 'package:analyzer/dart/ast/visitor.dart';
3+
import 'package:analyzer/dart/element/type.dart';
4+
5+
import '../../../../../utils/node_utils.dart';
6+
import '../../../lint_utils.dart';
7+
import '../../../models/internal_resolved_unit_result.dart';
8+
import '../../../models/issue.dart';
9+
import '../../../models/severity.dart';
10+
import '../../dart_rule_utils.dart';
11+
import '../../models/common_rule.dart';
12+
import '../../rule_utils.dart';
13+
14+
part 'visitor.dart';
15+
16+
class AvoidUnnecessaryTypeAssertions extends CommonRule {
17+
static const String ruleId = 'avoid-unnecessary-type-assertions';
18+
19+
AvoidUnnecessaryTypeAssertions([Map<String, Object> config = const {}])
20+
: super(
21+
id: ruleId,
22+
severity: readSeverity(config, Severity.style),
23+
excludes: readExcludes(config),
24+
);
25+
26+
@override
27+
Iterable<Issue> check(InternalResolvedUnitResult source) {
28+
final visitor = _Visitor();
29+
30+
source.unit.visitChildren(visitor);
31+
32+
return visitor.expressions.entries
33+
.map(
34+
(node) => createIssue(
35+
rule: this,
36+
location: nodeLocation(node: node.key, source: source),
37+
message: 'Avoid unnecessary "${node.value}" assertion.',
38+
),
39+
)
40+
.toList(growable: false);
41+
}
42+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
part of 'avoid_unnecessary_type_assertions.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _expressions = <Expression, String>{};
5+
6+
Map<Expression, String> get expressions => _expressions;
7+
8+
@override
9+
void visitMethodInvocation(MethodInvocation node) {
10+
super.visitMethodInvocation(node);
11+
12+
const methodName = 'whereType';
13+
14+
final isWhereTypeFunction = node.methodName.name == methodName;
15+
if (isIterableOrSubclass(node.realTarget?.staticType) &&
16+
isWhereTypeFunction &&
17+
node.target?.staticType is InterfaceType) {
18+
final interfaceType = node.target?.staticType as InterfaceType;
19+
final isTypeHasGeneric = interfaceType.typeArguments.isNotEmpty;
20+
21+
final isCastedHasGeneric =
22+
node.typeArguments?.arguments.isNotEmpty ?? false;
23+
if (isTypeHasGeneric &&
24+
isCastedHasGeneric &&
25+
_isUselessTypeCheck(
26+
interfaceType.typeArguments.first,
27+
node.typeArguments?.arguments.first.type,
28+
)) {
29+
_expressions[node] = methodName;
30+
}
31+
}
32+
}
33+
34+
@override
35+
void visitIsExpression(IsExpression node) {
36+
final objectType = node.expression.staticType;
37+
final castedType = node.type.type;
38+
if (_isUselessTypeCheck(objectType, castedType)) {
39+
_expressions[node] = 'is';
40+
}
41+
}
42+
43+
bool _isUselessTypeCheck(
44+
DartType? objectType,
45+
DartType? castedType,
46+
) {
47+
if (objectType == null || castedType == null) {
48+
return false;
49+
}
50+
51+
// Checked type name
52+
final typeName = objectType.getDisplayString(withNullability: true);
53+
// Casted type name with nullability
54+
final castedNameNull = castedType.getDisplayString(withNullability: true);
55+
// Casted type name without nullability
56+
final castedName = castedType.getDisplayString(withNullability: false);
57+
// Validation checks
58+
final isTypeSame = '$typeName?' == castedNameNull || typeName == castedName;
59+
final isTypeInheritor = _isInheritorType(objectType, castedNameNull);
60+
61+
final isTypeWithGeneric = objectType is InterfaceType &&
62+
castedType is InterfaceType &&
63+
_isTypeWithGeneric(objectType, castedType);
64+
65+
return isTypeSame || isTypeInheritor || isTypeWithGeneric;
66+
}
67+
68+
bool _isTypeWithGeneric(InterfaceType objectType, InterfaceType castedType) {
69+
final objectTypeArguments = objectType.typeArguments;
70+
final castedTypeArguments = castedType.typeArguments;
71+
final isHasGeneric = objectTypeArguments.isNotEmpty;
72+
final isCount = objectTypeArguments.length == castedTypeArguments.length;
73+
74+
if (isHasGeneric && isCount) {
75+
if (castedType.element.name == objectType.element.name) {
76+
for (var i = 0; i < objectTypeArguments.length; i++) {
77+
final isCheckUseless = _isUselessTypeCheck(
78+
objectTypeArguments[i],
79+
castedTypeArguments[i],
80+
);
81+
if (!isCheckUseless) {
82+
return false;
83+
}
84+
}
85+
86+
return true;
87+
}
88+
}
89+
90+
return false;
91+
}
92+
93+
bool _isInheritorType(DartType objectType, String castedNameNull) =>
94+
objectType is InterfaceType &&
95+
objectType.allSupertypes
96+
.any((value) => _isInheritor(value, castedNameNull));
97+
98+
bool _isInheritor(DartType? type, String typeName) =>
99+
type?.getDisplayString(withNullability: false) == typeName;
100+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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_unnecessary_type_assertions/avoid_unnecessary_type_assertions.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _path = 'avoid_unnecessary_type_assertions/examples';
8+
const _classExampleWithIs = '$_path/example_with_is.dart';
9+
const _classExampleCases = '$_path/example_cases.dart';
10+
11+
void main() {
12+
group('AvoidUnnecessaryTypeAssertions', () {
13+
test('initialization', () async {
14+
final unit = await RuleTestHelper.resolveFromFile(_classExampleWithIs);
15+
final issues = AvoidUnnecessaryTypeAssertions().check(unit);
16+
17+
RuleTestHelper.verifyInitialization(
18+
issues: issues,
19+
ruleId: 'avoid-unnecessary-type-assertions',
20+
severity: Severity.style,
21+
);
22+
});
23+
24+
test('reports about found all issues in example_with_is.dart', () async {
25+
final unit = await RuleTestHelper.resolveFromFile(_classExampleWithIs);
26+
final issues = AvoidUnnecessaryTypeAssertions().check(unit);
27+
28+
RuleTestHelper.verifyIssues(
29+
issues: issues,
30+
startOffsets: [
31+
120,
32+
228,
33+
539,
34+
584,
35+
630,
36+
672,
37+
718,
38+
1020,
39+
1053,
40+
],
41+
startLines: [
42+
6,
43+
8,
44+
21,
45+
22,
46+
23,
47+
24,
48+
25,
49+
38,
50+
39,
51+
],
52+
startColumns: [
53+
20,
54+
21,
55+
20,
56+
20,
57+
20,
58+
20,
59+
20,
60+
20,
61+
5,
62+
],
63+
endOffsets: [
64+
143,
65+
252,
66+
555,
67+
601,
68+
643,
69+
689,
70+
731,
71+
1039,
72+
1076,
73+
],
74+
locationTexts: [
75+
'regularString is String',
76+
'regularString is String?',
77+
'animal is Animal',
78+
'cat is HomeAnimal',
79+
'cat is Animal',
80+
'dog is HomeAnimal',
81+
'dog is Animal',
82+
'myList is List<int>',
83+
'myList.whereType<int>()',
84+
],
85+
messages: [
86+
'Avoid unnecessary "is" assertion.',
87+
'Avoid unnecessary "is" assertion.',
88+
'Avoid unnecessary "is" assertion.',
89+
'Avoid unnecessary "is" assertion.',
90+
'Avoid unnecessary "is" assertion.',
91+
'Avoid unnecessary "is" assertion.',
92+
'Avoid unnecessary "is" assertion.',
93+
'Avoid unnecessary "is" assertion.',
94+
'Avoid unnecessary "whereType" assertion.',
95+
],
96+
);
97+
});
98+
99+
test('reports about found all issues in example_cases.dart', () async {
100+
final unit = await RuleTestHelper.resolveFromFile(_classExampleCases);
101+
final issues = AvoidUnnecessaryTypeAssertions().check(unit);
102+
103+
RuleTestHelper.verifyIssues(
104+
issues: issues,
105+
startOffsets: [121, 235, 279, 454, 486, 514, 566],
106+
startLines: [10, 16, 19, 26, 27, 28, 29],
107+
startColumns: [14, 14, 5, 5, 5, 5, 21],
108+
endOffsets: [127, 253, 310, 473, 508, 537, 578],
109+
locationTexts: [
110+
'b is A',
111+
'regular is String?',
112+
"['1', '2'].whereType<String?>()",
113+
'myList is List<int>',
114+
'myList is List<Object>',
115+
'myList.whereType<int>()',
116+
'a is dynamic',
117+
],
118+
messages: [
119+
'Avoid unnecessary "is" assertion.',
120+
'Avoid unnecessary "is" assertion.',
121+
'Avoid unnecessary "whereType" assertion.',
122+
'Avoid unnecessary "is" assertion.',
123+
'Avoid unnecessary "is" assertion.',
124+
'Avoid unnecessary "whereType" assertion.',
125+
'Avoid unnecessary "is" assertion.',
126+
],
127+
);
128+
});
129+
});
130+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class A {}
2+
3+
class B extends A {}
4+
5+
class Example4 {
6+
final a = A();
7+
final b = B();
8+
9+
final res = a is B;
10+
final re = b is A; // LINT
11+
12+
final String? nullable;
13+
final String regular;
14+
15+
final s1 = nullable is String;
16+
final s2 = regular is String?; // LINT
17+
18+
main() {
19+
['1', '2'].whereType<String?>(); // LINT
20+
21+
dynamic a;
22+
final list = <dynamic>[1, 'as', 1];
23+
a is String;
24+
list.whereType<String>();
25+
final myList = [1, 2];
26+
myList is List<int>; //LINT
27+
myList is List<Object>;
28+
myList.whereType<int>(); //LINT
29+
final result2 = a is dynamic;
30+
31+
list.whereType();
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
class Example1 {
2+
final regularString = '';
3+
final String? nullableString = null;
4+
5+
void main() {
6+
final result = regularString is String; // LINT
7+
final result2 = nullableString is String?; // LINT
8+
final result3 = regularString is String?; // LINT
9+
final result4 = nullableString is String;
10+
}
11+
}
12+
13+
class Example2 {
14+
final Animal animal = Animal();
15+
final HomeAnimal homeAnimal = HomeAnimal();
16+
final Cat cat = Cat();
17+
final Dog dog = Dog();
18+
19+
void main() {
20+
final result = animal is HomeAnimal;
21+
final result = animal is Animal; // LINT
22+
final result = cat is HomeAnimal; // LINT
23+
final result = cat is Animal; // LINT
24+
final result = dog is HomeAnimal; // LINT
25+
final result = dog is Animal; // LINT
26+
final result = animal is Dog;
27+
final result = animal is Cat;
28+
final result = homeAnimal is Cat;
29+
final result = homeAnimal is Dog;
30+
final result = homeAnimal is dynamic;
31+
}
32+
}
33+
34+
class Example3 {
35+
final myList = <int>[1, 2, 3];
36+
37+
void main() {
38+
final result = myList is List<int>; // LINT
39+
myList.whereType<int>();
40+
myList.whereType<double>(); // LINT
41+
}
42+
}
43+
44+
class Animal {}
45+
46+
class HomeAnimal extends Animal {}
47+
48+
class Dog extends HomeAnimal {}
49+
50+
class Cat extends HomeAnimal {}

0 commit comments

Comments
 (0)