Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit ced6cbb

Browse files
authored
implement discarded_futures (#3431)
* implement `discarded_futures` * sort * ++ * FutureOr case * tweaks * updates * ++ tests
1 parent feb2aef commit ced6cbb

File tree

5 files changed

+396
-0
lines changed

5 files changed

+396
-0
lines changed

example/all.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ linter:
6565
- deprecated_consistency
6666
- diagnostic_describe_all_properties
6767
- directives_ordering
68+
- discarded_futures
6869
- do_not_use_environment
6970
- empty_catches
7071
- empty_constructor_bodies

lib/src/rules.dart

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import 'rules/depend_on_referenced_packages.dart';
6767
import 'rules/deprecated_consistency.dart';
6868
import 'rules/diagnostic_describe_all_properties.dart';
6969
import 'rules/directives_ordering.dart';
70+
import 'rules/discarded_futures.dart';
7071
import 'rules/do_not_use_environment.dart';
7172
import 'rules/empty_catches.dart';
7273
import 'rules/empty_constructor_bodies.dart';
@@ -279,6 +280,7 @@ void registerLintRules({bool inTestMode = false}) {
279280
..register(DeprecatedConsistency())
280281
..register(DiagnosticsDescribeAllProperties())
281282
..register(DirectivesOrdering())
283+
..register(DiscardedFutures())
282284
..register(DoNotUseEnvironment())
283285
..register(EmptyCatches())
284286
..register(EmptyConstructorBodies())

lib/src/rules/discarded_futures.dart

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/ast/ast.dart';
6+
import 'package:analyzer/dart/ast/visitor.dart';
7+
import 'package:analyzer/dart/element/element.dart';
8+
import 'package:analyzer/dart/element/type.dart';
9+
10+
import '../analyzer.dart';
11+
12+
const _desc = r"Don't invoke asynchronous functions in non-async blocks.";
13+
14+
const _details = r'''
15+
Making asynchronous calls in non-`async` functions is usually the sign of a
16+
programming error. In general these functions should be marked `async` and such
17+
futures should likely be awaited (as enforced by `unawaited_futures`).
18+
19+
**DON'T** invoke asynchronous functions in non-`async` blocks.
20+
21+
**BAD:**
22+
```dart
23+
void recreateDir(String path) {
24+
deleteDir(path);
25+
createDir(path);
26+
}
27+
28+
Future<void> deleteDir(String path) async {}
29+
30+
Future<void> createDir(String path) async {}
31+
```
32+
33+
**GOOD:**
34+
```dart
35+
Future<void> recreateDir(String path) async {
36+
await deleteDir(path);
37+
await createDir(path);
38+
}
39+
40+
Future<void> deleteDir(String path) async {}
41+
42+
Future<void> createDir(String path) async {}
43+
```
44+
''';
45+
46+
class DiscardedFutures extends LintRule {
47+
DiscardedFutures()
48+
: super(
49+
name: 'discarded_futures',
50+
description: _desc,
51+
details: _details,
52+
group: Group.errors);
53+
54+
@override
55+
void registerNodeProcessors(
56+
NodeLintRegistry registry, LinterContext context) {
57+
var visitor = _Visitor(this);
58+
registry.addConstructorDeclaration(this, visitor);
59+
registry.addFieldDeclaration(this, visitor);
60+
registry.addFunctionDeclaration(this, visitor);
61+
registry.addMethodDeclaration(this, visitor);
62+
registry.addTopLevelVariableDeclaration(this, visitor);
63+
}
64+
}
65+
66+
class _InvocationVisitor extends RecursiveAstVisitor<void> {
67+
final LintRule rule;
68+
_InvocationVisitor(this.rule);
69+
70+
@override
71+
void visitFunctionExpression(FunctionExpression node) {
72+
if (node.body.isAsynchronous) return;
73+
super.visitFunctionExpression(node);
74+
}
75+
76+
@override
77+
void visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
78+
if (node.staticInvokeType.isFuture) {
79+
rule.reportLint(node.function);
80+
}
81+
super.visitFunctionExpressionInvocation(node);
82+
}
83+
84+
@override
85+
void visitMethodInvocation(MethodInvocation node) {
86+
if (node.methodName.staticElement.isDartAsyncUnawaited) return;
87+
if (node.staticInvokeType.isFuture) {
88+
rule.reportLint(node.methodName);
89+
}
90+
super.visitMethodInvocation(node);
91+
}
92+
}
93+
94+
class _Visitor extends SimpleAstVisitor {
95+
final LintRule rule;
96+
97+
_Visitor(this.rule);
98+
99+
void check(FunctionBody body) {
100+
if (body.isAsynchronous) return;
101+
var visitor = _InvocationVisitor(rule);
102+
body.accept(visitor);
103+
}
104+
105+
void checkVariables(VariableDeclarationList variables) {
106+
if (variables.type?.type?.isFuture ?? false) return;
107+
for (var variable in variables.variables) {
108+
var initializer = variable.initializer;
109+
if (initializer is FunctionExpression) {
110+
check(initializer.body);
111+
}
112+
}
113+
}
114+
115+
@override
116+
void visitConstructorDeclaration(ConstructorDeclaration node) {
117+
check(node.body);
118+
}
119+
120+
@override
121+
void visitFieldDeclaration(FieldDeclaration node) {
122+
checkVariables(node.fields);
123+
}
124+
125+
@override
126+
void visitFunctionDeclaration(FunctionDeclaration node) {
127+
if (node.returnType?.type.isFuture ?? false) return;
128+
check(node.functionExpression.body);
129+
}
130+
131+
@override
132+
void visitMethodDeclaration(MethodDeclaration node) {
133+
if (node.returnType?.type.isFuture ?? false) return;
134+
check(node.body);
135+
}
136+
137+
@override
138+
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
139+
checkVariables(node.variables);
140+
}
141+
}
142+
143+
extension on DartType? {
144+
bool get isFuture {
145+
var self = this;
146+
DartType? returnType;
147+
if (self is FunctionType) {
148+
returnType = self.returnType;
149+
}
150+
if (self is InterfaceType) {
151+
returnType = self;
152+
}
153+
154+
return returnType != null &&
155+
(returnType.isDartAsyncFuture || returnType.isDartAsyncFutureOr);
156+
}
157+
}
158+
159+
extension ElementExtension on Element? {
160+
bool get isDartAsyncUnawaited {
161+
var self = this;
162+
return self is FunctionElement &&
163+
self.name == 'unawaited' &&
164+
self.library.isDartAsync;
165+
}
166+
}

test/rules/all.dart

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import 'avoid_void_async_test.dart' as avoid_void_async;
2727
import 'conditional_uri_does_not_exist_test.dart'
2828
as conditional_uri_does_not_exist;
2929
import 'deprecated_consistency_test.dart' as deprecated_consistency;
30+
import 'discarded_futures_test.dart' as discarded_futures;
3031
import 'file_names_test.dart' as file_names;
3132
import 'hash_and_equals_test.dart' as hash_and_equals;
3233
import 'library_private_types_in_public_api_test.dart'
@@ -88,6 +89,7 @@ void main() {
8889
avoid_void_async.main();
8990
conditional_uri_does_not_exist.main();
9091
deprecated_consistency.main();
92+
discarded_futures.main();
9193
file_names.main();
9294
hash_and_equals.main();
9395
library_private_types_in_public_api.main();

0 commit comments

Comments
 (0)