From 7d73eb3447e318674c1171d946302fc6c8691ac1 Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Thu, 6 Apr 2023 22:16:52 +0200 Subject: [PATCH] Disable unsupported_provider_value when a notifier returns "this" fixes #2302 --- .../src/lints/unsupported_provider_value.dart | 30 +++++++-- .../lints/unsupported_provider_value.dart | 29 +++++++++ .../lints/unsupported_provider_value.g.dart | 65 +++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/packages/riverpod_lint/lib/src/lints/unsupported_provider_value.dart b/packages/riverpod_lint/lib/src/lints/unsupported_provider_value.dart index 3998943de..66b5e54ad 100644 --- a/packages/riverpod_lint/lib/src/lints/unsupported_provider_value.dart +++ b/packages/riverpod_lint/lib/src/lints/unsupported_provider_value.dart @@ -1,9 +1,19 @@ +import 'package:analyzer/dart/element/type.dart'; +import 'package:analyzer/dart/element/type_system.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'; +extension on StatefulProviderDeclaration { + /// Returns whether the value exposed by the provider is the newly created + /// Notifier itself. + bool get returnsSelf { + return valueType == node.declaredElement?.thisType; + } +} + class UnsupportedProviderValue extends RiverpodLintRule { const UnsupportedProviderValue() : super(code: _code); @@ -21,16 +31,26 @@ class UnsupportedProviderValue extends RiverpodLintRule { ) { void checkCreatedType(GeneratorProviderDeclaration declaration) { String? invalidValueName; + if (notifierBaseType.isAssignableFromType(declaration.valueType)) { + invalidValueName = 'Notifier'; + } else if (asyncNotifierBaseType + .isAssignableFromType(declaration.valueType)) { + invalidValueName = 'AsyncNotifier'; + } + + /// If a provider returns itself, we allow it. This is to enable + /// ChangeNotifier-like mutable state. + if (invalidValueName != null && + declaration is StatefulProviderDeclaration && + declaration.returnsSelf) { + return; + } + if (stateNotifierType.isAssignableFromType(declaration.valueType)) { invalidValueName = 'StateNotifier'; } else if (changeNotifierType .isAssignableFromType(declaration.valueType)) { invalidValueName = 'ChangeNotifier'; - } else if (notifierBaseType.isAssignableFromType(declaration.valueType)) { - invalidValueName = 'Notifier'; - } else if (asyncNotifierBaseType - .isAssignableFromType(declaration.valueType)) { - invalidValueName = 'AsyncNotifier'; } if (invalidValueName != null) { diff --git a/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.dart b/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.dart index cf84e537c..d99c04d80 100644 --- a/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.dart +++ b/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.dart @@ -22,6 +22,24 @@ class StateNotifierClass extends _$StateNotifierClass { Future stateNotifierAsync(StateNotifierAsyncRef ref) async => MyStateNotifier(); +// Regression tests for https://github.com/rrousselGit/riverpod/issues/2302 +@riverpod +class SelfNotifier extends _$SelfNotifier { + Future build() async => this; +} + +// Regression tests for https://github.com/rrousselGit/riverpod/issues/2302 +@riverpod +class SyncSelfNotifier extends _$SyncSelfNotifier { + SyncSelfNotifier build() => this; +} + +// Regression tests for https://github.com/rrousselGit/riverpod/issues/2302 +@riverpod +class StreamSelfNotifier extends _$StreamSelfNotifier { + Stream build() => Stream.value(this); +} + @riverpod // expect_lint: unsupported_provider_value class StateNotifierClassAsync extends _$StateNotifierClassAsync { @@ -48,6 +66,12 @@ class MyChangeNotifier extends ChangeNotifier {} // expect_lint: unsupported_provider_value MyNotifier notifier(NotifierRef ref) => MyNotifier(); +@riverpod +// expect_lint: unsupported_provider_value +MyAutoDisposeNotifier autoDisposeNotifier(AutoDisposeNotifierRef ref) { + return MyAutoDisposeNotifier(); +} + @riverpod // expect_lint: unsupported_provider_value class NotifierClass extends _$NotifierClass { @@ -59,6 +83,11 @@ class MyNotifier extends Notifier { int build() => 0; } +class MyAutoDisposeNotifier extends AutoDisposeNotifier { + @override + int build() => 0; +} + @riverpod // expect_lint: unsupported_provider_value MyAsyncNotifier asyncNotifier(AsyncNotifierRef ref) => MyAsyncNotifier(); diff --git a/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.g.dart b/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.g.dart index 3c71f3b21..a578dceae 100644 --- a/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.g.dart +++ b/packages/riverpod_lint_flutter_test/test/goldens/lints/unsupported_provider_value.g.dart @@ -81,6 +81,23 @@ final notifierProvider = AutoDisposeProvider.internal( ); typedef NotifierRef = AutoDisposeProviderRef; +String _$autoDisposeNotifierHash() => + r'620df0fc11c887f01e125454afe8de553cfea6d0'; + +/// See also [autoDisposeNotifier]. +@ProviderFor(autoDisposeNotifier) +final autoDisposeNotifierProvider = + AutoDisposeProvider.internal( + autoDisposeNotifier, + name: r'autoDisposeNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$autoDisposeNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AutoDisposeNotifierRef = AutoDisposeProviderRef; String _$asyncNotifierHash() => r'c90348efac71d241468236924f6c6bc80ae0d0e0'; /// See also [asyncNotifier]. @@ -113,6 +130,54 @@ final stateNotifierClassProvider = ); typedef _$StateNotifierClass = AutoDisposeNotifier; +String _$selfNotifierHash() => r'5a857f5c92a9b7a35daa4e527bd333cf3d8d19ac'; + +/// See also [SelfNotifier]. +@ProviderFor(SelfNotifier) +final selfNotifierProvider = + AutoDisposeAsyncNotifierProvider.internal( + SelfNotifier.new, + name: r'selfNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$selfNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$SelfNotifier = AutoDisposeAsyncNotifier; +String _$syncSelfNotifierHash() => r'4f3a2463cb5693a5c8d7e772b4d7c9774b9ba637'; + +/// See also [SyncSelfNotifier]. +@ProviderFor(SyncSelfNotifier) +final syncSelfNotifierProvider = + AutoDisposeNotifierProvider.internal( + SyncSelfNotifier.new, + name: r'syncSelfNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$syncSelfNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$SyncSelfNotifier = AutoDisposeNotifier; +String _$streamSelfNotifierHash() => + r'18705475d157d8e592205406c0b884b7213d329e'; + +/// See also [StreamSelfNotifier]. +@ProviderFor(StreamSelfNotifier) +final streamSelfNotifierProvider = AutoDisposeStreamNotifierProvider< + StreamSelfNotifier, StreamSelfNotifier>.internal( + StreamSelfNotifier.new, + name: r'streamSelfNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$streamSelfNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$StreamSelfNotifier = AutoDisposeStreamNotifier; String _$stateNotifierClassAsyncHash() => r'06c519ed7dbdcd9440365dd2dc3ec12e603b6b7e';