diff --git a/packages/riverpod_analyzer_utils/CHANGELOG.md b/packages/riverpod_analyzer_utils/CHANGELOG.md index 443806344..fec82a744 100644 --- a/packages/riverpod_analyzer_utils/CHANGELOG.md +++ b/packages/riverpod_analyzer_utils/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased fix + +- Handle cascade operators in ref expressions + ## 0.2.0 - 2023-03-13 - Added `providerForType` TypeChecker for `ProviderFor` annotation diff --git a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/ref_invocation.dart b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/ref_invocation.dart index c3eb102dd..e6ba616b3 100644 --- a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/ref_invocation.dart +++ b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/ref_invocation.dart @@ -11,7 +11,7 @@ abstract class RefInvocation extends RiverpodAst MethodInvocation node, { required void Function() superCall, }) { - final targetType = node.target?.staticType; + final targetType = node.realTarget?.staticType; if (targetType == null) return null; // Since Ref is sealed, checking that the function is from the package:riverpod diff --git a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/widget_ref_invocation.dart b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/widget_ref_invocation.dart index af7cad9cb..d154dec47 100644 --- a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/widget_ref_invocation.dart +++ b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/widget_ref_invocation.dart @@ -11,7 +11,7 @@ abstract class WidgetRefInvocation extends RiverpodAst MethodInvocation node, { required void Function() superCall, }) { - final targetType = node.target?.staticType; + final targetType = node.realTarget?.staticType; if (targetType == null) return null; // Since Ref is sealed, checking that the function is from the package:riverpod diff --git a/packages/riverpod_analyzer_utils_tests/test/ref_invocation_test.dart b/packages/riverpod_analyzer_utils_tests/test/ref_invocation_test.dart index acf3d1f2b..377d832a8 100644 --- a/packages/riverpod_analyzer_utils_tests/test/ref_invocation_test.dart +++ b/packages/riverpod_analyzer_utils_tests/test/ref_invocation_test.dart @@ -36,6 +36,94 @@ final dependency = Provider((ref) { expect(result.refWatchInvocations.single.provider.providerElement, null); }); + testSource('Decodes ..watch', runGenerator: true, source: r''' +import 'package:riverpod/riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'foo.g.dart'; + +final dep = FutureProvider((ref) => 0); + +@Riverpod(keepAlive: true) +Future dep2(Dep2Ref ref) async => 0; + +@Riverpod(keepAlive: true) +class Dep3 extends _$Dep3 { + @override + Future build() async => 0; +} + +final provider = Provider((ref) { + ref + ..watch(dep) + ..watch(dep2Provider) + ..watch(dep3Provider); + + return 0; +}); + +''', (resolver) async { + final result = await resolver.resolveRiverpodAnalyssiResult(); + + expect(result.refWatchInvocations, hasLength(3)); + expect(result.refInvocations, result.refWatchInvocations); + + expect( + result.refWatchInvocations[0].node.toSource(), + '..watch(dep)', + ); + expect(result.refWatchInvocations[0].function.toSource(), 'watch'); + expect(result.refWatchInvocations[0].provider.node.toSource(), 'dep'); + expect(result.refWatchInvocations[0].provider.familyArguments, null); + expect(result.refWatchInvocations[0].provider.provider?.toSource(), 'dep'); + expect( + result.refWatchInvocations[0].provider.providerElement, + same(result.legacyProviderDeclarations.findByName('dep').providerElement), + ); + + expect( + result.refWatchInvocations[1].node.toSource(), + '..watch(dep2Provider)', + ); + expect(result.refWatchInvocations[1].function.toSource(), 'watch'); + expect( + result.refWatchInvocations[1].provider.node.toSource(), + 'dep2Provider', + ); + expect( + result.refWatchInvocations[1].provider.provider?.toSource(), + 'dep2Provider', + ); + expect( + result.refWatchInvocations[1].provider.providerElement, + same( + result.statelessProviderDeclarations.findByName('dep2').providerElement, + ), + ); + expect(result.refWatchInvocations[1].provider.familyArguments, null); + + expect( + result.refWatchInvocations[2].node.toSource(), + '..watch(dep3Provider)', + ); + expect(result.refWatchInvocations[2].function.toSource(), 'watch'); + expect( + result.refWatchInvocations[2].provider.node.toSource(), + 'dep3Provider', + ); + expect( + result.refWatchInvocations[2].provider.provider?.toSource(), + 'dep3Provider', + ); + expect( + result.refWatchInvocations[2].provider.providerElement, + same( + result.statefulProviderDeclarations.findByName('Dep3').providerElement, + ), + ); + expect(result.refWatchInvocations[2].provider.familyArguments, null); + }); + testSource('Decodes simple ref.watch usages', runGenerator: true, source: r''' import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/packages/riverpod_analyzer_utils_tests/test/widget_ref_invocation_test.dart b/packages/riverpod_analyzer_utils_tests/test/widget_ref_invocation_test.dart index 12f7ee80f..860c1a2a6 100644 --- a/packages/riverpod_analyzer_utils_tests/test/widget_ref_invocation_test.dart +++ b/packages/riverpod_analyzer_utils_tests/test/widget_ref_invocation_test.dart @@ -47,6 +47,103 @@ class Example extends ConsumerWidget { ); }); + testSource('Decodes ..watch', runGenerator: true, source: r''' +import 'package:riverpod/riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/material.dart'; + +part 'foo.g.dart'; + +final dep = FutureProvider((ref) => 0); + +@Riverpod(keepAlive: true) +Future dep2(Dep2Ref ref) async => 0; + +@Riverpod(keepAlive: true) +class Dep3 extends _$Dep3 { + @override + Future build() async => 0; +} + +class MyWidget extends ConsumerWidget { + const MyWidget({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref + ..watch(dep) + ..watch(dep2Provider) + ..watch(dep3Provider); + + return Container(); + } +} +''', (resolver) async { + final result = await resolver.resolveRiverpodAnalyssiResult(); + + expect(result.widgetRefWatchInvocations, hasLength(3)); + expect(result.widgetRefInvocations, result.widgetRefWatchInvocations); + + expect( + result.widgetRefWatchInvocations[0].node.toSource(), + '..watch(dep)', + ); + expect(result.widgetRefWatchInvocations[0].function.toSource(), 'watch'); + expect(result.widgetRefWatchInvocations[0].provider.node.toSource(), 'dep'); + expect(result.widgetRefWatchInvocations[0].provider.familyArguments, null); + expect( + result.widgetRefWatchInvocations[0].provider.provider?.toSource(), + 'dep', + ); + expect( + result.widgetRefWatchInvocations[0].provider.providerElement, + same(result.legacyProviderDeclarations.findByName('dep').providerElement), + ); + + expect( + result.widgetRefWatchInvocations[1].node.toSource(), + '..watch(dep2Provider)', + ); + expect(result.widgetRefWatchInvocations[1].function.toSource(), 'watch'); + expect( + result.widgetRefWatchInvocations[1].provider.node.toSource(), + 'dep2Provider', + ); + expect( + result.widgetRefWatchInvocations[1].provider.provider?.toSource(), + 'dep2Provider', + ); + expect( + result.widgetRefWatchInvocations[1].provider.providerElement, + same( + result.statelessProviderDeclarations.findByName('dep2').providerElement, + ), + ); + expect(result.widgetRefWatchInvocations[1].provider.familyArguments, null); + + expect( + result.widgetRefWatchInvocations[2].node.toSource(), + '..watch(dep3Provider)', + ); + expect(result.widgetRefWatchInvocations[2].function.toSource(), 'watch'); + expect( + result.widgetRefWatchInvocations[2].provider.node.toSource(), + 'dep3Provider', + ); + expect( + result.widgetRefWatchInvocations[2].provider.provider?.toSource(), + 'dep3Provider', + ); + expect( + result.widgetRefWatchInvocations[2].provider.providerElement, + same( + result.statefulProviderDeclarations.findByName('Dep3').providerElement, + ), + ); + expect(result.widgetRefWatchInvocations[2].provider.familyArguments, null); + }); + testSource('Decodes simple ref.watch usages', runGenerator: true, source: r''' import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/packages/riverpod_graph/lib/src/analyze.dart b/packages/riverpod_graph/lib/src/analyze.dart index 00a238a13..3d063b416 100644 --- a/packages/riverpod_graph/lib/src/analyze.dart +++ b/packages/riverpod_graph/lib/src/analyze.dart @@ -288,7 +288,7 @@ class ConsumerWidgetVisitor extends RecursiveAstVisitor { void visitMethodInvocation(MethodInvocation node) { super.visitMethodInvocation(node); - final targetTypeElement = node.target?.staticType?.element; + final targetTypeElement = node.realTarget?.staticType?.element; if (const {'watch', 'listen', 'read'}.contains(node.methodName.name) && targetTypeElement != null && @@ -548,7 +548,7 @@ class ProviderDependencyVisitor extends RecursiveAstVisitor { void visitMethodInvocation(MethodInvocation node) { super.visitMethodInvocation(node); - final targetTypeElement = node.target?.staticType?.element; + final targetTypeElement = node.realTarget?.staticType?.element; if (const {'watch', 'listen', 'read'}.contains(node.methodName.name) && targetTypeElement != null && @@ -632,8 +632,8 @@ VariableElement parseProviderFromExpression(Expression providerExpression) { // watch(SampleClass.familyProviders(id)) return staticElement.declaration.variable; } - final target = providerExpression.target; - if (target != null) return parseProviderFromExpression(target); + final target = providerExpression.realTarget; + return parseProviderFromExpression(target); } else if (providerExpression is PrefixedIdentifier) { if (providerExpression.name.isStartedUpperCaseLetter) { // watch(SomeClass.provider) @@ -655,7 +655,7 @@ VariableElement parseProviderFromExpression(Expression providerExpression) { return parseProviderFromExpression(providerExpression.function); } else if (providerExpression is MethodInvocation) { // watch(variable.select(...)) or watch(family(id).select(...)) - final target = providerExpression.target; + final target = providerExpression.realTarget; if (target != null) return parseProviderFromExpression(target); } diff --git a/packages/riverpod_lint/CHANGELOG.md b/packages/riverpod_lint/CHANGELOG.md index c3c8d2a5e..f64b87269 100644 --- a/packages/riverpod_lint/CHANGELOG.md +++ b/packages/riverpod_lint/CHANGELOG.md @@ -2,6 +2,7 @@ - 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 ## 1.1.7 - 2023-04-06 diff --git a/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.dart b/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.dart index 975a9638e..eadeff3f4 100644 --- a/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.dart +++ b/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.dart @@ -169,3 +169,9 @@ class ClassWatchGeneratedScopedButMissingDependencies return ref.watch(generatedScopedProvider); } } + +@Riverpod(dependencies: [generatedScoped]) +int regression2348(Regression2348Ref ref) { + ref..watch(generatedScopedProvider); + return 0; +} diff --git a/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.g.dart b/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.g.dart index 80fac3977..30f704b76 100644 --- a/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.g.dart +++ b/packages/riverpod_lint_flutter_test/test/goldens/lints/dependencies.g.dart @@ -346,6 +346,21 @@ final specifiedDependencyButNeverUsedProvider = ); typedef SpecifiedDependencyButNeverUsedRef = AutoDisposeProviderRef; +String _$regression2348Hash() => r'72fbbe420e9835c9843c28b7c9375ca3d99ca4b7'; + +/// See also [regression2348]. +@ProviderFor(regression2348) +final regression2348Provider = AutoDisposeProvider.internal( + regression2348, + name: r'regression2348Provider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$regression2348Hash, + dependencies: [generatedScopedProvider], + allTransitiveDependencies: [generatedScopedProvider], +); + +typedef Regression2348Ref = AutoDisposeProviderRef; String _$classWatchGeneratedRootButMissingDependenciesHash() => r'e36d7126a86ea9ded6dc66a6f33eabb2724455a9';