Skip to content

Commit

Permalink
fix(riverpod_lint): detects nested RefInvocations for ref.watch
Browse files Browse the repository at this point in the history
- adds 3rd level of nesting to ensure recursion works
  • Loading branch information
josh-burton committed Oct 24, 2024
1 parent 5ef007a commit 5d02a8b
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ mixin _ParseRefInvocationMixin on RecursiveAstVisitor<void> {
WidgetRefInvocation._parse(node, superCall: superCall);
if (widgetRefInvocation != null) {
visitWidgetRefInvocation(widgetRefInvocation);

for (final argument in widgetRefInvocation.node.argumentList.arguments
.whereType<InvocationExpression>()) {
argument.accept(this);
}
// Don't call super as WidgetRefInvocation should already be recursive
return;
}
Expand Down
146 changes: 145 additions & 1 deletion packages/riverpod_analyzer_utils_tests/test/ref_invocation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -420,18 +420,26 @@ part 'foo.g.dart';
final dep = FutureProvider((ref) => 0);
final dep2 = FutureProvider.family((ref, int arg) => 0);
final dep3 = FutureProvider.family((ref, int arg) => 0);
final provider = Provider<int>((ref) {
ref.read(dep2(ref.read(dep)));
return 0;
});
final provider2 = Provider<int>((ref) {
ref.read(dep3(ref.read(dep2(ref.read(dep)))));
return 0;
});
''', (resolver) async {
final result = await resolver.resolveRiverpodAnalysisResult();

expect(result.refReadInvocations, hasLength(2));
expect(result.refReadInvocations, hasLength(5));
expect(result.refInvocations, result.refReadInvocations);

// provider
expect(
result.refReadInvocations[0].node.toSource(),
'ref.read(dep2(ref.read(dep)))',
Expand All @@ -452,6 +460,25 @@ final provider = Provider<int>((ref) {
result.legacyProviderDeclarations.findByName('dep').providerElement,
),
);

// provider2
expect(
result.refReadInvocations[2].node.toSource(),
'ref.read(dep3(ref.read(dep2(ref.read(dep)))))',
);
expect(result.refReadInvocations[2].function.toSource(), 'read');
expect(
result.refReadInvocations[2].provider.providerElement,
same(
result.legacyProviderDeclarations.findByName('dep3').providerElement,
),
);

expect(
result.refReadInvocations[3].node.toSource(),
'ref.read(dep2(ref.read(dep)))',
);
expect(result.refReadInvocations[4].node.toSource(), 'ref.read(dep)');
});

testSource('Decodes unknown ref usages', source: '''
Expand Down Expand Up @@ -618,6 +645,123 @@ void fn(_Ref ref) {
);
});

testSource('Decodes nested ref.watch invocations with family providers',
runGenerator: true, source: '''
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'foo.g.dart';
final dep = FutureProvider((ref) => 0);
final dep2 = FutureProvider.family((ref, int arg) => 0);
final dep3 = FutureProvider.family((ref, int arg) => 0);
final provider = Provider<int>((ref) {
ref.watch(dep2(ref.watch(dep)));
return 0;
});
final provider2 = Provider<int>((ref) {
ref.watch(dep3(ref.watch(dep2(ref.watch(dep)))));
return 0;
});
''', (resolver) async {
final result = await resolver.resolveRiverpodAnalysisResult();

expect(result.refWatchInvocations, hasLength(5));
expect(result.refInvocations, result.refWatchInvocations);

// provider
expect(
result.refWatchInvocations[0].node.toSource(),
'ref.watch(dep2(ref.watch(dep)))',
);
expect(result.refWatchInvocations[0].function.toSource(), 'watch');
expect(
result.refWatchInvocations[0].provider.providerElement,
same(
result.legacyProviderDeclarations.findByName('dep2').providerElement,
),
);

expect(result.refWatchInvocations[1].node.toSource(), 'ref.watch(dep)');
expect(result.refWatchInvocations[1].function.toSource(), 'watch');
expect(
result.refWatchInvocations[1].provider.providerElement,
same(
result.legacyProviderDeclarations.findByName('dep').providerElement,
),
);

// provider2
expect(
result.refWatchInvocations[2].node.toSource(),
'ref.watch(dep3(ref.watch(dep2(ref.watch(dep)))))',
);
expect(result.refWatchInvocations[2].function.toSource(), 'watch');
expect(
result.refWatchInvocations[2].provider.providerElement,
same(
result.legacyProviderDeclarations.findByName('dep3').providerElement,
),
);

expect(
result.refWatchInvocations[3].node.toSource(),
'ref.watch(dep2(ref.watch(dep)))',
);
expect(result.refWatchInvocations[4].node.toSource(), 'ref.watch(dep)');
});

testSource('Decodes mix of nested ref.watch and ref.read invocations',
runGenerator: true, source: '''
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'foo.g.dart';
final dep = FutureProvider((ref) => 0);
final dep2 = FutureProvider.family((ref, int arg) => 0);
final provider = Provider<int>((ref) {
ref.watch(dep2(ref.read(dep)));
return 0;
});
''', (resolver) async {
final result = await resolver.resolveRiverpodAnalysisResult();

expect(result.refWatchInvocations, hasLength(1));
expect(result.refReadInvocations, hasLength(1));
expect(
result.refInvocations,
[...result.refWatchInvocations, ...result.refReadInvocations],
);

expect(
result.refWatchInvocations[0].node.toSource(),
'ref.watch(dep2(ref.read(dep)))',
);
expect(result.refWatchInvocations[0].function.toSource(), 'watch');
expect(
result.refWatchInvocations[0].provider.providerElement,
same(
result.legacyProviderDeclarations.findByName('dep2').providerElement,
),
);

expect(result.refReadInvocations[0].node.toSource(), 'ref.read(dep)');
expect(result.refReadInvocations[0].function.toSource(), 'read');
expect(
result.refReadInvocations[0].provider.providerElement,
same(
result.legacyProviderDeclarations.findByName('dep').providerElement,
),
);
});

testSource('Decodes provider.query ref.watch usages',
runGenerator: true, source: r'''
import 'package:riverpod/riverpod.dart';
Expand Down

0 comments on commit 5d02a8b

Please sign in to comment.