Skip to content

Commit

Permalink
use_build_context_synchronously: handle asynchrony in for- and do-sta…
Browse files Browse the repository at this point in the history
…tements better (dart-lang/linter#4554)
  • Loading branch information
srawlins authored Jul 12, 2023
1 parent e71d5cc commit 3637663
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 22 deletions.
8 changes: 5 additions & 3 deletions lib/src/rules/use_build_context_synchronously.dart
Original file line number Diff line number Diff line change
Expand Up @@ -722,13 +722,15 @@ class AsyncStateVisitor extends SimpleAstVisitor<AsyncState> {
/// Compute the [AsyncState] of a "block-like" node which has [statements].
AsyncState? _visitBlockLike(List<Statement> statements,
{required AstNode? parent}) {
var reference = this.reference;
if (reference is Statement) {
var index = statements.indexOf(reference as Statement);
var index = statements.indexOf(reference);
if (index >= 0) {
var precedingAsyncState = _inOrderAsyncStateGuardable(statements);
if (precedingAsyncState != null) return precedingAsyncState;
// TODO(srawlins): Also DoStatement and ForStatement.
if (parent is WhileStatement) {
if (parent is DoStatement ||
parent is ForStatement ||
parent is WhileStatement) {
// Check for asynchrony in the statements that _follow_ [reference],
// as they may lead to an async gap before we loop back to
// [reference].
Expand Down
138 changes: 137 additions & 1 deletion test/rules/use_build_context_synchronously_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,36 @@ void foo(BuildContext context) async {
expect(conditional.asyncStateFor(reference), isNull);
}

test_doWhileStatement_referenceInBody_asyncInBody() async {
await resolveCode(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
do {
await Future.value();
context /* ref */;
} while (true);
}
''');
var block = findNode.block('context /* ref */');
var reference = findNode.statement('context /* ref */');
expect(block.asyncStateFor(reference), AsyncState.asynchronous);
}

test_doWhileStatement_referenceInBody_asyncInBody2() async {
await resolveCode(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
do {
context /* ref */;
await Future.value();
} while (true);
}
''');
var block = findNode.block('context /* ref */');
var reference = findNode.statement('context /* ref */');
expect(block.asyncStateFor(reference), AsyncState.asynchronous);
}

test_extensionOverride_referenceAfter_awaitInArgument() async {
await resolveCode(r'''
import 'package:flutter/widgets.dart';
Expand Down Expand Up @@ -369,6 +399,36 @@ void foo(BuildContext context, int i) async {
expect(forElement.asyncStateFor(reference), AsyncState.mountedCheck);
}

test_forStatement_referenceInBody_asyncInBody() async {
await resolveCode(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
for (var a in []) {
await Future.value();
context /* ref */;
}
}
''');
var block = findNode.block('context /* ref */');
var reference = findNode.statement('context /* ref */');
expect(block.asyncStateFor(reference), AsyncState.asynchronous);
}

test_forStatement_referenceInBody_asyncInBody2() async {
await resolveCode(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
for (var a in []) {
context /* ref */;
await Future.value();
}
}
''');
var block = findNode.block('context /* ref */');
var reference = findNode.statement('context /* ref */');
expect(block.asyncStateFor(reference), AsyncState.asynchronous);
}

test_forStatementWithDeclaration_referenceAfter_awaitInPartCondition() async {
await resolveCode(r'''
import 'package:flutter/widgets.dart';
Expand Down Expand Up @@ -1604,6 +1664,26 @@ Future<void> f() async {}
]);
}

test_awaitBeforeForBody_referenceToContext_thenMountedGuard() async {
// Await, then for-each statement, and inside the for-body: use of
// BuildContext, then mounted guard, is REPORTED.
await assertDiagnostics(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
await f();
for (var e in []) {
Navigator.of(context);
if (context.mounted) return;
}
}
Future<void> f() async {}
''', [
lint(118, 21),
]);
}

test_awaitBeforeIfStatement_withReferenceToContext() async {
// Await, then use of BuildContext in an unrelated if-body, is REPORTED.
await assertDiagnostics(r'''
Expand Down Expand Up @@ -1654,7 +1734,6 @@ void foo(BuildContext context) async {
}
}
bool mounted = false;
Future<void> f() async {}
''', [
lint(113, 21),
Expand Down Expand Up @@ -1877,6 +1956,63 @@ void foo(BuildContext context) async {
''');
}

test_referenceToContextInDoWhileBody_thenAwait() async {
// Do-while statement, and inside the do-while-body: use of BuildContext,
// then await, is REPORTED.
await assertDiagnostics(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
do {
Navigator.of(context);
await f();
} while (true);
}
Future<void> f() async {}
''', [
lint(90, 21),
]);
}

test_referenceToContextInForBody_thenAwait() async {
// For-each statement, and inside the for-body: use of BuildContext, then
// await, is REPORTED.
await assertDiagnostics(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
for (var e in []) {
Navigator.of(context);
await f();
}
}
Future<void> f() async {}
''', [
lint(105, 21),
]);
}

test_referenceToContextInWhileBody_thenAwait() async {
// While statement, and inside the while-body: use of BuildContext, then
// await, is REPORTED.
await assertDiagnostics(r'''
import 'package:flutter/widgets.dart';
void foo(BuildContext context) async {
while (true) {
Navigator.of(context);
await f();
}
}
Future<void> f() async {}
''', [
lint(100, 21),
]);
}
}

extension on AstNode {
Expand Down
18 changes: 0 additions & 18 deletions test_data/rules/use_build_context_synchronously.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,6 @@ class _MyState extends State<MyWidget> {
Navigator.of(context).pushNamed('routeName'); // LINT
}

// Another conditional path.
void methodWithBuildContextParameter2a(BuildContext context) async {
bool f() => true;
while (f()) {
await Future<void>.delayed(Duration());
}
Navigator.of(context).pushNamed('routeName'); // LINT
}

// And another.
void methodWithBuildContextParameter2d(BuildContext context) async {
bool f() => true;
do {
await Future<void>.delayed(Duration());
} while (f());
Navigator.of(context).pushNamed('routeName'); // LINT
}

void methodWithBuildContextParameter2g(BuildContext context) async {
await Future<void>.delayed(Duration());
switch (1) {
Expand Down

0 comments on commit 3637663

Please sign in to comment.