Skip to content

Commit b650812

Browse files
srawlinsCommit Queue
authored andcommitted
linter: add unnecessary_unawaited lint rule
Work towards #46218 Change-Id: I0c6953463d053205cc1110f554578e6b85eac0db Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/426240 Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent b97f723 commit b650812

File tree

10 files changed

+284
-3
lines changed

10 files changed

+284
-3
lines changed

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,6 +2480,10 @@ LintCode.unnecessary_this:
24802480
status: hasFix
24812481
LintCode.unnecessary_to_list_in_spreads:
24822482
status: hasFix
2483+
LintCode.unnecessary_unawaited:
2484+
status: needsFix
2485+
notes: |-
2486+
A fix can be made to remove the 'unawaited' call.
24832487
LintCode.unnecessary_underscores:
24842488
status: hasFix
24852489
LintCode.unreachable_from_main:

pkg/linter/example/all.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ linter:
207207
- unnecessary_string_interpolations
208208
- unnecessary_this
209209
- unnecessary_to_list_in_spreads
210+
- unnecessary_unawaited
210211
- unnecessary_underscores
211212
- unreachable_from_main
212213
- unrelated_type_equality_checks

pkg/linter/lib/src/extensions.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,14 @@ extension ElementExtension on Element? {
329329
};
330330

331331
/// Whether this is an [Annotatable] which is annotated with `@awaitNotRequired`.
332-
bool get hasAwaitNotRequired =>
333-
this is Annotatable &&
334-
(this! as Annotatable).metadata2.hasAwaitNotRequired;
332+
bool get hasAwaitNotRequired {
333+
var self = this;
334+
if (self == null || self is! Annotatable) {
335+
return false;
336+
}
337+
return (self as Annotatable).metadata2.hasAwaitNotRequired ||
338+
(self is PropertyAccessorElement && self.variable3.hasAwaitNotRequired);
339+
}
335340

