From 45ff7ebdf6d55becabade309827c566183af500c Mon Sep 17 00:00:00 2001 From: Remi Rousselet Date: Tue, 18 Apr 2023 12:28:43 +0200 Subject: [PATCH] Added "Raw" typedef to: (#2473) - disable unsupported_provider_value knowingly - disable the convertion of Futures/Streams into AsyncValues --- packages/riverpod_analyzer_utils/CHANGELOG.md | 5 + .../generator_provider_declaration.dart | 14 + .../lib/src/riverpod_types.dart | 3 + .../generator_provider_declaration_test.dart | 44 ++ packages/riverpod_annotation/CHANGELOG.md | 12 + .../lib/src/riverpod_annotation.dart | 50 +- packages/riverpod_generator/CHANGELOG.md | 12 + .../lib/src/templates/family.dart | 37 +- .../lib/src/templates/stateful_provider.dart | 25 +- .../lib/src/templates/stateless_provider.dart | 18 +- .../test/integration/sync.dart | 52 ++ .../test/integration/sync.g.dart | 462 +++++++++++++++++- .../riverpod_generator/test/sync_test.dart | 67 +++ packages/riverpod_lint/CHANGELOG.md | 16 +- packages/riverpod_lint/README.md | 17 + .../src/lints/unsupported_provider_value.dart | 7 + .../lints/unsupported_provider_value.dart | 37 ++ .../lints/unsupported_provider_value.g.dart | 95 ++++ 18 files changed, 908 insertions(+), 65 deletions(-) diff --git a/packages/riverpod_analyzer_utils/CHANGELOG.md b/packages/riverpod_analyzer_utils/CHANGELOG.md index d32c6122b..691683212 100644 --- a/packages/riverpod_analyzer_utils/CHANGELOG.md +++ b/packages/riverpod_analyzer_utils/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased minor + +- Added `DartType.isRaw` extension property for checking if a type is from a `Raw` typedef. +- Added `isFromRiverpodAnnotation` type checker. + ## 0.2.1 - 2023-04-07 - Handle cascade operators in ref expressions diff --git a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/generator_provider_declaration.dart b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/generator_provider_declaration.dart index 00e082c58..9a1040585 100644 --- a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/generator_provider_declaration.dart +++ b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/generator_provider_declaration.dart @@ -1,5 +1,15 @@ part of '../riverpod_ast.dart'; +extension RawTypeX on DartType { + /// Returns whether this type is a `Raw` typedef from `package:riverpod_annotation`. + bool get isRaw { + final alias = this.alias; + if (alias == null) return false; + return alias.element.name == 'Raw' && + isFromRiverpodAnnotation.isExactly(alias.element); + } +} + extension on LibraryElement { static final _asyncValueCache = Expando(); @@ -79,6 +89,8 @@ DartType? _computeExposedType( DartType createdType, LibraryElement library, ) { + if (createdType.isRaw) return createdType; + if (createdType.isDartAsyncFuture || createdType.isDartAsyncFutureOr || createdType.isDartAsyncStream) { @@ -90,6 +102,8 @@ DartType? _computeExposedType( } DartType _getValueType(DartType createdType) { + if (createdType.isRaw) return createdType; + if (createdType.isDartAsyncFuture || createdType.isDartAsyncFutureOr || createdType.isDartAsyncStream) { diff --git a/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart b/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart index c04b5d5a9..b9b962e26 100644 --- a/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart +++ b/packages/riverpod_analyzer_utils/lib/src/riverpod_types.dart @@ -174,6 +174,9 @@ const widgetRefType = /// Checks that the value is coming from a `package:riverpod` package const isFromRiverpod = TypeChecker.fromPackage('riverpod'); +/// Checks that the value is coming from a `package:riverpod_annotation` package +const isFromRiverpodAnnotation = TypeChecker.fromPackage('riverpod_annotation'); + /// Checks that the value is coming from a `package:riverpod` package const isFromFlutterRiverpod = TypeChecker.fromPackage('flutter_riverpod'); diff --git a/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart b/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart index ecc2a5be3..d94c43226 100644 --- a/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart +++ b/packages/riverpod_analyzer_utils_tests/test/generator_provider_declaration_test.dart @@ -4,6 +4,50 @@ import 'package:test/test.dart'; import 'analyser_test_utils.dart'; void main() { + testSource('Parses Raw types', source: ''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@riverpod +Raw> value(ValueRef ref) async => 0; + +@riverpod +Future value2(Value2Ref ref) async => 0; + +@riverpod +Future> value3(Value3Ref ref) async => 0; +''', (resolver) async { + final result = await resolver.resolveRiverpodAnalyssiResult( + ignoreErrors: true, + ); + + final value = result.statelessProviderDeclarations.singleWhere( + (e) => e.name.toString() == 'value', + ); + final value2 = result.statelessProviderDeclarations.singleWhere( + (e) => e.name.toString() == 'value2', + ); + final value3 = result.statelessProviderDeclarations.singleWhere( + (e) => e.name.toString() == 'value3', + ); + expect(value.createdType.toString(), 'Future'); + expect(value.exposedType.toString(), 'Future'); + expect(value.valueType.toString(), 'Future'); + expect(value.createdType.isRaw, true); + expect(value.valueType.isRaw, true); + + expect(value2.createdType.toString(), 'Future'); + expect(value2.exposedType.toString(), 'AsyncValue'); + expect(value2.valueType.toString(), 'int'); + expect(value2.createdType.isRaw, false); + expect(value2.valueType.isRaw, false); + + expect(value3.createdType.toString(), 'Future'); + expect(value3.exposedType.toString(), 'AsyncValue'); + expect(value3.valueType.toString(), 'int'); + expect(value3.createdType.isRaw, false); + expect(value3.valueType.isRaw, true); + }); + testSource('Decode needsOverride/isScoped', source: ''' import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/packages/riverpod_annotation/CHANGELOG.md b/packages/riverpod_annotation/CHANGELOG.md index be9d3b4eb..37fac64ce 100644 --- a/packages/riverpod_annotation/CHANGELOG.md +++ b/packages/riverpod_annotation/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased minor + +- Added support for `Raw` typedef in the return value of providers. + This can be used to disable the conversion of Futures/Streams into AsyncValues + ```dart + @riverpod + Raw> myProvider(...) async => ...; + ... + // returns a Future instead of AsyncValue + Future value = ref.watch(myProvider); + ``` + ## 2.0.4 - 2023-04-07 - `riverpod` upgraded to `2.3.4` diff --git a/packages/riverpod_annotation/lib/src/riverpod_annotation.dart b/packages/riverpod_annotation/lib/src/riverpod_annotation.dart index 0219cc0da..4086d1242 100644 --- a/packages/riverpod_annotation/lib/src/riverpod_annotation.dart +++ b/packages/riverpod_annotation/lib/src/riverpod_annotation.dart @@ -3,10 +3,16 @@ import 'package:meta/meta.dart'; import 'package:meta/meta_meta.dart'; +import '../riverpod_annotation.dart'; + /// {@template riverpod_annotation.provider} /// An annotation placed on classes or functions. /// -/// This tells riverpod_generator to generate a provider out of the annotated +/// This tells riverpod_generator to generate a provider out of the annotated. +/// +/// By default, Riverpod will convert [Future]s and [Stream]s into [AsyncValue]s. +/// If this is undesired, you can use [Raw] to have Riverpod forcibly return +/// the raw [Future]/[Stream] instead. /// element. /// {@endtemplate} @Target({TargetKind.classType, TargetKind.function}) @@ -53,3 +59,45 @@ class ProviderFor { /// The code annotated by `@riverpod` final Object value; } + +/// {@template riverpod_annotation.raw} +/// An annotation for marking a value type as "should not be handled +/// by Riverpod". +/// +/// This is a type-alias to [T], and has no runtime effect. It is only used +/// as metadata for the code-generator/linter. +/// +/// This serves two purposes: +/// - It enables a provider to return a [Future]/[Stream] without +/// having the provider converting it into an [AsyncValue]. +/// ```dart +/// @riverpod +/// Raw> myProvider(...) async => ...; +/// ... +/// // returns a Future instead of AsyncValue +/// Future value = ref.watch(myProvider); +/// ``` +/// +/// - It can silence the linter when a provider returns a value that +/// is otherwise not supported, such as Flutter's `ChangeNotifier`: +/// ```dart +/// // Will not trigger the "unsupported return type" lint +/// @riverpod +/// Raw myProvider(...) => MyChangeNotifier(); +/// ``` +/// +/// The typedef can be used at various places within the return type declaration. +/// +/// For example, a valid return type is `Future>`. +/// This way, Riverpod will convert the [Future] into an [AsyncValue], and +/// the usage of `ChangeNotifier` will not trigger the linter: +/// +/// ```dart +/// @riverpod +/// Future> myProvider(...) async => ...; +/// ... +/// AsyncValue value = ref.watch(myProvider); +/// ``` +/// +/// {@endtemplate} +typedef Raw = T; diff --git a/packages/riverpod_generator/CHANGELOG.md b/packages/riverpod_generator/CHANGELOG.md index 886aa4ec2..fd3acbc80 100644 --- a/packages/riverpod_generator/CHANGELOG.md +++ b/packages/riverpod_generator/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased minor + +- Added support for `Raw` typedef in the return value of providers. + This can be used to disable the conversion of Futures/Streams into AsyncValues + ```dart + @riverpod + Raw> myProvider(...) async => ...; + ... + // returns a Future instead of AsyncValue + Future value = ref.watch(myProvider); + ``` + ## 2.1.6 - 2023-04-07 - If a provider has an empty list of dependencies, the generated list is now `const` diff --git a/packages/riverpod_generator/lib/src/templates/family.dart b/packages/riverpod_generator/lib/src/templates/family.dart index 918672c85..19241398c 100644 --- a/packages/riverpod_generator/lib/src/templates/family.dart +++ b/packages/riverpod_generator/lib/src/templates/family.dart @@ -44,18 +44,19 @@ class FamilyTemplate extends Template { required BuildYamlOptions options, }) { var leading = ''; - String providerType; if (!provider.annotation.element.keepAlive) { leading = 'AutoDispose'; } + var providerType = '${leading}Provider'; + final returnType = provider.createdType; - if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) { - providerType = '${leading}FutureProvider'; - } else if (returnType.isDartAsyncStream) { - providerType = '${leading}StreamProvider'; - } else { - providerType = '${leading}Provider'; + if (!returnType.isRaw) { + if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) { + providerType = '${leading}FutureProvider'; + } else if (returnType.isDartAsyncStream) { + providerType = '${leading}StreamProvider'; + } } final parameters = provider @@ -94,18 +95,18 @@ typedef $refName = ${providerType}Ref<${provider.valueType}>; leading = 'AutoDispose'; } - String providerType; - String notifierBaseType; + var providerType = '${leading}NotifierProviderImpl'; + var notifierBaseType = 'Buildless${leading}Notifier'; + final returnType = provider.createdType; - if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) { - providerType = '${leading}AsyncNotifierProviderImpl'; - notifierBaseType = 'Buildless${leading}AsyncNotifier'; - } else if (returnType.isDartAsyncStream) { - providerType = '${leading}StreamNotifierProviderImpl'; - notifierBaseType = 'Buildless${leading}StreamNotifier'; - } else { - providerType = '${leading}NotifierProviderImpl'; - notifierBaseType = 'Buildless${leading}Notifier'; + if (!returnType.isRaw) { + if (returnType.isDartAsyncFutureOr || returnType.isDartAsyncFuture) { + providerType = '${leading}AsyncNotifierProviderImpl'; + notifierBaseType = 'Buildless${leading}AsyncNotifier'; + } else if (returnType.isDartAsyncStream) { + providerType = '${leading}StreamNotifierProviderImpl'; + notifierBaseType = 'Buildless${leading}StreamNotifier'; + } } final parameters = provider.buildMethod.parameters!.parameterElements diff --git a/packages/riverpod_generator/lib/src/templates/stateful_provider.dart b/packages/riverpod_generator/lib/src/templates/stateful_provider.dart index 307261d27..079cd21ab 100644 --- a/packages/riverpod_generator/lib/src/templates/stateful_provider.dart +++ b/packages/riverpod_generator/lib/src/templates/stateful_provider.dart @@ -61,25 +61,24 @@ class StatefulProviderTemplate extends Template { @override void run(StringBuffer buffer) { - String notifierBaseType; - String providerType; var leading = ''; if (!provider.annotation.element.keepAlive) { leading = 'AutoDispose'; } + var notifierBaseType = '${leading}Notifier'; + var providerType = '${leading}NotifierProvider'; + final providerName = providerNameFor(provider.providerElement, options); - final returnType = provider.buildMethod.returnType?.type; - if ((returnType?.isDartAsyncFutureOr ?? false) || - (returnType?.isDartAsyncFuture ?? false)) { - notifierBaseType = '${leading}AsyncNotifier'; - providerType = '${leading}AsyncNotifierProvider'; - } else if (returnType?.isDartAsyncStream ?? false) { - notifierBaseType = '${leading}StreamNotifier'; - providerType = '${leading}StreamNotifierProvider'; - } else { - notifierBaseType = '${leading}Notifier'; - providerType = '${leading}NotifierProvider'; + final returnType = provider.createdType; + if (!returnType.isRaw) { + if ((returnType.isDartAsyncFutureOr) || (returnType.isDartAsyncFuture)) { + notifierBaseType = '${leading}AsyncNotifier'; + providerType = '${leading}AsyncNotifierProvider'; + } else if (returnType.isDartAsyncStream) { + notifierBaseType = '${leading}StreamNotifier'; + providerType = '${leading}StreamNotifierProvider'; + } } buffer.write(''' diff --git a/packages/riverpod_generator/lib/src/templates/stateless_provider.dart b/packages/riverpod_generator/lib/src/templates/stateless_provider.dart index 06dc5db87..02009ab5c 100644 --- a/packages/riverpod_generator/lib/src/templates/stateless_provider.dart +++ b/packages/riverpod_generator/lib/src/templates/stateless_provider.dart @@ -27,21 +27,21 @@ class StatelessProviderTemplate extends Template { @override void run(StringBuffer buffer) { - String providerType; var leading = ''; if (!provider.annotation.element.keepAlive) { leading = 'AutoDispose'; } - final returnType = provider.node.returnType?.type; - if ((returnType?.isDartAsyncFutureOr ?? false) || - (returnType?.isDartAsyncFuture ?? false)) { - providerType = '${leading}FutureProvider'; - } else if (returnType?.isDartAsyncStream ?? false) { - providerType = '${leading}StreamProvider'; - } else { - providerType = '${leading}Provider'; + var providerType = '${leading}Provider'; + + final returnType = provider.createdType; + if (!returnType.isRaw) { + if ((returnType.isDartAsyncFutureOr) || (returnType.isDartAsyncFuture)) { + providerType = '${leading}FutureProvider'; + } else if (returnType.isDartAsyncStream) { + providerType = '${leading}StreamProvider'; + } } final providerName = providerNameFor(provider.providerElement, options); diff --git a/packages/riverpod_generator/test/integration/sync.dart b/packages/riverpod_generator/test/integration/sync.dart index 7fac9c724..2cf69b39e 100644 --- a/packages/riverpod_generator/test/integration/sync.dart +++ b/packages/riverpod_generator/test/integration/sync.dart @@ -2,6 +2,58 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'sync.g.dart'; +@riverpod +Raw> rawFuture(RawFutureRef ref) async { + return 'Hello world'; +} + +@riverpod +Raw> rawStream(RawStreamRef ref) async* { + yield 'Hello world'; +} + +@riverpod +class RawFutureClass extends _$RawFutureClass { + @override + Raw> build() async { + return 'Hello world'; + } +} + +@riverpod +class RawStreamClass extends _$RawStreamClass { + @override + Raw> build() async* { + yield 'Hello world'; + } +} + +@riverpod +Raw> rawFamilyFuture(RawFamilyFutureRef ref, int id) async { + return 'Hello world'; +} + +@riverpod +Raw> rawFamilyStream(RawFamilyStreamRef ref, int id) async* { + yield 'Hello world'; +} + +@riverpod +class RawFamilyFutureClass extends _$RawFamilyFutureClass { + @override + Raw> build(int id) async { + return 'Hello world'; + } +} + +@riverpod +class RawFamilyStreamClass extends _$RawFamilyStreamClass { + @override + Raw> build(int id) async* { + yield 'Hello world'; + } +} + /// This is some documentation @riverpod String public(PublicRef ref) { diff --git a/packages/riverpod_generator/test/integration/sync.g.dart b/packages/riverpod_generator/test/integration/sync.g.dart index 307958cc0..d572aa021 100644 --- a/packages/riverpod_generator/test/integration/sync.g.dart +++ b/packages/riverpod_generator/test/integration/sync.g.dart @@ -6,38 +6,35 @@ part of 'sync.dart'; // RiverpodGenerator // ************************************************************************** -String _$publicHash() => r'138be35943899793ab085e711fe3f3d22696a3ba'; +String _$rawFutureHash() => r'5203a56065b768023770326281618e3229ccb530'; -/// This is some documentation -/// -/// Copied from [public]. -@ProviderFor(public) -final publicProvider = AutoDisposeProvider.internal( - public, - name: r'publicProvider', +/// See also [rawFuture]. +@ProviderFor(rawFuture) +final rawFutureProvider = AutoDisposeProvider>.internal( + rawFuture, + name: r'rawFutureProvider', debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$publicHash, + const bool.fromEnvironment('dart.vm.product') ? null : _$rawFutureHash, dependencies: null, allTransitiveDependencies: null, ); -typedef PublicRef = AutoDisposeProviderRef; -String _$supports$inNamesHash() => r'cbf929802fcbd0aa949ad72743d096fb3ef5f28f'; +typedef RawFutureRef = AutoDisposeProviderRef>; +String _$rawStreamHash() => r'2b764189753a8b74f47ba557a79416f00ef5cebd'; -/// See also [supports$inNames]. -@ProviderFor(supports$inNames) -final supports$inNamesProvider = AutoDisposeProvider.internal( - supports$inNames, - name: r'supports$inNamesProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$supports$inNamesHash, +/// See also [rawStream]. +@ProviderFor(rawStream) +final rawStreamProvider = AutoDisposeProvider>.internal( + rawStream, + name: r'rawStreamProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$rawStreamHash, dependencies: null, allTransitiveDependencies: null, ); -typedef Supports$inNamesRef = AutoDisposeProviderRef; -String _$familyHash() => r'14d1ee238ca608d547630d0e222ef4c5866e9e61'; +typedef RawStreamRef = AutoDisposeProviderRef>; +String _$rawFamilyFutureHash() => r'485f59512081852e51279658facc015309743864'; /// Copied from Dart SDK class _SystemHash { @@ -60,6 +57,201 @@ class _SystemHash { } } +typedef RawFamilyFutureRef = AutoDisposeProviderRef>; + +/// See also [rawFamilyFuture]. +@ProviderFor(rawFamilyFuture) +const rawFamilyFutureProvider = RawFamilyFutureFamily(); + +/// See also [rawFamilyFuture]. +class RawFamilyFutureFamily extends Family> { + /// See also [rawFamilyFuture]. + const RawFamilyFutureFamily(); + + /// See also [rawFamilyFuture]. + RawFamilyFutureProvider call( + int id, + ) { + return RawFamilyFutureProvider( + id, + ); + } + + @override + RawFamilyFutureProvider getProviderOverride( + covariant RawFamilyFutureProvider provider, + ) { + return call( + provider.id, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'rawFamilyFutureProvider'; +} + +/// See also [rawFamilyFuture]. +class RawFamilyFutureProvider extends AutoDisposeProvider> { + /// See also [rawFamilyFuture]. + RawFamilyFutureProvider( + this.id, + ) : super.internal( + (ref) => rawFamilyFuture( + ref, + id, + ), + from: rawFamilyFutureProvider, + name: r'rawFamilyFutureProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawFamilyFutureHash, + dependencies: RawFamilyFutureFamily._dependencies, + allTransitiveDependencies: + RawFamilyFutureFamily._allTransitiveDependencies, + ); + + final int id; + + @override + bool operator ==(Object other) { + return other is RawFamilyFutureProvider && other.id == id; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, id.hashCode); + + return _SystemHash.finish(hash); + } +} + +String _$rawFamilyStreamHash() => r'e778e5cfcb8ab381e2412f5c73213aaa03b93012'; +typedef RawFamilyStreamRef = AutoDisposeProviderRef>; + +/// See also [rawFamilyStream]. +@ProviderFor(rawFamilyStream) +const rawFamilyStreamProvider = RawFamilyStreamFamily(); + +/// See also [rawFamilyStream]. +class RawFamilyStreamFamily extends Family> { + /// See also [rawFamilyStream]. + const RawFamilyStreamFamily(); + + /// See also [rawFamilyStream]. + RawFamilyStreamProvider call( + int id, + ) { + return RawFamilyStreamProvider( + id, + ); + } + + @override + RawFamilyStreamProvider getProviderOverride( + covariant RawFamilyStreamProvider provider, + ) { + return call( + provider.id, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'rawFamilyStreamProvider'; +} + +/// See also [rawFamilyStream]. +class RawFamilyStreamProvider extends AutoDisposeProvider> { + /// See also [rawFamilyStream]. + RawFamilyStreamProvider( + this.id, + ) : super.internal( + (ref) => rawFamilyStream( + ref, + id, + ), + from: rawFamilyStreamProvider, + name: r'rawFamilyStreamProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawFamilyStreamHash, + dependencies: RawFamilyStreamFamily._dependencies, + allTransitiveDependencies: + RawFamilyStreamFamily._allTransitiveDependencies, + ); + + final int id; + + @override + bool operator ==(Object other) { + return other is RawFamilyStreamProvider && other.id == id; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, id.hashCode); + + return _SystemHash.finish(hash); + } +} + +String _$publicHash() => r'138be35943899793ab085e711fe3f3d22696a3ba'; + +/// This is some documentation +/// +/// Copied from [public]. +@ProviderFor(public) +final publicProvider = AutoDisposeProvider.internal( + public, + name: r'publicProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$publicHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef PublicRef = AutoDisposeProviderRef; +String _$supports$inNamesHash() => r'cbf929802fcbd0aa949ad72743d096fb3ef5f28f'; + +/// See also [supports$inNames]. +@ProviderFor(supports$inNames) +final supports$inNamesProvider = AutoDisposeProvider.internal( + supports$inNames, + name: r'supports$inNamesProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$supports$inNamesHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef Supports$inNamesRef = AutoDisposeProviderRef; +String _$familyHash() => r'14d1ee238ca608d547630d0e222ef4c5866e9e61'; typedef FamilyRef = AutoDisposeProviderRef; /// This is some documentation @@ -213,6 +405,234 @@ final generatedProvider = AutoDisposeProvider.internal( ); typedef GeneratedRef = AutoDisposeProviderRef; +String _$rawFutureClassHash() => r'bf66f1cdbd99118b8845d206e6a2611b3101f45c'; + +/// See also [RawFutureClass]. +@ProviderFor(RawFutureClass) +final rawFutureClassProvider = + AutoDisposeNotifierProvider>.internal( + RawFutureClass.new, + name: r'rawFutureClassProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawFutureClassHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$RawFutureClass = AutoDisposeNotifier>; +String _$rawStreamClassHash() => r'712cffcb2018cfb4ff45012c1aa6e43c8cbe9d5d'; + +/// See also [RawStreamClass]. +@ProviderFor(RawStreamClass) +final rawStreamClassProvider = + AutoDisposeNotifierProvider>.internal( + RawStreamClass.new, + name: r'rawStreamClassProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawStreamClassHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$RawStreamClass = AutoDisposeNotifier>; +String _$rawFamilyFutureClassHash() => + r'd7cacb0f2c51697d107de6daa68b242c04085dca'; + +abstract class _$RawFamilyFutureClass + extends BuildlessAutoDisposeNotifier> { + late final int id; + + Future build( + int id, + ); +} + +/// See also [RawFamilyFutureClass]. +@ProviderFor(RawFamilyFutureClass) +const rawFamilyFutureClassProvider = RawFamilyFutureClassFamily(); + +/// See also [RawFamilyFutureClass]. +class RawFamilyFutureClassFamily extends Family> { + /// See also [RawFamilyFutureClass]. + const RawFamilyFutureClassFamily(); + + /// See also [RawFamilyFutureClass]. + RawFamilyFutureClassProvider call( + int id, + ) { + return RawFamilyFutureClassProvider( + id, + ); + } + + @override + RawFamilyFutureClassProvider getProviderOverride( + covariant RawFamilyFutureClassProvider provider, + ) { + return call( + provider.id, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'rawFamilyFutureClassProvider'; +} + +/// See also [RawFamilyFutureClass]. +class RawFamilyFutureClassProvider extends AutoDisposeNotifierProviderImpl< + RawFamilyFutureClass, Future> { + /// See also [RawFamilyFutureClass]. + RawFamilyFutureClassProvider( + this.id, + ) : super.internal( + () => RawFamilyFutureClass()..id = id, + from: rawFamilyFutureClassProvider, + name: r'rawFamilyFutureClassProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawFamilyFutureClassHash, + dependencies: RawFamilyFutureClassFamily._dependencies, + allTransitiveDependencies: + RawFamilyFutureClassFamily._allTransitiveDependencies, + ); + + final int id; + + @override + bool operator ==(Object other) { + return other is RawFamilyFutureClassProvider && other.id == id; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, id.hashCode); + + return _SystemHash.finish(hash); + } + + @override + Future runNotifierBuild( + covariant RawFamilyFutureClass notifier, + ) { + return notifier.build( + id, + ); + } +} + +String _$rawFamilyStreamClassHash() => + r'321796a0befc43fb83f7ccfdcb6b011fc8c7c599'; + +abstract class _$RawFamilyStreamClass + extends BuildlessAutoDisposeNotifier> { + late final int id; + + Stream build( + int id, + ); +} + +/// See also [RawFamilyStreamClass]. +@ProviderFor(RawFamilyStreamClass) +const rawFamilyStreamClassProvider = RawFamilyStreamClassFamily(); + +/// See also [RawFamilyStreamClass]. +class RawFamilyStreamClassFamily extends Family> { + /// See also [RawFamilyStreamClass]. + const RawFamilyStreamClassFamily(); + + /// See also [RawFamilyStreamClass]. + RawFamilyStreamClassProvider call( + int id, + ) { + return RawFamilyStreamClassProvider( + id, + ); + } + + @override + RawFamilyStreamClassProvider getProviderOverride( + covariant RawFamilyStreamClassProvider provider, + ) { + return call( + provider.id, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'rawFamilyStreamClassProvider'; +} + +/// See also [RawFamilyStreamClass]. +class RawFamilyStreamClassProvider extends AutoDisposeNotifierProviderImpl< + RawFamilyStreamClass, Stream> { + /// See also [RawFamilyStreamClass]. + RawFamilyStreamClassProvider( + this.id, + ) : super.internal( + () => RawFamilyStreamClass()..id = id, + from: rawFamilyStreamClassProvider, + name: r'rawFamilyStreamClassProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawFamilyStreamClassHash, + dependencies: RawFamilyStreamClassFamily._dependencies, + allTransitiveDependencies: + RawFamilyStreamClassFamily._allTransitiveDependencies, + ); + + final int id; + + @override + bool operator ==(Object other) { + return other is RawFamilyStreamClassProvider && other.id == id; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, id.hashCode); + + return _SystemHash.finish(hash); + } + + @override + Stream runNotifierBuild( + covariant RawFamilyStreamClass notifier, + ) { + return notifier.build( + id, + ); + } +} + String _$publicClassHash() => r'f04884c039e6200ad3537feeecfc6e83828b5eb5'; /// This is some documentation diff --git a/packages/riverpod_generator/test/sync_test.dart b/packages/riverpod_generator/test/sync_test.dart index 6875eb924..2e1f5399e 100644 --- a/packages/riverpod_generator/test/sync_test.dart +++ b/packages/riverpod_generator/test/sync_test.dart @@ -9,6 +9,73 @@ import 'utils.dart'; void main() { // TODO test that the generated providers contain the docs from the annotated element + test('Supports Raw', () async { + final container = createContainer(); + + expect( + container.read(rawFutureProvider), + isA>(), + ); + expect( + container.read(rawFutureClassProvider), + isA>(), + ); + expect( + container.read(rawFutureClassProvider.notifier), + isA(), + ); + + expect( + container.read(rawStreamProvider), + isA>(), + ); + expect( + container.read(rawStreamClassProvider), + isA>(), + ); + expect( + container.read(rawStreamClassProvider.notifier), + isA(), + ); + + expect( + container.read(rawFamilyFutureProvider(0)), + isA>(), + ); + expect( + container.read(rawFamilyFutureClassProvider(0)), + isA>(), + ); + expect( + container.read(rawFamilyFutureClassProvider(0).notifier), + isA(), + ); + + expect( + container.read(rawFamilyStreamProvider(0)), + isA>(), + ); + expect( + container.read(rawFamilyStreamClassProvider(0)), + isA>(), + ); + expect( + container.read(rawFamilyStreamClassProvider(0).notifier), + isA(), + ); + }); + + test( + 'Creates a Provider if @riverpod is used on an stream function wrapped in Raw', + () async { + final container = createContainer(); + + final AutoDisposeProvider> provider = rawStreamProvider; + final Stream result = container.read(rawStreamProvider); + + await expectLater(result, emits('Hello world')); + }); + test('Creates a Provider if @riverpod is used on a synchronous function', () { final container = createContainer(); diff --git a/packages/riverpod_lint/CHANGELOG.md b/packages/riverpod_lint/CHANGELOG.md index 95b1e2349..1922d81aa 100644 --- a/packages/riverpod_lint/CHANGELOG.md +++ b/packages/riverpod_lint/CHANGELOG.md @@ -1,6 +1,16 @@ -## Unreleased patch +## Unreleased minor -- Improve documentation of `avoid_public_notifier_properties` +- Added support for `Raw` typedef in the return value of providers. + This can be used to silence `unsupported_provider_value` when a provider knowingly + returns an unsupported object. + + ```dart + // Will not trigger unsupported_provider_value + @riverpod + Raw myProvider(...) => MyChangeNotifier(); + ``` + +- Improved documentation of `avoid_public_notifier_properties` ## 1.2.0 - 2023-04-08 @@ -11,7 +21,7 @@ ## 1.1.8 - 2023-04-07 -- Disable unsupported_provider_value when a notifier returns "this" +- Disable `unsupported_provider_value` when a notifier returns "this" - Fix scoped_providers_should_specify_dependencies incorrectly triggering on functions other than "main" - Handle cascade operators in ref expressions - Fix `provider_dependencies` not considering dependencies inside methods diff --git a/packages/riverpod_lint/README.md b/packages/riverpod_lint/README.md index 5617c6d19..a4a073e88 100644 --- a/packages/riverpod_lint/README.md +++ b/packages/riverpod_lint/README.md @@ -436,6 +436,12 @@ manually creating a `Notifier`/`AsyncNotifier`. This lint warns against unsupported value types. +**Note**: + +In some cases, you may voluntarily want to return a `ChangeNotifier` & co, even though +riverpod_generator will neither listen nor disposes of the value. +In that scenario, you may explicitly wrap the value in `Raw`: + **Good**: ```dart @@ -447,6 +453,17 @@ class IntegerNotifier extends _$IntegerNotifier { @override int build() => 0; } + +// By using "Raw", we can explicitly return a ChangeNotifier in a provider +// without triggering `unsupported_provider_value`. +@riverpod +Raw myRouter(MyRouterRef ref) { + final router = GoRouter(...); + // Riverpod won't dispose the ChangeNotifier for you in this case. Don't forget + // to do it on your own! + ref.onDispose(router.dispose); + return router; +} ``` **Bad**: 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 dfd768048..be256f416 100644 --- a/packages/riverpod_lint/lib/src/lints/unsupported_provider_value.dart +++ b/packages/riverpod_lint/lib/src/lints/unsupported_provider_value.dart @@ -19,6 +19,9 @@ class UnsupportedProviderValue extends RiverpodLintRule { name: 'unsupported_provider_value', problemMessage: 'The riverpod_generator package does not support {0} values.', + correctionMessage: + 'If using {0} even though riverpod_generator does not support it, ' + 'you can wrap the type in "Raw" to silence the warning. For example by returning Raw<{0}>.', ); @override @@ -28,6 +31,10 @@ class UnsupportedProviderValue extends RiverpodLintRule { CustomLintContext context, ) { void checkCreatedType(GeneratorProviderDeclaration declaration) { + if (declaration.valueType.isRaw) { + return; + } + String? invalidValueName; if (notifierBaseType.isAssignableFromType(declaration.valueType)) { invalidValueName = 'Notifier'; 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 d99c04d80..6ff155393 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 @@ -11,6 +11,12 @@ int integer(IntegerRef ref) => 0; // expect_lint: unsupported_provider_value MyStateNotifier stateNotifier(StateNotifierRef ref) => MyStateNotifier(); +@riverpod +// expect_lint: unsupported_provider_value +Future asyncStateNotifier(AsyncStateNotifierRef ref) async { + return MyStateNotifier(); +} + @riverpod // expect_lint: unsupported_provider_value class StateNotifierClass extends _$StateNotifierClass { @@ -102,3 +108,34 @@ class MyAsyncNotifier extends AsyncNotifier { @override int build() => 0; } + +@riverpod +Raw rawNotifier(RawNotifierRef ref) => MyChangeNotifier(); + +@riverpod +Raw> rawFutureNotifier( + RawFutureNotifierRef ref, +) async { + return MyChangeNotifier(); +} + +@riverpod +Raw> rawStreamNotifier( + RawStreamNotifierRef ref, +) async* { + yield MyChangeNotifier(); +} + +@riverpod +Future> futureRawNotifier( + FutureRawNotifierRef ref, +) async { + return MyChangeNotifier(); +} + +@riverpod +Stream> streamRawNotifier( + StreamRawNotifierRef ref, +) async* { + yield MyChangeNotifier(); +} 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 a578dceae..6dab4f100 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 @@ -35,6 +35,23 @@ final stateNotifierProvider = AutoDisposeProvider.internal( ); typedef StateNotifierRef = AutoDisposeProviderRef; +String _$asyncStateNotifierHash() => + r'66442390f13e38cd9594f841a7610ab0f632db81'; + +/// See also [asyncStateNotifier]. +@ProviderFor(asyncStateNotifier) +final asyncStateNotifierProvider = + AutoDisposeFutureProvider.internal( + asyncStateNotifier, + name: r'asyncStateNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$asyncStateNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AsyncStateNotifierRef = AutoDisposeFutureProviderRef; String _$stateNotifierAsyncHash() => r'9a9b1986076dfdfa4490cc109f1bd0f112a7455c'; @@ -113,6 +130,84 @@ final asyncNotifierProvider = AutoDisposeProvider.internal( ); typedef AsyncNotifierRef = AutoDisposeProviderRef; +String _$rawNotifierHash() => r'c01adc70a8e08258bf5d13024aa8e9b86359a2b2'; + +/// See also [rawNotifier]. +@ProviderFor(rawNotifier) +final rawNotifierProvider = AutoDisposeProvider.internal( + rawNotifier, + name: r'rawNotifierProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$rawNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RawNotifierRef = AutoDisposeProviderRef; +String _$rawFutureNotifierHash() => r'883253dbf7ade868c44b288ec3da02be64dcfb20'; + +/// See also [rawFutureNotifier]. +@ProviderFor(rawFutureNotifier) +final rawFutureNotifierProvider = + AutoDisposeProvider>.internal( + rawFutureNotifier, + name: r'rawFutureNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawFutureNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RawFutureNotifierRef = AutoDisposeProviderRef>; +String _$rawStreamNotifierHash() => r'f22f6a906e275c6245365bf029e2dc217cf3a301'; + +/// See also [rawStreamNotifier]. +@ProviderFor(rawStreamNotifier) +final rawStreamNotifierProvider = + AutoDisposeProvider>.internal( + rawStreamNotifier, + name: r'rawStreamNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$rawStreamNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef RawStreamNotifierRef = AutoDisposeProviderRef>; +String _$futureRawNotifierHash() => r'd70ca757ff2539fc698ff924c135ee5e88a98018'; + +/// See also [futureRawNotifier]. +@ProviderFor(futureRawNotifier) +final futureRawNotifierProvider = + AutoDisposeFutureProvider.internal( + futureRawNotifier, + name: r'futureRawNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$futureRawNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef FutureRawNotifierRef = AutoDisposeFutureProviderRef; +String _$streamRawNotifierHash() => r'b1075c37ef3e8a83dfb9a3d469b76bd4855c336f'; + +/// See also [streamRawNotifier]. +@ProviderFor(streamRawNotifier) +final streamRawNotifierProvider = + AutoDisposeStreamProvider.internal( + streamRawNotifier, + name: r'streamRawNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$streamRawNotifierHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef StreamRawNotifierRef = AutoDisposeStreamProviderRef; String _$stateNotifierClassHash() => r'576978be5b8a02c212afe7afbe37c733a49ecbce';