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) {