diff --git a/pkg/linter/lib/src/rules/use_build_context_synchronously.dart b/pkg/linter/lib/src/rules/use_build_context_synchronously.dart index 927b0972fd6..9259c7e4747 100644 --- a/pkg/linter/lib/src/rules/use_build_context_synchronously.dart +++ b/pkg/linter/lib/src/rules/use_build_context_synchronously.dart @@ -262,6 +262,10 @@ class AsyncStateVisitor extends SimpleAstVisitor { AsyncState? visitCascadeExpression(CascadeExpression node) => _asynchronousIfAnyIsAsync([node.target, ...node.cascadeSections]); + @override + AsyncState? visitCaseClause(CaseClause node) => + node.guardedPattern.accept(this); + @override AsyncState? visitCatchClause(CatchClause node) => node.body.accept(this)?.asynchronousOrNull; @@ -269,7 +273,8 @@ class AsyncStateVisitor extends SimpleAstVisitor { @override AsyncState? visitConditionalExpression(ConditionalExpression node) => _visitIfLike( - condition: node.condition, + expression: node.condition, + caseClause: null, thenBranch: node.thenExpression, elseBranch: node.elseExpression, ); @@ -367,16 +372,22 @@ class AsyncStateVisitor extends SimpleAstVisitor { _asynchronousIfAnyIsAsync( [node.function, ...node.argumentList.arguments]); + @override + AsyncState? visitGuardedPattern(GuardedPattern node) => + node.whenClause?.accept(this); + @override AsyncState? visitIfElement(IfElement node) => _visitIfLike( - condition: node.expression, + expression: node.expression, + caseClause: node.caseClause, thenBranch: node.thenElement, elseBranch: node.elseElement, ); @override AsyncState? visitIfStatement(IfStatement node) => _visitIfLike( - condition: node.expression, + expression: node.expression, + caseClause: node.caseClause, thenBranch: node.thenStatement, elseBranch: node.elseStatement, ); @@ -708,26 +719,54 @@ class AsyncStateVisitor extends SimpleAstVisitor { .firstWhereOrNull((state) => state != null); } - /// Compute the [AsyncState] of an "if-like" node which has a [condition], a - /// [thenBranch], and a possible [elseBranch]. + /// Compute the [AsyncState] of an "if-like" node which has a [expression], a + /// possible [caseClause], a [thenBranch], and a possible [elseBranch]. AsyncState? _visitIfLike({ - required AstNode condition, + required Expression expression, + required CaseClause? caseClause, required AstNode thenBranch, required AstNode? elseBranch, }) { - if (reference == condition) { + if (reference == expression) { + // The async state of the condition is not affected by the case-clause, + // then-branch, or else-branch. return null; } - var conditionMountedCheck = condition.accept(this); + var expressionAsyncState = expression.accept(this); + if (reference == caseClause) { + return switch (expressionAsyncState) { + AsyncState.asynchronous => AsyncState.asynchronous, + AsyncState.mountedCheck => AsyncState.mountedCheck, + _ => null, + }; + } + + var caseClauseAsyncState = caseClause?.accept(this); + // The condition state is the combined state of `expression` and + // `caseClause`. + var conditionAsyncState = + switch ((expressionAsyncState, caseClauseAsyncState)) { + // If the left is uninteresting, just return the state of the right. + (null, _) => caseClauseAsyncState, + // If the right is uninteresting, just return the state of the left. + (_, null) => expressionAsyncState, + // Anything on the left followed by async on the right is async. + (_, AsyncState.asynchronous) => AsyncState.asynchronous, + // An async state on the left is superseded by the state on the right. + (AsyncState.asynchronous, _) => caseClauseAsyncState, + // Otherwise just use the state on the left. + (AsyncState.mountedCheck, _) => AsyncState.mountedCheck, + (AsyncState.notMountedCheck, _) => AsyncState.notMountedCheck, + }; if (reference == thenBranch) { - return switch (conditionMountedCheck) { + return switch (conditionAsyncState) { AsyncState.asynchronous => AsyncState.asynchronous, AsyncState.mountedCheck => AsyncState.mountedCheck, _ => null, }; } else if (reference == elseBranch) { - return switch (conditionMountedCheck) { + return switch (conditionAsyncState) { AsyncState.asynchronous => AsyncState.asynchronous, AsyncState.notMountedCheck => AsyncState.mountedCheck, _ => null, @@ -756,16 +795,15 @@ class AsyncStateVisitor extends SimpleAstVisitor { return AsyncState.asynchronous; } - if (conditionMountedCheck == AsyncState.asynchronous) { + if (conditionAsyncState == AsyncState.asynchronous) { return AsyncState.asynchronous; } - if (conditionMountedCheck == AsyncState.mountedCheck && elseTerminates) { + if (conditionAsyncState == AsyncState.mountedCheck && elseTerminates) { return AsyncState.notMountedCheck; } - if (conditionMountedCheck == AsyncState.notMountedCheck && - thenTerminates) { + if (conditionAsyncState == AsyncState.notMountedCheck && thenTerminates) { return AsyncState.notMountedCheck; } diff --git a/pkg/linter/test/rules/use_build_context_synchronously_test.dart b/pkg/linter/test/rules/use_build_context_synchronously_test.dart index 71642dc57fb..bbd455ea52b 100644 --- a/pkg/linter/test/rules/use_build_context_synchronously_test.dart +++ b/pkg/linter/test/rules/use_build_context_synchronously_test.dart @@ -758,6 +758,22 @@ void foo(BuildContext context) async { expect(block.asyncStateFor(reference), AsyncState.notMountedCheck); } + test_ifStatement_referenceInCaseWhen_asyncInCondition() async { + await resolveCode(r''' +import 'package:flutter/widgets.dart'; +void foo(BuildContext context, Future value) async { + if (await value case int() when f(context)) { + context /* ref */; + } +} + +bool f(BuildContext context) => true; +'''); + var ifStatement = findNode.ifStatement('if ('); + var reference = findNode.block('context /* ref */'); + expect(ifStatement.asyncStateFor(reference), AsyncState.asynchronous); + } + test_ifStatement_referenceInElse_asyncInCondition() async { await resolveCode(r''' import 'package:flutter/widgets.dart'; @@ -862,6 +878,20 @@ void foo(BuildContext context, bool m) async { expect(ifStatement.asyncStateFor(reference), AsyncState.mountedCheck); } + test_ifStatement_referenceInThen_asyncInCaseWhen() async { + await resolveCode(r''' +import 'package:flutter/widgets.dart'; +void foo(BuildContext context, Object value) async { + if (value case int() when await Future.value(true)) { + context /* ref */; + } +} +'''); + var ifStatement = findNode.ifStatement('if ('); + var reference = findNode.block('context /* ref */'); + expect(ifStatement.asyncStateFor(reference), AsyncState.asynchronous); + } + test_ifStatement_referenceInThen_asyncInCondition() async { await resolveCode(r''' import 'package:flutter/widgets.dart'; @@ -932,6 +962,20 @@ void foo(BuildContext context) async { expect(ifStatement.asyncStateFor(reference), AsyncState.asynchronous); } + test_ifStatement_referenceInThen_mountedGuardInCaseWhen() async { + await resolveCode(r''' +import 'package:flutter/widgets.dart'; +void foo(BuildContext context, Object value) async { + if (value case int() when context.mounted) { + context /* ref */; + } +} +'''); + var ifStatement = findNode.ifStatement('if ('); + var reference = findNode.block('context /* ref */'); + expect(ifStatement.asyncStateFor(reference), AsyncState.mountedCheck); + } + test_ifStatement_referenceInThen_mountedInCondition() async { await resolveCode(r''' import 'package:flutter/widgets.dart';