Linter. Issue 59814. Report unnecessary_async.

Bug: https://github.com/dart-lang/sdk/issues/59814
Change-Id: Iebf90315e7c443e00ea84c380f85ac6c84bc86c6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403320
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
index 25eae6c..11c411a 100644
--- a/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
+++ b/pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml
@@ -2401,6 +2401,10 @@
   status: needsFix
   notes: |-
     The fix is to encode the angle brackets.
+LintCode.unnecessary_async:
+  status: needsFix
+  notes: |-
+    And probably also quick assist that works even if returns `Future`.
 LintCode.unnecessary_await_in_return:
   status: hasFix
 LintCode.unnecessary_brace_in_string_interps:
diff --git a/pkg/analyzer/lib/src/dart/resolver/body_inference_context.dart b/pkg/analyzer/lib/src/dart/resolver/body_inference_context.dart
index d4e17b4..d91149f 100644
--- a/pkg/analyzer/lib/src/dart/resolver/body_inference_context.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/body_inference_context.dart
@@ -26,6 +26,14 @@
   /// Types of all `return` or `yield` statements in the body.
   final List<DartType> _returnTypes = [];
 
+  /// Whether the execution flow can reach the end of the body.
+  ///
+  /// For example here, because there is no `return` at the end.
+  /// ```
+  /// void f() {}
+  /// ```
+  bool mayCompleteNormally = true;
+
   factory BodyInferenceContext({
     required TypeSystemImpl typeSystem,
     required FunctionBodyImpl node,
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index c020b96..d167b5d 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -543,14 +543,16 @@
     required FunctionBodyImpl body,
     required SyntacticEntity errorNode,
   }) {
-    if (!flowAnalysis.flow!.isReachable) {
-      return;
-    }
-
     var bodyContext = body.bodyContext;
     if (bodyContext == null) {
       return;
     }
+
+    if (!flowAnalysis.flow!.isReachable) {
+      bodyContext.mayCompleteNormally = false;
+      return;
+    }
+
     var returnType = bodyContext.contextType;
     if (returnType == null) {
       if (errorNode is BlockFunctionBody) {
diff --git a/pkg/linter/CHANGELOG.md b/pkg/linter/CHANGELOG.md
index c6db35b..f249f76 100644
--- a/pkg/linter/CHANGELOG.md
+++ b/pkg/linter/CHANGELOG.md
@@ -4,6 +4,7 @@
 - new lint: `strict_top_level_inference`
 - new _(experimental)_ lint: `omit_obvious_property_types`
 - new _(experimental)_ lint: `specify_nonobvious_property_types`
+- new _(experimental)_ lint: `unnecessary_async`
 - new _(experimental)_ lint: `unsafe_variance`
 - removed lint: `package_api_docs`
 - removed lint: `unsafe_html`
diff --git a/pkg/linter/example/all.yaml b/pkg/linter/example/all.yaml
index 97eabdd..07fac79 100644
--- a/pkg/linter/example/all.yaml
+++ b/pkg/linter/example/all.yaml
@@ -180,6 +180,7 @@
     - type_literal_in_constant_pattern
     - unawaited_futures
     - unintended_html_in_doc_comment
+    - unnecessary_async
     - unnecessary_await_in_return
     - unnecessary_brace_in_string_interps
     - unnecessary_breaks
diff --git a/pkg/linter/lib/src/lint_codes.g.dart b/pkg/linter/lib/src/lint_codes.g.dart
index 9be2295..72762f5 100644
--- a/pkg/linter/lib/src/lint_codes.g.dart
+++ b/pkg/linter/lib/src/lint_codes.g.dart
@@ -1572,6 +1572,12 @@
     hasPublishedDocs: true,
   );
 
+  static const LintCode unnecessary_async = LinterLintCode(
+    LintNames.unnecessary_async,
+    "Don't make a function 'async' if it doesn't use 'await'.",
+    correctionMessage: "Try removing the 'async' modifier.",
+  );
+
   static const LintCode unnecessary_await_in_return = LinterLintCode(
     LintNames.unnecessary_await_in_return,
     "Unnecessary 'await'.",
diff --git a/pkg/linter/lib/src/lint_names.g.dart b/pkg/linter/lib/src/lint_names.g.dart
index 844d13a..f2dce25 100644
--- a/pkg/linter/lib/src/lint_names.g.dart
+++ b/pkg/linter/lib/src/lint_names.g.dart
@@ -480,6 +480,8 @@
   static const String unintended_html_in_doc_comment =
       'unintended_html_in_doc_comment';
 
+  static const String unnecessary_async = 'unnecessary_async';
+
   static const String unnecessary_await_in_return =
       'unnecessary_await_in_return';
 
diff --git a/pkg/linter/lib/src/rules.dart b/pkg/linter/lib/src/rules.dart
index 1f5d4f0..8d1efd6 100644
--- a/pkg/linter/lib/src/rules.dart
+++ b/pkg/linter/lib/src/rules.dart
@@ -198,6 +198,7 @@
 import 'rules/type_literal_in_constant_pattern.dart';
 import 'rules/unawaited_futures.dart';
 import 'rules/unintended_html_in_doc_comment.dart';
+import 'rules/unnecessary_async.dart';
 import 'rules/unnecessary_await_in_return.dart';
 import 'rules/unnecessary_brace_in_string_interps.dart';
 import 'rules/unnecessary_breaks.dart';
@@ -446,6 +447,7 @@
     ..registerLintRule(TypeLiteralInConstantPattern())
     ..registerLintRule(UnawaitedFutures())
     ..registerLintRule(UnintendedHtmlInDocComment())
+    ..registerLintRule(UnnecessaryAsync())
     ..registerLintRule(UnnecessaryAwaitInReturn())
     ..registerLintRule(UnnecessaryBraceInStringInterps())
     ..registerLintRule(UnnecessaryBreaks())
diff --git a/pkg/linter/lib/src/rules/unnecessary_async.dart b/pkg/linter/lib/src/rules/unnecessary_async.dart
new file mode 100644
index 0000000..30be4af
--- /dev/null
+++ b/pkg/linter/lib/src/rules/unnecessary_async.dart
@@ -0,0 +1,205 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
+import 'package:analyzer/dart/element/type.dart';
+// ignore: implementation_imports
+import 'package:analyzer/src/dart/ast/ast.dart';
+
+import '../analyzer.dart';
+
+const _desc = r'No await no async.';
+
+class UnnecessaryAsync extends LintRule {
+  UnnecessaryAsync()
+      : super(
+          name: LintNames.unnecessary_async,
+          description: _desc,
+          state: const State.experimental(),
+        );
+
+  @override
+  LintCode get lintCode => LinterLintCode.unnecessary_async;
+
+  @override
+  void registerNodeProcessors(
+    NodeLintRegistry registry,
+    LinterContext context,
+  ) {
+    var visitor = _Visitor(this);
+    registry.addFunctionDeclaration(this, visitor);
+    registry.addFunctionExpression(this, visitor);
+    registry.addMethodDeclaration(this, visitor);
+  }
+}
+
+class _HasAwaitVisitor extends RecursiveAstVisitor<void> {
+  bool hasAwait = false;
+  bool everyReturnHasValue = true;
+  bool returnsOnlyFuture = true;
+
+  @override
+  void visitAwaitExpression(AwaitExpression node) {
+    hasAwait = true;
+    super.visitAwaitExpression(node);
+  }
+
+  @override
+  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
+    _updateWithExpression(node.expression);
+    super.visitExpressionFunctionBody(node);
+  }
+
+  @override
+  void visitForElement(ForElement node) {
+    hasAwait |= node.awaitKeyword != null;
+    super.visitForElement(node);
+  }
+
+  @override
+  void visitForStatement(ForStatement node) {
+    hasAwait |= node.awaitKeyword != null;
+    super.visitForStatement(node);
+  }
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    // Stop the recursion.
+  }
+
+  @override
+  void visitReturnStatement(ReturnStatement node) {
+    var expression = node.expression;
+    if (expression != null) {
+      _updateWithExpression(expression);
+    } else {
+      everyReturnHasValue = false;
+    }
+
+    super.visitReturnStatement(node);
+  }
+
+  void _updateWithExpression(Expression expression) {
+    var type = expression.staticType;
+    if (!(type is InterfaceType &&
+        type.isDartAsyncFutureOrSubtype &&
+        type.nullabilitySuffix == NullabilitySuffix.none)) {
+      returnsOnlyFuture = false;
+    }
+  }
+}
+
+class _Visitor extends SimpleAstVisitor<void> {
+  final LintRule rule;
+
+  _Visitor(this.rule);
+
+  @override
+  void visitFunctionDeclaration(covariant FunctionDeclarationImpl node) {
+    var element = node.declaredFragment!.element;
+
+    _checkBody(
+      body: node.functionExpression.body,
+      returnType: element.returnType,
+    );
+  }
+
+  @override
+  void visitFunctionExpression(covariant FunctionExpressionImpl node) {
+    // Here we handle only closures.
+    if (node.parent is FunctionDeclaration) {
+      return;
+    }
+
+    var bodyContext = node.body.bodyContext;
+
+    _checkBody(
+      body: node.body,
+      returnType: bodyContext?.imposedType,
+    );
+  }
+
+  @override
+  void visitMethodDeclaration(covariant MethodDeclarationImpl node) {
+    var element = node.declaredFragment!.element;
+
+    _checkBody(
+      body: node.body,
+      returnType: element.returnType,
+    );
+  }
+
+  void _checkBody({
+    required FunctionBodyImpl body,
+    required DartType? returnType,
+  }) {
+    var asyncKeyword = body.keyword;
+    if (asyncKeyword == null || asyncKeyword.keyword != Keyword.ASYNC) {
+      return;
+    }
+
+    if (body.star != null) {
+      return;
+    }
+
+    var bodyContext = body.bodyContext;
+    if (bodyContext == null) {
+      return;
+    }
+
+    var visitor = _HasAwaitVisitor();
+    body.accept(visitor);
+
+    if (visitor.hasAwait) {
+      return;
+    }
+
+    // If no imposed return type, then any type is OK.
+    if (returnType == null) {
+      rule.reportLintForToken(asyncKeyword);
+      return;
+    }
+
+    // We don't have to return anything.
+    // So, the generated `Future` is not necessary.
+    if (returnType is VoidType) {
+      rule.reportLintForToken(asyncKeyword);
+      return;
+    }
+
+    // It is OK to return values into `FutureOr`.
+    // So, wrapping values into `Future` is not necessary.
+    if (returnType.isDartAsyncFutureOr) {
+      rule.reportLintForToken(asyncKeyword);
+      return;
+    }
+
+    // We handle only `Future<T>` below.
+    if (!returnType.isDartAsyncFuture) {
+      return;
+    }
+
+    // If the body may complete normally, we cannot remove `async`.
+    // This would make the body return `null`.
+    // And `null` is not the same as `Future.value(null)`.
+    if (bodyContext.mayCompleteNormally) {
+      return;
+    }
+
+    // If every `return` returns `Future`, we don't need wrapping.
+    if (visitor.everyReturnHasValue && visitor.returnsOnlyFuture) {
+      rule.reportLintForToken(asyncKeyword);
+      return;
+    }
+  }
+}
+
+extension on InterfaceType {
+  bool get isDartAsyncFutureOrSubtype {
+    var typeProvider = element3.library2.typeProvider;
+    return asInstanceOf2(typeProvider.futureElement2) != null;
+  }
+}
diff --git a/pkg/linter/messages.yaml b/pkg/linter/messages.yaml
index c252198..5d4e9fe 100644
--- a/pkg/linter/messages.yaml
+++ b/pkg/linter/messages.yaml
@@ -11632,6 +11632,35 @@
       /// \<assignment\> -> \<variable\> = \<expression\>`
       /// <http://foo.bar.baz>
       ```
+  unnecessary_async:
+    problemMessage: "Don't make a function 'async' if it doesn't use 'await'."
+    correctionMessage: "Try removing the 'async' modifier."
+    state:
+      experimental: "3.7"
+    categories: [style]
+    hasPublishedDocs: false
+    deprecatedDetails: |-
+      Functions that don't do `await` don't have to be `async`.
+
+      Usually such functions also don't have to return a `Future`, which allows
+      an invoker to avoid `await` in its code, etc. Synchronous code in
+      general runs faster, and is easier to reason about.
+
+      **BAD:**
+      ```dart
+      void f() async {
+        // await Future.delayed(const Duration(seconds: 2));
+        print(0);
+      }
+      ```
+
+      **GOOD:**
+      ```dart
+      void f() {
+        // await Future.delayed(const Duration(seconds: 2));
+        print(0);
+      }
+      ```
   unnecessary_await_in_return:
     problemMessage: "Unnecessary 'await'."
     correctionMessage: "Try removing the 'await'."
diff --git a/pkg/linter/test/rules/all.dart b/pkg/linter/test/rules/all.dart
index 8e71c6e..a63f369 100644
--- a/pkg/linter/test/rules/all.dart
+++ b/pkg/linter/test/rules/all.dart
@@ -253,6 +253,7 @@
 import 'unawaited_futures_test.dart' as unawaited_futures;
 import 'unintended_html_in_doc_comment_test.dart'
     as unintended_html_in_doc_comment;
+import 'unnecessary_async_test.dart' as unnecessary_async;
 import 'unnecessary_await_in_return_test.dart' as unnecessary_await_in_return;
 import 'unnecessary_brace_in_string_interps_test.dart'
     as unnecessary_brace_in_string_interps;
@@ -506,6 +507,7 @@
   type_literal_in_constant_pattern.main();
   unawaited_futures.main();
   unintended_html_in_doc_comment.main();
+  unnecessary_async.main();
   unnecessary_await_in_return.main();
   unnecessary_brace_in_string_interps.main();
   unnecessary_breaks.main();
diff --git a/pkg/linter/test/rules/unnecessary_async_test.dart b/pkg/linter/test/rules/unnecessary_async_test.dart
new file mode 100644
index 0000000..d0c4498
--- /dev/null
+++ b/pkg/linter/test/rules/unnecessary_async_test.dart
@@ -0,0 +1,457 @@
+// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../rule_test_support.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UnnecessaryAsyncTest);
+  });
+}
+
+@reflectiveTest
+class UnnecessaryAsyncTest extends LintRuleTest {
+  @override
+  String get lintRule => LintNames.unnecessary_async;
+
+  test_closure_imposedReturnTypeFuture_noAwait() async {
+    await assertNoDiagnostics(r'''
+void f() {
+  useFunction(() async {});
+}
+
+void useFunction(Future<void> Function() x) {}
+''');
+  }
+
+  test_closure_imposedReturnTypeVoid_hasAwait() async {
+    await assertNoDiagnostics(r'''
+void f() {
+  useFunction(() async {
+    await 0;
+  });
+}
+
+void useFunction(void Function() x) {}
+''');
+  }
+
+  test_closure_imposedReturnTypeVoid_noAwait() async {
+    await assertDiagnostics(r'''
+void f() {
+  useFunction(() async {});
+}
+
+void useFunction(void Function() x) {}
+''', [
+      lint(28, 5),
+    ]);
+  }
+
+  test_closure_noImposedReturnType_hasAwait() async {
+    await assertNoDiagnostics(r'''
+void f() {
+  var v = () async {
+    await 0;
+  };
+}
+''');
+  }
+
+  test_closure_noImposedReturnType_noAwait() async {
+    await assertDiagnostics(r'''
+void f() {
+  var v = () async {};
+}
+''', [
+      lint(24, 5),
+    ]);
+  }
+
+  test_localFunction_void_hasAwait() async {
+    await assertDiagnostics(r'''
+void foo() {
+  void f() async {
+    await 0;
+  }
+}
+''', [
+      error(WarningCode.UNUSED_ELEMENT, 20, 1),
+    ]);
+  }
+
+  test_localFunction_void_noAwait() async {
+    await assertDiagnostics(r'''
+void foo() {
+  void f() async {}
+}
+''', [
+      error(WarningCode.UNUSED_ELEMENT, 20, 1),
+      lint(24, 5),
+    ]);
+  }
+
+  test_localFunction_void_noAwait_outerHasAwait() async {
+    await assertDiagnostics(r'''
+Future<void> foo() async {
+  void f() async {}
+  await 0;
+}
+''', [
+      error(WarningCode.UNUSED_ELEMENT, 34, 1),
+      lint(38, 5),
+    ]);
+  }
+
+  test_method_future_returnFuture() async {
+    await assertDiagnostics(r'''
+class A {
+  Future<int> f() async {
+    return Future.value(0);
+  }
+}
+''', [
+      lint(28, 5),
+    ]);
+  }
+
+  test_method_future_returnValue() async {
+    await assertNoDiagnostics(r'''
+class A {
+  Future<int> f() async {
+    return 0;
+  }
+}
+''');
+  }
+
+  test_method_futureOr_returnFuture() async {
+    await assertDiagnostics(r'''
+import 'dart:async';
+
+class A {
+  FutureOr<int> f() async {
+    return Future.value(0);
+  }
+}
+''', [
+      lint(52, 5),
+    ]);
+  }
+
+  test_method_futureOr_returnValue() async {
+    await assertDiagnostics(r'''
+import 'dart:async';
+
+class A {
+  FutureOr<int> f() async {
+    return 0;
+  }
+}
+''', [
+      lint(52, 5),
+    ]);
+  }
+
+  test_method_void_hasAwaitExpression() async {
+    await assertNoDiagnostics(r'''
+class A {
+  void f() async {
+    await 0;
+  }
+}
+''');
+  }
+
+  test_method_void_noAwait() async {
+    await assertDiagnostics(r'''
+class A {
+  void f() async {}
+}
+''', [
+      lint(21, 5),
+    ]);
+  }
+
+  test_topLevelFunction_block_future_intNullable_returnNullable() async {
+    await assertNoDiagnostics(r'''
+Future<int?> f(int? x) async {
+  return x == null ? null : Future.value(x);
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_dynamic() async {
+    await assertNoDiagnostics(r'''
+dynamic f() async {
+  return 0;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_future_int_hasReturn_futureCustom() async {
+    await assertDiagnostics(r'''
+Future<int> f() async {
+  return MyFuture.value(0);
+}
+
+class MyFuture<T> implements Future<T> {
+  MyFuture.value(T _);
+
+  @override
+  noSuchMethod(invocation) => super.noSuchMethod(invocation);
+}
+''', [
+      lint(16, 5),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_future_int_returnFuture() async {
+    await assertDiagnostics(r'''
+Future<int> f() async {
+  return Future.value(0);
+}
+''', [
+      lint(16, 5),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_future_int_returnValue() async {
+    await assertNoDiagnostics(r'''
+Future<int> f() async {
+  return 0;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_future_intNullable_hasReturn_null() async {
+    await assertNoDiagnostics(r'''
+Future<int?> f() async {
+  return null;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_future_void_hasReturn_future() async {
+    await assertDiagnostics(r'''
+Future<void> f() async {
+  return foo();
+}
+
+Future<void> foo() {
+  return Future.value();
+}
+''', [
+      lint(17, 5),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_future_void_hasReturn_nothing() async {
+    await assertNoDiagnostics(r'''
+Future<void> f() async {
+  return;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_future_void_noReturn_atAll() async {
+    await assertNoDiagnostics(r'''
+Future<void> f() async {}
+''');
+  }
+
+  test_topLevelFunction_blockBody_future_void_noReturn_atEnd_hasReturnFuture() async {
+    await assertNoDiagnostics(r'''
+Future<void> f() async {
+  if (0 == 0) {
+    return Future.value();
+  }
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_future_void_noReturn_atEnd_hasReturnNothing() async {
+    await assertNoDiagnostics(r'''
+Future<void> f() async {
+  if (0 == 0) {
+    return;
+  }
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_int_hasReturn_future() async {
+    await assertDiagnostics(r'''
+Future<int>? f() async {
+  return Future.value(0);
+}
+''', [
+      lint(17, 5),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_int_hasReturn_value() async {
+    await assertNoDiagnostics(r'''
+Future<int>? f() async {
+  return 0;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_intNullable_hasReturn_null() async {
+    await assertNoDiagnostics(r'''
+Future<int?>? f() async {
+  return null;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_intNullable_noReturn() async {
+    await assertDiagnostics(r'''
+Future<int?>? f() async {}
+''', [
+      error(WarningCode.BODY_MIGHT_COMPLETE_NORMALLY_NULLABLE, 14, 1),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_void_hasReturn_nothing() async {
+    await assertNoDiagnostics(r'''
+Future<void>? f() async {
+  return;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_void_noReturn_atAll() async {
+    await assertNoDiagnostics(r'''
+Future<void>? f() async {}
+''');
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_void_noReturn_atEnd_hasReturnFuture() async {
+    await assertNoDiagnostics(r'''
+Future<void>? f() async {
+  if (0 == 0) {
+    return Future.value();
+  }
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_futureNullable_void_noReturn_atEnd_hasReturnNull() async {
+    await assertNoDiagnostics(r'''
+Future<void>? f() async {
+  if (0 == 0) {
+    return null;
+  }
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_futureOr_returnFuture() async {
+    await assertDiagnostics(r'''
+import 'dart:async';
+
+FutureOr<int> f() async {
+  return Future.value(0);
+}
+''', [
+      lint(40, 5),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_futureOr_returnValue() async {
+    await assertDiagnostics(r'''
+import 'dart:async';
+
+FutureOr<int> f() async {
+  return 0;
+}
+''', [
+      lint(40, 5),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_object() async {
+    await assertNoDiagnostics(r'''
+Object f() async {
+  return 0;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_void_hasAwait_expression() async {
+    await assertNoDiagnostics(r'''
+void f() async {
+  await 0;
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_void_hasAwait_forElement() async {
+    await assertNoDiagnostics(r'''
+void f(Stream<int> values) async {
+  [
+    await for (var value in values) value,
+  ];
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_void_hasAwait_forStatement() async {
+    await assertNoDiagnostics(r'''
+void f(Stream<int> values) async {
+  await for (var value in values) {}
+}
+''');
+  }
+
+  test_topLevelFunction_blockBody_void_hasAwait_nestedFunctionExpression() async {
+    await assertDiagnostics(r'''
+void f() async {
+  () async {
+    await 0;
+  }();
+}
+''', [
+      lint(9, 5),
+    ]);
+  }
+
+  test_topLevelFunction_blockBody_void_noAwait() async {
+    await assertDiagnostics(r'''
+void f() async {}
+''', [
+      lint(9, 5),
+    ]);
+  }
+
+  test_topLevelFunction_exprBody_future_int_returnFuture() async {
+    await assertDiagnostics(r'''
+Future<int> f() async => Future.value(0);
+''', [
+      lint(16, 5),
+    ]);
+  }
+
+  test_topLevelFunction_exprBody_future_int_returnValue() async {
+    await assertNoDiagnostics(r'''
+Future<int> f() async => 0;
+''');
+  }
+
+  test_topLevelFunction_exprBody_future_intNullable_returnNullable() async {
+    await assertNoDiagnostics(r'''
+Future<int?> f(int? x) async => x == null ? null : Future.value(x);
+''');
+  }
+
+  test_topLevelFunction_exprBody_futureNullable_intNullable_returnNull() async {
+    await assertNoDiagnostics(r'''
+Future<int?>? f() async => null;
+''');
+  }
+}