check-is-not-closed-after-async-gap
- Group
- Correctness
- Path
lint/correctness/check-is-not-closed-after-async-gap- Default
- On (recommended)
- Origin
- Falcon
- Domain
- bloc
- Rule
- 201 of 512
Flags Bloc add and emit calls after an await without a fresh isClosed check.
A Bloc or Cubit can close while asynchronous work is suspended. Check
!isClosed before dispatching or emitting, or return when isClosed is true.
Each await invalidates earlier checks. Both guarded branches and terminating
early-return guards are recognized, and nested functions are independent.
Awaits in call arguments invalidate even an enclosing guard.
Calls must target the enclosing package-backed Bloc/Cubit, or an Emitter
parameter of its event handler. An Emitter also accepts its own !isDone
guard. Unrelated receivers, shadowed names and opaque mixins are ignored.
Analysis follows straight-line statements and enclosing if branches;
uncertain loops, switch/try flow, pattern conditions, conditional collection
elements and optional/short-circuit evaluation are not treated as proof of
an unguarded asynchronous path.
Bad
await load();
emit(Loaded());
Good
await load();
if (isClosed) return;
emit(Loaded());
Invalid
import 'package:bloc/bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
Future<void> unguarded() async {
await load();
emit(1);
}
Future<void> explicitReceiver() async {
await load();
this.emit(2);
}
Future<void> staleCheck() async {
if (!isClosed) {
await load();
emit(3);
}
}
Future<void> wrongGuard(CounterCubit other) async {
await load();
if (!other.isClosed) {
emit(4);
}
}
Future<void> staleReturn() async {
if (isClosed) return;
await load();
emit(5);
}
Future<void> awaitedArgument() async {
emit(await value());
}
Future<void> guardedAwaitedArgument() async {
if (!isClosed) emit(await value());
}
Future<void> gapBeforeConditionalArgument(bool ready) async {
await load();
emit(ready ? await value() : 0);
}
Future<int> value() async => 6;
Future<void> load() async {}
}
class CounterBloc extends Bloc<int, int> {
CounterBloc() : super(0) {
on<int>((event, emit) async {
await load();
add(1);
emit(2);
});
}
Future<void> handle(int event, Emitter<int> emit) async {
await load();
emit(3);
}
Future<void> shadowedGuard(bool isClosed) async {
await load();
if (!isClosed) {
add(4);
}
}
Future<void> load() async {}
} await load(); emit(1); await load(); this.emit(2); await load(); emit(3); if (!other.isClosed) { emit(4); await load(); emit(5); Future<void> awaitedArgument() async { emit(await value()); Future<void> guardedAwaitedArgument() async { if (!isClosed) emit(await value()); await load(); emit(ready ? await value() : 0); await load(); add(1); add(1); emit(2); await load(); emit(3); if (!isClosed) { add(4);Valid
import 'package:bloc/bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void synchronous() { emit(0); }
Future<void> branchGuard() async {
await load();
if (!isClosed) { emit(1); }
}
Future<void> conjunctionGuard(bool ready) async {
await load();
if (!isClosed && ready) { emit(1); }
}
Future<void> disjunctionReturn(bool paused) async {
await load();
if (isClosed || paused) return;
emit(1);
}
Future<void> returnGuard() async {
await load();
if (isClosed) return;
emit(2);
}
Future<void> elseGuard() async {
await load();
if (this.isClosed) { print('closed'); } else { this.emit(3); }
}
Future<void> throwGuard() async {
await load();
if (isClosed) { throw StateError('closed'); }
emit(4);
}
Future<void> freshGuard() async {
await load();
if (isClosed) return;
await load();
if (isClosed) return;
emit(5);
}
Future<void> nestedIsolation() async {
await load();
final callback = () { emit(6); };
print(callback);
}
Future<void> nestedAwaitIsolation() async {
final callback = () async { await load(); };
print(callback);
emit(7);
}
void nestedAwaitArgument() {
emit((() async { await load(); return 7; }).hashCode);
}
Future<void> conditionalAwaitArgument(bool ready) async {
emit(ready ? await value() : 0);
}
Future<int> value() async => 7;
Future<void> unrelatedReceiver(CounterCubit other) async {
await load();
other.emit(8);
}
Future<void> localShadow(void Function(int) emit) async {
await load();
emit(9);
}
Future<void> shortCircuit() async {
await load();
!isClosed && send();
}
bool send() { emit(10); return true; }
Future<void> load() async {}
}
class Service {
Future<void> run() async {
await load();
emit(1);
}
void emit(int value) {}
Future<void> load() async {}
}
class CustomCubit extends Cubit<int> {
CustomCubit() : super(0);
void emit(int value) {}
}
class ChildCustomCubit extends CustomCubit {
Future<void> inheritedOverride() async {
await Future<void>.value();
emit(1);
}
}Configure check-is-not-closed-after-async-gap
Set its severity in falcon.json. See the configuration guide.
{
"linter": {
"rules": {
"correctness": {
"check-is-not-closed-after-async-gap": "error"
}
}
}
}Or silence one occurrence with a suppression comment; the reason is required.
// falcon-ignore lint/correctness/check-is-not-closed-after-async-gap: <reason>More correctness rules
- close-sinks
close_sinksrecommendedClose instances of
dart:coreSink. - collection-methods-unrelated-type
collection_methods_unrelated_typerecommendedReports collection lookups whose argument type is unrelated to the collection's type argument.
- comment-references
comment_referencesrecommendedFlags doc-comment references
[name]that resolve to nothing in scope. - correct-order-for-super-dispose
correct_order_for_super_disposerecommendedflutterRequire
super.dispose()to be the last call indispose. - depend-on-referenced-packages
depend_on_referenced_packagesrecommendedcross-fileRequire imported packages to be declared in
pubspec.yaml. - deprecated-member-use-from-same-package
deprecated_member_use_from_same_packageReports uses of deprecated declarations from their own package.
- diagnostic-describe-all-properties
diagnostic_describe_all_propertiesflutterRequires every public property of a
Diagnosticableclass to be referenced in its debug methods. - discarded-futures
discarded_futuresDon't discard
Futures returned by calls in synchronous functions.