diff --git a/README.md b/README.md index 6e896f8..4244ad5 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,5 @@ The example folder contains a dart project to unit test the rules. (see custom_l - [avoid_public_properties_on_bloc_and_cubit](docs/rules/avoid_public_properties_on_bloc_and_cubit.md) - [prefer_multi_bloc_listener](docs/rules/prefer_multi_bloc_listener.md) - [prefer_multi_bloc_provider](docs/rules/prefer_multi_bloc_provider.md) -- [prefer_multi_repository_provider](docs/rules/prefer_multi_repository_provider.md) \ No newline at end of file +- [prefer_multi_repository_provider](docs/rules/prefer_multi_repository_provider.md) +- [state_base_class_suffix](docs/rules/state_base_class_suffix.md) \ No newline at end of file diff --git a/docs/rules/state_base_class_suffix.md b/docs/rules/state_base_class_suffix.md new file mode 100644 index 0000000..eabeb9c --- /dev/null +++ b/docs/rules/state_base_class_suffix.md @@ -0,0 +1,28 @@ +state_base_class_suffix +=== +severity: WARNING + +The base state class should always be suffixed by `State`. + +## Example: + +❌ **BAD**: + +```dart +@immutable +sealed class HomepageData {} + +final class HomepageInitial extends HomepageData {} +``` + +✅ **GOOD**: + +```dart +sealed class HomepageState {} + +final class HomepageInitial extends HomepageState {} +``` + +## Additional Resources + +- [Bloc Library Documentation: Naming Conventions / State Conventions](https://bloclibrary.dev/naming-conventions/#state-conventions) diff --git a/example/lib/lint_rules/state_base_class_suffix_lint_rule_test_bloc.dart b/example/lib/lint_rules/state_base_class_suffix_lint_rule_test_bloc.dart new file mode 100644 index 0000000..40ae597 --- /dev/null +++ b/example/lib/lint_rules/state_base_class_suffix_lint_rule_test_bloc.dart @@ -0,0 +1,23 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +class StateBaseClassSuffixLintRuleTestBloc extends Bloc< + StateBaseClassSuffixLintRuleTestEvent, +// expect_lint: state_base_class_suffix + StateBaseClassSuffixLintRuleTestError> { + StateBaseClassSuffixLintRuleTestBloc() + : super(StateBaseClassSuffixLintRuleTestInitial()) { + on((event, emit) { + // TODO: implement event handler + }); + } +} + +@immutable +sealed class StateBaseClassSuffixLintRuleTestEvent {} + +@immutable +sealed class StateBaseClassSuffixLintRuleTestError {} + +final class StateBaseClassSuffixLintRuleTestInitial + extends StateBaseClassSuffixLintRuleTestError {} diff --git a/example/lib/lint_rules/state_base_class_suffix_lint_rule_test_cubit.dart b/example/lib/lint_rules/state_base_class_suffix_lint_rule_test_cubit.dart new file mode 100644 index 0000000..7d599a9 --- /dev/null +++ b/example/lib/lint_rules/state_base_class_suffix_lint_rule_test_cubit.dart @@ -0,0 +1,14 @@ +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +class StateBaseClassSuffixLintRuleTestCubit // expect_lint: state_base_class_suffix + extends Cubit { + StateBaseClassSuffixLintRuleTestCubit() + : super(StateBaseClassSuffixLintRuleTestInitial()); +} + +@immutable +sealed class StateBaseClassSuffixLintRuleTest {} + +final class StateBaseClassSuffixLintRuleTestInitial + extends StateBaseClassSuffixLintRuleTest {} diff --git a/lib/bloc_lint.dart b/lib/bloc_lint.dart index 6a5d5ec..4e7b133 100644 --- a/lib/bloc_lint.dart +++ b/lib/bloc_lint.dart @@ -3,6 +3,7 @@ import 'package:bloc_lint/lint_rules/avoid_public_properties_on_bloc_and_cubit_l import 'package:bloc_lint/lint_rules/prefer_multi_bloc_listener_lint_rule.dart'; import 'package:bloc_lint/lint_rules/prefer_multi_bloc_provider_lint_rule.dart'; import 'package:bloc_lint/lint_rules/prefer_multi_repository_provider_lint_rule.dart'; +import 'package:bloc_lint/lint_rules/state_base_class_suffix.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; PluginBase createPlugin() => _BlocLintPlugin(); @@ -15,5 +16,6 @@ class _BlocLintPlugin extends PluginBase { const PreferMultiBlocListenerLintRule(), const PreferMultiBlocProviderLintRule(), const PreferMultiRepositoryProviderLintRule(), + const StateBaseClassSuffix(), ]; } diff --git a/lib/lint_rules/state_base_class_suffix.dart b/lib/lint_rules/state_base_class_suffix.dart new file mode 100644 index 0000000..d737719 --- /dev/null +++ b/lib/lint_rules/state_base_class_suffix.dart @@ -0,0 +1,58 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:bloc_lint/bloc_lint_constants.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +class StateBaseClassSuffix extends DartLintRule { + const StateBaseClassSuffix() + : super( + code: const LintCode( + name: 'state_base_class_suffix', + problemMessage: + '''The base state class should always be suffixed by 'State'.''', + errorSeverity: ErrorSeverity.WARNING, + ), + ); + + static const _ignoredType = [ + 'int', + 'double', + 'num', + 'String', + 'bool', + 'List', + 'Set', + 'Map', + ]; + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + context.registry.addExtendsClause((node) { + final superClass = node.superclass; + TypeAnnotation? stateNode; + + if (superClass.element?.name == BlocLintConstants.cubitClass) { + stateNode = superClass.typeArguments?.arguments[0]; + } else if (superClass.element?.name == BlocLintConstants.blocClass) { + stateNode = superClass.typeArguments?.arguments[1]; + } + if (stateNode == null) { + return; + } + final stateType = stateNode.type as InterfaceType?; + if (stateType == null || _ignoredType.contains(stateType.element.name)) { + return; + } + + if (stateType.element.name.endsWith('State') == false) { + reporter.atNode(stateNode, code); + } + }); + } +} diff --git a/pubspec.lock b/pubspec.lock index a3d5e23..932a657 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -46,14 +46,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.12.0" - bloc: - dependency: transitive - description: - name: bloc - sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" - url: "https://pub.dev" - source: hosted - version: "8.1.4" boolean_selector: dependency: transitive description: @@ -62,14 +54,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" checked_yaml: dependency: transitive description: @@ -182,19 +166,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - flutter: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - flutter_bloc: - dependency: "direct dev" - description: - name: flutter_bloc - sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a - url: "https://pub.dev" - source: hosted - version: "8.1.6" freezed_annotation: dependency: transitive description: @@ -291,14 +262,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" meta: dependency: transitive description: @@ -315,14 +278,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" node_preamble: dependency: transitive description: @@ -355,14 +310,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - provider: - dependency: transitive - description: - name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c - url: "https://pub.dev" - source: hosted - version: "6.1.2" pub_semver: dependency: transitive description: @@ -419,11 +366,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" source_map_stack_trace: dependency: transitive description: @@ -536,14 +478,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" very_good_analysis: dependency: "direct dev" description: @@ -610,4 +544,3 @@ packages: version: "3.1.2" sdks: dart: ">=3.5.0 <4.0.0" - flutter: ">=1.16.0"