336341
bool get isDartCorePrint {
337342
var self = this;

pkg/linter/lib/src/lint_codes.g.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,6 +1827,14 @@ class LinterLintCode extends LintCode {
18271827
hasPublishedDocs: true,
18281828
);
18291829

1830+
static const LintCode unnecessary_unawaited = LinterLintCode(
1831+
LintNames.unnecessary_unawaited,
1832+
"Unnecessary use of 'unawaited'.",
1833+
correctionMessage:
1834+
"Try removing the use of 'unawaited', as the unawaited element is "
1835+
"annotated with '@awaitNotRequired'.",
1836+
);
1837+
18301838
static const LintCode unnecessary_underscores = LinterLintCode(
18311839
LintNames.unnecessary_underscores,
18321840
"Unnecessary use of multiple underscores.",

pkg/linter/lib/src/lint_names.g.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,8 @@ abstract final class LintNames {
545545
static const String unnecessary_to_list_in_spreads =
546546
'unnecessary_to_list_in_spreads';
547547

548+
static const String unnecessary_unawaited = 'unnecessary_unawaited';
549+
548550
static const String unnecessary_underscores = 'unnecessary_underscores';
549551

550552
static const String unreachable_from_main = 'unreachable_from_main';

pkg/linter/lib/src/rules.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ import 'rules/unnecessary_string_escapes.dart';
226226
import 'rules/unnecessary_string_interpolations.dart';
227227
import 'rules/unnecessary_this.dart';
228228
import 'rules/unnecessary_to_list_in_spreads.dart';
229+
import 'rules/unnecessary_unawaited.dart';
229230
import 'rules/unnecessary_underscores.dart';
230231
import 'rules/unreachable_from_main.dart';
231232
import 'rules/unrelated_type_equality_checks.dart';
@@ -478,6 +479,7 @@ void registerLintRules() {
478479
..registerLintRule(UnnecessaryStringInterpolations())
479480
..registerLintRule(UnnecessaryThis())
480481
..registerLintRule(UnnecessaryToListInSpreads())
482+
..registerLintRule(UnnecessaryUnawaited())
481483
..registerLintRule(UnnecessaryUnderscores())
482484
..registerLintRule(UnreachableFromMain())
483485
..registerLintRule(UnrelatedTypeEqualityChecks())
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2025, 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+
9+
import '../analyzer.dart';
10+
import '../extensions.dart';
11+
12+
const _desc = r"Unnecessary use of 'unawaited'.";
13+
14+
class UnnecessaryUnawaited extends LintRule {
15+
UnnecessaryUnawaited()
16+
: super(name: LintNames.unnecessary_unawaited, description: _desc);
17+
18+
@override
19+
LintCode get lintCode => LinterLintCode.unnecessary_unawaited;
20+
21+
@override
22+
void registerNodeProcessors(
23+
NodeLintRegistry registry,
24+
LinterContext context,
25+
) {
26+
var visitor = _Visitor(this);
27+
registry.addMethodInvocation(this, visitor);
28+
}
29+
}
30+
31+
class _Visitor extends SimpleAstVisitor<void> {
32+
final LintRule rule;
33+
34+
_Visitor(this.rule);
35+
36+
@override
37+
void visitMethodInvocation(MethodInvocation node) {
38+
if (!node.isUnawaitedFunction) return;
39+
if (node.target != null) return;
40+
41+
// If there are 0 or more than 1 arguments, then a different error is
42+
// reported.
43+
if (node.argumentList.arguments.length != 1) return;
44+
45+
var argument = node.argumentList.arguments.first;
46+
var element = switch (argument.unParenthesized) {
47+
BinaryExpression(:var element) => element,
48+
MethodInvocation(:var methodName) => methodName.element,
49+
PrefixExpression(:var element) => element,
50+
PrefixedIdentifier(:var identifier) => identifier.element,
51+
PropertyAccess(:var propertyName) => propertyName.element,
52+
SimpleIdentifier(:var element) => element,
53+
_ => null,
54+
};
55+
if (element is! Annotatable) return;
56+
if (element.hasAwaitNotRequired) {
57+
rule.reportAtNode(node.methodName);
58+
}
59+
}
60+
}
61+
62+
extension on MethodInvocation {
63+
bool get isUnawaitedFunction =>
64+
methodName.name == 'unawaited' &&
65+
methodName.element?.library2?.name3 == 'dart.async';
66+
}

pkg/linter/messages.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13586,6 +13586,38 @@ LintCode:
1358613586
...['foo', 'bar', 'baz'].map((String s) => Text(s)),
1358713587
]
1358813588
```
13589+
unnecessary_unawaited:
13590+
problemMessage: "Unnecessary use of 'unawaited'."
13591+
correctionMessage: "Try removing the use of 'unawaited', as the unawaited element is annotated with '@awaitNotRequired'."
13592+
state:
13593+
stable: "3.9"
13594+
categories: [brevity, unintentional, unusedCode]
13595+
hasPublishedDocs: false
13596+
deprecatedDetails: |-
13597+
A call to a function, method, or operator, or a reference to a field,
13598+
getter, or top-level variable which is annotated with `@awaitNotRequired`
13599+
does not necessarily need to be awaited, nor does it need to be wrapped in
13600+
a call to `unawaited()`.
13601+
13602+
**BAD:**
13603+
```dart
13604+
@awaitNotRequired
13605+
Future<LogMessage> log(String message) { ... }
13606+
13607+
void f() {
13608+
unawaited(log('Message.'));
13609+
}
13610+
```
13611+
13612+
**GOOD:**
13613+
```dart
13614+
@awaitNotRequired
13615+
Future<LogMessage> log(String message) { ... }
13616+
13617+
void f() {
13618+
log('Message.');
13619+
}
13620+
```
1358913621
unnecessary_underscores:
1359013622
problemMessage: "Unnecessary use of multiple underscores."
1359113623
correctionMessage: "Try using '_'."

pkg/linter/test/rules/all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ import 'unnecessary_string_interpolations_test.dart'
288288
import 'unnecessary_this_test.dart' as unnecessary_this;
289289
import 'unnecessary_to_list_in_spreads_test.dart'
290290
as unnecessary_to_list_in_spreads;
291+
import 'unnecessary_unawaited_test.dart' as unnecessary_unawaited;
291292
import 'unnecessary_underscores_test.dart' as unnecessary_underscores;
292293
import 'unreachable_from_main_test.dart' as unreachable_from_main;
293294
import 'unrelated_type_equality_checks_test.dart'
@@ -536,6 +537,7 @@ void main() {
536537
unnecessary_string_interpolations.main();
537538
unnecessary_this.main();
538539
unnecessary_to_list_in_spreads.main();
540+
unnecessary_unawaited.main();
539541
unnecessary_underscores.main();
540542
unreachable_from_main.main();
541543
unrelated_type_equality_checks.main();
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright (c) 2025, 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:test_reflective_loader/test_reflective_loader.dart';
6+
7+
import '../rule_test_support.dart';
8+
9+
void main() {
10+
defineReflectiveSuite(() {
11+
defineReflectiveTests(UnnecessaryUnawaitedTest);
12+
});
13+
}
14+
15+
@reflectiveTest
16+
class UnnecessaryUnawaitedTest extends LintRuleTest {
17+
@override
18+
bool get addMetaPackageDep => true;
19+
20+
@override
21+
String get lintRule => LintNames.unnecessary_unawaited;
22+
23+
test_binaryOperator_annotated() async {
24+
await assertDiagnostics(
25+
r'''
26+
import 'dart:async';
27+
import 'package:meta/meta.dart';
28+
void f(C c) {
29+
unawaited(c + c);
30+
}
31+
class C {
32+
@awaitNotRequired
33+
Future<void> operator +(C c) => Future.value();
34+
}
35+
''',
36+
[lint(70, 9)],
37+
);
38+
}
39+
40+
test_binaryOperator_notAnnotated() async {
41+
await assertNoDiagnostics(r'''
42+
import 'dart:async';
43+
void f(C c) {
44+
unawaited(c + c);
45+
}
46+
class C {
47+
Future<void> operator +(C c) => Future.value();
48+
}
49+
''');
50+
}
51+
52+
test_function_annotated() async {
53+
await assertDiagnostics(
54+
r'''
55+
import 'dart:async';
56+
import 'package:meta/meta.dart';
57+
void f() {
58+
unawaited(f2());
59+
}
60+
@awaitNotRequired
61+
Future<void> f2() => Future.value();
62+
''',
63+
[lint(67, 9)],
64+
);
65+
}
66+
67+
test_function_notAnnotated() async {
68+
await assertNoDiagnostics(r'''
69+
import 'dart:async';
70+
void f() {
71+
unawaited(f2());
72+
}
73+
Future<void> f2() => Future.value();
74+
''');
75+
}
76+
77+
test_method_annotated() async {
78+
await assertDiagnostics(
79+
r'''
80+
import 'dart:async';
81+
import 'package:meta/meta.dart';
82+
void f(C c) {
83+
unawaited(c.m());
84+
}
85+
class C {
86+
@awaitNotRequired
87+
Future<void> m() => Future.value();
88+
}
89+
''',
90+
[lint(70, 9)],
91+
);
92+
}
93+
94+
test_method_notAnnotated() async {
95+
await assertNoDiagnostics(r'''
96+
import 'dart:async';
97+
void f(C c) {
98+
unawaited(c.m());
99+
}
100+
class C {
101+
Future<void> m() => Future.value();
102+
}
103+
''');
104+
}
105+
106+
test_topLevelVariable_annotated() async {
107+
await assertDiagnostics(
108+
r'''
109+
import 'dart:async';
110+
import 'package:meta/meta.dart';
111+
void f() {
112+
unawaited(f2);
113+
}
114+
@awaitNotRequired
115+
Future<void> f2 = Future.value();
116+
''',
117+
[lint(67, 9)],
118+
);
119+
}
120+
121+
test_topLevelVariable_notAnnotated() async {
122+
await assertNoDiagnostics(r'''
123+
import 'dart:async';
124+
void f() {
125+
unawaited(f2);
126+
}
127+
Future<void> f2 = Future.value();
128+
''');
129+
}
130+
131+
test_unaryOperator_annotated() async {
132+
await assertDiagnostics(
133+
r'''
134+
import 'dart:async';
135+
import 'package:meta/meta.dart';
136+
void f(C c) {
137+
unawaited(-c);
138+
}
139+
class C {
140+
@awaitNotRequired
141+
Future<void> operator -() => Future.value();
142+
}
143+
''',
144+
[lint(70, 9)],
145+
);
146+
}
147+
148+
test_unaryOperator_notAnnotated() async {
149+
await assertNoDiagnostics(r'''
150+
import 'dart:async';
151+
void f(C c) {
152+
unawaited(-c);
153+
}
154+
class C {
155+
Future<void> operator -() => Future.value();
156+
}
157+
''');
158+
}
159+
}

0 commit comments

Comments
 (0)