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

Commit 3dd127e

Browse files
feat: add static code diagnostic avoid-redundant-async (#1002)
* feat: add static code diagnostic avoid-redundant-async * fix: correctly handle return void Co-authored-by: Dmitry Krutskikh <[email protected]>
1 parent 1d8e535 commit 3dd127e

File tree

8 files changed

+246
-0
lines changed

8 files changed

+246
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* feat: add static code diagnostic [`avoid-redundant-async`](https://dartcodemetrics.dev/docs/rules/common/avoid-redundant-async).
56
* feat: add static code diagnostic [`prefer-correct-test-file-name`](https://dartcodemetrics.dev/docs/rules/common/prefer-correct-test-file-name).
67
* feat: add static code diagnostic [`prefer-iterable-of`](https://dartcodemetrics.dev/docs/rules/common/prefer-iterable-of).
78

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

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'rules_list/avoid_non_ascii_symbols/avoid_non_ascii_symbols_rule.dart';
1515
import 'rules_list/avoid_non_null_assertion/avoid_non_null_assertion_rule.dart';
1616
import 'rules_list/avoid_passing_async_when_sync_expected/avoid_passing_async_when_sync_expected_rule.dart';
1717
import 'rules_list/avoid_preserve_whitespace_false/avoid_preserve_whitespace_false_rule.dart';
18+
import 'rules_list/avoid_redundant_async/avoid_redundant_async_rule.dart';
1819
import 'rules_list/avoid_returning_widgets/avoid_returning_widgets_rule.dart';
1920
import 'rules_list/avoid_shrink_wrap_in_lists/avoid_shrink_wrap_in_lists_rule.dart';
2021
import 'rules_list/avoid_throw_in_catch_block/avoid_throw_in_catch_block_rule.dart';
@@ -82,6 +83,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
8283
AvoidPassingAsyncWhenSyncExpectedRule.ruleId:
8384
AvoidPassingAsyncWhenSyncExpectedRule.new,
8485
AvoidPreserveWhitespaceFalseRule.ruleId: AvoidPreserveWhitespaceFalseRule.new,
86+
AvoidRedundantAsyncRule.ruleId: AvoidRedundantAsyncRule.new,
8587
AvoidReturningWidgetsRule.ruleId: AvoidReturningWidgetsRule.new,
8688
AvoidShrinkWrapInListsRule.ruleId: AvoidShrinkWrapInListsRule.new,
8789
AvoidThrowInCatchBlockRule.ruleId: AvoidThrowInCatchBlockRule.new,
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/token.dart';
5+
import 'package:analyzer/dart/ast/visitor.dart';
6+
7+
import '../../../../../utils/node_utils.dart';
8+
import '../../../lint_utils.dart';
9+
import '../../../models/internal_resolved_unit_result.dart';
10+
import '../../../models/issue.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 AvoidRedundantAsyncRule extends CommonRule {
18+
static const String ruleId = 'avoid-redundant-async';
19+
20+
static const _warningMessage =
21+
"'async' keyword is redundant, consider removing it.";
22+
23+
AvoidRedundantAsyncRule([Map<String, Object> config = const {}])
24+
: super(
25+
id: ruleId,
26+
severity: readSeverity(config, Severity.warning),
27+
excludes: readExcludes(config),
28+
);
29+
30+
@override
31+
Iterable<Issue> check(InternalResolvedUnitResult source) {
32+
final visitor = _Visitor();
33+
34+
source.unit.visitChildren(visitor);
35+
36+
return visitor.declarations.map((declaration) => createIssue(
37+
rule: this,
38+
location: nodeLocation(
39+
node: declaration,
40+
source: source,
41+
),
42+
message: _warningMessage,
43+
));
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
part of 'avoid_redundant_async_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _declarations = <Declaration>[];
5+
6+
Iterable<Declaration> get declarations => _declarations;
7+
8+
@override
9+
void visitMethodDeclaration(MethodDeclaration node) {
10+
super.visitMethodDeclaration(node);
11+
12+
if (_hasRedundantAsync(node.body)) {
13+
_declarations.add(node);
14+
}
15+
}
16+
17+
@override
18+
void visitFunctionDeclaration(FunctionDeclaration node) {
19+
super.visitFunctionDeclaration(node);
20+
21+
if (_hasRedundantAsync(node.functionExpression.body)) {
22+
_declarations.add(node);
23+
}
24+
}
25+
26+
bool _hasRedundantAsync(FunctionBody body) {
27+
final hasAsyncKeyword = body.keyword?.type == Keyword.ASYNC;
28+
if (!hasAsyncKeyword) {
29+
return false;
30+
}
31+
32+
if (body is ExpressionFunctionBody) {
33+
final type = body.expression.staticType;
34+
35+
if (type != null && !type.isDartAsyncFuture) {
36+
return false;
37+
}
38+
}
39+
40+
final asyncVisitor = _AsyncVisitor();
41+
body.parent?.visitChildren(asyncVisitor);
42+
43+
return !asyncVisitor.hasValidAsync;
44+
}
45+
}
46+
47+
class _AsyncVisitor extends RecursiveAstVisitor<void> {
48+
bool hasValidAsync = false;
49+
50+
@override
51+
void visitReturnStatement(ReturnStatement node) {
52+
super.visitReturnStatement(node);
53+
54+
final type = node.expression?.staticType;
55+
56+
if (type == null || !type.isDartAsyncFuture) {
57+
hasValidAsync = true;
58+
}
59+
}
60+
61+
@override
62+
void visitThrowExpression(ThrowExpression node) {
63+
super.visitThrowExpression(node);
64+
65+
hasValidAsync = true;
66+
}
67+
68+
@override
69+
void visitAwaitExpression(AwaitExpression node) {
70+
super.visitAwaitExpression(node);
71+
72+
hasValidAsync = true;
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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_redundant_async/avoid_redundant_async_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'avoid_redundant_async/examples/example.dart';
8+
9+
void main() {
10+
group('AvoidRedundantAsyncRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = AvoidRedundantAsyncRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'avoid-redundant-async',
18+
severity: Severity.warning,
19+
);
20+
});
21+
22+
test('reports about found issues with the default config', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = AvoidRedundantAsyncRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [12, 22],
29+
startColumns: [1, 3],
30+
locationTexts: [
31+
'Future<int> fastestBranch(Future<int> left, Future<int> right) async {\n'
32+
' return Future.any([left, right]);\n'
33+
'}',
34+
"Future<String> anotherAsyncMethod() async => Future.value('value');",
35+
],
36+
messages: [
37+
"'async' keyword is redundant, consider removing it.",
38+
"'async' keyword is redundant, consider removing it.",
39+
],
40+
);
41+
});
42+
});
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Future<void> usesAwait(Future<String> later) async {
2+
print(await later);
3+
}
4+
5+
Future<void> asyncError() async {
6+
throw 'Error!';
7+
}
8+
9+
Future<void> asyncValue() async => 'value';
10+
11+
// LINT
12+
Future<int> fastestBranch(Future<int> left, Future<int> right) async {
13+
return Future.any([left, right]);
14+
}
15+
16+
class SomeClass {
17+
void syncMethod() {}
18+
19+
Future<int> asyncMethod() async => 1;
20+
21+
// LINT
22+
Future<String> anotherAsyncMethod() async => Future.value('value');
23+
24+
Future<String> someAsyncMethod(Future<String> later) => later;
25+
26+
Future<void> report(Iterable<String> records) async {
27+
if (records.isEmpty) {
28+
return;
29+
}
30+
31+
print(records);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import RuleDetails from '@site/src/components/RuleDetails';
2+
3+
<RuleDetails version="4.19.0" severity="warning" />
4+
5+
Checks for redundant `async` in a method or function body.
6+
7+
Cases where `async` is useful include:
8+
9+
- The function body has `await`.
10+
- An error is returnted asynchronously. `async` and then `throw` is shorter than return `Future.error(...)`.
11+
- A value is returned and it will be impliclty wrapped in a future. `async` is shorter than `Future.value(...)`.
12+
13+
Additional resoureces:
14+
15+
- <https://dart.dev/guides/language/effective-dart/usage#dont-use-async-when-it-has-no-useful-effect>
16+
17+
### Example
18+
19+
**❌ Bad:**
20+
21+
```dart
22+
Future<void> afterTwoThings(Future<void> first, Future<void> second) async {
23+
return Future.wait([first, second]);
24+
}
25+
```
26+
27+
**✅ Good:**
28+
29+
```dart
30+
Future<void> usesAwait(Future<String> later) async {
31+
print(await later);
32+
}
33+
34+
Future<void> asyncError() async {
35+
throw 'Error!';
36+
}
37+
38+
Future<void> asyncValue() async => 'value';
39+
```

website/docs/rules/index.mdx

+9
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ Rules are grouped by category to help you understand their purpose. Each rule ha
132132
function is expected.
133133
</RuleEntry>
134134

135+
<RuleEntry
136+
name="avoid-redundant-async"
137+
type="common"
138+
severity="warning"
139+
version="4.19.0"
140+
>
141+
Checks for redundant <code>async</code> in a method or function body.
142+
</RuleEntry>
143+
135144
<RuleEntry
136145
name="avoid-throw-in-catch-block"
137146
type="common"

0 commit comments

Comments
 (0)