Update use_build_context_synchronously to track async state in a single visitor. (#4390)

This is massive, apologies.
diff --git a/lib/src/rules/use_build_context_synchronously.dart b/lib/src/rules/use_build_context_synchronously.dart
index 49e95eb..13cae60 100644
--- a/lib/src/rules/use_build_context_synchronously.dart
+++ b/lib/src/rules/use_build_context_synchronously.dart
@@ -85,222 +85,626 @@
   }
 }
 
-class _AwaitVisitor extends RecursiveAstVisitor {
-  bool hasAwait = false;
-
-  @override
-  void visitAwaitExpression(AwaitExpression node) {
-    hasAwait = true;
-  }
-
-  @override
-  void visitBlockFunctionBody(BlockFunctionBody node) {
-    // Stop visiting when we arrive at a function body.
-    // Awaits inside it don't matter.
-  }
-
-  @override
-  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
-    // Stop visiting when we arrive at a function body.
-    // Awaits inside it don't matter.
-  }
-}
-
-/// An enum of two values which describe the presence of a "mounted check."
+/// An enum whose values describe the state of asynchrony that a certain node
+/// has in the syntax tree, with respect to another node.
 ///
 /// A mounted check is a check of whether a bool-typed identifier, 'mounted',
 /// is checked to be `true` or `false`, in a position which affects control
 /// flow.
