[analysis_server] Prevent duplicate completions for extensions for libraries that import them

Fixes https://github.com/dart-lang/sdk/issues/56320

Change-Id: I4645165857c082cb26ac09eb12df3106769fb25a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/382821
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart b/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart
index 334fb20..ed1399f 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/declaration_helper.dart
@@ -463,14 +463,16 @@
       required Set<String> excludedGetters,
       required bool includeMethods,
       required bool includeSetters}) {
-    var applicableExtensions = library.accessibleExtensions.applicableTo(
-      targetLibrary: library,
-      // Ignore nullability, consistent with non-extension members.
-      targetType: type.isDartCoreNull
-          ? type
-          : library.typeSystem.promoteToNonNull(type),
-      strictCasts: false,
-    );
+    var applicableExtensions = library.exportNamespace.definedNames.values
+        .whereType<ExtensionElement>()
+        .applicableTo(
+          targetLibrary: library,
+          // Ignore nullability, consistent with non-extension members.
+          targetType: type.isDartCoreNull
+              ? type
+              : library.typeSystem.promoteToNonNull(type),
+          strictCasts: false,
+        );
     var importData = ImportData(
         libraryUri: library.source.uri, prefix: null, isNotImported: true);
     for (var instantiatedExtension in applicableExtensions) {
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_completion_pass.dart b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_completion_pass.dart
index 03b0a17..524b5b6 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_completion_pass.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_completion_pass.dart
@@ -143,7 +143,7 @@
       var element = elementResult.element;
       if (element == library) {
         // Don't suggest elements from the library in which completion is being
-        // requested. They've already been sugpested.
+        // requested. They've already been suggested.
         continue;
       }
 
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 97e08d6..6a597ef 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -3319,7 +3319,6 @@
   }
 
   /// Verify extensions can be auto-imported if not already in-scope.
-  @FailingTest(issue: 'https://github.com/dart-lang/sdk/issues/56320')
   Future<void> test_unimportedSymbols_extension() async {
     // Define extensions in 'extensions.dart'.
     newFile(
diff --git a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
index 95c021b..cda1ade 100644
--- a/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
+++ b/pkg/analysis_server/test/src/services/completion/dart/completion_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/protocol_server.dart';
+import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import '../../../../completion_test_support.dart';
@@ -223,21 +224,51 @@
     assertHasCompletion('m');
   }
 
-  @failingTest
   Future<void> test_explicitTarget_method_notImported() async {
-    // Available suggestions data doesn't yet have information about extension
-    // methods.
-    newFile(convertPath('/project/bin/lib.dart'), '''
+    newFile(convertPath('$testPackageLibPath/extensions.dart'), '''
 extension E on String {
   void m() {}
 }
 ''');
+    // Files that merely import extensions do not add completions for them.
+    newFile(convertPath('$testPackageLibPath/imported.dart'), '''
+import 'extensions.dart';
+''');
     await getTestCodeSuggestions('''
 void f(String s) {
   s.^;
 }
 ''');
-    assertHasCompletion('m');
+    var matchingSuggestions = suggestions.where((s) => s.completion == 'm');
+    expect(matchingSuggestions, hasLength(1));
+    expect(
+        matchingSuggestions.single.libraryUri, 'package:test/extensions.dart');
+  }
+
+  /// Extensions can also be imported from libraries that export them.
+  Future<void> test_explicitTarget_method_notImported_exported() async {
+    newFile(convertPath('$testPackageLibPath/extensions.dart'), '''
+extension E on String {
+  void m() {}
+}
+''');
+    newFile(convertPath('$testPackageLibPath/reexported.dart'), '''
+export 'extensions.dart';
+''');
+    await getTestCodeSuggestions('''
+void f(String s) {
+  s.^;
+}
+''');
+    var matchingSuggestions = suggestions.where((s) => s.completion == 'm');
+    expect(matchingSuggestions, hasLength(2));
+    expect(
+      matchingSuggestions.map((s) => s.libraryUri),
+      [
+        'package:test/extensions.dart',
+        'package:test/reexported.dart',
+      ],
+    );
   }
 
   Future<void> test_explicitTarget_method_sameUnit() async {