diff --git a/packages/riverpod_analyzer_utils/CHANGELOG.md b/packages/riverpod_analyzer_utils/CHANGELOG.md index 73ddbf700..8aa375fc1 100644 --- a/packages/riverpod_analyzer_utils/CHANGELOG.md +++ b/packages/riverpod_analyzer_utils/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased patch + +- Correctly parse import aliases when used inside `ref.watch(<...>)` + ## 0.3.4 - 2023-09-27 - Fixed `refInvocations` not getting parsed for generated providers with arguments. diff --git a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/provider_listenable_expression.dart b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/provider_listenable_expression.dart index 9c5630986..6c0d05f5e 100644 --- a/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/provider_listenable_expression.dart +++ b/packages/riverpod_analyzer_utils/lib/src/riverpod_ast/provider_listenable_expression.dart @@ -6,6 +6,7 @@ class ProviderListenableExpression extends RiverpodAst { ProviderListenableExpression._({ required this.node, required this.provider, + required this.providerPrefix, required this.providerElement, required this.familyArguments, }); @@ -13,8 +14,8 @@ class ProviderListenableExpression extends RiverpodAst { static ProviderListenableExpression? _parse(Expression? expression) { if (expression == null) return null; - // print('oy $expression // ${expression.runtimeType}'); SimpleIdentifier? provider; + SimpleIdentifier? providerPrefix; ProviderDeclarationElement? providerElement; ArgumentList? familyArguments; @@ -26,6 +27,7 @@ class ProviderListenableExpression extends RiverpodAst { provider = expression; final element = expression.staticElement; if (element is PropertyAccessorElement) { + // watch(provider) DartObject? annotation; try { annotation = @@ -50,7 +52,13 @@ class ProviderListenableExpression extends RiverpodAst { parseExpression(expression.target); } else if (expression is PrefixedIdentifier) { // watch(expression.modifier) - parseExpression(expression.prefix); + final element = expression.prefix.staticElement; + if (element is PrefixElement) { + providerPrefix = expression.prefix; + parseExpression(expression.identifier); + } else { + parseExpression(expression.prefix); + } } else if (expression is IndexExpression) { // watch(expression[]) parseExpression(expression.target); @@ -65,6 +73,7 @@ class ProviderListenableExpression extends RiverpodAst { return ProviderListenableExpression._( node: expression, provider: provider, + providerPrefix: providerPrefix, providerElement: providerElement, // Make sure `(){}()` doesn't report an argument list even though it's not a provider familyArguments: provider == null ? null : familyArguments, @@ -96,6 +105,7 @@ class ProviderListenableExpression extends RiverpodAst { } final Expression node; + final SimpleIdentifier? providerPrefix; final SimpleIdentifier? provider; final ProviderDeclarationElement? providerElement; diff --git a/packages/riverpod_analyzer_utils_tests/test/analyser_test_utils.dart b/packages/riverpod_analyzer_utils_tests/test/analyser_test_utils.dart index 0bc48cfdb..7c4934978 100644 --- a/packages/riverpod_analyzer_utils_tests/test/analyser_test_utils.dart +++ b/packages/riverpod_analyzer_utils_tests/test/analyser_test_utils.dart @@ -20,6 +20,7 @@ void testSource( String description, Future Function(Resolver resolver) run, { required String source, + Map files = const {}, bool runGenerator = false, }) { final testId = _testNumber++; @@ -33,10 +34,19 @@ void testSource( final enclosingZone = Zone.current; + final otherSources = { + for (final entry in files.entries) + '$packageName|lib/${entry.key}': + 'library "${entry.key}"; ${entry.value}', + }; + String? generated; if (runGenerator) { final analysisResult = await resolveSources( - {'$packageName|lib/foo.dart': sourceWithLibrary}, + { + '$packageName|lib/foo.dart': sourceWithLibrary, + ...otherSources, + }, (resolver) { return resolver.resolveRiverpodLibraryResult( ignoreErrors: true, @@ -50,6 +60,7 @@ void testSource( '$packageName|lib/foo.dart': sourceWithLibrary, if (generated != null) '$packageName|lib/foo.g.dart': 'part of "foo.dart";$generated', + ...otherSources, }, (resolver) async { try { final originalZone = Zone.current; 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 cd41531f8..1fa59efc4 100644 --- a/packages/riverpod_analyzer_utils_tests/test/ref_invocation_test.dart +++ b/packages/riverpod_analyzer_utils_tests/test/ref_invocation_test.dart @@ -4,6 +4,47 @@ import 'package:test/test.dart'; import 'analyser_test_utils.dart'; void main() { + testSource( + 'Parses import aliases', + runGenerator: true, + files: { + 'file.dart': ''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; +final aProvider = Provider((ref) => 0); +''', + }, + source: ''' +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'file.dart' as alias; +part 'foo.g.dart'; + +@Riverpod(keepAlive: true) +int aliased(AliasedRef ref) { + ref.watch(alias.aProvider); + return 0; +} +''', + (resolver) async { + final result = await resolver.resolveRiverpodAnalyssiResult(); + + expect(result.refWatchInvocations, hasLength(1)); + expect(result.refInvocations.single.function.toSource(), 'watch'); + expect( + result.refInvocations.single.node.toSource(), + 'ref.watch(alias.aProvider)', + ); + + expect( + result.refWatchInvocations.single.provider.provider?.toSource(), + 'aProvider', + ); + expect( + result.refWatchInvocations.single.provider.providerPrefix?.toSource(), + 'alias', + ); + }, + ); + testSource('Decode watch expressions with syntax errors', source: ''' import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/packages/riverpod_lint/CHANGELOG.md b/packages/riverpod_lint/CHANGELOG.md index fa610b8d4..c2e08a865 100644 --- a/packages/riverpod_lint/CHANGELOG.md +++ b/packages/riverpod_lint/CHANGELOG.md @@ -1,6 +1,7 @@ ## Unreleased minor - Added `avoid_build_context_in_providers` (thanks to @charlescyt) +- Fixed false positive with `avoid_manual_providers_as_generated_provider_dependency` when using import aliases ## 2.1.1 - 2023-09-27 diff --git a/packages/riverpod_lint_flutter_test/test/lints/another.dart b/packages/riverpod_lint_flutter_test/test/lints/another.dart new file mode 100644 index 000000000..a7d58b340 --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/another.dart @@ -0,0 +1,8 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'another.g.dart'; + +final aProvider = Provider((ref) => 0); + +@riverpod +int b(BRef ref) => 0; diff --git a/packages/riverpod_lint_flutter_test/test/lints/another.g.dart b/packages/riverpod_lint_flutter_test/test/lints/another.g.dart new file mode 100644 index 000000000..0dfd589fc --- /dev/null +++ b/packages/riverpod_lint_flutter_test/test/lints/another.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'another.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$bHash() => r'52593050701642f22b31c590f20c003dc2ee1579'; + +/// See also [b]. +@ProviderFor(b) +final bProvider = AutoDisposeProvider.internal( + b, + name: r'bProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$bHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef BRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter diff --git a/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart b/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart index c155f01a7..5d05e88c8 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/dependencies.dart @@ -1,4 +1,7 @@ +// ignore_for_file: unused_field + import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'another.dart' as import_alias; part 'dependencies.g.dart'; @@ -198,3 +201,23 @@ int familyDep2(FamilyDep2Ref ref, int p) { final test = ref.watch(familyDepProvider(0)); return test * p; } + +// Regression test for https://github.com/rrousselGit/riverpod/issues/2935 +@riverpod +int alias(AliasRef ref) { + // expect_lint: avoid_manual_providers_as_generated_provider_dependency + ref.watch(import_alias.aProvider); + ref.watch(import_alias.bProvider); + return 0; +} + +// Regression test for https://github.com/rrousselGit/riverpod/issues/2935 +@riverpod +class AliasClass extends _$AliasClass { + // expect_lint: avoid_manual_providers_as_generated_provider_dependency + late final int _a = ref.read(import_alias.aProvider); + late final int _b = ref.read(import_alias.bProvider); + + @override + int build() => 0; +} diff --git a/packages/riverpod_lint_flutter_test/test/lints/dependencies.g.dart b/packages/riverpod_lint_flutter_test/test/lints/dependencies.g.dart index bdca0999b..119d53d6d 100644 --- a/packages/riverpod_lint_flutter_test/test/lints/dependencies.g.dart +++ b/packages/riverpod_lint_flutter_test/test/lints/dependencies.g.dart @@ -672,6 +672,20 @@ class _FamilyDep2ProviderElement extends AutoDisposeProviderElement int get p => (origin as FamilyDep2Provider).p; } +String _$aliasHash() => r'871c6c7ab22e4bbed2dc46917daf42e7fc1b9d88'; + +/// See also [alias]. +@ProviderFor(alias) +final aliasProvider = AutoDisposeProvider.internal( + alias, + name: r'aliasProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$aliasHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef AliasRef = AutoDisposeProviderRef; String _$classWatchGeneratedRootButMissingDependenciesHash() => r'e36d7126a86ea9ded6dc66a6f33eabb2724455a9'; @@ -729,5 +743,20 @@ final regression2417Provider = ); typedef _$Regression2417 = AutoDisposeNotifier; +String _$aliasClassHash() => r'f5c1f43e7541638274ca7dc334a713763c9c8071'; + +/// See also [AliasClass]. +@ProviderFor(AliasClass) +final aliasClassProvider = + AutoDisposeNotifierProvider.internal( + AliasClass.new, + name: r'aliasClassProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$aliasClassHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$AliasClass = AutoDisposeNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, inference_failure_on_uninitialized_variable, inference_failure_on_function_return_type, inference_failure_on_untyped_parameter