-enum _MountedCheck {
-  positive,
-  negative;
+enum _AsyncState {
+  /// A value indicating that a node contains an "asynchronous gap" which is
+  /// not definitely guarded with a mounted check.
+  asynchronous,
 
-  _MountedCheck get negate => switch (this) {
-        _MountedCheck.positive => _MountedCheck.negative,
-        _MountedCheck.negative => _MountedCheck.positive,
-      };
+  /// A value indicating that a node contains a positive mounted check that can
+  /// guard a certain other node.
+  mountedCheck,
+
+  /// A value indicating that a node contains a negative mounted check that can
+  /// guard a certain other node.
+  notMountedCheck;
+
+  _AsyncState? get asynchronousOrNull =>
+      this == asynchronous ? asynchronous : null;
 }
 
-/// A visitor whose `visit*` methods return whether a "mounted check" is found
-/// which properly guards [child].
+/// A visitor whose `visit*` methods return the async state between a given node
+/// and [reference].
 ///
-/// The entrypoint for this visitor is [AstNodeExtension.isMountedCheckFor].
+/// The entrypoint for this visitor is [_AstNodeExtension.asyncStateFor].
 ///
-/// A mounted check "guards" [child] if control flow can only reach [child] if
-/// 'mounted' is `true`. Such checks can take many forms:
+/// Each `visit*` method can return one of three values:
+/// * `null` means there is no interesting asynchrony between node and
+///   [reference].
+/// * [_AsyncState.asynchronous] means the node contains an asynchronous gap
+///   which is not guarded with a mounted check.
+/// * [_AsyncState.mountedCheck] means the node guards [reference] with a
+///   positive mounted check.
+/// * [_AsyncState.notMountedCheck] means the node guards [reference] with a
+///   negative mounted check.
+///
+/// (For all `visit*` methods except the entrypoint call, the value is
+/// intermediate, and is only used in calculating the value for parent nodes.)
+///
+/// A node that contains a mounted check "guards" [reference] if control flow
+/// can only reach [reference] if 'mounted' is `true`. Such checks can take many
+/// forms:
 ///
 /// * A mounted check in an if-condition can be a simple guard for nodes in the
 ///   if's then-statement or the if's else-statement, depending on the polarity
-///   of the check. So `if (mounted) { child; }` has a proper mounted check and
-///   `if (!mounted) {} else { child; }` has a proper mounted check.
+///   of the check. So `if (mounted) { reference; }` has a proper mounted check
+///   and `if (!mounted) {} else { reference; }` has a proper mounted check.
 /// * A statement in a series of statements containing a mounted check can guard
 ///   the later statements if control flow definitely exits in the case of a
-///   `false` value for 'mounted'. So `if (mounted) { return; } child;` has a
-///   proper mounted check.
+///   `false` value for 'mounted'. So `if (!mounted) { return; } reference;` has
+///   a proper mounted check.
 /// * A mounted check in a try-statement can only guard later statements if it
 ///   is found in the `finally` section, as no statements found in the `try`
 ///   section or any `catch` sections are not guaranteed to have run before the
 ///   later statements.
 /// * etc.
 ///
-/// Each `visit*` method can return one of three values:
-/// * `null` means the node does not guard [child] with a mounted check.
-/// * [_MountedCheck.positive] means the node guards [child] with a positive
-///   mounted check.
-/// * [_MountedCheck.negative] means the node guards [child] with a negative
-///   mounted check.
-class _MountedCheckVisitor extends SimpleAstVisitor<_MountedCheck> {
+/// The `visit*` methods generally fall into three categories:
+///
+/// * A node may affect control flow, such that a contained mounted check may
+///   properly guard [reference]. See [visitIfStatement] for one of the most
+///   complicated examples.
+/// * A node may be one component of a mounted check. An associated `visit*`
+///   method builds up such a mounted check from inner expressions. For example,
+///   given `!(context.mounted)`, the  notion of a mounted check is built from
+///   the PrefixedIdentifier, the ParenthesizedExpression, and the
+///   PrefixExpression (from inside to outside).
+/// * Otherwise, a node may just contain an asynchronous gap. The vast majority
+///   of node types fall into this category. Most of these `visit*` methods
+///   use [_AsyncState.asynchronousOrNull] or [_asynchronousIfAnyIsAsync].
+class _AsyncStateVisitor extends SimpleAstVisitor<_AsyncState> {
   static const mountedName = 'mounted';
 
-  final AstNode child;
+  final AstNode reference;
 
-  _MountedCheckVisitor({required this.child});
+  _AsyncStateVisitor({required this.reference});
 
   @override
-  _MountedCheck? visitBinaryExpression(BinaryExpression node) {
-    // TODO(srawlins): Currently this method doesn't take `child` into account;
-    // it assumes `child` is part of a statement that follows this expression.
-    // We need to account for `child` being an actual descendent of `node` in
-    // order to properly handle code like
-    // * `if (mounted || child)`,
-    // * `if (!mounted && child)`,
-    // * `if (mounted || (condition && child))`,
-    // * `if ((mounted || condition) && child)`, etc.
-    if (node.isAnd) {
-      return node.leftOperand.accept(this) ?? node.rightOperand.accept(this);
-    } else if (node.isOr) {
-      return node.leftOperand.accept(this) ?? node.rightOperand.accept(this);
-    } else {
-      // TODO(srawlins): What about `??`?
+  _AsyncState? visitAdjacentStrings(AdjacentStrings node) =>
+      _asynchronousIfAnyIsAsync(node.strings);
+
+  @override
+  _AsyncState? visitAsExpression(AsExpression node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitAssignmentExpression(AssignmentExpression node) =>
+      _inOrderAsyncState([
+        (node: node.leftHandSide, mountedCanGuard: false),
+        (node: node.rightHandSide, mountedCanGuard: true),
+      ]);
+
+  @override
+  _AsyncState? visitAwaitExpression(AwaitExpression node) =>
+      // An expression _inside_ an await is executed before the await, and so is
+      // safe; otherwise asynchronous.
+      reference == node.expression ? null : _AsyncState.asynchronous;
+
+  @override
+  _AsyncState? visitBinaryExpression(BinaryExpression node) {
+    if (node.leftOperand == reference) {
       return null;
+    } else if (node.rightOperand == reference) {
+      var leftGuardState = node.leftOperand.accept(this);
+      return switch (leftGuardState) {
+        _AsyncState.asynchronous => _AsyncState.asynchronous,
+        _AsyncState.mountedCheck when node.isAnd => _AsyncState.mountedCheck,
+        _AsyncState.notMountedCheck when node.isOr =>
+          _AsyncState.notMountedCheck,
+        _ => null,
+      };
+    }
+
+    // `reference` follows `node`, or an ancestor of `node`.
+
+    if (node.isAnd) {
+      var leftGuardState = node.leftOperand.accept(this);
+      var rightGuardState = node.rightOperand.accept(this);
+      return switch ((leftGuardState, rightGuardState)) {
+        // If the left is uninteresting, just return the state of the right.
+        (null, _) => rightGuardState,
+        // If the right is uninteresting, just return the state of the left.
+        (_, null) => leftGuardState,
+        // Anything on the left followed by async on the right is async.
+        (_, _AsyncState.asynchronous) => _AsyncState.asynchronous,
+        // An async state on the left is superceded by the state on the right.
+        (_AsyncState.asynchronous, _) => rightGuardState,
+        // Otherwise just use the state on the left.
+        (_AsyncState.mountedCheck, _) => _AsyncState.mountedCheck,
+        (_AsyncState.notMountedCheck, _) => _AsyncState.notMountedCheck,
+      };
+    } else if (node.isOr) {
+      var leftGuardState = node.leftOperand.accept(this);
+      var rightGuardState = node.rightOperand.accept(this);
+      return switch ((leftGuardState, rightGuardState)) {
+        // Anything on the left followed by async on the right is async.
+        (_, _AsyncState.asynchronous) => _AsyncState.asynchronous,
+        // Async on the left followed by anything on the right is async.
+        (_AsyncState.asynchronous, _) => _AsyncState.asynchronous,
+        // A mounted guard only applies if both sides are guarded.
+        (_AsyncState.mountedCheck, _AsyncState.mountedCheck) =>
+          _AsyncState.mountedCheck,
+        (_AsyncState.notMountedCheck, _AsyncState.notMountedCheck) =>
+          _AsyncState.notMountedCheck,
+        // Otherwise it's just uninteresting.
+        (_, _) => null,
+      };
+    } else {
+      // Outside of a binary logical operation, a mounted check cannot guard a
+      // later expression, so only check for asynchronous code.
+      return node.leftOperand.accept(this)?.asynchronousOrNull ??
+          node.rightOperand.accept(this)?.asynchronousOrNull;
     }
   }
 
   @override
-  _MountedCheck? visitBlock(Block node) {
-    for (var statement in node.statements) {
-      var mountedCheck = statement.accept(this);
-      if (mountedCheck != null) {
-        return mountedCheck;
+  _AsyncState? visitBlock(Block node) {
+    for (var i = node.statements.length - 1; i >= 0; i--) {
+      var statement = node.statements[i];
+      var asyncState = statement.accept(this);
+      if (asyncState != null) {
+        // Walking from the last statement to the first, as soon as we encounter
+        // asynchronous code or a mounted check (positive or negative), that's
+        // the state of the block.
+        return asyncState;
       }
     }
     return null;
   }
 
   @override
-  _MountedCheck? visitConditionalExpression(ConditionalExpression node) {
-    if (child == node.condition) return null;
+  _AsyncState? visitBlockFunctionBody(BlockFunctionBody node) =>
+      // Stop visiting when we arrive at a function body.
+      // Awaits and mounted checks inside it don't matter.
+      null;
+
+  @override
+  _AsyncState? visitCascadeExpression(CascadeExpression node) =>
+      _asynchronousIfAnyIsAsync([node.target, ...node.cascadeSections]);
+
+  @override
+  _AsyncState? visitConditionalExpression(ConditionalExpression node) {
+    if (reference == node.condition) return null;
 
     var conditionMountedCheck = node.condition.accept(this);
     if (conditionMountedCheck == null) return null;
 
-    if (child == node.thenExpression) {
-      return conditionMountedCheck == _MountedCheck.positive
-          ? _MountedCheck.positive
+    if (reference == node.thenExpression) {
+      return conditionMountedCheck == _AsyncState.mountedCheck
+          ? _AsyncState.mountedCheck
           : null;
-    } else if (child == node.elseExpression) {
-      return conditionMountedCheck == _MountedCheck.negative
-          ? _MountedCheck.positive
+    } else if (reference == node.elseExpression) {
+      return conditionMountedCheck == _AsyncState.notMountedCheck
+          ? _AsyncState.mountedCheck
           : null;
     } else {
-      // `child` is (or is a child of) a statement that comes after `node`
-      // in a NodeList.
+      // `reference` is a statement that comes after `node` in a NodeList.
 
       // TODO(srawlins): What if `thenExpression` has an `await`?
-      if (conditionMountedCheck == _MountedCheck.negative &&
+      if (conditionMountedCheck == _AsyncState.notMountedCheck &&
           node.thenExpression.terminatesControl) {
-        return _MountedCheck.positive;
-      } else if (conditionMountedCheck == _MountedCheck.positive &&
+        return _AsyncState.mountedCheck;
+      } else if (conditionMountedCheck == _AsyncState.mountedCheck &&
           node.elseExpression.terminatesControl) {
-        return _MountedCheck.positive;
+        return _AsyncState.mountedCheck;
       }
       return null;
     }
   }
 
   @override
-  _MountedCheck? visitIfStatement(IfStatement node) {
-    if (child == node.expression) {
-      // In this situation, any possible mounted check would be a _descendent_
-      // of `child`; it would not be a valid mounted check for `child`.
+  _AsyncState? visitDoStatement(DoStatement node) {
+    if (node.body == reference) {
+      // After one loop, an `await` in the condition can affect the body.
+      return node.condition.accept(this)?.asynchronousOrNull;
+    } else if (node.condition == reference) {
+      // TODO(srawlins): The repetition gets tricky. In this code:
+      // `do print('hi') while (await f(context));`, the `await` is not unsafe
+      // for `f(context)` when just looking at the condition without looking at
+      // the context of the do-statement. However, as the code can loop, the
+      // `await` _is_ unsafe. It can unwrap to
+      // `print('hi'); await f(context); print('hi'); await f(context);`.
+      return node.body.accept(this)?.asynchronousOrNull;
+    } else {
+      return node.condition.accept(this)?.asynchronousOrNull ??
+          node.body.accept(this)?.asynchronousOrNull;
+    }
+  }
+
+  @override
+  _AsyncState? visitExpressionFunctionBody(ExpressionFunctionBody node) =>
+      // Stop visiting when we arrive at a function body.
+      // Awaits and mounted checks inside it don't matter.
+      null;
+
+  @override
+  _AsyncState? visitExpressionStatement(ExpressionStatement node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitExtensionOverride(ExtensionOverride node) =>
+      // TODO(srawlins): The target? Like `E(await foo).m()`.
+      _asynchronousIfAnyIsAsync(node.argumentList.arguments);
+
+  @override
+  _AsyncState? visitForElement(ForElement node) {
+    var forLoopParts = node.forLoopParts;
+    var referenceIsBody = node.body == reference;
+    return switch (forLoopParts) {
+      ForPartsWithDeclarations() => _inOrderAsyncState([
+          for (var declaration in forLoopParts.variables.variables)
+            (node: declaration, mountedCanGuard: false),
+          (node: forLoopParts.condition, mountedCanGuard: referenceIsBody),
+          for (var updater in forLoopParts.updaters)
+            (node: updater, mountedCanGuard: false),
+          (node: node.body, mountedCanGuard: false),
+        ]),
+      ForPartsWithExpression() => _inOrderAsyncState([
+          (node: forLoopParts.initialization, mountedCanGuard: false),
+          (node: forLoopParts.condition, mountedCanGuard: referenceIsBody),
+          for (var updater in forLoopParts.updaters)
+            (node: updater, mountedCanGuard: false),
+          (node: node.body, mountedCanGuard: false),
+        ]),
+      ForEachParts() => _inOrderAsyncState([
+          (node: forLoopParts.iterable, mountedCanGuard: false),
+          (node: node.body, mountedCanGuard: false),
+        ]),
+      _ => null,
+    };
+  }
+
+  @override
+  _AsyncState? visitForStatement(ForStatement node) {
+    var forLoopParts = node.forLoopParts;
+    var referenceIsBody = node.body == reference;
+    return switch (forLoopParts) {
+      ForPartsWithDeclarations() => _inOrderAsyncState([
+          for (var declaration in forLoopParts.variables.variables)
+            (node: declaration, mountedCanGuard: false),
+          // The body can be guarded by the condition.
+          (node: forLoopParts.condition, mountedCanGuard: referenceIsBody),
+          for (var updater in forLoopParts.updaters)
+            (node: updater, mountedCanGuard: false),
+          (node: node.body, mountedCanGuard: false),
+        ]),
+      ForPartsWithExpression() => _inOrderAsyncState([
+          (node: forLoopParts.initialization, mountedCanGuard: false),
+          // The body can be guarded by the condition.
+          (node: forLoopParts.condition, mountedCanGuard: referenceIsBody),
+          for (var updater in forLoopParts.updaters)
+            (node: updater, mountedCanGuard: false),
+          (node: node.body, mountedCanGuard: false),
+        ]),
+      ForEachParts() => _inOrderAsyncState([
+          (node: forLoopParts.iterable, mountedCanGuard: false),
+          (node: node.body, mountedCanGuard: false),
+        ]),
+      _ => null,
+    };
+  }
+
+  @override
+  _AsyncState? visitFunctionExpressionInvocation(
+          FunctionExpressionInvocation node) =>
+      _asynchronousIfAnyIsAsync(
+          [node.function, ...node.argumentList.arguments]);
+
+  @override
+  _AsyncState? visitIfElement(IfElement node) {
+    if (reference == node.expression) {
+      return null;
+    }
+    var conditionMountedCheck = node.expression.accept(this);
+    if (reference == node.thenElement) {
+      return switch (conditionMountedCheck) {
+        _AsyncState.asynchronous => _AsyncState.asynchronous,
+        _AsyncState.mountedCheck => _AsyncState.mountedCheck,
+        _ => null,
+      };
+    } else if (reference == node.elseElement) {
+      return switch (conditionMountedCheck) {
+        _AsyncState.asynchronous => _AsyncState.asynchronous,
+        _AsyncState.notMountedCheck => _AsyncState.mountedCheck,
+        _ => null,
+      };
+    } else {
+      return conditionMountedCheck?.asynchronousOrNull ??
+          node.thenElement.accept(this)?.asynchronousOrNull ??
+          node.elseElement?.accept(this)?.asynchronousOrNull;
+    }
+  }
+
+  @override
+  _AsyncState? visitIfStatement(IfStatement node) {
+    if (reference == node.expression) {
       return null;
     }
     var conditionMountedCheck = node.expression.accept(this);
 
-    if (child == node.thenStatement) {
-      return conditionMountedCheck == _MountedCheck.positive
-          ? _MountedCheck.positive
-          : null;
-    } else if (child == node.elseStatement) {
-      return conditionMountedCheck == _MountedCheck.negative
-          ? _MountedCheck.positive
-          : null;
+    if (reference == node.thenStatement) {
+      return switch (conditionMountedCheck) {
+        _AsyncState.asynchronous => _AsyncState.asynchronous,
+        _AsyncState.mountedCheck => _AsyncState.mountedCheck,
+        _ => null,
+      };
+    } else if (reference == node.elseStatement) {
+      return switch (conditionMountedCheck) {
+        _AsyncState.asynchronous => _AsyncState.asynchronous,
+        _AsyncState.notMountedCheck => _AsyncState.mountedCheck,
+        _ => null,
+      };
     } else {
-      // `child` is (or is a child of) a statement that comes after `node`
-      // in a NodeList.
-      if (conditionMountedCheck == null) {
-        var thenMountedCheck = node.thenStatement.accept(this);
-        var elseMountedCheck = node.elseStatement?.accept(this);
-        // [node] is a positive mounted check if each of its branches is, is a
-        // negative mounted check if each of its branches is, and otherwise is
-        // not a mounted check.
-        return thenMountedCheck == elseMountedCheck ? thenMountedCheck : null;
-      }
+      // `reference` is a statement that comes after `node`, or an ancestor of
+      // `node`, in a NodeList.
+      switch (conditionMountedCheck) {
+        case null:
+          var thenMountedCheck = node.thenStatement.accept(this);
+          var elseMountedCheck = node.elseStatement?.accept(this);
+          if (thenMountedCheck == _AsyncState.asynchronous &&
+              !node.thenStatement.terminatesControl) {
+            return _AsyncState.asynchronous;
+          } else if (elseMountedCheck == _AsyncState.asynchronous &&
+              node.elseStatement?.terminatesControl == false) {
+            return _AsyncState.asynchronous;
+          } else {
+            return thenMountedCheck == elseMountedCheck
+                ? thenMountedCheck
+                : null;
+          }
 
-      if (conditionMountedCheck == _MountedCheck.positive) {
-        var elseStatement = node.elseStatement;
-        if (elseStatement == null) {
-          // The mounted check in the if-condition does not guard `child`.
-          return null;
-        }
+        case _AsyncState.asynchronous:
+          if (node.thenStatement.accept(this) == _AsyncState.notMountedCheck &&
+              node.elseStatement?.accept(this) == _AsyncState.notMountedCheck) {
+            // Mounted checks in both the then- and else-statements guard
+            // against the asynchronous code in the condition.
+            return _AsyncState.notMountedCheck;
+          } else {
+            return _AsyncState.asynchronous;
+          }
 
-        // TODO(srawlins): If `thenStatement` has an `await`, then we don't
-        // have a valid mounted check, unless the `await` is followed by
-        // another mounted check...
-        return elseStatement.terminatesControl ? _MountedCheck.positive : null;
-      } else {
-        // `child` is (or is a child of) a statement that comes after `node`
-        // in a NodeList.
+        case _AsyncState.mountedCheck:
+          var elseStatement = node.elseStatement;
+          if (elseStatement == null) {
+            // The mounted check in the if-condition does not guard `reference`.
+            return null;
+          }
 
-        // TODO(srawlins): If `elseStatement` has an `await`, then we don't
-        // have a valid mounted check, unless the `await` is followed by
-        // another mounted check...
-        return node.thenStatement.terminatesControl
-            ? _MountedCheck.negative
-            : null;
+          return elseStatement.terminatesControl
+              ? _AsyncState.mountedCheck
+              : null;
+
+        case _AsyncState.notMountedCheck:
+          // `reference` is a statement that comes after `node` in a NodeList.
+
+          // TODO(srawlins): If `elseStatement` has an `await`, then we don't
+          // have a valid mounted check, unless the `await` is followed by
+          // another mounted check...
+          return node.thenStatement.terminatesControl
+              ? _AsyncState.notMountedCheck
+              : null;
       }
     }
   }
 
   @override
-  _MountedCheck? visitPrefixedIdentifier(PrefixedIdentifier node) =>
-      node.identifier.name == mountedName ? _MountedCheck.positive : null;
+  _AsyncState? visitIndexExpression(IndexExpression node) =>
+      _asynchronousIfAnyIsAsync([node.target, node.index]);
 
   @override
-  _MountedCheck? visitPrefixExpression(PrefixExpression node) {
+  _AsyncState? visitInstanceCreationExpression(
+          InstanceCreationExpression node) =>
+      _asynchronousIfAnyIsAsync(node.argumentList.arguments);
+
+  @override
+  _AsyncState? visitInterpolationExpression(InterpolationExpression node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitIsExpression(IsExpression node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitLabeledStatement(LabeledStatement node) =>
+      node.statement.accept(this);
+
+  @override
+  _AsyncState? visitListLiteral(ListLiteral node) =>
+      _asynchronousIfAnyIsAsync(node.elements);
+
+  @override
+  _AsyncState? visitMapLiteralEntry(MapLiteralEntry node) =>
+      _asynchronousIfAnyIsAsync([node.key, node.value]);
+
+  @override
+  _AsyncState? visitMethodInvocation(MethodInvocation node) =>
+      _asynchronousIfAnyIsAsync([node.target, ...node.argumentList.arguments]);
+
+  @override
+  _AsyncState? visitNamedExpression(NamedExpression node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitParenthesizedExpression(ParenthesizedExpression node) =>
+      node.expression.accept(this);
+
+  @override
+  _AsyncState? visitPostfixExpression(PostfixExpression node) =>
+      node.operand.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitPrefixedIdentifier(PrefixedIdentifier node) =>
+      node.identifier.name == mountedName ? _AsyncState.mountedCheck : null;
+
+  @override
+  _AsyncState? visitPrefixExpression(PrefixExpression node) {
     if (node.isNot) {
-      var mountedCheck = node.operand.accept(this);
-      return mountedCheck?.negate;
+      var guardState = node.operand.accept(this);
+      return switch (guardState) {
+        _AsyncState.mountedCheck => _AsyncState.notMountedCheck,
+        _AsyncState.notMountedCheck => _AsyncState.mountedCheck,
+        _ => guardState,
+      };
     } else {
       return null;
     }
   }
 
   @override
-  _MountedCheck? visitSimpleIdentifier(SimpleIdentifier node) =>
-      node.name == mountedName ? _MountedCheck.positive : null;
+  _AsyncState? visitPropertyAccess(PropertyAccess node) =>
+      node.target?.accept(this)?.asynchronousOrNull;
 
   @override
-  _MountedCheck? visitTryStatement(TryStatement node) {
+  _AsyncState? visitRecordLiteral(RecordLiteral node) =>
+      _asynchronousIfAnyIsAsync(node.fields);
+
+  @override
+  _AsyncState? visitSetOrMapLiteral(SetOrMapLiteral node) =>
+      _asynchronousIfAnyIsAsync(node.elements);
+
+  @override
+  _AsyncState? visitSimpleIdentifier(SimpleIdentifier node) =>
+      node.name == mountedName ? _AsyncState.mountedCheck : null;
+
+  @override
+  _AsyncState? visitSpreadElement(SpreadElement node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitStringInterpolation(StringInterpolation node) =>
+      _asynchronousIfAnyIsAsync(node.elements);
+
+  @override
+  _AsyncState? visitSwitchCase(SwitchCase node) =>
+      _asynchronousIfAnyIsAsync([node.expression, ...node.statements]);
+
+  @override
+  _AsyncState? visitSwitchPatternCase(SwitchPatternCase node) =>
+      _asynchronousIfAnyIsAsync(
+          [node.guardedPattern.whenClause, ...node.statements]);
+
+  @override
+  _AsyncState? visitSwitchDefault(SwitchDefault node) =>
+      _asynchronousIfAnyIsAsync(node.statements);
+
+  @override
+  _AsyncState? visitSwitchExpression(SwitchExpression node) =>
+      // TODO(srawlins): Support mounted guard checks in case patterns.
+      _asynchronousIfAnyIsAsync([node.expression, ...node.cases]);
+
+  @override
+  _AsyncState? visitSwitchExpressionCase(SwitchExpressionCase node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitSwitchStatement(SwitchStatement node) =>
+      // TODO(srawlins): Check for definite exits and mounted checks in the
+      // members.
+      node.expression.accept(this)?.asynchronousOrNull ??
+      _asynchronousIfAnyIsAsync(node.members);
+
+  @override
+  _AsyncState? visitTryStatement(TryStatement node) {
+    if (node.body == reference) {
+      return null;
+    } else if (node.catchClauses.any((clause) => clause == reference)) {
+      return node.body.accept(this)?.asynchronousOrNull;
+    } else if (node.finallyBlock == reference) {
+      return node.body.accept(this);
+    }
+
     // Only statements in the `finally` section of a try-statement can
     // sufficiently guard statements following the try-statement.
-    var statements = node.finallyBlock?.statements;
-    if (statements == null) {
-      return null;
+    return node.finallyBlock?.accept(this) ?? node.body.accept(this);
+  }
+
+  @override
+  _AsyncState? visitVariableDeclaration(VariableDeclaration node) =>
+      node.initializer?.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitVariableDeclarationStatement(
+          VariableDeclarationStatement node) =>
+      _asynchronousIfAnyIsAsync([
+        for (var variable in node.variables.variables) variable.initializer,
+      ]);
+
+  @override
+  _AsyncState? visitWhenClause(WhenClause node) =>
+      // TODO: Support mounted guard checks in when clause.
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitWhileStatement(WhileStatement node) =>
+      // TODO(srawlins): if the condition is a mounted guard and `reference` is
+      // the body or follows the while.
+      // A while-statement's body is not guaranteed to execute, so no mounted
+      // checks properly guard.
+      node.condition.accept(this)?.asynchronousOrNull ??
+      node.body.accept(this)?.asynchronousOrNull;
+
+  @override
+  _AsyncState? visitYieldStatement(YieldStatement node) =>
+      node.expression.accept(this)?.asynchronousOrNull;
+
+  /// Returns [_AsyncState.asynchronous] if visiting any of [nodes] returns
+  /// [_AsyncState.asynchronous], otherwise `null`.
+  ///
+  /// This function does not take mounted checks into account, so it cannot be
+  /// used when [nodes] can affect control flow.
+  _AsyncState? _asynchronousIfAnyIsAsync(List<AstNode?> nodes) {
+    var index = nodes.indexOf(reference);
+    if (index < 0) {
+      return nodes.any((node) => node?.accept(this) == _AsyncState.asynchronous)
+          ? _AsyncState.asynchronous
+          : null;
+    } else {
+      return nodes
+              .take(index)
+              .any((node) => node?.accept(this) == _AsyncState.asynchronous)
+          ? _AsyncState.asynchronous
+          : null;
     }
-    for (var statement in statements) {
-      var mountedCheck = statement.accept(this);
-      if (mountedCheck == _MountedCheck.negative) return _MountedCheck.negative;
+  }
+
+  /// Walks backwards through [nodes] looking for "interesting" async states,
+  /// determining the async state of [nodes], with respect to [reference].
+  ///
+  /// [nodes] is a list of records, each with an [AstNode] and a field
+  /// representing whether a mounted check in the node can guard [reference].
+  ///
+  /// [nodes] must be in expected execution order. [reference] can be one of
+  /// [nodes], or can follow [nodes], or can follow an ancestor of [nodes].
+  ///
+  /// If [reference] is one of the [nodes], this traversal starts at the node
+  /// that precedes it, rather than at the end of the list.
+  _AsyncState? _inOrderAsyncState(
+      List<({AstNode? node, bool mountedCanGuard})> nodes) {
+    if (nodes.isEmpty) return null;
+    if (nodes.first.node == reference) return null;
+    var referenceIndex =
+        nodes.indexWhere((element) => element.node == reference);
+    var startingIndex =
+        referenceIndex > 0 ? referenceIndex - 1 : nodes.length - 1;
+
+    for (var i = startingIndex; i >= 0; i--) {
+      var (:node, :mountedCanGuard) = nodes[i];
+      if (node == null) continue;
+      var asyncState = node.accept(this);
+      if (!mountedCanGuard && asyncState == _AsyncState.asynchronous) {
+        return _AsyncState.asynchronous;
+      }
+      if (mountedCanGuard && asyncState != null) {
+        // Walking from the last node to the first, as soon as we encounter a
+        // mounted check (positive or negative) or asynchronous code, that's
+        // the state of the whole series.
+        return asyncState;
+      }
     }
     return null;
   }
@@ -328,11 +732,12 @@
       var index = statements.indexOf(child);
       for (var i = index - 1; i >= 0; i--) {
         var s = statements[i];
-        if (s.isMountedCheckFor(child)) {
-          return false;
-        } else if (s.isAsync) {
+        var asyncState = s.asyncStateFor(child);
+        if (asyncState == _AsyncState.asynchronous) {
           rule.reportLint(node);
           return true;
+        } else if (asyncState.isGuarded) {
+          return false;
         }
       }
       return true;
@@ -360,25 +765,28 @@
         if (!keepChecking) {
           return;
         }
-      } else if (parent is ConditionalExpression) {
-        if (child != parent.condition && parent.condition.hasAwait) {
+      } else if (parent is CollectionElement) {
+        // mounted ? ... : ...
+        var asyncState = parent.asyncStateFor(child);
+        if (asyncState == _AsyncState.asynchronous) {
           rule.reportLint(node);
           return;
+        } else if (asyncState.isGuarded) {
+          return;
         }
-
-        // mounted ? ... : ...
-        if (parent.isMountedCheckFor(child)) {
+      } else if (parent is TryStatement) {
+        var asyncState = parent.asyncStateFor(child);
+        if (asyncState == _AsyncState.asynchronous) {
+          rule.reportLint(node);
           return;
         }
       } else if (parent is IfStatement) {
-        // Only check the actual statement(s), not the if condition.
-        if (child is Statement && parent.expression.hasAwait) {
+        // if (mounted) { ... }
+        var asyncState = parent.asyncStateFor(child);
+        if (asyncState == _AsyncState.asynchronous) {
           rule.reportLint(node);
           return;
-        }
-
-        // if (mounted) { ... }
-        if (parent.isMountedCheckFor(child)) {
+        } else if (asyncState.isGuarded) {
           return;
         }
       }
@@ -410,7 +818,7 @@
   }
 
   @override
-  visitPrefixedIdentifier(PrefixedIdentifier node) {
+  void visitPrefixedIdentifier(PrefixedIdentifier node) {
     if (node.identifier.name == mountedName) {
       // Accessing `context.mounted` does not count as a "use" of a
       // `BuildContext` which needs to be guarded by a mounted check.
@@ -425,7 +833,12 @@
   }
 }
 
-extension AstNodeExtension on AstNode {
+extension on _AsyncState? {
+  bool get isGuarded =>
+      this == _AsyncState.mountedCheck || this == _AsyncState.notMountedCheck;
+}
+
+extension _AstNodeExtension on AstNode {
   bool get terminatesControl {
     var self = this;
     if (self is Block) {
@@ -442,13 +855,12 @@
     return accept(ExitDetector()) ?? false;
   }
 
-  /// Returns whether `this` is a node which guards [child] with a **mounted
-  /// check**.
+  /// Returns the asynchronous state that exists between `this` and [reference].
   ///
-  /// [child] must be a direct child of `this`, or a sibling of `this`
-  /// in a List of [Statement]s.
-  bool isMountedCheckFor(AstNode child) =>
-      accept(_MountedCheckVisitor(child: child)) != null;
+  /// [reference] must be a direct child of `this`, or a sibling of `this`
+  /// in a List of [AstNode]s.
+  _AsyncState? asyncStateFor(AstNode reference) =>
+      accept(_AsyncStateVisitor(reference: reference));
 }
 
 extension on PrefixExpression {
@@ -497,36 +909,9 @@
     }
     return false;
   }
-
-  /// Whether this has an [AwaitExpression] inside.
-  bool get hasAwait {
-    var visitor = _AwaitVisitor();
-    accept(visitor);
-    return visitor.hasAwait;
-  }
 }
 
 extension on Statement {
-  /// Whether this statement has an [AwaitExpression] inside.
-  bool get isAsync {
-    var self = this;
-    if (self is IfStatement) {
-      if (self.expression.hasAwait) return true;
-      // If the then-statement definitely exits, and if there is no
-      // else-statement or the else-statement also definitely exits, then any
-      // `await`s inside do not count.
-      if (self.thenStatement.terminatesControl) {
-        var elseStatement = self.elseStatement;
-        if (elseStatement == null || elseStatement.terminatesControl) {
-          return false;
-        }
-      }
-    }
-    var visitor = _AwaitVisitor();
-    accept(visitor);
-    return visitor.hasAwait;
-  }
-
   /// Whether this statement terminates control, via a [BreakStatement], a
   /// [ContinueStatement], or other definite exits, as determined by
   /// [ExitDetector].
diff --git a/test/rules/use_build_context_synchronously_test.dart b/test/rules/use_build_context_synchronously_test.dart
index a9b646f..f6f196b 100644
--- a/test/rules/use_build_context_synchronously_test.dart
+++ b/test/rules/use_build_context_synchronously_test.dart
@@ -24,6 +24,24 @@
   @override
   String get testPackageRootPath => '$workspaceRootPath/lib';
 
+  test_assignmentExpressionContainsMountedCheck_thenReferenceToContext() async {
+    // Assignment statement-expression with mounted check, then use of
+    // BuildContext in if-then statement, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await c();
+  var m = context.mounted;
+  Navigator.of(context);
+}
+
+Future<void> c() async {}
+''', [
+      lint(121, 21),
+    ]);
+  }
+
   test_await_afterReferenceToContext() async {
     // Use of BuildContext, then await, in statement block is OK.
     await assertNoDiagnostics(r'''
@@ -164,24 +182,85 @@
     ]);
   }
 
-  test_awaitBeforeIf_mountedExitGuardInIf_beforeReferenceToContext() async {
-    // Await, then a proper "exit if not mounted" guard in an if-condition (or'd
-    // with another bool), then use of BuildContext, is OK.
+  test_awaitBeforeConditional_mountedGuard5() async {
+    // Await, then an "if mounted" guard in a conditional expression, an await
+    // in the conditional-else, and use of BuildContext afterward, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await c();
+  mounted ? 'x' : await c();
+  Navigator.of(context);
+}
+Future<void> c() async => true;
+bool mounted = false;
+''', [
+      lint(123, 21),
+    ]);
+  }
+
+  test_awaitBeforeIf_awaitAndMountedGuard() async {
+    // Await, then if-condition with an await "&&" a mounted check, then use of
+    // BuildContext in the if-body, is OK.
     await assertNoDiagnostics(r'''
 import 'package:flutter/widgets.dart';
 
 void foo(BuildContext context) async {
   await f();
-  if (c || !mounted) return;
-  Navigator.of(context);
+  if (await c() && mounted) {
+    Navigator.of(context);
+  }
 }
 
 bool mounted = false;
 Future<void> f() async {}
-bool get c => true;
+Future<bool> c() async => true;
 ''');
   }
 
+  test_awaitBeforeIf_AwaitOrMountedGuard() async {
+    // Await, then if-condition with an await "||" a mounted check, then use of
+    // BuildContext in the if-body, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await f();
+  if (await c() || mounted) {
+    Navigator.of(context);
+  }
+}
+
+bool mounted = false;
+Future<void> f() async {}
+Future<bool> c() async => true;
+''', [
+      lint(126, 21),
+    ]);
+  }
+
+  test_awaitBeforeIf_ConditionOrMountedGuard() async {
+    // Await, then if-condition with a condition "||" a mounted check, then use
+    // of BuildContext in the if-body, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await f();
+  if (c() || mounted) {
+    Navigator.of(context);
+  }
+}
+
+bool mounted = false;
+Future<void> f() async {}
+bool c() => true;
+''', [
+      lint(120, 21),
+    ]);
+  }
+
   test_awaitBeforeIf_mountedExitGuardInIf_beforeReferenceToContext2() async {
     // Await, then a proper "exit if not mounted" guard in an if-condition,
     // then use of BuildContext, is OK.
@@ -238,6 +317,77 @@
 ''');
   }
 
+  test_awaitBeforeIf_mountedExitGuardInIf_beforeReferenceToContext5() async {
+    // Await, then an unrelated if/else, and both the then-statement and the
+    // else-statement contain an "exit if not mounted" guard and an await, then
+    // use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await f();
+  if (1 == 2) {
+    if (!mounted) return;
+    await f();
+  } else {
+    if (!mounted) return;
+    await f();
+  }
+  Navigator.of(context);
+}
+
+bool mounted = false;
+Future<void> f() async {}
+''', [
+      lint(207, 21),
+    ]);
+  }
+
+  test_awaitBeforeIf_mountedExitGuardInIf_beforeReferenceToContext6() async {
+    // Await, then an unrelated if/else, and the then-statement contains an
+    // await, and the else-statement contains an "exit if not mounted" check,
+    // then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await f();
+  if (1 == 2) {
+    await f();
+  } else {
+    if (!mounted) return;
+  }
+  Navigator.of(context);
+}
+
+bool mounted = false;
+Future<void> f() async {}
+''', [
+      lint(166, 21),
+    ]);
+  }
+
+  test_awaitBeforeIf_mountedGuardAndAwait() async {
+    // Await, then if-condition with a mounted check "&&" an await, then use of
+    // BuildContext in the if-body, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await f();
+  if (mounted && await c()) {
+    Navigator.of(context);
+  }
+}
+
+bool mounted = false;
+Future<void> f() async {}
+Future<bool> c() async => true;
+''', [
+      lint(126, 21),
+    ]);
+  }
+
   test_awaitBeforeIf_mountedGuardInIf1() async {
     // Await, then a proper "if mounted" guard in an if-condition, then use of
     // BuildContext in the if-body, is OK.
@@ -354,6 +504,291 @@
 ''');
   }
 
+  test_awaitInAdjacentStrings_beforeReferenceToContext() async {
+    // Await in an adjacent strings, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  '' '${await c()}';
+  Navigator.of(context);
+}
+Future<bool> c() async => true;
+''', [
+      lint(102, 21),
+    ]);
+  }
+
+  test_awaitInAdjacentStrings_beforeReferenceToContext2() async {
+    // Await in adjacent strings, then use of BuildContext in later in same
+    // adjacent strings, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  '${await c()}' '${Navigator.of(context)}';
+}
+Future<bool> c() async => true;
+''', [
+      lint(99, 21),
+    ]);
+  }
+
+  test_awaitInCascadeSection_beforeReferenceToContext() async {
+    // Await in a cascade target, then use of BuildContext in same cascade, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  []..add(await c())..add(Navigator.of(context));
+}
+Future<int> c() async => 1;
+''', [
+      lint(105, 21),
+    ]);
+  }
+
+  test_awaitInCascadeTarget_beforeReferenceToContext() async {
+    // Await in a cascade target, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c())..toString();
+  Navigator.of(context);
+}
+Future<bool> c() async => true;
+''', [
+      lint(108, 21),
+    ]);
+  }
+
+  test_awaitInCascadeTarget_beforeReferenceToContext2() async {
+    // Await in a cascade target, then use of BuildContext in same cascade, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c())..add(Navigator.of(context));
+}
+Future<List<void>> c() async => [];
+''', [
+      lint(98, 21),
+    ]);
+  }
+
+  test_awaitInCascadeTarget_beforeReferenceToContext3() async {
+    // Await in a cascade target, then use of BuildContext in same cascade, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c())..add(1)..add(Navigator.of(context));
+}
+Future<List<void>> c() async => [];
+''', [
+      lint(106, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithDeclaration_beforeReferenceToContext() async {
+    // Await in for-element for-parts-with-declaration variables, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [
+    for (var i = await c(); i < 5; i++) 'text',
+  ];
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(138, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithDeclaration_beforeReferenceToContext2() async {
+    // Await in for-element for-parts-with-declaration condition, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [
+    for (var i = 0; i < await c(); i++) 'text',
+  ];
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(138, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithDeclaration_beforeReferenceToContext3() async {
+    // Await, then mounted check in for-element for-parts-with-declaration
+    // condition, then use of
+    // BuildContext in the same for-element body, is OK.
+    await assertNoDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await c();
+  [
+    for (var i = 0; context.mounted; i++)
+      Navigator.of(context),
+  ];
+}
+Future<void> c() async => 1;
+''');
+  }
+
+  test_awaitInForElementWithDeclaration_beforeReferenceToContext4() async {
+    // Await in for-element for-parts-with-declaration updaters, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [
+    for (var i = 0; i < 5; i += await c()) 'text',
+  ];
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(141, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithEach_beforeReferenceToContext() async {
+    // Await in for-element for-each-parts condition, then use of BuildContext,
+    // is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [
+    for (var e in await c()) 'text',
+  ];
+  Navigator.of(context);
+}
+Future<List<int>> c() async => [];
+''', [
+      lint(127, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithEach_beforeReferenceToContext2() async {
+    // Await in for-element for-each-parts condition, then use of BuildContext,
+    // is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [
+    for (var e in []) await c(),
+  ];
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(123, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithExpression_beforeReferenceToContext() async {
+    // Await in for-element for-parts-with-expression variables, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, int i) async {
+  [
+    for (i = await c(); i < 5; i++) 'text',
+  ];
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(141, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithExpression_beforeReferenceToContext2() async {
+    // Await in for-element for-parts-with-expression condition, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, int i) async {
+  [
+    for (i = 0; i < await c(); i++) 'text',
+  ];
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(141, 21),
+    ]);
+  }
+
+  test_awaitInForElementWithExpression_beforeReferenceToContext3() async {
+    // Await, then mounted check in for-element for-parts-with-expression
+    // condition, then use of BuildContext in the same for-element body, is OK.
+    await assertNoDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, int i) async {
+  await c();
+  [
+    for (var i = 0; context.mounted; i++)
+      Navigator.of(context),
+  ];
+}
+Future<void> c() async => 1;
+''');
+  }
+
+  test_awaitInForElementWithExpression_beforeReferenceToContext4() async {
+    // Await in for-element for-parts-with-expression updaters, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, int i) async {
+  [
+    for (i = 0; i < 5; i += await c()) 'text',
+  ];
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(144, 21),
+    ]);
+  }
+
+  test_awaitInFunctionExpressionInvocation_beforeReferenceToContext() async {
+    // Await in a function expression invocation function, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  ((await c()).add)(1);
+  Navigator.of(context);
+}
+Future<List<int>> c() async => [];
+''', [
+      lint(105, 21),
+    ]);
+  }
+
   // https://github.com/dart-lang/linter/issues/3457
   test_awaitInIfCondition_aboveReferenceToContext() async {
     // Await in an if-condition, then use of BuildContext in the if-body, is
@@ -432,6 +867,69 @@
 ''');
   }
 
+  test_awaitInIfElement_beforeReferenceToContext() async {
+    // Await in an if-element condition, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [if (await c()) 1];
+  Navigator.of(context);
+}
+Future<bool> c() async => false;
+''', [
+      lint(103, 21),
+    ]);
+  }
+
+  test_awaitInIfElement_beforeReferenceToContext2() async {
+    // Await in an if-element condition, then use of BuildContext in
+    // then-expression, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [if (await c()) Navigator.of(context)];
+}
+Future<bool> c() async => false;
+''', [
+      lint(97, 21),
+    ]);
+  }
+
+  test_awaitInIfElement_beforeReferenceToContext3() async {
+    // Await, then mounted check in an if-element condition, then use of
+    // BuildContext in then-expression, is OK.
+    await assertNoDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await c();
+  [if (context.mounted) Navigator.of(context)];
+}
+Future<void> c() async {}
+''');
+  }
+
+  test_awaitInIfElement_beforeReferenceToContext4() async {
+    // Await, then mounted check in an if-element condition, then use of
+    // BuildContext in then-expression, is OK.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await c();
+  [
+    if (context.mounted) 1
+    else Navigator.of(context)
+  ];
+}
+Future<void> c() async {}
+''', [
+      lint(132, 21),
+    ]);
+  }
+
   test_awaitInIfReferencesContext_beforeReferenceToContext() async {
     // Await in an if-condition, then use of BuildContext in if-then statement,
     // is REPORTED.
@@ -523,6 +1021,337 @@
     ]);
   }
 
+  test_awaitInIndexExpressionIndex_beforeReferenceToContext() async {
+    // Await in an index expression index, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, List<Object> list) async {
+  list[await c()] = Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(118, 21),
+    ]);
+  }
+
+  test_awaitInInstanceCreationExpression_beforeReferenceToContext() async {
+    // Await in an instance creation expression parameter, then use of
+    // BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  List.filled(await c(), Navigator.of(context));
+}
+Future<int> c() async => 1;
+''', [
+      lint(104, 21),
+    ]);
+  }
+
+  test_awaitInIsExpression_beforeReferenceToContext2() async {
+    // Await in a record literal, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await c() is int;
+  Navigator.of(context);
+}
+Future<bool> c() async => true;
+''', [
+      lint(101, 21),
+    ]);
+  }
+
+  test_awaitInMethodInvocation_beforeReferenceToContext() async {
+    // Await in a method invocation target, then use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c()).add(1);
+  Navigator.of(context);
+}
+Future<List<int>> c() async => [];
+''', [
+      lint(103, 21),
+    ]);
+  }
+
+  test_awaitInMethodInvocation_beforeReferenceToContext2() async {
+    // Await in a method invocation parameter, then use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [].indexOf(1, await c());
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(109, 21),
+    ]);
+  }
+
+  test_awaitInMethodInvocation_beforeReferenceToContext3() async {
+    // Await in a method invocation parameter, then use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  f(await c(), Navigator.of(context));
+}
+Future<int> c() async => 1;
+void f(int a, NavigatorState b) {}
+''', [
+      lint(94, 21),
+    ]);
+  }
+
+  test_awaitInPostfixExpression_beforeReferenceToContext() async {
+    // Await in postfix expression, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c())!;
+  Navigator.of(context);
+}
+Future<bool?> c() async => true;
+''', [
+      lint(97, 21),
+    ]);
+  }
+
+  test_awaitInPropertyAccess_beforeReferenceToContext() async {
+    // Await in property access, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c()).isEven;
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(103, 21),
+    ]);
+  }
+
+  test_awaitInRecordLiteral_beforeReferenceToContext() async {
+    // Await in a record literal, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c(), );
+  Navigator.of(context);
+}
+Future<bool> c() async => true;
+''', [
+      lint(98, 21),
+    ]);
+  }
+
+  test_awaitInRecordLiteral_beforeReferenceToContext2() async {
+    // Await in a record literal, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (f: await c(), );
+  Navigator.of(context);
+}
+Future<bool> c() async => true;
+''', [
+      lint(101, 21),
+    ]);
+  }
+
+  test_awaitInSpread_beforeReferenceToContext() async {
+    // Await in a spread element, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  [...(await c())];
+  Navigator.of(context);
+}
+Future<List<int>> c() async => [];
+''', [
+      lint(101, 21),
+    ]);
+  }
+
+  test_awaitInStringInterpolation_beforeReferenceToContext() async {
+    // Await in a string interpolation, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, int i) async {
+  '${await c()}';
+  Navigator.of(context);
+}
+Future<String> c() async => '';
+''', [
+      lint(106, 21),
+    ]);
+  }
+
+  test_awaitInSwitchExpressionCase_beforeReferenceToContext() async {
+    // Await in a switch expression case, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (switch (1) {
+    _ => await c(),
+  });
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(123, 21),
+    ]);
+  }
+
+  test_awaitInSwitchExpressionCase_beforeReferenceToContext2() async {
+    // Await in a switch expression condition, then use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (switch (await c()) {
+    _ => 7,
+  });
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(123, 21),
+    ]);
+  }
+
+  test_awaitInSwitchStatementCase_beforeReferenceToContext() async {
+    // Await in a switch statement case, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, int i) async {
+  switch (i) {
+    case 1:
+      await c();
+  }
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(136, 21),
+    ]);
+  }
+
+  test_awaitInSwitchStatementCaseGuard_beforeReferenceToContext() async {
+    // Await in a switch statement case guard, then use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  switch (1) {
+    case 1 when await c():
+  }
+  Navigator.of(context);
+}
+Future<bool> c() async => true;
+''', [
+      lint(127, 21),
+    ]);
+  }
+
+  test_awaitInSwitchStatementDefault_beforeReferenceToContext() async {
+    // Await in a switch statement default case, then use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, int i) async {
+  switch (i) {
+    default:
+      await c();
+  }
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(137, 21),
+    ]);
+  }
+
+  test_awaitInTryBody_beforeReferenceToContext() async {
+    // Await in a try-body, then use of BuildContext in try-catch clause,
+    // is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  try {
+    await c();
+  } on Exception {
+  }
+  Navigator.of(context);
+}
+Future<void> c() async {}
+''', [
+      lint(127, 21),
+    ]);
+  }
+
+  test_awaitInTryBody_beforeReferenceToContextInCatchClause() async {
+    // Await in a try-body, then use of BuildContext in try-catch clause,
+    // is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  try {
+    await c();
+  } on Exception {
+    Navigator.of(context);
+  }
+}
+Future<void> c() async {}
+''', [
+      lint(125, 21),
+    ]);
+  }
+
+  test_awaitInTryBody_beforeReferenceToContextInTryBody() async {
+    // Await in a try-body, then use of BuildContext in try-body,
+    // is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  try {
+    await c();
+    Navigator.of(context);
+  } on Exception {
+    return;
+  }
+}
+Future<void> c() async {}
+''', [
+      lint(106, 21),
+    ]);
+  }
+
   @FailingTest(reason: 'Logic not implemented yet.')
   test_awaitInWhileBody_afterReferenceToContext() async {
     // While-true statement, and inside the while-body: use of
@@ -581,6 +1410,21 @@
     ]);
   }
 
