diff --git a/packages/riverpod_analyzer_utils/lib/src/errors.dart b/packages/riverpod_analyzer_utils/lib/src/errors.dart index e6ef4bb9d..eed83d768 100644 --- a/packages/riverpod_analyzer_utils/lib/src/errors.dart +++ b/packages/riverpod_analyzer_utils/lib/src/errors.dart @@ -20,6 +20,8 @@ enum RiverpodAnalysisErrorCode { providerOrFamilyExpressionParseError, invalidRetryArgument, mutationReturnTypeMismatch, + mutationIsStatic, + mutationIsAbstract, } class RiverpodAnalysisError { diff --git a/packages/riverpod_analyzer_utils/lib/src/nodes/providers/notifier.dart b/packages/riverpod_analyzer_utils/lib/src/nodes/providers/notifier.dart index 4d199ed79..dfe10e6aa 100644 --- a/packages/riverpod_analyzer_utils/lib/src/nodes/providers/notifier.dart +++ b/packages/riverpod_analyzer_utils/lib/src/nodes/providers/notifier.dart @@ -144,6 +144,29 @@ extension MutationMethodDeclarationX on MethodDeclaration { final mutationElement = MutationElement._parse(element); if (mutationElement == null) return null; + if (isStatic) { + errorReporter( + RiverpodAnalysisError( + 'Mutations cannot be static.', + targetNode: this, + targetElement: element, + code: RiverpodAnalysisErrorCode.mutationIsStatic, + ), + ); + return null; + } + if (isAbstract) { + errorReporter( + RiverpodAnalysisError( + 'Mutations cannot be abstract.', + targetNode: this, + targetElement: element, + code: RiverpodAnalysisErrorCode.mutationIsAbstract, + ), + ); + return null; + } + final expectedReturnType = thisOrAncestorOfType()! .members .whereType() diff --git a/packages/riverpod_analyzer_utils_tests/test/mutation_test.dart b/packages/riverpod_analyzer_utils_tests/test/mutation_test.dart index 3179f4a81..d9dccd451 100644 --- a/packages/riverpod_analyzer_utils_tests/test/mutation_test.dart +++ b/packages/riverpod_analyzer_utils_tests/test/mutation_test.dart @@ -57,4 +57,42 @@ class SyncNotifier extends _$SyncNotifier { ), ); }); + + testSource('rejects abstract/static mutations', source: r''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +@riverpod +class Abstract extends _$Abstract { + @override + int build() => 0; + + @mutation + Future a(); + + @mutation + static Future b() async => 42; +} +''', (resolver, unit, units) async { + final result = + await resolver.resolveRiverpodAnalysisResult(ignoreErrors: true); + + expect(result.errors, hasLength(2)); + + expect( + result.errors, + everyElement( + isA() + .having((e) => e.targetNode, 'node', isNotNull) + .having((e) => e.targetElement, 'element', isNotNull) + .having( + (e) => e.code, + 'code', + anyOf( + RiverpodAnalysisErrorCode.mutationIsAbstract, + RiverpodAnalysisErrorCode.mutationIsStatic, + ), + ), + ), + ); + }); } diff --git a/packages/riverpod_generator/test/mutation_test.dart b/packages/riverpod_generator/test/mutation_test.dart index a57cfbd45..23bc7395c 100644 --- a/packages/riverpod_generator/test/mutation_test.dart +++ b/packages/riverpod_generator/test/mutation_test.dart @@ -9,12 +9,6 @@ import 'integration/mutation.dart'; import 'mock.dart'; void main() { -// Error: -// - Mutation returns a non-FutureOr type -// - mutation is not on a notifier method -// mutation is static -// mutation is abstract - test('Can listen a mutation', () async { final container = ProviderContainer.test(); final listener = ListenerMock();