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

Commit 7a05c6e

Browse files
authored
feat: add prefer-async-await rule (#686)
* feat: add prefer-async-await rule * chore: update withCommentOrMetadata
1 parent 6ea082e commit 7a05c6e

File tree

8 files changed

+175
-1
lines changed

8 files changed

+175
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Unreleased
44

5-
* feat: add static code diagnostic `avoid-dynamic`.
5+
* feat: add static code diagnostics `avoid-dynamic`, `prefer-async-await`.
66

77
## 4.10.1
88

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import 'rules_list/no_equal_arguments/no_equal_arguments_rule.dart';
2828
import 'rules_list/no_equal_then_else/no_equal_then_else_rule.dart';
2929
import 'rules_list/no_magic_number/no_magic_number_rule.dart';
3030
import 'rules_list/no_object_declaration/no_object_declaration_rule.dart';
31+
import 'rules_list/prefer_async_await/prefer_async_await_rule.dart';
3132
import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart';
3233
import 'rules_list/prefer_const_border_radius/prefer_const_border_radius_rule.dart';
3334
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length_rule.dart';
@@ -89,6 +90,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
8990
NoEqualThenElseRule.ruleId: (config) => NoEqualThenElseRule(config),
9091
NoMagicNumberRule.ruleId: (config) => NoMagicNumberRule(config),
9192
NoObjectDeclarationRule.ruleId: (config) => NoObjectDeclarationRule(config),
93+
PreferAsyncAwaitRule.ruleId: (config) => PreferAsyncAwaitRule(config),
9294
PreferConditionalExpressionsRule.ruleId: (config) =>
9395
PreferConditionalExpressionsRule(config),
9496
PreferConstBorderRadiusRule.ruleId: (config) =>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 PreferAsyncAwaitRule extends CommonRule {
17+
static const ruleId = 'prefer-async-await';
18+
static const _warningMessage =
19+
'Prefer using async/await syntax instead of .then invocations';
20+
21+
PreferAsyncAwaitRule([Map<String, Object> config = const {}])
22+
: super(
23+
id: ruleId,
24+
severity: readSeverity(config, Severity.style),
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.invocations
35+
.map((invocation) => createIssue(
36+
rule: this,
37+
location: nodeLocation(
38+
node: invocation,
39+
source: source,
40+
withCommentOrMetadata: false,
41+
),
42+
message: _warningMessage,
43+
))
44+
.toList(growable: false);
45+
}
46+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
part of 'prefer_async_await_rule.dart';
2+
3+
class _Visitor extends RecursiveAstVisitor<void> {
4+
final _invocations = <MethodInvocation>[];
5+
6+
Iterable<MethodInvocation> get invocations => _invocations;
7+
8+
@override
9+
void visitMethodInvocation(MethodInvocation node) {
10+
super.visitMethodInvocation(node);
11+
12+
final target = node.realTarget;
13+
14+
if ((target?.staticType?.isDartAsyncFuture ?? false) &&
15+
node.methodName.name == 'then') {
16+
_invocations.add(node);
17+
}
18+
}
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'dart:async';
2+
3+
void function() async {
4+
final future = Future.delayed(Duration(microseconds: 100), () => 6);
5+
6+
future.then((value) => print(value)); // LINT
7+
8+
future..then((value) => print(value)); // LINT
9+
10+
final value = await future;
11+
12+
final anotherFuture = getFuture().then((value) => value + 5); // LINT
13+
14+
final anotherValue = await anotherFuture;
15+
}
16+
17+
Future<int> getFuture() {
18+
return Future.delayed(Duration(microseconds: 100), () => 5)
19+
.then((value) => value + 1); // LINT
20+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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/prefer_async_await/prefer_async_await_rule.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../../../../../helpers/rule_test_helper.dart';
6+
7+
const _examplePath = 'prefer_async_await/examples/example.dart';
8+
9+
void main() {
10+
group('PreferAsyncAwaitRule', () {
11+
test('initialization', () async {
12+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
13+
final issues = PreferAsyncAwaitRule().check(unit);
14+
15+
RuleTestHelper.verifyInitialization(
16+
issues: issues,
17+
ruleId: 'prefer-async-await',
18+
severity: Severity.style,
19+
);
20+
});
21+
22+
test('reports about found issues', () async {
23+
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
24+
final issues = PreferAsyncAwaitRule().check(unit);
25+
26+
RuleTestHelper.verifyIssues(
27+
issues: issues,
28+
startLines: [6, 8, 12, 18],
29+
startColumns: [3, 9, 25, 10],
30+
locationTexts: [
31+
'future.then((value) => print(value))',
32+
'..then((value) => print(value))',
33+
'getFuture().then((value) => value + 5)',
34+
'Future.delayed(Duration(microseconds: 100), () => 5)\n'
35+
' .then((value) => value + 1)',
36+
],
37+
messages: [
38+
'Prefer using async/await syntax instead of .then invocations',
39+
'Prefer using async/await syntax instead of .then invocations',
40+
'Prefer using async/await syntax instead of .then invocations',
41+
'Prefer using async/await syntax instead of .then invocations',
42+
],
43+
);
44+
});
45+
});
46+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Prefer async await
2+
3+
## Rule id {#rule-id}
4+
5+
prefer-async-await
6+
7+
## Severity {#severity}
8+
9+
Style
10+
11+
## Description {#description}
12+
13+
Recommends to use async/await syntax to handle Futures result instead of `.then()` invocation. Also can help prevent errors with mixed `await` and `.then()` usages, since awaiting the result of a `Future` with `.then()` invocation awaits the completion of `.then()`.
14+
15+
### Example {#example}
16+
17+
Bad:
18+
19+
```dart
20+
Future<void> main() async {
21+
someFuture.then((result) => handleResult(result)); // LINT
22+
23+
await (foo.asyncMethod()).then((result) => handleResult(result)); // LINT
24+
}
25+
```
26+
27+
Good:
28+
29+
```dart
30+
Future<void> main() async {
31+
final result = await someFuture; (result) => handleResult(result));
32+
handleResult(result);
33+
34+
final anotherResult = await foo.asyncMethod();
35+
handleResult(anotherResult);
36+
}
37+
```

website/docs/rules/overview.md

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

104104
Warns when a class member is declared with Object type.
105105

106+
- [prefer-async-await](./common/prefer-async-await.md)
107+
108+
Recommends to use async/await syntax to handle Futures result instead of `.then()` invocation.
109+
106110
- [prefer-conditional-expressions](./common/prefer-conditional-expressions.md) &nbsp; ![Has auto-fix](https://img.shields.io/badge/-has%20auto--fix-success)
107111

108112
Recommends to use a conditional expression instead of assigning to the same thing or return statement in each branch of an if statement.

0 commit comments

Comments
 (0)