Tests for extension members completion, support not yet imported.
Change-Id: Idd0d2ad814f2365fa724b246f8d84e56e7dedd25
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/218669
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/extension_member_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/extension_member_contributor.dart
index 256bf7f..aa75988 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/extension_member_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/extension_member_contributor.dart
@@ -15,17 +15,15 @@
/// A contributor that produces suggestions based on the members of an
/// extension.
class ExtensionMemberContributor extends DartCompletionContributor {
- late MemberSuggestionBuilder memberBuilder;
+ late final memberBuilder = MemberSuggestionBuilder(request, builder);
ExtensionMemberContributor(
DartCompletionRequest request,
SuggestionBuilder builder,
) : super(request, builder);
- @override
- Future<void> computeSuggestions() async {
+ void addExtensions(List<ExtensionElement> extensions) {
var containingLibrary = request.libraryElement;
- memberBuilder = MemberSuggestionBuilder(request, builder);
var defaultKind = request.target.isFunctionalArgument()
? CompletionSuggestionKind.IDENTIFIER
@@ -44,7 +42,7 @@
if (classOrMixin != null) {
var type = classOrMixin.declaredElement?.thisType;
if (type != null) {
- _addExtensionMembers(containingLibrary, defaultKind, type);
+ _addExtensionMembers(extensions, defaultKind, type);
}
} else {
var extension = request.target.containingNode
@@ -59,7 +57,7 @@
extendedType.element, type.element);
_addTypeMembers(type, defaultKind, inheritanceDistance);
}
- _addExtensionMembers(containingLibrary, defaultKind, extendedType);
+ _addExtensionMembers(extensions, defaultKind, extendedType);
}
}
}
@@ -103,15 +101,23 @@
// invoked on a non-null value.
type = containingLibrary.typeSystem.promoteToNonNull(type);
}
- _addExtensionMembers(containingLibrary, defaultKind, type);
+ _addExtensionMembers(extensions, defaultKind, type);
expression.staticType;
}
}
- void _addExtensionMembers(LibraryElement containingLibrary,
+ @override
+ Future<void> computeSuggestions() async {
+ addExtensions(
+ request.libraryElement.accessibleExtensions,
+ );
+ }
+
+ void _addExtensionMembers(List<ExtensionElement> extensions,
CompletionSuggestionKind? kind, DartType type) {
+ var containingLibrary = request.libraryElement;
var typeSystem = containingLibrary.typeSystem;
- for (var extension in containingLibrary.accessibleExtensions) {
+ for (var extension in extensions) {
var extendedType =
extension.resolvedExtendedType(containingLibrary, type);
if (extendedType != null && typeSystem.isSubtypeOf(type, extendedType)) {
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
index 938228c..629fe475 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
@@ -6,13 +6,13 @@
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
+import 'package:analysis_server/src/services/completion/dart/extension_member_contributor.dart';
import 'package:analysis_server/src/services/completion/dart/local_library_contributor.dart';
-import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'
- show SuggestionBuilder;
+import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
-import 'package:analyzer/src/dart/resolver/scope.dart';
import 'package:analyzer/src/lint/pub.dart';
import 'package:analyzer/src/workspace/pub.dart';
import 'package:collection/collection.dart';
@@ -36,10 +36,6 @@
@override
Future<void> computeSuggestions() async {
- if (!request.includeIdentifiers) {
- return;
- }
-
var session = request.result.session as AnalysisSessionImpl;
var analysisDriver = session.getDriver(); // ignore: deprecated_member_use
@@ -52,6 +48,9 @@
return;
}
+ // Use single instance to track getter / setter pairs.
+ var extensionContributor = ExtensionMemberContributor(request, builder);
+
var knownFiles = fsState.knownFiles.toList();
for (var file in knownFiles) {
onFile?.call(file);
@@ -70,10 +69,17 @@
continue;
}
+ var exportNamespace = elementResult.element.exportNamespace;
+ var exportElements = exportNamespace.definedNames.values.toList();
+
var newSuggestions = builder.markSuggestions();
- _buildSuggestions(
- elementResult.element.exportNamespace,
+ if (request.includeIdentifiers) {
+ _buildSuggestions(exportElements);
+ }
+
+ extensionContributor.addExtensions(
+ exportElements.whereType<ExtensionElement>().toList(),
);
newSuggestions.setLibraryUriToImportIndex(() {
@@ -93,9 +99,9 @@
}
}
- void _buildSuggestions(Namespace namespace) {
+ void _buildSuggestions(List<Element> elements) {
var visitor = LibraryElementSuggestionBuilder(request, builder);
- for (var element in namespace.definedNames.values) {
+ for (var element in elements) {
element.accept(visitor);
}
}
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index 615fc09..2a46330 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.dart
@@ -892,7 +892,7 @@
suggestions.withCompletion('foo02').assertSingle().assertMethod();
}
- Future<void> test_prefixed_extensionGetters() async {
+ Future<void> test_prefixed_expression_extensionGetters() async {
await _configureWithWorkspaceRoot();
var responseValidator = await _getTestCodeSuggestions(r'''
@@ -922,7 +922,255 @@
suggestionsValidator.withCompletion('foo02').assertSingle().assertGetter();
}
- /// TODO(scheglov) Also not imported, with type checks.
+ Future<void> test_prefixed_expression_extensionGetters_notImported() async {
+ await _configureWithWorkspaceRoot();
+
+ newFile('$testPackageLibPath/a.dart', content: '''
+extension E1 on int {
+ int get foo01 => 0;
+ int get bar => 0;
+}
+
+extension E2 on int {
+ int get foo02 => 0;
+}
+
+extension E3 on double {
+ int get foo03 => 0;
+}
+''');
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+void f() {
+ 0.foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01', 'foo02']);
+
+ suggestionsValidator.withCompletion('foo01').assertSingle()
+ ..assertGetter()
+ ..assertLibraryToImport('package:test/a.dart');
+ suggestionsValidator.withCompletion('foo02').assertSingle()
+ ..assertGetter()
+ ..assertLibraryToImport('package:test/a.dart');
+ }
+
+ Future<void>
+ test_prefixed_expression_extensionGetters_notImported_private() async {
+ await _configureWithWorkspaceRoot();
+
+ newFile('$testPackageLibPath/a.dart', content: '''
+extension E1 on int {
+ int get foo01 => 0;
+}
+
+extension _E2 on int {
+ int get foo02 => 0;
+}
+
+extension on int {
+ int get foo03 => 0;
+}
+''');
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+void f() {
+ 0.foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01']);
+
+ suggestionsValidator.withCompletion('foo01').assertSingle()
+ ..assertGetter()
+ ..assertLibraryToImport('package:test/a.dart');
+ }
+
+ Future<void> test_prefixed_expression_extensionMethods() async {
+ await _configureWithWorkspaceRoot();
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+extension E1 on int {
+ void foo01() {}
+ void foo02() {}
+ void bar() {}
+}
+
+extension E2 on double {
+ void foo03() {}
+}
+
+void f() {
+ 0.foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01', 'foo02']);
+
+ suggestionsValidator.withCompletion('foo01').assertSingle().assertMethod();
+ suggestionsValidator.withCompletion('foo02').assertSingle().assertMethod();
+ }
+
+ Future<void> test_prefixed_expression_extensionMethods_notImported() async {
+ await _configureWithWorkspaceRoot();
+
+ newFile('$testPackageLibPath/a.dart', content: '''
+extension E1 on int {
+ void foo01() {}
+ void bar() {}
+}
+
+extension E2 on int {
+ void foo02() {}
+}
+
+extension E3 on double {
+ void foo03() {}
+}
+''');
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+void f() {
+ 0.foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01', 'foo02']);
+
+ suggestionsValidator.withCompletion('foo01').assertSingle()
+ ..assertMethod()
+ ..assertLibraryToImport('package:test/a.dart');
+ suggestionsValidator.withCompletion('foo02').assertSingle()
+ ..assertMethod()
+ ..assertLibraryToImport('package:test/a.dart');
+ }
+
+ Future<void> test_prefixed_expression_extensionSetters() async {
+ await _configureWithWorkspaceRoot();
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+extension E1 on int {
+ set foo01(int _) {}
+ set foo02(int _) {}
+ set bar(int _) {}
+}
+
+extension E2 on double {
+ set foo03(int _) {}
+}
+
+void f() {
+ 0.foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01', 'foo02']);
+
+ suggestionsValidator.withCompletion('foo01').assertSingle().assertSetter();
+ suggestionsValidator.withCompletion('foo02').assertSingle().assertSetter();
+ }
+
+ Future<void> test_prefixed_expression_extensionSetters_notImported() async {
+ await _configureWithWorkspaceRoot();
+
+ newFile('$testPackageLibPath/a.dart', content: '''
+extension E1 on int {
+ set foo01(int _) {}
+ set bar(int _) {}
+}
+
+extension E2 on int {
+ set foo02(int _) {}
+}
+
+extension E3 on double {
+ set foo03(int _) {}
+}
+''');
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+void f() {
+ 0.foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01', 'foo02']);
+
+ suggestionsValidator.withCompletion('foo01').assertSingle()
+ ..assertSetter()
+ ..assertLibraryToImport('package:test/a.dart');
+ suggestionsValidator.withCompletion('foo02').assertSingle()
+ ..assertSetter()
+ ..assertLibraryToImport('package:test/a.dart');
+ }
+
+ Future<void>
+ test_prefixed_expression_extensionSetters_notImported_private() async {
+ await _configureWithWorkspaceRoot();
+
+ newFile('$testPackageLibPath/a.dart', content: '''
+extension E1 on int {
+ set foo01(int _) {}
+}
+
+extension _E2 on int {
+ set foo02(int _) {}
+}
+
+extension on int {
+ set foo03(int _) {}
+}
+''');
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+void f() {
+ 0.foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01']);
+
+ suggestionsValidator.withCompletion('foo01').assertSingle()
+ ..assertSetter()
+ ..assertLibraryToImport('package:test/a.dart');
+ }
+
Future<void> test_prefixed_extensionGetters_imported() async {
await _configureWithWorkspaceRoot();
@@ -957,6 +1205,58 @@
suggestionsValidator.withCompletion('foo02').assertSingle().assertGetter();
}
+ Future<void> test_prefixed_extensionOverride_extensionGetters() async {
+ await _configureWithWorkspaceRoot();
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+extension E1 on int {
+ int get foo01 => 0;
+}
+
+extension E2 on int {
+ int get foo02 => 0;
+}
+
+void f() {
+ E1(0).foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01']);
+ suggestionsValidator.withCompletion('foo01').assertSingle().assertGetter();
+ }
+
+ Future<void> test_prefixed_extensionOverride_extensionMethods() async {
+ await _configureWithWorkspaceRoot();
+
+ var responseValidator = await _getTestCodeSuggestions(r'''
+extension E1 on int {
+ void foo01() {}
+}
+
+extension E2 on int {
+ void foo01() {}
+}
+
+void f() {
+ E1(0).foo0^
+}
+''');
+
+ responseValidator
+ ..assertComplete()
+ ..assertReplacementBack(4);
+
+ var suggestionsValidator = responseValidator.suggestions;
+ suggestionsValidator.assertCompletions(['foo01']);
+ suggestionsValidator.withCompletion('foo01').assertSingle().assertMethod();
+ }
+
Future<void> test_unprefixed_filters() async {
await _configureWithWorkspaceRoot();
@@ -2248,6 +2548,11 @@
expect(suggestion.element?.kind, ElementKind.METHOD);
}
+ void assertSetter() {
+ expect(suggestion.kind, CompletionSuggestionKind.INVOCATION);
+ expect(suggestion.element?.kind, ElementKind.SETTER);
+ }
+
void assertTopLevelVariable() {
expect(suggestion.kind, CompletionSuggestionKind.INVOCATION);
expect(suggestion.element?.kind, ElementKind.TOP_LEVEL_VARIABLE);
@@ -2333,6 +2638,10 @@
return withElementKind(ElementKind.CONSTRUCTOR);
}
+ SuggestionsValidator withElementGetter() {
+ return withElementKind(ElementKind.GETTER);
+ }
+
SuggestionsValidator withElementKind(ElementKind kind) {
return SuggestionsValidator(
suggestions.where((suggestion) {