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

Commit 1ef030a

Browse files
committed
feat: add avoid-unnecessary-conditionals rule
1 parent 7db92de commit 1ef030a

File tree

8 files changed

+208
-5
lines changed

8 files changed

+208
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* fix: remove recursive traversal for [`ban-name`](https://dartcodemetrics.dev/docs/rules/common/ban-name) rule.
66
* feat: add static code diagnostic [`prefer-using-list-view`](https://dartcodemetrics.dev/docs/rules/flutter/prefer-using-list-view).
7+
* feat: add static code diagnostic [`avoid-unnecessary-conditionals`](https://dartcodemetrics.dev/docs/rules/common/avoid-unnecessary-conditionals).
78

89
## 5.1.0
910

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

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
2222
import 'rules_list/avoid_shrink_wrap_in_lists/avoid_shrink_wrap_in_lists_rule.dart';
2323
import 'rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart';
2424
import 'rules_list/avoid_top_level_members_in_tests/avoid_top_level_members_in_tests_rule.dart';
25+
import 'rules_list/avoid_unnecessary_conditionals/avoid_unnecessary_conditionals_rule.dart';
2526
import 'rules_list/avoid_unnecessary_setstate/avoid_unnecessary_setstate_rule.dart';
2627
import 'rules_list/avoid_unnecessary_type_assertions/avoid_unnecessary_type_assertions_rule.dart';
2728
import 'rules_list/avoid_unnecessary_type_casts/avoid_unnecessary_type_casts_rule.dart';
@@ -96,6 +97,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
9697
AvoidShrinkWrapInListsRule.ruleId: AvoidShrinkWrapInListsRule.new,
9798
AvoidThrowInCatchBlockRule.ruleId: AvoidThrowInCatchBlockRule.new,
9899
AvoidTopLevelMembersInTestsRule.ruleId: AvoidTopLevelMembersInTestsRule.new,
100+
AvoidUnnecessaryConditionalsRule.ruleId: AvoidUnnecessaryConditionalsRule.new,
99101
AvoidUnnecessarySetStateRule.ruleId: AvoidUnnecessarySetStateRule.new,
100102
AvoidUnnecessaryTypeAssertionsRule.ruleId:
101103
AvoidUnnecessaryTypeAssertionsRule.new,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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/replacement.dart';
11+
import '../../../models/severity.dart';
12+
import '../../models/common_rule.dart';
13+
import '../../rule_utils.dart';
14+
15+
part 'visitor.dart';
16+
17+
class AvoidUnnecessaryConditionalsRule extends CommonRule {
18+
static const String ruleId = 'avoid-unnecessary-conditionals';
19+
20+
static const _warning = 'Avoid unnecessary conditional expressions.';
21+
static const _correctionMessage =
22+
'Remove unnecessary conditional expression.';
23+
24+
AvoidUnnecessaryConditionalsRule([Map<String, Object> config = const {}])
25+
: super(
26+
id: ruleId,
27+
severity: readSeverity(config, Severity.warning),
28+
excludes: readExcludes(config),
29+
includes: readIncludes(config),
30+
);
31+
32+
@override
33+
Iterable<Issue> check(InternalResolvedUnitResult source) {
34+
final visitor = _Visitor();
35+
36+
source.unit.visitChildren(visitor);
37+
38+
return visitor.conditionalsInfo
39+
.map((info) => createIssue(
40+
rule: this,
41+
location: nodeLocation(node: info.expression, source: source),
42+
message: _warning,
43+
replacement: _createReplacement(info),
44+
))
45+
.toList(growable: false);
46+
}
47+
48+
Replacement? _createReplacement(_ConditionalInfo info) {
49+
final condition = info.expression.condition;
50+
final correction = '${info.isInverted ? "!" : ""}$condition';
51+
52+
return Replacement(comment: _correctionMessage, replacement: correction);
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
part of 'avoid_unnecessary_conditionals_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _conditionalsInfo = <_ConditionalInfo>[];
5+
6+
Iterable<_ConditionalInfo> get conditionalsInfo => _conditionalsInfo;
7+
8+
_Visitor();
9+
10+
@override
11+
void visitConditionalExpression(ConditionalExpression node) {
12+
super.visitConditionalExpression(node);
13+
14+
final thenExpression = node.thenExpression;
15+
final elseExpression = node.elseExpression;
16+
17+
if (thenExpression is BooleanLiteral && elseExpression is BooleanLiteral) {
18+
final isInverted = !thenExpression.value && elseExpression.value;
19+
20+
_conditionalsInfo.add(
21+
_ConditionalInfo(expression: node, isInverted: isInverted),
22+
);
23+
}
24+
}
25+
}
26+
27+
class _ConditionalInfo {
28+
final ConditionalExpression expression;
29+
final bool isInverted;
30+
31+
const _ConditionalInfo({
32+
required this.expression,
33+
required this.isInverted,
34+
});
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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_conditionals/avoid_unnecessary_conditionals_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_unnecessary_conditionals/examples/example.dart';
8+
9+
void main() {
10+
group('AvoidUnnecessaryConditionalsRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = AvoidUnnecessaryConditionalsRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'avoid-unnecessary-conditionals',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = AvoidUnnecessaryConditionalsRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [7, 13, 16, 18],
29+
startColumns: [19, 10, 15, 15],
30+
locationTexts: [
31+
'str.isEmpty ? true : false',
32+
'str.isEmpty ? false : true',
33+
'foo ? false : true',
34+
'foo ? true : false',
35+
],
36+
messages: [
37+
'Avoid unnecessary conditional expressions.',
38+
'Avoid unnecessary conditional expressions.',
39+
'Avoid unnecessary conditional expressions.',
40+
'Avoid unnecessary conditional expressions.',
41+
],
42+
replacements: [
43+
'str.isEmpty',
44+
'!str.isEmpty',
45+
'!foo',
46+
'foo',
47+
],
48+
replacementComments: [
49+
'Remove unnecessary conditional expression.',
50+
'Remove unnecessary conditional expression.',
51+
'Remove unnecessary conditional expression.',
52+
'Remove unnecessary conditional expression.',
53+
],
54+
);
55+
});
56+
});
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
void main() {
2+
const str = '';
3+
4+
final assignment = str.isEmpty ? otherFunction() : bar();
5+
final boolean = str.isEmpty ? bar() : baz();
6+
7+
final another = str.isEmpty ? true : false; // LINT
8+
}
9+
10+
bool foo() {
11+
const str = '';
12+
13+
return str.isEmpty ? false : true; // LINT
14+
}
15+
16+
bool bar() => foo ? false : true; // LINT
17+
18+
bool baz() => foo ? true : false; // LINT
19+
20+
String otherFunction() => 'str';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import RuleDetails from '@site/src/components/RuleDetails';
2+
3+
<RuleDetails version="5.2.0" severity="warning" hasFix />
4+
5+
Checks for unnessesary conditional expressions.
6+
7+
### Example {#example}
8+
9+
**❌ Bad:**
10+
11+
```dart
12+
bool baz() => foo ? true : false; // LINT
13+
14+
bool bar() => foo ? false : true; // LINT
15+
```
16+
17+
**✅ Good:**
18+
19+
```dart
20+
bool baz() => foo;
21+
22+
bool bar() => !foo;
23+
```

website/docs/rules/index.mdx

+16-5
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
2424
hasConfig
2525
hasFix
2626
>
27-
Enforces named argument order in function and constructor invocations
28-
to be the same as corresponding named parameter declaration order.
27+
Enforces named argument order in function and constructor invocations to be
28+
the same as corresponding named parameter declaration order.
2929
</RuleEntry>
3030

3131
<RuleEntry
@@ -182,6 +182,16 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
182182
inside a test file.
183183
</RuleEntry>
184184

185+
<RuleEntry
186+
name="avoid-unnecessary-conditionals"
187+
type="common"
188+
severity="warning"
189+
version="5.2.0"
190+
hasFix
191+
>
192+
Checks for unnessesary conditional expressions.
193+
</RuleEntry>
194+
185195
<RuleEntry
186196
name="avoid-unnecessary-type-assertions"
187197
type="common"
@@ -487,8 +497,8 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
487497
version="5.1.0"
488498
hasConfig
489499
>
490-
Suggests to use static class member instead of global constants, variables
491-
and functions.
500+
Suggests to use static class member instead of global constants, variables and
501+
functions.
492502
</RuleEntry>
493503

494504
<RuleEntry
@@ -654,7 +664,8 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
654664
severity="performance"
655665
version="5.2.0"
656666
>
657-
Warns when a <code>Column</code> widget with only <code>children</code> parameter is wrapped in a <code>SingleChildScrollView</code> widget.
667+
Warns when a <code>Column</code> widget with only <code>children</code>{' '}
668+
parameter is wrapped in a <code>SingleChildScrollView</code> widget.
658669
</RuleEntry>
659670

660671
## Intl specific {#intl-specific}

0 commit comments

Comments
 (0)