From ccfa40de2826169780893aeca20ce9d39e01f503 Mon Sep 17 00:00:00 2001 From: Charles Tsang Date: Tue, 3 Oct 2023 16:47:04 +0800 Subject: [PATCH 1/7] Add new lint incorrect_usage_of_ref_method --- packages/riverpod_lint/README.md | 86 ++++++ packages/riverpod_lint/lib/riverpod_lint.dart | 2 + .../lints/incorrect_usage_of_ref_method.dart | 290 ++++++++++++++++++ .../lints/avoid_ref_inside_state_dispose.dart | 2 + .../test/lints/dependencies.dart | 3 +- .../lints/incorrect_usage_of_ref_method.dart | 142 +++++++++ .../incorrect_usage_of_ref_method.g.dart | 56 ++++ .../test/lints/provider_parameters.dart | 2 + 8 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.g.dart diff --git a/packages/riverpod_lint/README.md b/packages/riverpod_lint/README.md index 45c1db595..ed7e030ac 100644 --- a/packages/riverpod_lint/README.md +++ b/packages/riverpod_lint/README.md @@ -53,6 +53,7 @@ Riverpod_lint adds various warnings with quick fixes and refactoring options, su - [avoid\_ref\_inside\_state\_dispose](#avoid_ref_inside_state_dispose) - [notifier\_build (riverpod\_generator only)](#notifier_build-riverpod_generator-only) - [async\_value\_nullable\_patttern](#async_value_nullable_patttern) + - [incorrect\_usage\_of\_ref\_method](#incorrect_usage_of_ref_method) - [All assists](#all-assists) - [Wrap widgets with a `Consumer`](#wrap-widgets-with-a-consumer) - [Wrap widgets with a `ProviderScope`](#wrap-widgets-with-a-providerscope) @@ -643,6 +644,91 @@ switch (...) { } ``` +### incorrect_usage_of_ref_method + +Warn if the `ref.watch`/`ref.read`/`ref.listen`/`ref.listenManual` methods are used incorrectly. + +**Good**: + +```dart +final fnProvider = Provider((ref) { + ref.watch(provider); + ref.listen(provider, ...); + return 0; +}); + +class MyNotifier extends Notifier { + int get _readGetter => ref.read(provider); + + @override + int build() { + ref.watch(provider); + ref.listen(provider, ...); + return 0; + } + + void someMethod() { + ref.read(provider); + } +} + +void initState() { + ref.listenManual(provider, ...); +} + +Widget build(ctx, ref) { + ref.watch(provider); + ref.listen(provider, ...); + + return Button( + onPressed: () { + ref.read(provider); + ref.listenManual(provider, ...); + } + ); +} +``` + +**Bad**: + +```dart +final fnProvider = Provider((ref) { + ref.read(provider); // use watch instead + return 0; +}); + +class MyNotifier extends Notifier { + int get _watchGetter => ref.watch(provider); // use read instead + + @override + int build() { + ref.read(provider); // use watch instead + return 0; + } + + void someMethod() { + ref.watch(provider); // use read instead + ref.listen(provider, ...); // place listen in build instead + } +} + +void initState() { + ref.listen(provider, ...); // use listenManual instead +} + +Widget build(ctx, ref) { + ref.read(provider); // use watch instead + ref.listenManual(provider, ...); // use listen instead + + return Button( + onPressed: () { + ref.watch(provider); // use read instead + ref.listen(provider, ...); // use listenManual instead + } + ); +} +``` + ## All assists ### Wrap widgets with a `Consumer` diff --git a/packages/riverpod_lint/lib/riverpod_lint.dart b/packages/riverpod_lint/lib/riverpod_lint.dart index 2ae985663..ad5d932c4 100644 --- a/packages/riverpod_lint/lib/riverpod_lint.dart +++ b/packages/riverpod_lint/lib/riverpod_lint.dart @@ -13,6 +13,7 @@ import 'src/lints/avoid_manual_providers_as_generated_provider_dependency.dart'; import 'src/lints/avoid_public_notifier_properties.dart'; import 'src/lints/avoid_ref_inside_state_dispose.dart'; import 'src/lints/functional_ref.dart'; +import 'src/lints/incorrect_usage_of_ref_method.dart'; import 'src/lints/missing_provider_scope.dart'; import 'src/lints/notifier_build.dart'; import 'src/lints/notifier_extends.dart'; @@ -39,6 +40,7 @@ class _RiverpodPlugin extends PluginBase { const AvoidRefInsideStateDispose(), const NotifierBuild(), const AsyncValueNullablePattern(), + const IncorrectUsageOfRefMethod(), ]; @override diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart new file mode 100644 index 000000000..d3451f289 --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart @@ -0,0 +1,290 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfRefMethod extends RiverpodLintRule { + const IncorrectUsageOfRefMethod() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_ref_method', + problemMessage: 'Incorrect usage of {0}.', + correctionMessage: 'Use {1} instead.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addRefWatchInvocation((invocation) { + final parent = invocation.parent; + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (parent is LegacyProviderDeclaration || + parent is FunctionalProviderDeclaration || + methodName == 'build') return; + + reporter.reportErrorForNode( + code, + invocation.node.methodName, + [ + 'ref.watch', + 'ref.read', + ], + ); + }); + + riverpodRegistry(context).addRefReadInvocation((invocation) { + final parent = invocation.parent; + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (!(parent is LegacyProviderDeclaration || + parent is FunctionalProviderDeclaration || + methodName == 'build')) return; + + reporter.reportErrorForNode( + code, + invocation.node.methodName, + [ + 'ref.read', + 'ref.watch', + ], + ); + }); + + riverpodRegistry(context).addRefListenInvocation((invocation) { + final parent = invocation.parent; + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (parent is LegacyProviderDeclaration || + parent is FunctionalProviderDeclaration || + methodName == 'build') return; + + reporter.reportErrorForNode( + code, + invocation.node.methodName, + [ + 'ref.listen', + 'ref.listen in build', + ], + ); + }); + + riverpodRegistry(context).addWidgetRefWatchInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression == null && methodName == 'build') return; + + reporter.reportErrorForNode( + code, + invocation.node.methodName, + [ + 'ref.watch', + 'ref.read', + ], + ); + }); + + riverpodRegistry(context).addWidgetRefReadInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression != null || methodName != 'build') return; + + reporter.reportErrorForNode( + code, + invocation.node.methodName, + [ + 'ref.read', + 'ref.watch', + ], + ); + }); + + riverpodRegistry(context).addWidgetRefListenInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression == null && methodName == 'build') return; + + reporter.reportErrorForNode( + code, + invocation.node.methodName, + [ + 'ref.listen', + 'ref.listenManual', + ], + ); + }); + + riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression != null || methodName != 'build') return; + + reporter.reportErrorForNode( + code, + invocation.node.methodName, + [ + 'ref.listenManual', + 'ref.listen', + ], + ); + }); + } + + @override + List getFixes() => [UseCorrectRefMethod()]; +} + +class UseCorrectRefMethod extends RiverpodFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + riverpodRegistry(context).addRefWatchInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.read', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'read', + ); + }); + }); + + riverpodRegistry(context).addRefReadInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.watch', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'watch', + ); + }); + }); + + riverpodRegistry(context).addWidgetRefWatchInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.read', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'read', + ); + }); + }); + + riverpodRegistry(context).addWidgetRefReadInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.watch', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'watch', + ); + }); + }); + + riverpodRegistry(context).addWidgetRefListenInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.listenManual', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'listenManual', + ); + }); + }); + + riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + if (methodName != 'build') return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.listen', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'listen', + ); + }); + }); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart b/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart index 3fc91393e..91c3142ed 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart @@ -1,3 +1,5 @@ +// ignore_for_file: incorrect_usage_of_ref_method + import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; diff --git a/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart b/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart index 5d05e88c8..8fe29e738 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart @@ -1,6 +1,7 @@ -// ignore_for_file: unused_field +// ignore_for_file: unused_field, incorrect_usage_of_ref_method import 'package:riverpod_annotation/riverpod_annotation.dart'; + import 'another.dart' as import_alias; part 'dependencies.g.dart'; diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart new file mode 100644 index 000000000..92e7b2ae2 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart @@ -0,0 +1,142 @@ +// ignore_for_file: unused_element + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'incorrect_usage_of_ref_method.g.dart'; + +final aProvider = Provider((ref) => 0); + +@riverpod +int b(BRef ref) => 0; + +final nonCodeGenFunctionalProvider = Provider((ref) { + // using ref.watch in providers is fine + ref.watch(aProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.read(aProvider); + // using ref.listen in providers is fine + ref.listen(aProvider, (previous, next) {}); + return 0; +}); + +class NonCodeGenNotifier extends Notifier { + // expect_lint: incorrect_usage_of_ref_method + int get _watchGetter => ref.watch(aProvider); + // using ref.read in getters is fine + int get _readGetter => ref.read(aProvider); + + @override + int build() { + // using ref.watch in build is fine + ref.watch(aProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.read(aProvider); + // using ref.listen in build is fine + ref.listen(aProvider, (previous, next) {}); + return 0; + } + + void someMethod() { + // expect_lint: incorrect_usage_of_ref_method + ref.watch(aProvider); + // using ref.read in methods is fine + ref.read(aProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.listen(aProvider, (previous, next) {}); + } +} + +@riverpod +int codeGenFunctionalProvider(CodeGenFunctionalProviderRef ref) { + // using ref.watch in build is fine + ref.watch(bProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.read(bProvider); + // using ref.listen in build is fine + ref.listen(bProvider, (previous, next) {}); + return 0; +} + +@riverpod +class CodeGenNotifier extends _$CodeGenNotifier { + @override + int build() { + // using ref.watch in build is fine + ref.watch(bProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.read(bProvider); + // using ref.listen in build is fine + ref.listen(bProvider, (previous, next) {}); + return 0; + } + + void someMethod() { + // expect_lint: incorrect_usage_of_ref_method + ref.watch(bProvider); + // using ref.read in methods is fine + ref.read(bProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.listen(bProvider, (previous, next) {}); + } +} + +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + @override + void initState() { + super.initState(); + // expect_lint: incorrect_usage_of_ref_method + ref.watch(aProvider); + // using ref.read in initState is fine + ref.read(aProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.listen(aProvider, (previous, next) {}); + // using listenManual in initState is fine + ref.listenManual(aProvider, (previous, next) {}); + } + + void someMethod() { + // expect_lint: incorrect_usage_of_ref_method + ref.watch(aProvider); + // using ref.read in methods is fine + ref.read(aProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.listen(aProvider, (previous, next) {}); + // using ref.listenManual in methods is fine + ref.listenManual(aProvider, (previous, next) {}); + } + + @override + Widget build(BuildContext context) { + // using ref.watch in build is fine + ref.watch(aProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.read(aProvider); + // using ref.listen in build is fine + ref.listen(aProvider, (previous, next) {}); + // expect_lint: incorrect_usage_of_ref_method + ref.listenManual(aProvider, (previous, next) {}); + + return FilledButton( + onPressed: () { + // expect_lint: incorrect_usage_of_ref_method + ref.watch(aProvider); + // using ref.read in callbacks is fine + ref.read(aProvider); + // expect_lint: incorrect_usage_of_ref_method + ref.listen(aProvider, (previous, next) {}); + // using ref.listenManual in callbacks is fine + ref.listenManual(aProvider, (previous, next) {}); + }, + child: Placeholder(), + ); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.g.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.g.dart new file mode 100644 index 000000000..e93a7d482 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'incorrect_usage_of_ref_method.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$bHash() => r'52593050701642f22b31c590f20c003dc2ee1579'; + +/// See also [b]. +@ProviderFor(b) +final bProvider = AutoDisposeProvider.internal( + b, + name: r'bProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$bHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef BRef = AutoDisposeProviderRef; +String _$codeGenFunctionalProviderHash() => + r'6fa6e5bedb8f66810d727a1adb9a5dddc09ccd2c'; + +/// See also [codeGenFunctionalProvider]. +@ProviderFor(codeGenFunctionalProvider) +final codeGenFunctionalProviderProvider = AutoDisposeProvider.internal( + codeGenFunctionalProvider, + name: r'codeGenFunctionalProviderProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$codeGenFunctionalProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CodeGenFunctionalProviderRef = AutoDisposeProviderRef; +String _$codeGenNotifierHash() => r'63ce4a996f158e222193fbc7b16c90ff5e6dc3a5'; + +/// See also [CodeGenNotifier]. +@ProviderFor(CodeGenNotifier) +final codeGenNotifierProvider = + AutoDisposeNotifierProvider.internal( + CodeGenNotifier.new, + name: r'codeGenNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$codeGenNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CodeGenNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter diff --git a/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart b/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart index 90fb9420c..3529999bc 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart @@ -1,3 +1,5 @@ +// ignore_for_file: incorrect_usage_of_ref_method + import 'package:flutter/widgets.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; From 84ffbd1b66acf0e640f61c949e0d125d392424d9 Mon Sep 17 00:00:00 2001 From: Charles Tsang Date: Tue, 3 Oct 2023 22:51:52 +0800 Subject: [PATCH 2/7] Split to separate lints --- packages/riverpod_lint/lib/riverpod_lint.dart | 16 +- .../lints/incorrect_usage_of_ref_listen.dart | 39 +++ .../lints/incorrect_usage_of_ref_method.dart | 290 ------------------ .../lints/incorrect_usage_of_ref_read.dart | 70 +++++ .../lints/incorrect_usage_of_ref_watch.dart | 70 +++++ .../incorrect_usage_of_widget_ref_listen.dart | 68 ++++ ...ect_usage_of_widget_ref_listen_manual.dart | 74 +++++ .../incorrect_usage_of_widget_ref_read.dart | 68 ++++ .../incorrect_usage_of_widget_ref_watch.dart | 68 ++++ .../lints/avoid_ref_inside_state_dispose.dart | 2 +- .../test/lints/dependencies.dart | 2 +- .../lints/incorrect_usage_of_ref_listen.dart | 50 +++ ...t => incorrect_usage_of_ref_listen.g.dart} | 6 +- .../lints/incorrect_usage_of_ref_method.dart | 142 --------- .../lints/incorrect_usage_of_ref_read.dart | 50 +++ .../lints/incorrect_usage_of_ref_read.g.dart | 56 ++++ .../lints/incorrect_usage_of_ref_watch.dart | 50 +++ .../lints/incorrect_usage_of_ref_watch.g.dart | 56 ++++ .../incorrect_usage_of_widget_ref_listen.dart | 39 +++ ...ect_usage_of_widget_ref_listen_manual.dart | 39 +++ .../incorrect_usage_of_widget_ref_read.dart | 39 +++ .../incorrect_usage_of_widget_ref_watch.dart | 39 +++ .../test/lints/provider_parameters.dart | 3 +- 23 files changed, 896 insertions(+), 440 deletions(-) create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart delete mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart rename packages/riverpod_lint_flutter_test/test/lints/{incorrect_usage_of_ref_method.g.dart => incorrect_usage_of_ref_listen.g.dart} (91%) delete mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.g.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.g.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart diff --git a/packages/riverpod_lint/lib/riverpod_lint.dart b/packages/riverpod_lint/lib/riverpod_lint.dart index ad5d932c4..aceff5621 100644 --- a/packages/riverpod_lint/lib/riverpod_lint.dart +++ b/packages/riverpod_lint/lib/riverpod_lint.dart @@ -13,7 +13,13 @@ import 'src/lints/avoid_manual_providers_as_generated_provider_dependency.dart'; import 'src/lints/avoid_public_notifier_properties.dart'; import 'src/lints/avoid_ref_inside_state_dispose.dart'; import 'src/lints/functional_ref.dart'; -import 'src/lints/incorrect_usage_of_ref_method.dart'; +import 'src/lints/incorrect_usage_of_ref_listen.dart'; +import 'src/lints/incorrect_usage_of_ref_read.dart'; +import 'src/lints/incorrect_usage_of_ref_watch.dart'; +import 'src/lints/incorrect_usage_of_widget_ref_listen.dart'; +import 'src/lints/incorrect_usage_of_widget_ref_listen_manual.dart'; +import 'src/lints/incorrect_usage_of_widget_ref_read.dart'; +import 'src/lints/incorrect_usage_of_widget_ref_watch.dart'; import 'src/lints/missing_provider_scope.dart'; import 'src/lints/notifier_build.dart'; import 'src/lints/notifier_extends.dart'; @@ -40,7 +46,13 @@ class _RiverpodPlugin extends PluginBase { const AvoidRefInsideStateDispose(), const NotifierBuild(), const AsyncValueNullablePattern(), - const IncorrectUsageOfRefMethod(), + const IncorrectUsageOfRefListen(), + const IncorrectUsageOfRefRead(), + const IncorrectUsageOfRefWatch(), + const IncorrectUsageOfWidgetRefListen(), + const IncorrectUsageOfWidgetRefListenManual(), + const IncorrectUsageOfWidgetRefRead(), + const IncorrectUsageOfWidgetRefWatch(), ]; @override diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart new file mode 100644 index 000000000..e9c14b3df --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart @@ -0,0 +1,39 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfRefListen extends RiverpodLintRule { + const IncorrectUsageOfRefListen() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_ref_listen', + problemMessage: 'Incorrect usage of ref.listen.', + correctionMessage: 'Put ref.listen in build method.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addRefListenInvocation((invocation) { + final parent = invocation.parent; + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (parent is LegacyProviderDeclaration || + parent is FunctionalProviderDeclaration || + methodName == 'build') return; + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } +} diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart deleted file mode 100644 index d3451f289..000000000 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_method.dart +++ /dev/null @@ -1,290 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/error/error.dart'; -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; - -import '../riverpod_custom_lint.dart'; - -class IncorrectUsageOfRefMethod extends RiverpodLintRule { - const IncorrectUsageOfRefMethod() : super(code: _code); - - static const _code = LintCode( - name: 'incorrect_usage_of_ref_method', - problemMessage: 'Incorrect usage of {0}.', - correctionMessage: 'Use {1} instead.', - errorSeverity: ErrorSeverity.WARNING, - ); - - @override - void run( - CustomLintResolver resolver, - ErrorReporter reporter, - CustomLintContext context, - ) { - riverpodRegistry(context).addRefWatchInvocation((invocation) { - final parent = invocation.parent; - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (parent is LegacyProviderDeclaration || - parent is FunctionalProviderDeclaration || - methodName == 'build') return; - - reporter.reportErrorForNode( - code, - invocation.node.methodName, - [ - 'ref.watch', - 'ref.read', - ], - ); - }); - - riverpodRegistry(context).addRefReadInvocation((invocation) { - final parent = invocation.parent; - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (!(parent is LegacyProviderDeclaration || - parent is FunctionalProviderDeclaration || - methodName == 'build')) return; - - reporter.reportErrorForNode( - code, - invocation.node.methodName, - [ - 'ref.read', - 'ref.watch', - ], - ); - }); - - riverpodRegistry(context).addRefListenInvocation((invocation) { - final parent = invocation.parent; - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (parent is LegacyProviderDeclaration || - parent is FunctionalProviderDeclaration || - methodName == 'build') return; - - reporter.reportErrorForNode( - code, - invocation.node.methodName, - [ - 'ref.listen', - 'ref.listen in build', - ], - ); - }); - - riverpodRegistry(context).addWidgetRefWatchInvocation((invocation) { - final functionExpression = - invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (functionExpression == null && methodName == 'build') return; - - reporter.reportErrorForNode( - code, - invocation.node.methodName, - [ - 'ref.watch', - 'ref.read', - ], - ); - }); - - riverpodRegistry(context).addWidgetRefReadInvocation((invocation) { - final functionExpression = - invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (functionExpression != null || methodName != 'build') return; - - reporter.reportErrorForNode( - code, - invocation.node.methodName, - [ - 'ref.read', - 'ref.watch', - ], - ); - }); - - riverpodRegistry(context).addWidgetRefListenInvocation((invocation) { - final functionExpression = - invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (functionExpression == null && methodName == 'build') return; - - reporter.reportErrorForNode( - code, - invocation.node.methodName, - [ - 'ref.listen', - 'ref.listenManual', - ], - ); - }); - - riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { - final functionExpression = - invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (functionExpression != null || methodName != 'build') return; - - reporter.reportErrorForNode( - code, - invocation.node.methodName, - [ - 'ref.listenManual', - 'ref.listen', - ], - ); - }); - } - - @override - List getFixes() => [UseCorrectRefMethod()]; -} - -class UseCorrectRefMethod extends RiverpodFix { - @override - void run( - CustomLintResolver resolver, - ChangeReporter reporter, - CustomLintContext context, - AnalysisError analysisError, - List others, - ) { - riverpodRegistry(context).addRefWatchInvocation((invocation) { - if (!invocation.node.methodName.sourceRange - .intersects(analysisError.sourceRange)) return; - - final changeBuilder = reporter.createChangeBuilder( - message: 'Use ref.read', - priority: 80, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - invocation.node.methodName.sourceRange, - 'read', - ); - }); - }); - - riverpodRegistry(context).addRefReadInvocation((invocation) { - if (!invocation.node.methodName.sourceRange - .intersects(analysisError.sourceRange)) return; - - final changeBuilder = reporter.createChangeBuilder( - message: 'Use ref.watch', - priority: 80, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - invocation.node.methodName.sourceRange, - 'watch', - ); - }); - }); - - riverpodRegistry(context).addWidgetRefWatchInvocation((invocation) { - if (!invocation.node.methodName.sourceRange - .intersects(analysisError.sourceRange)) return; - - final changeBuilder = reporter.createChangeBuilder( - message: 'Use ref.read', - priority: 80, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - invocation.node.methodName.sourceRange, - 'read', - ); - }); - }); - - riverpodRegistry(context).addWidgetRefReadInvocation((invocation) { - if (!invocation.node.methodName.sourceRange - .intersects(analysisError.sourceRange)) return; - - final changeBuilder = reporter.createChangeBuilder( - message: 'Use ref.watch', - priority: 80, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - invocation.node.methodName.sourceRange, - 'watch', - ); - }); - }); - - riverpodRegistry(context).addWidgetRefListenInvocation((invocation) { - if (!invocation.node.methodName.sourceRange - .intersects(analysisError.sourceRange)) return; - - final changeBuilder = reporter.createChangeBuilder( - message: 'Use ref.listenManual', - priority: 80, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - invocation.node.methodName.sourceRange, - 'listenManual', - ); - }); - }); - - riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { - if (!invocation.node.methodName.sourceRange - .intersects(analysisError.sourceRange)) return; - - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - if (methodName != 'build') return; - - final changeBuilder = reporter.createChangeBuilder( - message: 'Use ref.listen', - priority: 80, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - invocation.node.methodName.sourceRange, - 'listen', - ); - }); - }); - } -} diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart new file mode 100644 index 000000000..e141e43eb --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart @@ -0,0 +1,70 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfRefRead extends RiverpodLintRule { + const IncorrectUsageOfRefRead() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_ref_read', + problemMessage: 'Incorrect usage of ref.read.', + correctionMessage: 'Use ref.watch instead.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addRefReadInvocation((invocation) { + final parent = invocation.parent; + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (!(parent is LegacyProviderDeclaration || + parent is FunctionalProviderDeclaration || + methodName == 'build')) return; + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } + + @override + List getFixes() => [UseRefWatch()]; +} + +class UseRefWatch extends RiverpodFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + riverpodRegistry(context).addRefReadInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.watch', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'watch', + ); + }); + }); + } +} diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart new file mode 100644 index 000000000..b47d9b68f --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart @@ -0,0 +1,70 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfRefWatch extends RiverpodLintRule { + const IncorrectUsageOfRefWatch() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_ref_watch', + problemMessage: 'Incorrect usage of ref.watch.', + correctionMessage: 'Use ref.read instead.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addRefWatchInvocation((invocation) { + final parent = invocation.parent; + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (parent is LegacyProviderDeclaration || + parent is FunctionalProviderDeclaration || + methodName == 'build') return; + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } + + @override + List getFixes() => [UseRefRead()]; +} + +class UseRefRead extends RiverpodFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + riverpodRegistry(context).addRefWatchInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.read', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'read', + ); + }); + }); + } +} diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart new file mode 100644 index 000000000..6ca4bb9f1 --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart @@ -0,0 +1,68 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfWidgetRefListen extends RiverpodLintRule { + const IncorrectUsageOfWidgetRefListen() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_widget_ref_listen', + problemMessage: 'Incorrect usage of ref.listen.', + correctionMessage: 'Use ref.listenManual instead.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addWidgetRefListenInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression == null && methodName == 'build') return; + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } + + @override + List getFixes() => [UseWidgetRefListenManual()]; +} + +class UseWidgetRefListenManual extends RiverpodFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + riverpodRegistry(context).addWidgetRefListenInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.listenManual', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'listenManual', + ); + }); + }); + } +} diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart new file mode 100644 index 000000000..236019b1b --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart @@ -0,0 +1,74 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfWidgetRefListenManual extends RiverpodLintRule { + const IncorrectUsageOfWidgetRefListenManual() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_widget_ref_listen_manual', + problemMessage: 'Incorrect usage of ref.listenManual.', + correctionMessage: 'Use ref.listen instead.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression != null || methodName != 'build') return; + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } + + @override + List getFixes() => [UseWidgetRefListen()]; +} + +class UseWidgetRefListen extends RiverpodFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + if (methodName != 'build') return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.listen', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'listen', + ); + }); + }); + } +} diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart new file mode 100644 index 000000000..3d17e96f8 --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart @@ -0,0 +1,68 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfWidgetRefRead extends RiverpodLintRule { + const IncorrectUsageOfWidgetRefRead() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_widget_ref_read', + problemMessage: 'Incorrect usage of ref.read.', + correctionMessage: 'Use ref.watch instead.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addWidgetRefReadInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression != null || methodName != 'build') return; + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } + + @override + List getFixes() => [UseWidgetRefWatch()]; +} + +class UseWidgetRefWatch extends RiverpodFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + riverpodRegistry(context).addWidgetRefReadInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.watch', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'watch', + ); + }); + }); + } +} diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart new file mode 100644 index 000000000..591799132 --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart @@ -0,0 +1,68 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfWidgetRefWatch extends RiverpodLintRule { + const IncorrectUsageOfWidgetRefWatch() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_widget_ref_watch', + problemMessage: 'Incorrect usage of ref.watch.', + correctionMessage: 'Use ref.read instead.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addWidgetRefWatchInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + final methodName = invocation.node + .thisOrAncestorOfType() + ?.name + .lexeme; + + if (functionExpression == null && methodName == 'build') return; + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } + + @override + List getFixes() => [UseWidgetRefRead()]; +} + +class UseWidgetRefRead extends RiverpodFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + riverpodRegistry(context).addWidgetRefWatchInvocation((invocation) { + if (!invocation.node.methodName.sourceRange + .intersects(analysisError.sourceRange)) return; + + final changeBuilder = reporter.createChangeBuilder( + message: 'Use ref.read', + priority: 80, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + invocation.node.methodName.sourceRange, + 'read', + ); + }); + }); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart b/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart index 91c3142ed..21ce741dc 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/avoid_ref_inside_state_dispose.dart @@ -1,4 +1,4 @@ -// ignore_for_file: incorrect_usage_of_ref_method +// ignore_for_file: incorrect_usage_of_ref_method, incorrect_usage_of_widget_ref_watch import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; diff --git a/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart b/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart index 8fe29e738..390e4c4aa 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart @@ -1,4 +1,4 @@ -// ignore_for_file: unused_field, incorrect_usage_of_ref_method +// ignore_for_file: unused_field, incorrect_usage_of_ref_method, incorrect_usage_of_ref_watch import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart new file mode 100644 index 000000000..be6d6be75 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart @@ -0,0 +1,50 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'incorrect_usage_of_ref_listen.g.dart'; + +final aProvider = Provider((ref) => 0); + +@riverpod +int b(BRef ref) => 0; + +final nonCodeGenFunctionalProvider = Provider((ref) { + // using ref.listen in providers is fine + ref.listen(aProvider, (previous, next) {}); + return 0; +}); + +class NonCodeGenNotifier extends Notifier { + @override + int build() { + // using ref.listen in build is fine + ref.listen(aProvider, (previous, next) {}); + return 0; + } + + void someMethod() { + // expect_lint: incorrect_usage_of_ref_listen + ref.listen(aProvider, (previous, next) {}); + } +} + +@riverpod +int codeGenFunctionalProvider(CodeGenFunctionalProviderRef ref) { + // using ref.listen in build is fine + ref.listen(bProvider, (previous, next) {}); + return 0; +} + +@riverpod +class CodeGenNotifier extends _$CodeGenNotifier { + @override + int build() { + // using ref.listen in build is fine + ref.listen(bProvider, (previous, next) {}); + return 0; + } + + void someMethod() { + // expect_lint: incorrect_usage_of_ref_listen + ref.listen(bProvider, (previous, next) {}); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.g.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.g.dart similarity index 91% rename from packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.g.dart rename to packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.g.dart index e93a7d482..d54920488 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.g.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'incorrect_usage_of_ref_method.dart'; +part of 'incorrect_usage_of_ref_listen.dart'; // ************************************************************************** // RiverpodGenerator @@ -21,7 +21,7 @@ final bProvider = AutoDisposeProvider.internal( typedef BRef = AutoDisposeProviderRef; String _$codeGenFunctionalProviderHash() => - r'6fa6e5bedb8f66810d727a1adb9a5dddc09ccd2c'; + r'a81fe0fdb2c0bafc6cbbf2911b3899bdedfd09e4'; /// See also [codeGenFunctionalProvider]. @ProviderFor(codeGenFunctionalProvider) @@ -36,7 +36,7 @@ final codeGenFunctionalProviderProvider = AutoDisposeProvider.internal( ); typedef CodeGenFunctionalProviderRef = AutoDisposeProviderRef; -String _$codeGenNotifierHash() => r'63ce4a996f158e222193fbc7b16c90ff5e6dc3a5'; +String _$codeGenNotifierHash() => r'bd91c9cee2bc4e98e96e418102fd696d03f715b8'; /// See also [CodeGenNotifier]. @ProviderFor(CodeGenNotifier) diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart deleted file mode 100644 index 92e7b2ae2..000000000 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_method.dart +++ /dev/null @@ -1,142 +0,0 @@ -// ignore_for_file: unused_element - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'incorrect_usage_of_ref_method.g.dart'; - -final aProvider = Provider((ref) => 0); - -@riverpod -int b(BRef ref) => 0; - -final nonCodeGenFunctionalProvider = Provider((ref) { - // using ref.watch in providers is fine - ref.watch(aProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.read(aProvider); - // using ref.listen in providers is fine - ref.listen(aProvider, (previous, next) {}); - return 0; -}); - -class NonCodeGenNotifier extends Notifier { - // expect_lint: incorrect_usage_of_ref_method - int get _watchGetter => ref.watch(aProvider); - // using ref.read in getters is fine - int get _readGetter => ref.read(aProvider); - - @override - int build() { - // using ref.watch in build is fine - ref.watch(aProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.read(aProvider); - // using ref.listen in build is fine - ref.listen(aProvider, (previous, next) {}); - return 0; - } - - void someMethod() { - // expect_lint: incorrect_usage_of_ref_method - ref.watch(aProvider); - // using ref.read in methods is fine - ref.read(aProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.listen(aProvider, (previous, next) {}); - } -} - -@riverpod -int codeGenFunctionalProvider(CodeGenFunctionalProviderRef ref) { - // using ref.watch in build is fine - ref.watch(bProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.read(bProvider); - // using ref.listen in build is fine - ref.listen(bProvider, (previous, next) {}); - return 0; -} - -@riverpod -class CodeGenNotifier extends _$CodeGenNotifier { - @override - int build() { - // using ref.watch in build is fine - ref.watch(bProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.read(bProvider); - // using ref.listen in build is fine - ref.listen(bProvider, (previous, next) {}); - return 0; - } - - void someMethod() { - // expect_lint: incorrect_usage_of_ref_method - ref.watch(bProvider); - // using ref.read in methods is fine - ref.read(bProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.listen(bProvider, (previous, next) {}); - } -} - -class Example extends ConsumerStatefulWidget { - const Example({super.key}); - - @override - ConsumerState createState() => _ExampleState(); -} - -class _ExampleState extends ConsumerState { - @override - void initState() { - super.initState(); - // expect_lint: incorrect_usage_of_ref_method - ref.watch(aProvider); - // using ref.read in initState is fine - ref.read(aProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.listen(aProvider, (previous, next) {}); - // using listenManual in initState is fine - ref.listenManual(aProvider, (previous, next) {}); - } - - void someMethod() { - // expect_lint: incorrect_usage_of_ref_method - ref.watch(aProvider); - // using ref.read in methods is fine - ref.read(aProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.listen(aProvider, (previous, next) {}); - // using ref.listenManual in methods is fine - ref.listenManual(aProvider, (previous, next) {}); - } - - @override - Widget build(BuildContext context) { - // using ref.watch in build is fine - ref.watch(aProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.read(aProvider); - // using ref.listen in build is fine - ref.listen(aProvider, (previous, next) {}); - // expect_lint: incorrect_usage_of_ref_method - ref.listenManual(aProvider, (previous, next) {}); - - return FilledButton( - onPressed: () { - // expect_lint: incorrect_usage_of_ref_method - ref.watch(aProvider); - // using ref.read in callbacks is fine - ref.read(aProvider); - // expect_lint: incorrect_usage_of_ref_method - ref.listen(aProvider, (previous, next) {}); - // using ref.listenManual in callbacks is fine - ref.listenManual(aProvider, (previous, next) {}); - }, - child: Placeholder(), - ); - } -} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart new file mode 100644 index 000000000..d908e87de --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart @@ -0,0 +1,50 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'incorrect_usage_of_ref_read.g.dart'; + +final aProvider = Provider((ref) => 0); + +@riverpod +int b(BRef ref) => 0; + +final nonCodeGenFunctionalProvider = Provider((ref) { + // expect_lint: incorrect_usage_of_ref_read + ref.read(aProvider); + return 0; +}); + +class NonCodeGenNotifier extends Notifier { + @override + int build() { + // expect_lint: incorrect_usage_of_ref_read + ref.read(aProvider); + return 0; + } + + void someMethod() { + // using ref.read in methods is fine + ref.read(aProvider); + } +} + +@riverpod +int codeGenFunctionalProvider(CodeGenFunctionalProviderRef ref) { + // expect_lint: incorrect_usage_of_ref_read + ref.read(bProvider); + return 0; +} + +@riverpod +class CodeGenNotifier extends _$CodeGenNotifier { + @override + int build() { + // expect_lint: incorrect_usage_of_ref_read + ref.read(bProvider); + return 0; + } + + void someMethod() { + // using ref.read in methods is fine + ref.read(bProvider); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.g.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.g.dart new file mode 100644 index 000000000..cbe5f6a50 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'incorrect_usage_of_ref_read.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$bHash() => r'52593050701642f22b31c590f20c003dc2ee1579'; + +/// See also [b]. +@ProviderFor(b) +final bProvider = AutoDisposeProvider.internal( + b, + name: r'bProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$bHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef BRef = AutoDisposeProviderRef; +String _$codeGenFunctionalProviderHash() => + r'38b123db45f42d88e7c6285711a5ca60e0ee0efc'; + +/// See also [codeGenFunctionalProvider]. +@ProviderFor(codeGenFunctionalProvider) +final codeGenFunctionalProviderProvider = AutoDisposeProvider.internal( + codeGenFunctionalProvider, + name: r'codeGenFunctionalProviderProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$codeGenFunctionalProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CodeGenFunctionalProviderRef = AutoDisposeProviderRef; +String _$codeGenNotifierHash() => r'ee1a4d6460618293607b68cc3776aaa665c4a8ce'; + +/// See also [CodeGenNotifier]. +@ProviderFor(CodeGenNotifier) +final codeGenNotifierProvider = + AutoDisposeNotifierProvider.internal( + CodeGenNotifier.new, + name: r'codeGenNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$codeGenNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CodeGenNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart new file mode 100644 index 000000000..6640062d1 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart @@ -0,0 +1,50 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'incorrect_usage_of_ref_watch.g.dart'; + +final aProvider = Provider((ref) => 0); + +@riverpod +int b(BRef ref) => 0; + +final nonCodeGenFunctionalProvider = Provider((ref) { + // using ref.watch in providers is fine + ref.watch(aProvider); + return 0; +}); + +class NonCodeGenNotifier extends Notifier { + @override + int build() { + // using ref.watch in build is fine + ref.watch(aProvider); + return 0; + } + + void someMethod() { + // expect_lint: incorrect_usage_of_ref_watch + ref.watch(aProvider); + } +} + +@riverpod +int codeGenFunctionalProvider(CodeGenFunctionalProviderRef ref) { + // using ref.watch in build is fine + ref.watch(bProvider); + return 0; +} + +@riverpod +class CodeGenNotifier extends _$CodeGenNotifier { + @override + int build() { + // using ref.watch in build is fine + ref.watch(bProvider); + return 0; + } + + void someMethod() { + // expect_lint: incorrect_usage_of_ref_watch + ref.watch(bProvider); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.g.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.g.dart new file mode 100644 index 000000000..423674f76 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'incorrect_usage_of_ref_watch.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$bHash() => r'52593050701642f22b31c590f20c003dc2ee1579'; + +/// See also [b]. +@ProviderFor(b) +final bProvider = AutoDisposeProvider.internal( + b, + name: r'bProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$bHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef BRef = AutoDisposeProviderRef; +String _$codeGenFunctionalProviderHash() => + r'5873f54272eb2a1fc7a441fef9ebca2e5047e3a8'; + +/// See also [codeGenFunctionalProvider]. +@ProviderFor(codeGenFunctionalProvider) +final codeGenFunctionalProviderProvider = AutoDisposeProvider.internal( + codeGenFunctionalProvider, + name: r'codeGenFunctionalProviderProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$codeGenFunctionalProviderHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef CodeGenFunctionalProviderRef = AutoDisposeProviderRef; +String _$codeGenNotifierHash() => r'83f359676e23475e68a151b9e289436af0afb70d'; + +/// See also [CodeGenNotifier]. +@ProviderFor(CodeGenNotifier) +final codeGenNotifierProvider = + AutoDisposeNotifierProvider.internal( + CodeGenNotifier.new, + name: r'codeGenNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$codeGenNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$CodeGenNotifier = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart new file mode 100644 index 000000000..953604c9c --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final provider = Provider((ref) => 0); + +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + @override + void initState() { + super.initState(); + // expect_lint: incorrect_usage_of_widget_ref_listen + ref.listen(provider, (previous, next) {}); + } + + void someMethod() { + // expect_lint: incorrect_usage_of_widget_ref_listen + ref.listen(provider, (previous, next) {}); + } + + @override + Widget build(BuildContext context) { + // using ref.listen in build is fine + ref.listen(provider, (previous, next) {}); + + return FilledButton( + onPressed: () { + // expect_lint: incorrect_usage_of_widget_ref_listen + ref.listen(provider, (previous, next) {}); + }, + child: Placeholder(), + ); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart new file mode 100644 index 000000000..ddacb0e17 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final provider = Provider((ref) => 0); + +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + @override + void initState() { + super.initState(); + // using listenManual in initState is fine + ref.listenManual(provider, (previous, next) {}); + } + + void someMethod() { + // using ref.listenManual in methods is fine + ref.listenManual(provider, (previous, next) {}); + } + + @override + Widget build(BuildContext context) { + // expect_lint: incorrect_usage_of_widget_ref_listen_manual + ref.listenManual(provider, (previous, next) {}); + + return FilledButton( + onPressed: () { + // using ref.listenManual in callbacks is fine + ref.listenManual(provider, (previous, next) {}); + }, + child: Placeholder(), + ); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart new file mode 100644 index 000000000..d7e5aaa2f --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final provider = Provider((ref) => 0); + +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + @override + void initState() { + super.initState(); + // using ref.read in initState is fine + ref.read(provider); + } + + void someMethod() { + // using ref.read in methods is fine + ref.read(provider); + } + + @override + Widget build(BuildContext context) { + // expect_lint: incorrect_usage_of_widget_ref_read + ref.read(provider); + + return FilledButton( + onPressed: () { + // using ref.read in callbacks is fine + ref.read(provider); + }, + child: Placeholder(), + ); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart new file mode 100644 index 000000000..7b377d7ea --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final provider = Provider((ref) => 0); + +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + @override + void initState() { + super.initState(); + // expect_lint: incorrect_usage_of_widget_ref_watch + ref.watch(provider); + } + + void someMethod() { + // expect_lint: incorrect_usage_of_widget_ref_watch + ref.watch(provider); + } + + @override + Widget build(BuildContext context) { + // using ref.watch in build is fine + ref.watch(provider); + + return FilledButton( + onPressed: () { + // expect_lint: incorrect_usage_of_widget_ref_watch + ref.watch(provider); + }, + child: Placeholder(), + ); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart b/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart index 3529999bc..cea6fe538 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/provider_parameters.dart @@ -1,4 +1,5 @@ -// ignore_for_file: incorrect_usage_of_ref_method +// ignore_for_file: incorrect_usage_of_ref_read, incorrect_usage_of_widget_ref_read +// ignore_for_file: incorrect_usage_of_widget_ref_listen_manual import 'package:flutter/widgets.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; From 005210ea0c190ef9e0c5f2a6a13bcf72303ccd80 Mon Sep 17 00:00:00 2001 From: Charles Tsang Date: Tue, 3 Oct 2023 23:26:28 +0800 Subject: [PATCH 3/7] Update readme --- packages/riverpod_lint/README.md | 181 ++++++++++++++++++++++++++----- 1 file changed, 156 insertions(+), 25 deletions(-) diff --git a/packages/riverpod_lint/README.md b/packages/riverpod_lint/README.md index ed7e030ac..535cef173 100644 --- a/packages/riverpod_lint/README.md +++ b/packages/riverpod_lint/README.md @@ -53,7 +53,13 @@ Riverpod_lint adds various warnings with quick fixes and refactoring options, su - [avoid\_ref\_inside\_state\_dispose](#avoid_ref_inside_state_dispose) - [notifier\_build (riverpod\_generator only)](#notifier_build-riverpod_generator-only) - [async\_value\_nullable\_patttern](#async_value_nullable_patttern) - - [incorrect\_usage\_of\_ref\_method](#incorrect_usage_of_ref_method) + - [incorrect\_usage\_of\_ref\_listen](#incorrect_usage_of_ref_listen) + - [incorrect\_usage\_of\_ref\_read](#incorrect_usage_of_ref_read) + - [incorrect\_usage\_of\_ref\_watch](#incorrect_usage_of_ref_watch) + - [incorrect\_usage\_of\_widget\_ref\_listen](#incorrect_usage_of_widget_ref_listen) + - [incorrect\_usage\_of\_widget\_ref\_listen\_manual](#incorrect_usage_of_widget_ref_listen_manual) + - [incorrect\_usage\_of\_widget\_ref\_read](#incorrect_usage_of_widget_ref_read) + - [incorrect\_usage\_of\_widget\_ref\_watch](#incorrect_usage_of_widget_ref_watch) - [All assists](#all-assists) - [Wrap widgets with a `Consumer`](#wrap-widgets-with-a-consumer) - [Wrap widgets with a `ProviderScope`](#wrap-widgets-with-a-providerscope) @@ -644,48 +650,54 @@ switch (...) { } ``` -### incorrect_usage_of_ref_method +### incorrect_usage_of_ref_listen -Warn if the `ref.watch`/`ref.read`/`ref.listen`/`ref.listenManual` methods are used incorrectly. +Warn if the `Ref.listen` method is used incorrectly. **Good**: ```dart final fnProvider = Provider((ref) { - ref.watch(provider); ref.listen(provider, ...); return 0; }); class MyNotifier extends Notifier { - int get _readGetter => ref.read(provider); - @override int build() { - ref.watch(provider); ref.listen(provider, ...); return 0; } +} +``` + +**Bad**: + +```dart +class MyNotifier extends Notifier {] + @override + int build() {...} void someMethod() { - ref.read(provider); + ref.listen(provider, ...); // place listen in build instead } } +``` -void initState() { - ref.listenManual(provider, ...); -} +### incorrect_usage_of_ref_read -Widget build(ctx, ref) { - ref.watch(provider); - ref.listen(provider, ...); +Warn if the `Ref.read` method is used incorrectly. - return Button( - onPressed: () { - ref.read(provider); - ref.listenManual(provider, ...); - } - ); +**Good**: + +```dart +class MyNotifier extends Notifier { + @override + int build() {...} + + void someMethod() { + ref.read(provider); + } } ``` @@ -698,32 +710,151 @@ final fnProvider = Provider((ref) { }); class MyNotifier extends Notifier { - int get _watchGetter => ref.watch(provider); // use read instead - @override int build() { ref.read(provider); // use watch instead return 0; } +} +``` + +### incorrect_usage_of_ref_watch + +Warn if the `Ref.watch` method is used incorrectly. + +**Good**: + +```dart +final fnProvider = Provider((ref) { + ref.watch(provider); + return 0; +}); + +class MyNotifier extends Notifier { + @override + int build() { + ref.watch(provider); + return 0; + } +} +``` + +**Bad**: + +```dart +class MyNotifier extends Notifier { + @override + int build() {...} void someMethod() { ref.watch(provider); // use read instead - ref.listen(provider, ...); // place listen in build instead } } +``` + +### incorrect_usage_of_widget_ref_listen + +Warn if the `WidgetRef.listen` method is used incorrectly. + +**Good**: + +```dart +Widget build(ctx, ref) { + ref.listen(provider, ...); + return ... +} +``` +**Bad**: + +```dart void initState() { ref.listen(provider, ...); // use listenManual instead } Widget build(ctx, ref) { - ref.read(provider); // use watch instead + return Button( + onPressed: () { + ref.listen(provider, ...); // use listenManual instead + } + ); +} +``` + +### incorrect_usage_of_widget_ref_listen_manual + +Warn if the `WidgetRef.listenManual` method is used incorrectly. + +**Good**: + +```dart +void initState() { + ref.listenManual(provider, ...); +} + +Widget build(ctx, ref) { + return Button( + onPressed: () { + ref.listenManual(provider, ...); + } + ); +} +``` + +**Bad**: + +```dart +Widget build(ctx, ref) { ref.listenManual(provider, ...); // use listen instead + return ... +} +``` + +### incorrect_usage_of_widget_ref_read +Warn if the `WidgetRef.read` method is used incorrectly. + +**Good**: + +```dart +Widget build(ctx, ref) { + return Button( + onPressed: () { + ref.read(provider); + } + ); +} +``` + +**Bad**: + +```dart +Widget build(ctx, ref) { + ref.read(provider); // use watch instead + return ... +} +``` + +### incorrect_usage_of_widget_ref_watch + +Warn if the `WidgetRef.watch` method is used incorrectly. + +**Good**: + +```dart +Widget build(ctx, ref) { + ref.watch(provider); + return ... +} +``` + +**Bad**: + +```dart +Widget build(ctx, ref) { return Button( onPressed: () { ref.watch(provider); // use read instead - ref.listen(provider, ...); // use listenManual instead } ); } From 551cb752856a0c9b3288cc4b7204eb1ac9fc974d Mon Sep 17 00:00:00 2001 From: Charles Tsang Date: Tue, 3 Oct 2023 23:40:10 +0800 Subject: [PATCH 4/7] Remove incorrect_usage_of_widget_ref_listen_manual --- packages/riverpod_lint/README.md | 30 -------- packages/riverpod_lint/lib/riverpod_lint.dart | 2 - ...ect_usage_of_widget_ref_listen_manual.dart | 74 ------------------- ...ect_usage_of_widget_ref_listen_manual.dart | 39 ---------- 4 files changed, 145 deletions(-) delete mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart delete mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart diff --git a/packages/riverpod_lint/README.md b/packages/riverpod_lint/README.md index 535cef173..3694a2664 100644 --- a/packages/riverpod_lint/README.md +++ b/packages/riverpod_lint/README.md @@ -57,7 +57,6 @@ Riverpod_lint adds various warnings with quick fixes and refactoring options, su - [incorrect\_usage\_of\_ref\_read](#incorrect_usage_of_ref_read) - [incorrect\_usage\_of\_ref\_watch](#incorrect_usage_of_ref_watch) - [incorrect\_usage\_of\_widget\_ref\_listen](#incorrect_usage_of_widget_ref_listen) - - [incorrect\_usage\_of\_widget\_ref\_listen\_manual](#incorrect_usage_of_widget_ref_listen_manual) - [incorrect\_usage\_of\_widget\_ref\_read](#incorrect_usage_of_widget_ref_read) - [incorrect\_usage\_of\_widget\_ref\_watch](#incorrect_usage_of_widget_ref_watch) - [All assists](#all-assists) @@ -781,35 +780,6 @@ Widget build(ctx, ref) { } ``` -### incorrect_usage_of_widget_ref_listen_manual - -Warn if the `WidgetRef.listenManual` method is used incorrectly. - -**Good**: - -```dart -void initState() { - ref.listenManual(provider, ...); -} - -Widget build(ctx, ref) { - return Button( - onPressed: () { - ref.listenManual(provider, ...); - } - ); -} -``` - -**Bad**: - -```dart -Widget build(ctx, ref) { - ref.listenManual(provider, ...); // use listen instead - return ... -} -``` - ### incorrect_usage_of_widget_ref_read Warn if the `WidgetRef.read` method is used incorrectly. diff --git a/packages/riverpod_lint/lib/riverpod_lint.dart b/packages/riverpod_lint/lib/riverpod_lint.dart index aceff5621..263f444e6 100644 --- a/packages/riverpod_lint/lib/riverpod_lint.dart +++ b/packages/riverpod_lint/lib/riverpod_lint.dart @@ -17,7 +17,6 @@ import 'src/lints/incorrect_usage_of_ref_listen.dart'; import 'src/lints/incorrect_usage_of_ref_read.dart'; import 'src/lints/incorrect_usage_of_ref_watch.dart'; import 'src/lints/incorrect_usage_of_widget_ref_listen.dart'; -import 'src/lints/incorrect_usage_of_widget_ref_listen_manual.dart'; import 'src/lints/incorrect_usage_of_widget_ref_read.dart'; import 'src/lints/incorrect_usage_of_widget_ref_watch.dart'; import 'src/lints/missing_provider_scope.dart'; @@ -50,7 +49,6 @@ class _RiverpodPlugin extends PluginBase { const IncorrectUsageOfRefRead(), const IncorrectUsageOfRefWatch(), const IncorrectUsageOfWidgetRefListen(), - const IncorrectUsageOfWidgetRefListenManual(), const IncorrectUsageOfWidgetRefRead(), const IncorrectUsageOfWidgetRefWatch(), ]; diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart deleted file mode 100644 index 236019b1b..000000000 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/error/error.dart'; -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; - -import '../riverpod_custom_lint.dart'; - -class IncorrectUsageOfWidgetRefListenManual extends RiverpodLintRule { - const IncorrectUsageOfWidgetRefListenManual() : super(code: _code); - - static const _code = LintCode( - name: 'incorrect_usage_of_widget_ref_listen_manual', - problemMessage: 'Incorrect usage of ref.listenManual.', - correctionMessage: 'Use ref.listen instead.', - errorSeverity: ErrorSeverity.WARNING, - ); - - @override - void run( - CustomLintResolver resolver, - ErrorReporter reporter, - CustomLintContext context, - ) { - riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { - final functionExpression = - invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - - if (functionExpression != null || methodName != 'build') return; - - reporter.reportErrorForNode(code, invocation.node.methodName); - }); - } - - @override - List getFixes() => [UseWidgetRefListen()]; -} - -class UseWidgetRefListen extends RiverpodFix { - @override - void run( - CustomLintResolver resolver, - ChangeReporter reporter, - CustomLintContext context, - AnalysisError analysisError, - List others, - ) { - riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { - if (!invocation.node.methodName.sourceRange - .intersects(analysisError.sourceRange)) return; - - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; - if (methodName != 'build') return; - - final changeBuilder = reporter.createChangeBuilder( - message: 'Use ref.listen', - priority: 80, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - invocation.node.methodName.sourceRange, - 'listen', - ); - }); - }); - } -} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart deleted file mode 100644 index ddacb0e17..000000000 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -final provider = Provider((ref) => 0); - -class Example extends ConsumerStatefulWidget { - const Example({super.key}); - - @override - ConsumerState createState() => _ExampleState(); -} - -class _ExampleState extends ConsumerState { - @override - void initState() { - super.initState(); - // using listenManual in initState is fine - ref.listenManual(provider, (previous, next) {}); - } - - void someMethod() { - // using ref.listenManual in methods is fine - ref.listenManual(provider, (previous, next) {}); - } - - @override - Widget build(BuildContext context) { - // expect_lint: incorrect_usage_of_widget_ref_listen_manual - ref.listenManual(provider, (previous, next) {}); - - return FilledButton( - onPressed: () { - // using ref.listenManual in callbacks is fine - ref.listenManual(provider, (previous, next) {}); - }, - child: Placeholder(), - ); - } -} From 1603cad1927e0a7c5fe569b97f6010120bb8abe1 Mon Sep 17 00:00:00 2001 From: Charles Tsang Date: Tue, 3 Oct 2023 23:54:54 +0800 Subject: [PATCH 5/7] Fix false positive for incorrect_usage_of_widget_ref_watch when using ref.watch inside Consumer/HookConsumer --- .../lib/src/riverpod_types.dart | 12 +++++++++ packages/riverpod_lint/README.md | 27 +++++++++++++++---- .../incorrect_usage_of_widget_ref_watch.dart | 10 +++++++ .../incorrect_usage_of_widget_ref_listen.dart | 9 ++++++- .../incorrect_usage_of_widget_ref_watch.dart | 9 ++++++- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart b/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart index 6dd3fe9c4..a09a51f0d 100644 --- a/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart +++ b/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart @@ -210,6 +210,18 @@ bool isRiverpodRef(DartType targetType) { /// Either `WidgetRef` or `Ref` const anyRefType = TypeChecker.any([widgetRefType, refType]); +const consumerType = TypeChecker.fromName( + 'Consumer', + packageName: 'flutter_riverpod', +); + +const hookConsumerType = TypeChecker.fromName( + 'HookConsumer', + packageName: 'hooks_riverpod', +); + +const anyConsumerType = TypeChecker.any([consumerType, hookConsumerType]); + /// [TypeChecker for `ConsumerWidget`` const consumerWidgetType = TypeChecker.fromName( 'ConsumerWidget', diff --git a/packages/riverpod_lint/README.md b/packages/riverpod_lint/README.md index 3694a2664..b1b410bfb 100644 --- a/packages/riverpod_lint/README.md +++ b/packages/riverpod_lint/README.md @@ -772,10 +772,17 @@ void initState() { } Widget build(ctx, ref) { - return Button( + return FilledButton( onPressed: () { ref.listen(provider, ...); // use listenManual instead - } + }, + child: Consumer( + builder: (context, ref, child) { + ref.listen(provider, ...); // use listenManual instead + return child!; + }, + child: Placeholder(), + ), ); } ``` @@ -788,7 +795,7 @@ Warn if the `WidgetRef.read` method is used incorrectly. ```dart Widget build(ctx, ref) { - return Button( + return FilledButton( onPressed: () { ref.read(provider); } @@ -814,7 +821,17 @@ Warn if the `WidgetRef.watch` method is used incorrectly. ```dart Widget build(ctx, ref) { ref.watch(provider); - return ... + return FilledButton( + onPressed: () {...}, + child: Consumer( + builder: (context, ref, child) { + // using ref.watch in Consumer is fine + ref.watch(provider); + return child!; + }, + child: Placeholder(), + ), + ); } ``` @@ -822,7 +839,7 @@ Widget build(ctx, ref) { ```dart Widget build(ctx, ref) { - return Button( + return FilledButton( onPressed: () { ref.watch(provider); // use read instead } diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart index 591799132..a9673ffd7 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart @@ -2,6 +2,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; import '../riverpod_custom_lint.dart'; @@ -31,6 +32,15 @@ class IncorrectUsageOfWidgetRefWatch extends RiverpodLintRule { if (functionExpression == null && methodName == 'build') return; + if (functionExpression != null) { + final parent = functionExpression + .thisOrAncestorOfType(); + final parentType = parent?.staticType; + if (parentType != null && anyConsumerType.isExactlyType(parentType)) { + return; + } + } + reporter.reportErrorForNode(code, invocation.node.methodName); }); } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart index 953604c9c..157071596 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart @@ -33,7 +33,14 @@ class _ExampleState extends ConsumerState { // expect_lint: incorrect_usage_of_widget_ref_listen ref.listen(provider, (previous, next) {}); }, - child: Placeholder(), + child: Consumer( + builder: (context, ref, child) { + // expect_lint: incorrect_usage_of_widget_ref_listen + ref.listen(provider, (previous, next) {}); + return child!; + }, + child: Placeholder(), + ), ); } } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart index 7b377d7ea..da71540ec 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart @@ -33,7 +33,14 @@ class _ExampleState extends ConsumerState { // expect_lint: incorrect_usage_of_widget_ref_watch ref.watch(provider); }, - child: Placeholder(), + child: Consumer( + builder: (context, ref, child) { + // using ref.watch in Consumer is fine + ref.watch(provider); + return child!; + }, + child: Placeholder(), + ), ); } } From 8b426ae812077e1919a6814d61877afb083ec0f9 Mon Sep 17 00:00:00 2001 From: Charles Tsang Date: Sat, 7 Oct 2023 14:56:47 +0800 Subject: [PATCH 6/7] Add incorrect_usage_of_widget_ref_listen_manual --- packages/riverpod_lint/README.md | 26 +++++++ packages/riverpod_lint/lib/riverpod_lint.dart | 2 + ...ect_usage_of_widget_ref_listen_manual.dart | 72 +++++++++++++++++++ ...ect_usage_of_widget_ref_listen_manual.dart | 59 +++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart create mode 100644 packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart diff --git a/packages/riverpod_lint/README.md b/packages/riverpod_lint/README.md index 390473712..479bc55c3 100644 --- a/packages/riverpod_lint/README.md +++ b/packages/riverpod_lint/README.md @@ -804,6 +804,32 @@ class MyNotifier extends Notifier { } ``` +### incorrect_usage_of_widget_ref_listen_manual + +Warn if the `WidgetRef.listenManual` method is used incorrectly. + +**Good**: + +```dart +Widget build(BuildContext context) { + // manually closing the subscription when using ref.listenManual in build + sub?.close(); + sub = ref.listenManual(provider, (previous, next) {}); + + return ... +} +``` + +**Bad**: + +```dart +Widget build(BuildContext context) { + ref.listenManual(provider, (previous, next) {}); + + return ... +} +``` + ### incorrect_usage_of_widget_ref_listen Warn if the `WidgetRef.listen` method is used incorrectly. diff --git a/packages/riverpod_lint/lib/riverpod_lint.dart b/packages/riverpod_lint/lib/riverpod_lint.dart index 1938e2363..a0a3c4dae 100644 --- a/packages/riverpod_lint/lib/riverpod_lint.dart +++ b/packages/riverpod_lint/lib/riverpod_lint.dart @@ -17,6 +17,7 @@ import 'src/lints/incorrect_usage_of_ref_listen.dart'; import 'src/lints/incorrect_usage_of_ref_read.dart'; import 'src/lints/incorrect_usage_of_ref_watch.dart'; import 'src/lints/incorrect_usage_of_widget_ref_listen.dart'; +import 'src/lints/incorrect_usage_of_widget_ref_listen_manual.dart'; import 'src/lints/incorrect_usage_of_widget_ref_read.dart'; import 'src/lints/incorrect_usage_of_widget_ref_watch.dart'; import 'src/lints/missing_provider_scope.dart'; @@ -50,6 +51,7 @@ class _RiverpodPlugin extends PluginBase { const IncorrectUsageOfRefListen(), const IncorrectUsageOfRefRead(), const IncorrectUsageOfRefWatch(), + const IncorrectUsageOfWidgetRefListenManual(), const IncorrectUsageOfWidgetRefListen(), const IncorrectUsageOfWidgetRefRead(), const IncorrectUsageOfWidgetRefWatch(), diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart new file mode 100644 index 000000000..f4ef7d5f7 --- /dev/null +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart @@ -0,0 +1,72 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:collection/collection.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; + +import '../riverpod_custom_lint.dart'; + +class IncorrectUsageOfWidgetRefListenManual extends RiverpodLintRule { + const IncorrectUsageOfWidgetRefListenManual() : super(code: _code); + + static const _code = LintCode( + name: 'incorrect_usage_of_widget_ref_listen_manual', + problemMessage: 'Incorrect usage of ref.listenManual.', + correctionMessage: 'Try canceling the subscription manually.', + errorSeverity: ErrorSeverity.WARNING, + ); + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { + final functionExpression = + invocation.node.thisOrAncestorOfType(); + if (functionExpression != null) return; + + final method = invocation.node.thisOrAncestorOfType(); + if (method == null || method.name.lexeme != 'build') return; + + final parent = invocation.node.parent; + final grandParent = parent?.parent; + final body = method.body; + if (parent is AssignmentExpression && + grandParent is ExpressionStatement && + parent.leftHandSide is SimpleIdentifier && + body is BlockFunctionBody) { + final variableName = parent.leftHandSide as SimpleIdentifier; + final statements = body.block.statements; + + final subscriptionClose = + statements.whereType().firstWhereOrNull( + (statement) { + final expression = statement.expression; + if (expression is! MethodInvocation) return false; + + final target = expression.target; + if (target is! SimpleIdentifier) return false; + + return target.name == variableName.name && + expression.methodName.name == 'close'; + }, + ); + + if (subscriptionClose != null) { + final refListenManualIndex = statements.indexOf(grandParent); + final subscriptionCloseIndex = statements.indexOf(subscriptionClose); + + if (subscriptionCloseIndex < refListenManualIndex) { + return; + } + } + + reporter.reportErrorForNode(code, invocation.node.methodName); + } + + reporter.reportErrorForNode(code, invocation.node.methodName); + }); + } +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart new file mode 100644 index 000000000..845bf1070 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +final provider = Provider((ref) => 0); + +class Example extends ConsumerStatefulWidget { + const Example({super.key}); + + @override + ConsumerState createState() => _ExampleState(); +} + +class _ExampleState extends ConsumerState { + ProviderSubscription? subA; + ProviderSubscription? subB; + ProviderSubscription? subC; + + @override + void initState() { + super.initState(); + ref.listenManual(provider, (previous, next) {}); + } + + void someMethod() { + ref.listenManual(provider, (previous, next) {}); + } + + @override + Widget build(BuildContext context) { + // closing the subscription before ref.listenManual is fine + subA?.close(); + subA = ref.listenManual(provider, (previous, next) {}); + + // closing the subscription after ref.listenManual triggers the lint + // expect_lint: incorrect_usage_of_widget_ref_listen_manual + subB = ref.listenManual(provider, (previous, next) {}); + subB?.close(); + + // not closing the subscription before ref.listenManual triggers the lint + // expect_lint: incorrect_usage_of_widget_ref_listen_manual + subC = ref.listenManual(provider, (previous, next) {}); + + // expect_lint: incorrect_usage_of_widget_ref_listen_manual + ref.listenManual(provider, (previous, next) {}); + + return FilledButton( + onPressed: () { + ref.listenManual(provider, (previous, next) {}); + }, + child: Consumer( + builder: (context, ref, child) { + ref.listenManual(provider, (previous, next) {}); + return child!; + }, + child: Placeholder(), + ), + ); + } +} From 1f586801ec5473c261d97d74965926f08c40f4e0 Mon Sep 17 00:00:00 2001 From: Charles Tsang Date: Mon, 23 Oct 2023 21:20:29 +0800 Subject: [PATCH 7/7] Refactor --- packages/riverpod_lint/README.md | 52 +++++++----- .../lints/incorrect_usage_of_ref_listen.dart | 64 ++++++++++++-- .../lints/incorrect_usage_of_ref_read.dart | 12 ++- .../lints/incorrect_usage_of_ref_watch.dart | 8 +- .../incorrect_usage_of_widget_ref_listen.dart | 21 +++-- ...ect_usage_of_widget_ref_listen_manual.dart | 83 +++++++++++-------- .../incorrect_usage_of_widget_ref_read.dart | 9 +- .../incorrect_usage_of_widget_ref_watch.dart | 25 +++--- .../lints/incorrect_usage_of_ref_listen.dart | 47 ++++++++++- .../lints/incorrect_usage_of_ref_read.dart | 4 +- .../lints/incorrect_usage_of_ref_watch.dart | 8 +- .../incorrect_usage_of_widget_ref_listen.dart | 10 ++- ...ect_usage_of_widget_ref_listen_manual.dart | 38 +++++---- .../incorrect_usage_of_widget_ref_read.dart | 2 +- .../incorrect_usage_of_widget_ref_watch.dart | 8 +- 15 files changed, 263 insertions(+), 128 deletions(-) diff --git a/packages/riverpod_lint/README.md b/packages/riverpod_lint/README.md index f88aa7611..f091f1d7b 100644 --- a/packages/riverpod_lint/README.md +++ b/packages/riverpod_lint/README.md @@ -715,11 +715,19 @@ final fnProvider = Provider((ref) { }); class MyNotifier extends Notifier { + ProviderSubscription? _sub; + @override int build() { ref.listen(provider, ...); return 0; } + + void method(){ + // manually closing the subscription when using ref.listen in methods + _sub?.close(); + _sub = ref.listen(provider, ...); + } } ``` @@ -730,8 +738,8 @@ class MyNotifier extends Notifier {] @override int build() {...} - void someMethod() { - ref.listen(provider, ...); // place listen in build instead + void method() { + ref.listen(provider, ...); } } ``` @@ -747,7 +755,7 @@ class MyNotifier extends Notifier { @override int build() {...} - void someMethod() { + void method() { ref.read(provider); } } @@ -814,8 +822,7 @@ Warn if the `WidgetRef.listenManual` method is used incorrectly. Widget build(BuildContext context) { // manually closing the subscription when using ref.listenManual in build sub?.close(); - sub = ref.listenManual(provider, (previous, next) {}); - + sub = ref.listenManual(provider, ...); return ... } ``` @@ -824,8 +831,7 @@ Widget build(BuildContext context) { ```dart Widget build(BuildContext context) { - ref.listenManual(provider, (previous, next) {}); - + ref.listenManual(provider, ...); return ... } ``` @@ -837,9 +843,19 @@ Warn if the `WidgetRef.listen` method is used incorrectly. **Good**: ```dart -Widget build(ctx, ref) { +Widget build(context, ref) { ref.listen(provider, ...); - return ... + return FilledButton( + onPressed: () {...}, + child: Consumer( + builder: (context, ref, child) { + // using ref.listen in Consumer is fine + ref.listen(provider, ...); + return child!; + }, + child: Placeholder(), + ), + ); } ``` @@ -850,18 +866,12 @@ void initState() { ref.listen(provider, ...); // use listenManual instead } -Widget build(ctx, ref) { +Widget build(context, ref) { return FilledButton( onPressed: () { ref.listen(provider, ...); // use listenManual instead }, - child: Consumer( - builder: (context, ref, child) { - ref.listen(provider, ...); // use listenManual instead - return child!; - }, - child: Placeholder(), - ), + child: Placeholder(), ); } ``` @@ -873,7 +883,7 @@ Warn if the `WidgetRef.read` method is used incorrectly. **Good**: ```dart -Widget build(ctx, ref) { +Widget build(context, ref) { return FilledButton( onPressed: () { ref.read(provider); @@ -885,7 +895,7 @@ Widget build(ctx, ref) { **Bad**: ```dart -Widget build(ctx, ref) { +Widget build(context, ref) { ref.read(provider); // use watch instead return ... } @@ -898,7 +908,7 @@ Warn if the `WidgetRef.watch` method is used incorrectly. **Good**: ```dart -Widget build(ctx, ref) { +Widget build(context, ref) { ref.watch(provider); return FilledButton( onPressed: () {...}, @@ -917,7 +927,7 @@ Widget build(ctx, ref) { **Bad**: ```dart -Widget build(ctx, ref) { +Widget build(context, ref) { return FilledButton( onPressed: () { ref.watch(provider); // use read instead diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart index e9c14b3df..4f8973db7 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_listen.dart @@ -1,6 +1,8 @@ import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; +import 'package:collection/collection.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; @@ -12,7 +14,7 @@ class IncorrectUsageOfRefListen extends RiverpodLintRule { static const _code = LintCode( name: 'incorrect_usage_of_ref_listen', problemMessage: 'Incorrect usage of ref.listen.', - correctionMessage: 'Put ref.listen in build method.', + correctionMessage: 'Try closing the returned subscription properly.', errorSeverity: ErrorSeverity.WARNING, ); @@ -23,17 +25,65 @@ class IncorrectUsageOfRefListen extends RiverpodLintRule { CustomLintContext context, ) { riverpodRegistry(context).addRefListenInvocation((invocation) { - final parent = invocation.parent; - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; + final parent = invocation.node.parent; + final methodDeclaration = + invocation.node.thisOrAncestorOfType(); if (parent is LegacyProviderDeclaration || parent is FunctionalProviderDeclaration || - methodName == 'build') return; + methodDeclaration == null || + methodDeclaration.name.lexeme == 'build') return; + + if (_isSubscriptionClosed( + invocation: invocation.node, + functionBody: methodDeclaration.body, + )) return; reporter.reportErrorForNode(code, invocation.node.methodName); }); } + + bool _isSubscriptionClosed({ + required MethodInvocation invocation, + required FunctionBody functionBody, + }) { + final parent = invocation.parent; + final grandParent = parent?.parent; + final body = functionBody; + + if (parent is AssignmentExpression && + grandParent is ExpressionStatement && + body is BlockFunctionBody) { + final subscriptionElement = parent.writeElement; + if (subscriptionElement is! PropertyAccessorElement) return false; + + final statements = body.block.statements; + final closeSubscriptionStatement = + statements.whereType().firstWhereOrNull( + (statement) { + final expression = statement.expression; + if (expression is! MethodInvocation) return false; + + final methodTarget = expression.realTarget; + if (methodTarget is! SimpleIdentifier) return false; + + final methodTargetElement = methodTarget.staticElement; + if (methodTargetElement is! PropertyAccessorElement) return false; + + return methodTargetElement.variable == subscriptionElement.variable && + expression.methodName.name == 'close'; + }, + ); + + if (closeSubscriptionStatement != null) { + final refListenManualIndex = statements.indexOf(grandParent); + final closeSubscriptionIndex = + statements.indexOf(closeSubscriptionStatement); + + if (closeSubscriptionIndex < refListenManualIndex) return true; + } + } + + return false; + } } diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart index e141e43eb..33f7a5ac7 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_read.dart @@ -24,14 +24,12 @@ class IncorrectUsageOfRefRead extends RiverpodLintRule { ) { riverpodRegistry(context).addRefReadInvocation((invocation) { final parent = invocation.parent; - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; + final methodDeclaration = + invocation.node.thisOrAncestorOfType(); - if (!(parent is LegacyProviderDeclaration || - parent is FunctionalProviderDeclaration || - methodName == 'build')) return; + if (parent is! LegacyProviderDeclaration && + parent is! FunctionalProviderDeclaration && + methodDeclaration?.name.lexeme != 'build') return; reporter.reportErrorForNode(code, invocation.node.methodName); }); diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart index b47d9b68f..b1e882d8d 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_ref_watch.dart @@ -24,14 +24,12 @@ class IncorrectUsageOfRefWatch extends RiverpodLintRule { ) { riverpodRegistry(context).addRefWatchInvocation((invocation) { final parent = invocation.parent; - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; + final methodDeclaration = + invocation.node.thisOrAncestorOfType(); if (parent is LegacyProviderDeclaration || parent is FunctionalProviderDeclaration || - methodName == 'build') return; + methodDeclaration?.name.lexeme == 'build') return; reporter.reportErrorForNode(code, invocation.node.methodName); }); diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart index 6ca4bb9f1..d77e435a2 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen.dart @@ -2,6 +2,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:riverpod_analyzer_utils/riverpod_analyzer_utils.dart'; import '../riverpod_custom_lint.dart'; @@ -24,17 +25,27 @@ class IncorrectUsageOfWidgetRefListen extends RiverpodLintRule { riverpodRegistry(context).addWidgetRefListenInvocation((invocation) { final functionExpression = invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; + final methodDeclaration = + invocation.node.thisOrAncestorOfType(); - if (functionExpression == null && methodName == 'build') return; + if (functionExpression == null && + methodDeclaration?.name.lexeme == 'build') return; + + if (functionExpression != null && _isInConsumer(invocation.node)) { + return; + } reporter.reportErrorForNode(code, invocation.node.methodName); }); } + bool _isInConsumer(AstNode node) { + final instanceCreationExpression = + node.thisOrAncestorOfType(); + final createdType = instanceCreationExpression?.staticType; + return createdType != null && anyConsumerType.isExactlyType(createdType); + } + @override List getFixes() => [UseWidgetRefListenManual()]; } diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart index f4ef7d5f7..a364e1a25 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_listen_manual.dart @@ -1,4 +1,5 @@ import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/error/error.dart'; import 'package:analyzer/error/listener.dart'; import 'package:collection/collection.dart'; @@ -12,7 +13,7 @@ class IncorrectUsageOfWidgetRefListenManual extends RiverpodLintRule { static const _code = LintCode( name: 'incorrect_usage_of_widget_ref_listen_manual', problemMessage: 'Incorrect usage of ref.listenManual.', - correctionMessage: 'Try canceling the subscription manually.', + correctionMessage: 'Try closing the returned subscription properly.', errorSeverity: ErrorSeverity.WARNING, ); @@ -25,48 +26,62 @@ class IncorrectUsageOfWidgetRefListenManual extends RiverpodLintRule { riverpodRegistry(context).addWidgetRefListenManualInvocation((invocation) { final functionExpression = invocation.node.thisOrAncestorOfType(); - if (functionExpression != null) return; + final methodDeclaration = + invocation.node.thisOrAncestorOfType(); - final method = invocation.node.thisOrAncestorOfType(); - if (method == null || method.name.lexeme != 'build') return; + if (methodDeclaration != null && + methodDeclaration.name.lexeme == 'build' && + functionExpression == null) { + if (_isSubscriptionClosed( + invocation: invocation.node, + functionBody: methodDeclaration.body, + )) return; - final parent = invocation.node.parent; - final grandParent = parent?.parent; - final body = method.body; - if (parent is AssignmentExpression && - grandParent is ExpressionStatement && - parent.leftHandSide is SimpleIdentifier && - body is BlockFunctionBody) { - final variableName = parent.leftHandSide as SimpleIdentifier; - final statements = body.block.statements; + reporter.reportErrorForNode(code, invocation.node.methodName); + } + }); + } - final subscriptionClose = - statements.whereType().firstWhereOrNull( - (statement) { - final expression = statement.expression; - if (expression is! MethodInvocation) return false; + bool _isSubscriptionClosed({ + required MethodInvocation invocation, + required FunctionBody functionBody, + }) { + final parent = invocation.parent; + final grandParent = parent?.parent; - final target = expression.target; - if (target is! SimpleIdentifier) return false; + if (parent is AssignmentExpression && + grandParent is ExpressionStatement && + functionBody is BlockFunctionBody) { + final subscriptionElement = parent.writeElement; + if (subscriptionElement is! PropertyAccessorElement) return false; - return target.name == variableName.name && - expression.methodName.name == 'close'; - }, - ); + final statements = functionBody.block.statements; + final closeSubscriptionStatement = + statements.whereType().firstWhereOrNull( + (statement) { + final expression = statement.expression; + if (expression is! MethodInvocation) return false; - if (subscriptionClose != null) { - final refListenManualIndex = statements.indexOf(grandParent); - final subscriptionCloseIndex = statements.indexOf(subscriptionClose); + final methodTarget = expression.realTarget; + if (methodTarget is! SimpleIdentifier) return false; - if (subscriptionCloseIndex < refListenManualIndex) { - return; - } - } + final methodTargetElement = methodTarget.staticElement; + if (methodTargetElement is! PropertyAccessorElement) return false; - reporter.reportErrorForNode(code, invocation.node.methodName); + return methodTargetElement.variable == subscriptionElement.variable && + expression.methodName.name == 'close'; + }, + ); + + if (closeSubscriptionStatement != null) { + final refListenManualIndex = statements.indexOf(grandParent); + final closeSubscriptionIndex = + statements.indexOf(closeSubscriptionStatement); + + if (closeSubscriptionIndex < refListenManualIndex) return true; } + } - reporter.reportErrorForNode(code, invocation.node.methodName); - }); + return false; } } diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart index 3d17e96f8..29d8989be 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_read.dart @@ -24,12 +24,11 @@ class IncorrectUsageOfWidgetRefRead extends RiverpodLintRule { riverpodRegistry(context).addWidgetRefReadInvocation((invocation) { final functionExpression = invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; + final methodDeclaration = + invocation.node.thisOrAncestorOfType(); - if (functionExpression != null || methodName != 'build') return; + if (functionExpression != null || + methodDeclaration?.name.lexeme != 'build') return; reporter.reportErrorForNode(code, invocation.node.methodName); }); diff --git a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart index a9673ffd7..7987d713a 100644 --- a/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart +++ b/packages/riverpod_lint/lib/src/lints/incorrect_usage_of_widget_ref_watch.dart @@ -25,26 +25,27 @@ class IncorrectUsageOfWidgetRefWatch extends RiverpodLintRule { riverpodRegistry(context).addWidgetRefWatchInvocation((invocation) { final functionExpression = invocation.node.thisOrAncestorOfType(); - final methodName = invocation.node - .thisOrAncestorOfType() - ?.name - .lexeme; + final methodDeclaration = + invocation.node.thisOrAncestorOfType(); - if (functionExpression == null && methodName == 'build') return; + if (functionExpression == null && + methodDeclaration?.name.lexeme == 'build') return; - if (functionExpression != null) { - final parent = functionExpression - .thisOrAncestorOfType(); - final parentType = parent?.staticType; - if (parentType != null && anyConsumerType.isExactlyType(parentType)) { - return; - } + if (functionExpression != null && _isInsideConsumer(invocation.node)) { + return; } reporter.reportErrorForNode(code, invocation.node.methodName); }); } + bool _isInsideConsumer(AstNode node) { + final instanceCreationExpression = + node.thisOrAncestorOfType(); + final createdType = instanceCreationExpression?.staticType; + return createdType != null && anyConsumerType.isExactlyType(createdType); + } + @override List getFixes() => [UseWidgetRefRead()]; } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart index be6d6be75..ade821e38 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_listen.dart @@ -1,3 +1,6 @@ +// ignore_for_file: unused_field + +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'incorrect_usage_of_ref_listen.g.dart'; @@ -8,12 +11,16 @@ final aProvider = Provider((ref) => 0); int b(BRef ref) => 0; final nonCodeGenFunctionalProvider = Provider((ref) { - // using ref.listen in providers is fine + // using ref.listen in function providers is fine ref.listen(aProvider, (previous, next) {}); return 0; }); class NonCodeGenNotifier extends Notifier { + ProviderSubscription? _subA; + ProviderSubscription? _subB; + ProviderSubscription? _subC; + @override int build() { // using ref.listen in build is fine @@ -21,7 +28,21 @@ class NonCodeGenNotifier extends Notifier { return 0; } - void someMethod() { + void method() { + // using ref.listen in methods and properly closing the returned subscription + // is fine + _subA?.close(); + _subA = ref.listen(aProvider, (previous, next) {}); + + // using ref.listen in methods without closing the returned subscription + // properly triggers the lint + // expect_lint: incorrect_usage_of_ref_listen + _subB = ref.listen(aProvider, (previous, next) {}); + _subB?.close(); + + // expect_lint: incorrect_usage_of_ref_listen + _subC = ref.listen(aProvider, (previous, next) {}); + // expect_lint: incorrect_usage_of_ref_listen ref.listen(aProvider, (previous, next) {}); } @@ -29,13 +50,17 @@ class NonCodeGenNotifier extends Notifier { @riverpod int codeGenFunctionalProvider(CodeGenFunctionalProviderRef ref) { - // using ref.listen in build is fine + // using ref.listen in functional providers is fine ref.listen(bProvider, (previous, next) {}); return 0; } @riverpod class CodeGenNotifier extends _$CodeGenNotifier { + ProviderSubscription? _subA; + ProviderSubscription? _subB; + ProviderSubscription? _subC; + @override int build() { // using ref.listen in build is fine @@ -43,7 +68,21 @@ class CodeGenNotifier extends _$CodeGenNotifier { return 0; } - void someMethod() { + void method() { + // using ref.listen in methods and properly closing the returned subscription + // is fine + _subA?.close(); + _subA = ref.listen(bProvider, (previous, next) {}); + + // using ref.listen in methods without closing the returned subscription + // properly triggers the lint + // expect_lint: incorrect_usage_of_ref_listen + _subB = ref.listen(bProvider, (previous, next) {}); + _subB?.close(); + + // expect_lint: incorrect_usage_of_ref_listen + _subC = ref.listen(bProvider, (previous, next) {}); + // expect_lint: incorrect_usage_of_ref_listen ref.listen(bProvider, (previous, next) {}); } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart index d908e87de..4f17d79aa 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_read.dart @@ -21,7 +21,7 @@ class NonCodeGenNotifier extends Notifier { return 0; } - void someMethod() { + void method() { // using ref.read in methods is fine ref.read(aProvider); } @@ -43,7 +43,7 @@ class CodeGenNotifier extends _$CodeGenNotifier { return 0; } - void someMethod() { + void method() { // using ref.read in methods is fine ref.read(bProvider); } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart index 6640062d1..f62acaa1c 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_ref_watch.dart @@ -8,7 +8,7 @@ final aProvider = Provider((ref) => 0); int b(BRef ref) => 0; final nonCodeGenFunctionalProvider = Provider((ref) { - // using ref.watch in providers is fine + // using ref.watch in functional providers is fine ref.watch(aProvider); return 0; }); @@ -21,7 +21,7 @@ class NonCodeGenNotifier extends Notifier { return 0; } - void someMethod() { + void method() { // expect_lint: incorrect_usage_of_ref_watch ref.watch(aProvider); } @@ -29,7 +29,7 @@ class NonCodeGenNotifier extends Notifier { @riverpod int codeGenFunctionalProvider(CodeGenFunctionalProviderRef ref) { - // using ref.watch in build is fine + // using ref.watch in function providers is fine ref.watch(bProvider); return 0; } @@ -43,7 +43,7 @@ class CodeGenNotifier extends _$CodeGenNotifier { return 0; } - void someMethod() { + void method() { // expect_lint: incorrect_usage_of_ref_watch ref.watch(bProvider); } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart index 157071596..3edb460e0 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen.dart @@ -18,7 +18,7 @@ class _ExampleState extends ConsumerState { ref.listen(provider, (previous, next) {}); } - void someMethod() { + void method() { // expect_lint: incorrect_usage_of_widget_ref_listen ref.listen(provider, (previous, next) {}); } @@ -28,14 +28,20 @@ class _ExampleState extends ConsumerState { // using ref.listen in build is fine ref.listen(provider, (previous, next) {}); + void nestedFunction() { + // expect_lint: incorrect_usage_of_widget_ref_listen + ref.listen(provider, (previous, next) {}); + } + return FilledButton( onPressed: () { // expect_lint: incorrect_usage_of_widget_ref_listen ref.listen(provider, (previous, next) {}); + nestedFunction(); }, child: Consumer( builder: (context, ref, child) { - // expect_lint: incorrect_usage_of_widget_ref_listen + // using ref.listen in Consumer is fine ref.listen(provider, (previous, next) {}); return child!; }, diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart index 845bf1070..11801d65a 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_listen_manual.dart @@ -1,3 +1,5 @@ +// ignore_for_file: unused_field + import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -11,9 +13,9 @@ class Example extends ConsumerStatefulWidget { } class _ExampleState extends ConsumerState { - ProviderSubscription? subA; - ProviderSubscription? subB; - ProviderSubscription? subC; + ProviderSubscription? _subA; + ProviderSubscription? _subB; + ProviderSubscription? _subC; @override void initState() { @@ -27,33 +29,33 @@ class _ExampleState extends ConsumerState { @override Widget build(BuildContext context) { - // closing the subscription before ref.listenManual is fine - subA?.close(); - subA = ref.listenManual(provider, (previous, next) {}); + // using ref.listenManual in build and properly closing the returned + // subscription is fine + _subA?.close(); + _subA = ref.listenManual(provider, (previous, next) {}); - // closing the subscription after ref.listenManual triggers the lint + // using ref.listenManual in build without closing the returned subscription + // properly triggers the lint // expect_lint: incorrect_usage_of_widget_ref_listen_manual - subB = ref.listenManual(provider, (previous, next) {}); - subB?.close(); + _subB = ref.listenManual(provider, (previous, next) {}); + _subB?.close(); - // not closing the subscription before ref.listenManual triggers the lint // expect_lint: incorrect_usage_of_widget_ref_listen_manual - subC = ref.listenManual(provider, (previous, next) {}); + _subC = ref.listenManual(provider, (previous, next) {}); // expect_lint: incorrect_usage_of_widget_ref_listen_manual ref.listenManual(provider, (previous, next) {}); + void nestedFunction() { + ref.listenManual(provider, (previous, next) {}); + } + return FilledButton( onPressed: () { ref.listenManual(provider, (previous, next) {}); + nestedFunction(); }, - child: Consumer( - builder: (context, ref, child) { - ref.listenManual(provider, (previous, next) {}); - return child!; - }, - child: Placeholder(), - ), + child: Placeholder(), ); } } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart index d7e5aaa2f..2eadb29ca 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_read.dart @@ -18,7 +18,7 @@ class _ExampleState extends ConsumerState { ref.read(provider); } - void someMethod() { + void method() { // using ref.read in methods is fine ref.read(provider); } diff --git a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart index da71540ec..b7953f2da 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/incorrect_usage_of_widget_ref_watch.dart @@ -18,7 +18,7 @@ class _ExampleState extends ConsumerState { ref.watch(provider); } - void someMethod() { + void method() { // expect_lint: incorrect_usage_of_widget_ref_watch ref.watch(provider); } @@ -28,10 +28,16 @@ class _ExampleState extends ConsumerState { // using ref.watch in build is fine ref.watch(provider); + void nestedFunction() { + // expect_lint: incorrect_usage_of_widget_ref_watch + ref.watch(provider); + } + return FilledButton( onPressed: () { // expect_lint: incorrect_usage_of_widget_ref_watch ref.watch(provider); + nestedFunction(); }, child: Consumer( builder: (context, ref, child) {