From b86a80718c6148f1a83aca7e5194d4c0d2c0a6e1 Mon Sep 17 00:00:00 2001 From: Katekko Date: Thu, 31 Jul 2025 20:59:31 -0300 Subject: [PATCH 1/4] feat(lint): add avoid_async_emit rule with tests and documentation --- .../avoid_async_emit/BadSnippet.mdx | 19 ++ .../avoid_async_emit/GoodSnippet.astro | 20 ++ .../docs/lint-rules/avoid_async_emit.mdx | 40 +++ packages/bloc_lint/lib/all.yaml | 1 + packages/bloc_lint/lib/bloc_lint.dart | 1 + packages/bloc_lint/lib/src/linter.dart | 8 +- .../lib/src/rules/avoid_async_emit.dart | 155 +++++++++ packages/bloc_lint/lib/src/rules/rules.dart | 1 + .../test/src/rules/avoid_async_emit_test.dart | 299 ++++++++++++++++++ 9 files changed, 541 insertions(+), 3 deletions(-) create mode 100644 docs/src/components/lint-rules/avoid_async_emit/BadSnippet.mdx create mode 100644 docs/src/components/lint-rules/avoid_async_emit/GoodSnippet.astro create mode 100644 docs/src/content/docs/lint-rules/avoid_async_emit.mdx create mode 100644 packages/bloc_lint/lib/src/rules/avoid_async_emit.dart create mode 100644 packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart diff --git a/docs/src/components/lint-rules/avoid_async_emit/BadSnippet.mdx b/docs/src/components/lint-rules/avoid_async_emit/BadSnippet.mdx new file mode 100644 index 00000000000..5bc12ea872b --- /dev/null +++ b/docs/src/components/lint-rules/avoid_async_emit/BadSnippet.mdx @@ -0,0 +1,19 @@ +import { Code } from '@astrojs/starlight/components'; +import { transformerMetaHighlight } from '@shikijs/transformers'; + + { + MyCubit() : super(0); + + Future loadData() async { + final data = await Future.value(42); + + emit(data); // LINT + } +} +`} +lang="dart" title="my_cubit.dart" +transformers={[transformerMetaHighlight()]} class='warning' meta="{9}" /> \ No newline at end of file diff --git a/docs/src/components/lint-rules/avoid_async_emit/GoodSnippet.astro b/docs/src/components/lint-rules/avoid_async_emit/GoodSnippet.astro new file mode 100644 index 00000000000..60baca67c29 --- /dev/null +++ b/docs/src/components/lint-rules/avoid_async_emit/GoodSnippet.astro @@ -0,0 +1,20 @@ +--- +import { Code } from '@astrojs/starlight/components'; +const code = ` +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + final data = await Future.value(42); + + if (isClosed) return; + + emit(data); + } +} +`; +--- + + \ No newline at end of file diff --git a/docs/src/content/docs/lint-rules/avoid_async_emit.mdx b/docs/src/content/docs/lint-rules/avoid_async_emit.mdx new file mode 100644 index 00000000000..837c2866b4d --- /dev/null +++ b/docs/src/content/docs/lint-rules/avoid_async_emit.mdx @@ -0,0 +1,40 @@ +--- +title: Avoid async emit +description: The avoid_sync_emit rule. +--- + +import { Badge } from '@astrojs/starlight/components'; +import EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro'; +import BadSnippet from '~/components/lint-rules/avoid_async_emit/BadSnippet.mdx'; +import GoodSnippet from '~/components/lint-rules/avoid_async_emit/GoodSnippet.astro'; + +
+ + +
+ +Do not use emit across asynchronous gaps. + +## Rationale + +DON'T use emit across asynchronous gaps. +Using emit after an asynchronous gap (such as after an await or inside an async callback) is unsafe because the Cubit or Bloc may have been closed while awaiting, leading to exceptions or unexpected behavior. Always ensure emit is called synchronously or properly guarded to avoid emitting on a closed instance. + +## Examples + +**Avoid** emit after async method + +**BAD**: + + + +**GOOD**: + + + +## Enable + +To enable the `avoid_async_emit` rule, add it to your +`analysis_options.yaml` under `bloc` > `rules`: + + \ No newline at end of file diff --git a/packages/bloc_lint/lib/all.yaml b/packages/bloc_lint/lib/all.yaml index 4e49f57b6a6..a583fd9a5b1 100644 --- a/packages/bloc_lint/lib/all.yaml +++ b/packages/bloc_lint/lib/all.yaml @@ -1,5 +1,6 @@ bloc: rules: + - avoid_async_emit - avoid_build_context_extensions - avoid_flutter_imports - avoid_public_bloc_methods diff --git a/packages/bloc_lint/lib/bloc_lint.dart b/packages/bloc_lint/lib/bloc_lint.dart index 44743626735..ddee6c38209 100644 --- a/packages/bloc_lint/lib/bloc_lint.dart +++ b/packages/bloc_lint/lib/bloc_lint.dart @@ -8,6 +8,7 @@ export 'src/lint_rule.dart' show LintRule, LintRuleBuilder; export 'src/linter.dart' show LintContext, Linter; export 'src/rules/rules.dart' show + AvoidAsyncEmit, AvoidBuildContextExtensions, AvoidFlutterImports, AvoidPublicBlocMethods, diff --git a/packages/bloc_lint/lib/src/linter.dart b/packages/bloc_lint/lib/src/linter.dart index 5b899bd390f..15732fd8aa6 100644 --- a/packages/bloc_lint/lib/src/linter.dart +++ b/packages/bloc_lint/lib/src/linter.dart @@ -24,6 +24,7 @@ final allRules = { PreferCubit.rule: PreferCubit.new, PreferFileNamingConventions.rule: PreferFileNamingConventions.new, PreferVoidPublicCubitMethods.rule: PreferVoidPublicCubitMethods.new, + AvoidAsyncEmit.rule: AvoidAsyncEmit.new, }; /// {@template linter} @@ -71,9 +72,10 @@ class Linter { final results = {canonicalizedPath: diagnostics}; final path = canonicalizedPath.toLongPath(); final cwd = File(path).parent; - final pubspecLock = findPubspecLock(cwd); - if (pubspecLock == null) return results; - if (!pubspecLock.packages.keys.contains('bloc')) return results; + // TODO(katekko): TEMP FOR TESTING + // final pubspecLock = findPubspecLock(cwd); + // if (pubspecLock == null) return results; + // if (!pubspecLock.packages.keys.contains('bloc')) return results; final analysisOptions = findAnalysisOptions(cwd); if (analysisOptions == null) return results; final relativePath = p diff --git a/packages/bloc_lint/lib/src/rules/avoid_async_emit.dart b/packages/bloc_lint/lib/src/rules/avoid_async_emit.dart new file mode 100644 index 00000000000..5ab67091348 --- /dev/null +++ b/packages/bloc_lint/lib/src/rules/avoid_async_emit.dart @@ -0,0 +1,155 @@ +import 'package:bloc_lint/bloc_lint.dart'; + +/// {@template avoid_async_emit} +/// The avoid_async_emit lint rule. +/// {@endtemplate} +class AvoidAsyncEmit extends LintRule { + /// {@macro avoid_async_emit} + AvoidAsyncEmit([Severity? severity]) + : super(name: rule, severity: severity ?? Severity.warning); + + /// The name of the lint rule. + static const rule = 'avoid_async_emit'; + + @override + Listener create(LintContext context) => _Listener(context); +} + +class _Listener extends Listener { + _Listener(this.context); + + final LintContext context; + + bool _inAsyncMethod = false; + bool _isACubit = false; + + int _isClosedGuardLevel = 0; + int _awaitInGuardLevel = 0; + + @override + Future handleAsyncModifier(Token? beginToken, Token? endToken) async { + if (!_isACubit) return; + + if (beginToken?.lexeme != 'async') return; + + _inAsyncMethod = true; + } + + @override + void beginAwaitExpression(Token token) { + if (!_isACubit) return; + if (!_inAsyncMethod) return; + + if (_isClosedGuardLevel > 0) { + _awaitInGuardLevel = _isClosedGuardLevel; + } + } + + @override + void beginIfStatement(Token token) { + if (!_isACubit) return; + if (!_inAsyncMethod) return; + + final next = token.next; + if (next?.lexeme == '(') { + final cond1 = next?.next; + // if (!isClosed) + if (cond1?.lexeme == '!' && + cond1?.next?.lexeme == 'isClosed' && + cond1?.next?.next?.lexeme == ')') { + _isClosedGuardLevel++; + } + + // if (isClosed == false) + if (cond1?.lexeme == 'isClosed' && + cond1?.next?.lexeme == '==' && + cond1?.next?.next?.lexeme == 'false') { + _isClosedGuardLevel++; + } + + // if (isClosed) return; + if (cond1?.lexeme == 'isClosed' && cond1?.next?.lexeme == ')') { + final afterParen = cond1?.next?.next; + if (afterParen?.lexeme == 'return' && afterParen?.next?.lexeme == ';') { + _isClosedGuardLevel++; + } + if (afterParen?.lexeme == '{' && + afterParen?.next?.lexeme == 'return' && + afterParen?.next?.next?.lexeme == ';' && + afterParen?.next?.next?.next?.lexeme == '}') { + _isClosedGuardLevel++; + } + } + + // if (isClosed == false) return; + // or if (isClosed == false) { return; } + if (cond1?.lexeme == 'isClosed' && + cond1?.next?.lexeme == '==' && + cond1?.next?.next?.lexeme == 'false' && + cond1?.next?.next?.next?.lexeme == ')') { + final afterParen = cond1?.next?.next?.next?.next; + if (afterParen?.lexeme == 'return' && afterParen?.next?.lexeme == ';') { + _isClosedGuardLevel++; + } + if (afterParen?.lexeme == '{' && + afterParen?.next?.lexeme == 'return' && + afterParen?.next?.next?.lexeme == ';' && + afterParen?.next?.next?.next?.lexeme == '}') { + _isClosedGuardLevel++; + } + } + } + } + + @override + void endIfStatement(Token ifToken, Token? elseToken, Token endToken) { + if (!_isACubit) return; + if (!_inAsyncMethod) return; + + final next = ifToken.next; + if (next?.lexeme == '(') { + final cond1 = next?.next; + if (cond1?.lexeme == '!' && cond1?.next?.lexeme == 'isClosed') { + if (_isClosedGuardLevel > 0) _isClosedGuardLevel--; + if (_awaitInGuardLevel > _isClosedGuardLevel) { + _awaitInGuardLevel = _isClosedGuardLevel; + } + } + if (cond1?.lexeme == 'isClosed' && + cond1?.next?.lexeme == '==' && + cond1?.next?.next?.lexeme == 'false') { + if (_isClosedGuardLevel > 0) _isClosedGuardLevel--; + if (_awaitInGuardLevel > _isClosedGuardLevel) { + _awaitInGuardLevel = _isClosedGuardLevel; + } + } + } + } + + @override + void handleIdentifier(Token token, IdentifierContext _) { + final extendsACubit = + token.lexeme == 'Cubit' && token.previous?.lexeme == 'extends'; + + if (extendsACubit) { + _isACubit = true; + } + + if (!_isACubit) return; + if (!_inAsyncMethod) return; + + if (token.lexeme == 'emit') { + if (_isClosedGuardLevel > 0 && _awaitInGuardLevel < _isClosedGuardLevel) { + return; + } + + context.reportToken( + token: token, + message: ''' +Avoid calling emit inside async methods without guarding with isClosed.''', + hint: ''' +Guard emit with if (!isClosed) or if (isClosed) return; before calling emit.''', + ); + } + } +} diff --git a/packages/bloc_lint/lib/src/rules/rules.dart b/packages/bloc_lint/lib/src/rules/rules.dart index b3979cc2bb0..8353e6d3711 100644 --- a/packages/bloc_lint/lib/src/rules/rules.dart +++ b/packages/bloc_lint/lib/src/rules/rules.dart @@ -1,3 +1,4 @@ +export 'avoid_async_emit.dart'; export 'avoid_build_context_extensions.dart'; export 'avoid_flutter_imports.dart'; export 'avoid_public_bloc_methods.dart'; diff --git a/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart b/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart new file mode 100644 index 00000000000..522c5b7948c --- /dev/null +++ b/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart @@ -0,0 +1,299 @@ +import 'package:bloc_lint/src/rules/avoid_async_emit.dart'; +import 'package:test/test.dart'; + +import '../lint_test_helper.dart'; + +void main() { + group(AvoidAsyncEmit, () { + lintTest( + 'does not report when emit is guarded by if (!isClosed)', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future foo() async { + if (!isClosed) emit(1); + } +} +''', + ); + + lintTest( + 'does not report when emit is guarded by if (isClosed == false)', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future foo() async { + if (isClosed == false) emit(1); + } +} +''', + ); + + lintTest( + 'reports when await is before emit in guard', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future foo() async { + if (!isClosed) { + await Future.value(1); + emit(1); + ^^^^ + } + } +} +''', + ); + + lintTest( + 'reports when guard is a function call', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + bool isClosedCalculator() => isClosed; + + Future foo() async { + if (!isClosedCalculator()) { + emit(1); + ^^^^ + } + } +} +''', + ); + + lintTest( + 'does not report when emit is called in sync method', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + void loadData() { + emit(5); + } +} +''', + ); + + lintTest( + // ignore: lines_longer_than_80_chars + 'does not report when emit is guarded by if (!isClosed) with await before', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + final data = await Future.value(42); + if (!isClosed) { + emit(data); + } + } +} +''', + ); + + lintTest( + 'does not report when emit is guarded by if (isClosed) return;', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + final data = await Future.value(42); + if (isClosed) { + return; + } + emit(data); + } +} +''', + ); + + lintTest( + // ignore: lines_longer_than_80_chars + 'does not report when emit is guarded by if (isClosed == false) {return;}', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + final data = await Future.value(42); + if (isClosed == false) { + return; + } + emit(data); + } +} +''', + ); + + lintTest( + 'reports when emit is not guarded in async method', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + final data = await Future.value(42); + + emit(data); + ^^^^ + } +} +''', + ); + + lintTest( + 'reports when await is before emit in if (!isClosed) block', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + int data = await Future.value(42); + if (!isClosed) { + data = await Future.value(52); + emit(data); + ^^^^ + } + } +} +''', + ); + + lintTest( + 'reports when emit is outside if (!isClosed) block', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + int data = await Future.value(42); + if(!isClosed) { + print('sike'); + } + emit(data); + ^^^^ + } +} +''', + ); + + lintTest( + 'reports when guard is a complex expression', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future loadData() async { + int data = await Future.value(42); + if(!isClosed && state == 15) { + emit(data); + ^^^^ + } + } +} +''', + ); + + lintTest( + 'reports when guard is a function call', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + bool isClosedCalculator() => isClosed; + + Future loadData() async { + int data = await Future.value(42); + if(!isClosedCalculator()) { + emit(data); + ^^^^ + } + } +} +''', + ); + + lintTest( + 'reports when guard is a variable comparison', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + + final bool x = true; + + Future loadData() async { + int data = await Future.value(42); + if(isClosed == x) { + return; + } + emit(data); + ^^^^ + } +} +''', + ); + }); +} From 8b2af31afff535ac08044a6f8922279420ebda0c Mon Sep 17 00:00:00 2001 From: Katekko Date: Thu, 31 Jul 2025 21:34:34 -0300 Subject: [PATCH 2/4] refactor(linter): remove temporary testing code for pubspecLock validation --- packages/bloc_lint/lib/src/linter.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/bloc_lint/lib/src/linter.dart b/packages/bloc_lint/lib/src/linter.dart index 15732fd8aa6..ea42bf499d6 100644 --- a/packages/bloc_lint/lib/src/linter.dart +++ b/packages/bloc_lint/lib/src/linter.dart @@ -72,10 +72,9 @@ class Linter { final results = {canonicalizedPath: diagnostics}; final path = canonicalizedPath.toLongPath(); final cwd = File(path).parent; - // TODO(katekko): TEMP FOR TESTING - // final pubspecLock = findPubspecLock(cwd); - // if (pubspecLock == null) return results; - // if (!pubspecLock.packages.keys.contains('bloc')) return results; + final pubspecLock = findPubspecLock(cwd); + if (pubspecLock == null) return results; + if (!pubspecLock.packages.keys.contains('bloc')) return results; final analysisOptions = findAnalysisOptions(cwd); if (analysisOptions == null) return results; final relativePath = p From f688b0f5a22a207c2fa701179125934f9ec6415b Mon Sep 17 00:00:00 2001 From: Katekko Date: Thu, 31 Jul 2025 22:14:23 -0300 Subject: [PATCH 3/4] refactor(avoid_async_emit): rename variable for clarity and update lint tests for Bloc --- .../lib/src/rules/avoid_async_emit.dart | 21 +++---- .../test/src/rules/avoid_async_emit_test.dart | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/packages/bloc_lint/lib/src/rules/avoid_async_emit.dart b/packages/bloc_lint/lib/src/rules/avoid_async_emit.dart index 5ab67091348..33a23c6c339 100644 --- a/packages/bloc_lint/lib/src/rules/avoid_async_emit.dart +++ b/packages/bloc_lint/lib/src/rules/avoid_async_emit.dart @@ -21,14 +21,14 @@ class _Listener extends Listener { final LintContext context; bool _inAsyncMethod = false; - bool _isACubit = false; + bool _isBlocOrCubit = false; int _isClosedGuardLevel = 0; int _awaitInGuardLevel = 0; @override Future handleAsyncModifier(Token? beginToken, Token? endToken) async { - if (!_isACubit) return; + if (!_isBlocOrCubit) return; if (beginToken?.lexeme != 'async') return; @@ -37,7 +37,7 @@ class _Listener extends Listener { @override void beginAwaitExpression(Token token) { - if (!_isACubit) return; + if (!_isBlocOrCubit) return; if (!_inAsyncMethod) return; if (_isClosedGuardLevel > 0) { @@ -47,7 +47,7 @@ class _Listener extends Listener { @override void beginIfStatement(Token token) { - if (!_isACubit) return; + if (!_isBlocOrCubit) return; if (!_inAsyncMethod) return; final next = token.next; @@ -103,7 +103,7 @@ class _Listener extends Listener { @override void endIfStatement(Token ifToken, Token? elseToken, Token endToken) { - if (!_isACubit) return; + if (!_isBlocOrCubit) return; if (!_inAsyncMethod) return; final next = ifToken.next; @@ -128,14 +128,15 @@ class _Listener extends Listener { @override void handleIdentifier(Token token, IdentifierContext _) { - final extendsACubit = - token.lexeme == 'Cubit' && token.previous?.lexeme == 'extends'; + final extendsBlocOrCubit = + (token.lexeme == 'Cubit' || token.lexeme == 'Bloc') && + token.previous?.lexeme == 'extends'; - if (extendsACubit) { - _isACubit = true; + if (extendsBlocOrCubit) { + _isBlocOrCubit = true; } - if (!_isACubit) return; + if (!_isBlocOrCubit) return; if (!_inAsyncMethod) return; if (token.lexeme == 'emit') { diff --git a/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart b/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart index 522c5b7948c..d5c38425b7e 100644 --- a/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart +++ b/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart @@ -293,6 +293,62 @@ class MyCubit extends Cubit { ^^^^ } } +''', + ); + + lintTest( + 'does not report when emit is guarded by if (!isClosed) in Bloc', + rule: AvoidAsyncEmit.new, + path: 'my_bloc.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyBloc extends Bloc { + MyBloc() : super(0); + + @override + Stream mapEventToState(int event) async* { + if (!isClosed) emit(1); + } +} +''', + ); + + lintTest( + 'reports when emit is not guarded in async method in Bloc', + rule: AvoidAsyncEmit.new, + path: 'my_bloc.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyBloc extends Bloc { + MyBloc() : super(0); + + @override + Stream mapEventToState(int event) async* { + await Future.value(42); + emit(1); + ^^^^ + } +} +''', + ); + + lintTest( + 'does not report when emit is called in sync method in Bloc', + rule: AvoidAsyncEmit.new, + path: 'my_bloc.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyBloc extends Bloc { + MyBloc() : super(0); + + @override + Stream mapEventToState(int event) sync* { + emit(1); + } +} ''', ); }); From f5df1969d321817555d0d24970097a8a1058fd21 Mon Sep 17 00:00:00 2001 From: Katekko Date: Fri, 1 Aug 2025 10:21:27 -0300 Subject: [PATCH 4/4] test(avoid_async_emit): add tests for emit guard conditions in MyCubit --- .../test/src/rules/avoid_async_emit_test.dart | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart b/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart index d5c38425b7e..b873c55d0d0 100644 --- a/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart +++ b/packages/bloc_lint/test/src/rules/avoid_async_emit_test.dart @@ -121,7 +121,7 @@ class MyCubit extends Cubit { ); lintTest( - 'does not report when emit is guarded by if (isClosed) return;', + 'does not report when emit is guarded by if (isClosed){ return; }', rule: AvoidAsyncEmit.new, path: 'my_cubit.dart', content: ''' @@ -352,4 +352,42 @@ class MyBloc extends Bloc { ''', ); }); + + lintTest( + 'does not report when emit is guarded by if (isClosed) return;', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future foo() async { + await Future.value(1); + if (isClosed) return; + emit(1); + } +} +''', + ); + + lintTest( + 'does not report when emit is guarded by if (isClosed == false) return;', + rule: AvoidAsyncEmit.new, + path: 'my_cubit.dart', + content: ''' +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MyCubit extends Cubit { + MyCubit() : super(0); + + Future foo() async { + await Future.value(1); + if (isClosed == false) return; + emit(1); + } +} +''', + ); }