Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Code } from '@astrojs/starlight/components';
import { transformerMetaHighlight } from '@shikijs/transformers';

<Code
code={`
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class IncrementButton extends StatelessWidget {
const IncrementButton({super.key});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
BlocProvider.of<CounterCubit>(context).increment();
},
);
}
}
`}
lang="dart" title="increment_button.dart"
transformers={[transformerMetaHighlight()]} class='warning' meta="{12}" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
import { Code } from '@astrojs/starlight/components';

const code = `
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class IncrementButton extends StatelessWidget {
const IncrementButton({super.key});

@override
Widget build(BuildContext context) {
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
context.read<CounterCubit>().increment();
},
);
}
}
`;
---

<Code code={code} lang="dart" title="increment_button.dart" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Prefer BuildContext Extensions
description: The prefer_build_context_extensions rule.
---

import { Badge } from '@astrojs/starlight/components';
import EnableRuleSnippet from '~/components/lint-rules/EnableRuleSnippet.astro';
import BadSnippet from '~/components/lint-rules/prefer_build_context_extensions/BadSnippet.mdx';
import GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions/GoodSnippet.astro';

<div class="badges">
<Badge text="new" />
<Badge text="dart" variant="note" />
</div>

Prefer using `BuildContext` extensions to access a `Bloc` or `Repository`
instance.

:::note

This lint rule was introduced in version `0.3.2` of
[`package:bloc_lint`](https://pub.dev/packages/bloc_lint)

:::

## Rationale

For consistency, prefer using `BuildContext` extensions like `context.read`,
`context.watch`, and `context.select` instead of `BlocProvider.of`,
`RepositoryProvider.of`, `BlocBuilder` or `BlocSelector`.

| explicit method | extension |
| ------------------------------------------------------------------- | --------------------- |
| `BlocProvider.of<Bloc>(context, listen: false)` | `context.read<Bloc>` |
| `BlocBuilder<Bloc, State>(...)` or `BlocProvider.of<Bloc>(context)` | `context.watch<Bloc>` |
| `BlocSelector<Bloc, State>(...)` | `context.select` |

## Examples

**Avoid** using `BlocProvider.of<T>(context)` to access a `Bloc` instance.

**BAD**:

<BadSnippet />

**GOOD**:

<GoodSnippet />

## Enable

To enable the `prefer_build_context_extensions` rule, add it to your
`analysis_options.yaml` under `bloc` > `rules`:

<EnableRuleSnippet name="prefer_build_context_extensions" />
1 change: 1 addition & 0 deletions packages/bloc_lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ For more information, check out the [official documentation](https://bloclibrary
- [avoid_public_bloc_methods](https://bloclibrary.dev/lint-rules/avoid_public_bloc_methods)
- [avoid_public_fields](https://bloclibrary.dev/lint-rules/avoid_public_fields)
- [prefer_bloc](https://bloclibrary.dev/lint-rules/prefer_bloc)
- [prefer_build_context_extensions](https://bloclibrary.dev/lint-rules/prefer_build_context_extensions)
- [prefer_cubit](https://bloclibrary.dev/lint-rules/prefer_cubit)
- [prefer_file_naming_conventions](https://bloclibrary.dev/lint-rules/prefer_file_naming_conventions)
- [prefer_void_public_cubit_methods](https://bloclibrary.dev/lint-rules/prefer_void_public_cubit_methods)
Expand Down
1 change: 1 addition & 0 deletions packages/bloc_lint/lib/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ bloc:
- avoid_public_bloc_methods
- avoid_public_fields
- prefer_bloc
- prefer_build_context_extensions
- prefer_cubit
- prefer_file_naming_conventions
- prefer_void_public_cubit_methods
1 change: 1 addition & 0 deletions packages/bloc_lint/lib/bloc_lint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export 'src/rules/rules.dart'
AvoidPublicBlocMethods,
AvoidPublicFields,
PreferBloc,
PreferBuildContextExtensions,
PreferCubit,
PreferFileNamingConventions,
PreferVoidPublicCubitMethods;
Expand Down
1 change: 1 addition & 0 deletions packages/bloc_lint/lib/src/linter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ final allRules = <String, LintRuleBuilder>{
AvoidPublicBlocMethods.rule: AvoidPublicBlocMethods.new,
AvoidPublicFields.rule: AvoidPublicFields.new,
PreferBloc.rule: PreferBloc.new,
PreferBuildContextExtensions.rule: PreferBuildContextExtensions.new,
PreferCubit.rule: PreferCubit.new,
PreferFileNamingConventions.rule: PreferFileNamingConventions.new,
PreferVoidPublicCubitMethods.rule: PreferVoidPublicCubitMethods.new,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:bloc_lint/bloc_lint.dart';

/// {@template prefer_build_context_extensions}
/// The prefer_build_context_extensions lint rule.
/// {@endtemplate}
class PreferBuildContextExtensions extends LintRule {
/// {@macro prefer_build_context_extensions}
PreferBuildContextExtensions([Severity? severity])
: super(name: rule, severity: severity ?? Severity.warning);

/// The name of the lint rule.
static const rule = 'prefer_build_context_extensions';

@override
Listener create(LintContext context) => _Listener(context);
}

class _Listener extends Listener {
_Listener(this.context);

final LintContext context;

static const _message = 'Prefer using BuildContext extensions.';

@override
void handleIdentifier(Token token, IdentifierContext _) {
final provider = tryParseProvider(token);
if (provider != null) {
return context.reportTokenRange(
beginToken: provider,
endToken: token,
message: _message,
hint: '''
Avoid using ${provider.lexeme}.of<T>.
Prefer using context.read or context.watch instead.''',
);
}

final blocBuilder = tryParseBlocBuilder(token);
if (blocBuilder != null) {
return context.reportTokenRange(
beginToken: blocBuilder,
endToken: token,
message: _message,
hint: '''
Avoid using ${blocBuilder.lexeme}.
Prefer using context.watch instead.''',
);
}

final blocSelector = tryParseBlocSelector(token);
if (blocSelector != null) {
return context.reportTokenRange(
beginToken: blocSelector,
endToken: token,
message: _message,
hint: '''
Avoid using ${blocSelector.lexeme}.
Prefer using context.select instead.''',
);
}
}

Token? tryParseProvider(Token token) {
if (token.lexeme != 'of') return null;

final prev = token.previous;
if (prev == null) return null;
if (prev.type != TokenType.PERIOD) return null;

final next = token.next;
if (next == null) return null;

final target = prev.previous;
if (target == null) return null;

const providers = <String>{'BlocProvider', 'RepositoryProvider'};
return providers.contains(target.lexeme) ? target : null;
}

Token? tryParseBlocBuilder(Token token) {
if (token.lexeme != 'BlocBuilder') return null;
return (token.next is BeginToken) ? token : null;
}

Token? tryParseBlocSelector(Token token) {
if (token.lexeme != 'BlocSelector') return null;
return (token.next is BeginToken) ? token : null;
}
}
1 change: 1 addition & 0 deletions packages/bloc_lint/lib/src/rules/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export 'avoid_flutter_imports.dart';
export 'avoid_public_bloc_methods.dart';
export 'avoid_public_fields.dart';
export 'prefer_bloc.dart';
export 'prefer_build_context_extensions.dart';
export 'prefer_cubit.dart';
export 'prefer_file_naming_conventions.dart';
export 'prefer_void_public_cubit_methods.dart';
Loading