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

feat: add prefer-async-await rule #686

Merged
merged 3 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Unreleased

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

## 4.10.1

Expand Down
2 changes: 2 additions & 0 deletions lib/src/analyzers/lint_analyzer/rules/rules_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'rules_list/no_equal_arguments/no_equal_arguments_rule.dart';
import 'rules_list/no_equal_then_else/no_equal_then_else_rule.dart';
import 'rules_list/no_magic_number/no_magic_number_rule.dart';
import 'rules_list/no_object_declaration/no_object_declaration_rule.dart';
import 'rules_list/prefer_async_await/prefer_async_await_rule.dart';
import 'rules_list/prefer_conditional_expressions/prefer_conditional_expressions_rule.dart';
import 'rules_list/prefer_const_border_radius/prefer_const_border_radius_rule.dart';
import 'rules_list/prefer_correct_identifier_length/prefer_correct_identifier_length_rule.dart';
Expand Down Expand Up @@ -89,6 +90,7 @@ final _implementedRules = <String, Rule Function(Map<String, Object>)>{
NoEqualThenElseRule.ruleId: (config) => NoEqualThenElseRule(config),
NoMagicNumberRule.ruleId: (config) => NoMagicNumberRule(config),
NoObjectDeclarationRule.ruleId: (config) => NoObjectDeclarationRule(config),
PreferAsyncAwaitRule.ruleId: (config) => PreferAsyncAwaitRule(config),
PreferConditionalExpressionsRule.ruleId: (config) =>
PreferConditionalExpressionsRule(config),
PreferConstBorderRadiusRule.ruleId: (config) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ignore_for_file: public_member_api_docs

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';

import '../../../../../utils/node_utils.dart';
import '../../../lint_utils.dart';
import '../../../models/internal_resolved_unit_result.dart';
import '../../../models/issue.dart';
import '../../../models/severity.dart';
import '../../models/common_rule.dart';
import '../../rule_utils.dart';

part 'visitor.dart';

class PreferAsyncAwaitRule extends CommonRule {
static const ruleId = 'prefer-async-await';
static const _warningMessage =
'Prefer using async/await syntax instead of .then invocations';

PreferAsyncAwaitRule([Map<String, Object> config = const {}])
: super(
id: ruleId,
severity: readSeverity(config, Severity.style),
excludes: readExcludes(config),
);

@override
Iterable<Issue> check(InternalResolvedUnitResult source) {
final visitor = _Visitor();

source.unit.visitChildren(visitor);

return visitor.invocations
.map((invocation) => createIssue(
rule: this,
location: nodeLocation(
node: invocation,
source: source,
withCommentOrMetadata: false,
),
message: _warningMessage,
))
.toList(growable: false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
part of 'prefer_async_await_rule.dart';

class _Visitor extends RecursiveAstVisitor<void> {
final _invocations = <MethodInvocation>[];

Iterable<MethodInvocation> get invocations => _invocations;

@override
void visitMethodInvocation(MethodInvocation node) {
super.visitMethodInvocation(node);

final target = node.realTarget;

if ((target?.staticType?.isDartAsyncFuture ?? false) &&
node.methodName.name == 'then') {
_invocations.add(node);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:async';

void function() async {
final future = Future.delayed(Duration(microseconds: 100), () => 6);

future.then((value) => print(value)); // LINT

future..then((value) => print(value)); // LINT

final value = await future;

final anotherFuture = getFuture().then((value) => value + 5); // LINT

final anotherValue = await anotherFuture;
}

Future<int> getFuture() {
return Future.delayed(Duration(microseconds: 100), () => 5)
.then((value) => value + 1); // LINT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/models/severity.dart';
import 'package:dart_code_metrics/src/analyzers/lint_analyzer/rules/rules_list/prefer_async_await/prefer_async_await_rule.dart';
import 'package:test/test.dart';

import '../../../../../helpers/rule_test_helper.dart';

const _examplePath = 'prefer_async_await/examples/example.dart';

void main() {
group('PreferAsyncAwaitRule', () {
test('initialization', () async {
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
final issues = PreferAsyncAwaitRule().check(unit);

RuleTestHelper.verifyInitialization(
issues: issues,
ruleId: 'prefer-async-await',
severity: Severity.style,
);
});

test('reports about found issues', () async {
final unit = await RuleTestHelper.resolveFromFile(_examplePath);
final issues = PreferAsyncAwaitRule().check(unit);

RuleTestHelper.verifyIssues(
issues: issues,
startLines: [6, 8, 12, 18],
startColumns: [3, 9, 25, 10],
locationTexts: [
'future.then((value) => print(value))',
'..then((value) => print(value))',
'getFuture().then((value) => value + 5)',
'Future.delayed(Duration(microseconds: 100), () => 5)\n'
' .then((value) => value + 1)',
],
messages: [
'Prefer using async/await syntax instead of .then invocations',
'Prefer using async/await syntax instead of .then invocations',
'Prefer using async/await syntax instead of .then invocations',
'Prefer using async/await syntax instead of .then invocations',
],
);
});
});
}
37 changes: 37 additions & 0 deletions website/docs/rules/common/prefer-async-await.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Prefer async await

## Rule id {#rule-id}

prefer-async-await

## Severity {#severity}

Style

## Description {#description}

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()`.

### Example {#example}

Bad:

```dart
Future<void> main() async {
someFuture.then((result) => handleResult(result)); // LINT

await (foo.asyncMethod()).then((result) => handleResult(result)); // LINT
}
```

Good:

```dart
Future<void> main() async {
final result = await someFuture; (result) => handleResult(result));
handleResult(result);

final anotherResult = await foo.asyncMethod();
handleResult(anotherResult);
}
```
4 changes: 4 additions & 0 deletions website/docs/rules/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ Rules configuration is [described here](../getting-started/configuration#configu

Warns when a class member is declared with Object type.

- [prefer-async-await](./common/prefer-async-await.md)

Recommends to use async/await syntax to handle Futures result instead of `.then()` invocation.

- [prefer-conditional-expressions](./common/prefer-conditional-expressions.md) &nbsp; ![Has auto-fix](https://img.shields.io/badge/-has%20auto--fix-success)

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