+  test_awaitInYield_beforeReferenceToContext() async {
+    // Await in a yield expression, then use of BuildContext, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+Stream<int> foo(BuildContext context) async* {
+  yield await c();
+  Navigator.of(context);
+}
+Future<int> c() async => 1;
+''', [
+      lint(108, 21),
+    ]);
+  }
+
   test_awaitThenExitInIf_afterReferenceToContext() async {
     // Use of BuildContext, then await-and-return in an if-body and
     // await-and-return in the associated else, then use of BuildContext, is OK.
@@ -602,26 +1446,21 @@
   }
 
   test_awaitThenExitInIf_beforeReferenceToContext() async {
-    // Await-and-return in an if-body and await-and-return in the associated
-    // else, then use of BuildContext, is OK.
-    await assertDiagnostics(r'''
+    // Await-and-return in an if-body, then use of BuildContext, is OK.
+    await assertNoDiagnostics(
+      r'''
 import 'package:flutter/widgets.dart';
 
 void foo(BuildContext context) async {
   if (1 == 2) {
     await c();
     return;
-  } else {
-    await c();
-    return;
   }
   Navigator.of(context);
 }
 Future<bool> c() async => true;
-''', [
-      // No lint.
-      error(WarningCode.DEAD_CODE, 166, 22),
-    ]);
+''',
+    );
   }
 
   test_conditionalOperator() async {
@@ -676,6 +1515,122 @@
     ]);
   }
 
+  test_ifConditionContainsMountedAndReferenceToContext() async {
+    // Binary expression contains mounted check AND use of BuildContext, is
+    // OK.
+    await assertNoDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(
+    BuildContext context, bool Function(BuildContext) condition) async {
+  await c();
+  if (context.mounted && condition(context)) {
+    return;
+  }
+}
+
+Future<void> c() async {}
+''');
+  }
+
+  test_ifConditionContainsMountedCheckInAssignmentLhs_thenReferenceToContext() async {
+    // If-condition contains assignment with mounted check on LHS, then use of
+    // BuildContext in if-then statement, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  await c();
+  if (A(context.mounted).b = false) {
+    Navigator.of(context);
+  }
+}
+
+class A {
+  bool b;
+  A(this.b);
+}
+
+Future<void> c() async {}
+''', [
+      lint(134, 21),
+    ]);
+  }
+
+  test_ifConditionContainsMountedCheckInAssignmentRhs_thenReferenceToContext() async {
+    // If-condition contains assignment with mounted check in RHS, then use of
+    // BuildContext in if-then statement, is OK.
+    await assertNoDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context, bool m) async {
+  await c();
+  if (m = context.mounted) {
+    Navigator.of(context);
+  }
+}
+
+Future<void> c() async {}
+''');
+  }
+
+  test_ifConditionContainsMountedOrReferenceToContext() async {
+    // Binary expression contains mounted check OR use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(
+    BuildContext context, bool Function(BuildContext) condition) async {
+  await c();
+  if (context.mounted || condition(context)) {
+    return;
+  }
+}
+
+Future<void> c() async {}
+''', [
+      lint(161, 18),
+    ]);
+  }
+
+  test_ifConditionContainsNotMountedAndReferenceToContext() async {
+    // Binary expression contains not-mounted check AND use of BuildContext, is
+    // REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(
+    BuildContext context, bool Function(BuildContext) condition) async {
+  await c();
+  if (!context.mounted && condition(context)) {
+    return;
+  }
+}
+
+Future<void> c() async {}
+''', [
+      lint(162, 18),
+    ]);
+  }
+
+  test_methodCall_targetIsAsync_contextRefFollows() async {
+    // Method call with async code in target and use of BuildContext in
+    // following statement, is REPORTED.
+    await assertDiagnostics(r'''
+import 'package:flutter/widgets.dart';
+
+void foo(BuildContext context) async {
+  (await c()).add(1);
+  Navigator.of(context);
+}
+
+Future<List<int>> c() async => [];
+''', [
+      lint(103, 21),
+    ]);
+  }
+
   test_noAwaitBefore_ifEmptyThen_methodInvocation() async {
     await assertNoDiagnostics(r'''
 import 'package:flutter/widgets.dart';
diff --git a/test_data/rules/use_build_context_synchronously.dart b/test_data/rules/use_build_context_synchronously.dart
index b9c1334..91c0802 100644
--- a/test_data/rules/use_build_context_synchronously.dart
+++ b/test_data/rules/use_build_context_synchronously.dart
@@ -56,15 +56,6 @@
 
 void unawaited(Future<void> future) {}
 
-void methodWithBuildContextParameter2f(BuildContext context) async {
-  try {
-    await Future<void>.delayed(Duration());
-    f(context); // LINT
-  } on Exception {
-    f(context); // TODO: LINT
-  }
-}
-
 class WidgetStateContext {
   bool get mounted => false;
 }
@@ -241,25 +232,6 @@
     }
   }
 
-  void methodWithBuildContextParameter2h(BuildContext context) async {
-    try {
-      await Future<void>.delayed(Duration());
-    } finally {
-      // ...
-    }
-
-    try {
-      // ...
-    } on Exception catch (e) {
-      if (!mounted) return;
-      f(context); // OK
-      return;
-    }
-
-    if (!mounted) return;
-    f(context); // OK
-  }
-
   void methodWithBuildContextParameter2i(BuildContext context) async {
     try {
       await Future<void>.delayed(Duration());