Skip to content

Commit

Permalink
Handle cascade operators in ref expressions (#2434)
Browse files Browse the repository at this point in the history
fixes #2348
  • Loading branch information
rrousselGit committed Apr 7, 2023
1 parent 65ea983 commit 70470fa
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 7 deletions.
4 changes: 4 additions & 0 deletions packages/riverpod_analyzer_utils/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## Unreleased fix

- Handle cascade operators in ref expressions

## 0.2.0 - 2023-03-13

- Added `providerForType` TypeChecker for `ProviderFor` annotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> dep2(Dep2Ref ref) async => 0;
@Riverpod(keepAlive: true)
class Dep3 extends _$Dep3 {
@override
Future<int> build() async => 0;
}
final provider = Provider<int>((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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> dep2(Dep2Ref ref) async => 0;
@Riverpod(keepAlive: true)
class Dep3 extends _$Dep3 {
@override
Future<int> 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';
Expand Down
10 changes: 5 additions & 5 deletions packages/riverpod_graph/lib/src/analyze.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ class ConsumerWidgetVisitor extends RecursiveAstVisitor<void> {
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 &&
Expand Down Expand Up @@ -548,7 +548,7 @@ class ProviderDependencyVisitor extends RecursiveAstVisitor<void> {
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 &&
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions packages/riverpod_lint/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,9 @@ class ClassWatchGeneratedScopedButMissingDependencies
return ref.watch(generatedScopedProvider);
}
}

@Riverpod(dependencies: [generatedScoped])
int regression2348(Regression2348Ref ref) {
ref..watch(generatedScopedProvider);
return 0;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 70470fa

Please sign in to comment.