Add a pass for suggesting elements that are not yet imported

This CL makes 3 different changes. I'm happy to split them out into
smaller CLs if you'd like me to.

The first is to add the class `NotImportedCompletionPass`, which is the
proposed replacement for the `NotImportedContributor`. I believe that
the pass is complete, but it currently doesn't contribute any
suggestions because the methods invoked by the two "operations" aren't
yet implemented.

That said, it does do real work in terms of searching through the
libraries that aren't imported. That work is currently duplicated by
the contributor, which would likely cause a performance degredation.
As a result, I've commented out the invocation of the pass (but have
verified that every test passes when it's not commented out).

The second is to start suggesting extension members from local
extensions. I'm not sure why, but prior to this CL they appear to have
only been suggested by the `NotImportedContributor`, which isn't
always run. I'm not sure where members from imported extensions are
currently being imported, but I'll figure that out before I remove
the `NotImportedContributor`.

The third is to change the element being compared against when
computing the inheritance distance. We were previously using the
container of the element being suggested, which caused the inheritance
distance to always be the same, no matter what the type hierarchy
looks like. We need better tests for relevance, but I didn't add any
in this CL.

Change-Id: Ic82a15cab5d022f89da234a8b31bc6bca34ccb5c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/363760
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart
index eb75b76..66ad3b7 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/candidate_suggestion.dart
@@ -195,14 +195,14 @@
   /// The element on which the suggestion is based.
   final FieldElement element;
 
-  /// The class from which the field is being referenced, or `null` if the class
-  /// is not being referenced from within a class.
-  final ClassElement? referencingClass;
+  /// The element defined by the declaration in which the suggestion is to be
+  /// applied, or `null` if the completion is in a static context.
+  final InterfaceElement? referencingInterface;
 
   /// Initialize a newly created candidate suggestion to suggest the [element].
   FieldSuggestion(
       {required this.element,
-      required this.referencingClass,
+      required this.referencingInterface,
       required super.score});
 
   @override
@@ -430,13 +430,15 @@
   /// The element on which the suggestion is based.
   final MethodElement element;
 
-  final ClassElement? referencingClass;
+  /// The element defined by the declaration in which the suggestion is to be
+  /// applied, or `null` if the completion is in a static context.
+  final InterfaceElement? referencingInterface;
 
   /// Initialize a newly created candidate suggestion to suggest the [element].
   MethodSuggestion(
       {required super.kind,
       required this.element,
-      required this.referencingClass,
+      required this.referencingInterface,
       required super.score});
 
   @override
@@ -532,12 +534,14 @@
   /// The element on which the suggestion is based.
   final PropertyAccessorElement element;
 
-  final ClassElement? referencingClass;
+  /// The element defined by the declaration in which the suggestion is to be
+  /// applied, or `null` if the completion is in a static context.
+  final InterfaceElement? referencingInterface;
 
   /// Initialize a newly created candidate suggestion to suggest the [element].
   PropertyAccessSuggestion(
       {required this.element,
-      required this.referencingClass,
+      required this.referencingInterface,
       required super.score});
 
   @override
@@ -770,7 +774,7 @@
         } else {
           suggestField(fieldElement,
               inheritanceDistance: _inheritanceDistance(
-                  suggestion.referencingClass,
+                  suggestion.referencingInterface,
                   suggestion.element.enclosingElement));
         }
       case FormalParameterSuggestion():
@@ -814,7 +818,8 @@
           suggestion.element,
           kind: kind,
           inheritanceDistance: _inheritanceDistance(
-              suggestion.referencingClass, suggestion.element.enclosingElement),
+              suggestion.referencingInterface,
+              suggestion.element.enclosingElement),
         );
       case MixinSuggestion():
         libraryUriStr = suggestion.libraryUriStr;
@@ -836,7 +841,7 @@
         );
       case PropertyAccessSuggestion():
         var inheritanceDistance = 0.0;
-        var referencingClass = suggestion.referencingClass;
+        var referencingClass = suggestion.referencingInterface;
         var declaringClass = suggestion.element.enclosingElement;
         if (referencingClass != null && declaringClass is InterfaceElement) {
           inheritanceDistance = request.featureComputer
@@ -895,7 +900,7 @@
   /// Returns the inheritance distance from the [referencingClass] to the
   /// [declaringClass].
   double _inheritanceDistance(
-      ClassElement? referencingClass, Element? declaringClass) {
+      InterfaceElement? referencingClass, Element? declaringClass) {
     var distance = 0.0;
     if (referencingClass != null && declaringClass is InterfaceElement) {
       distance = request.featureComputer
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index 691e07c..2ae38a1 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -9,6 +9,7 @@
 import 'package:analysis_server/src/services/completion/dart/completion_state.dart';
 import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
 import 'package:analysis_server/src/services/completion/dart/in_scope_completion_pass.dart';
+import 'package:analysis_server/src/services/completion/dart/not_imported_completion_pass.dart';
 import 'package:analysis_server/src/services/completion/dart/not_imported_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
@@ -119,11 +120,19 @@
 
     var collector = SuggestionCollector();
     try {
-      performance.run(
+      var selection = request.unit.select(offset: request.offset, length: 0);
+      if (selection == null) {
+        throw AbortCompletion();
+      }
+      var matcher = request.targetPrefix.isEmpty
+          ? NoPrefixMatcher()
+          : FuzzyMatcher(request.targetPrefix);
+      var state = CompletionState(request, selection, budget, matcher);
+      /*var operations =*/ performance.run(
         'InScopeCompletionPass',
         (performance) {
-          _runFirstPass(
-            request: request,
+          return _runFirstPass(
+            state: state,
             collector: collector,
             builder: builder,
             suggestOverrides: enableOverrideContributor,
@@ -131,6 +140,12 @@
           );
         },
       );
+      // if (operations.isNotEmpty && notImportedSuggestions != null) {
+      //   performance.run('NotImportedCompletionPass', (performance) {
+      //     NotImportedCompletionPass(state, collector, operations)
+      //         .computeSuggestions(performance: performance);
+      //   });
+      // }
       for (var contributor in contributors) {
         await performance.runAsync(
           '${contributor.runtimeType}',
@@ -155,22 +170,16 @@
     return builder.suggestions.toList();
   }
 
-  // Run the first pass of the code completion algorithm.
-  void _runFirstPass({
-    required DartCompletionRequest request,
+  /// Run the first pass of the code completion algorithm.
+  ///
+  /// Returns the operations that need to be performed in the second pass.
+  List<NotImportedOperation> _runFirstPass({
+    required CompletionState state,
     required SuggestionCollector collector,
     required SuggestionBuilder builder,
     required bool suggestOverrides,
     required bool suggestUris,
   }) {
-    var selection = request.unit.select(offset: request.offset, length: 0);
-    if (selection == null) {
-      throw AbortCompletion();
-    }
-    var matcher = request.targetPrefix.isEmpty
-        ? NoPrefixMatcher()
-        : FuzzyMatcher(request.targetPrefix);
-    var state = CompletionState(request, selection, budget, matcher);
     var pass = InScopeCompletionPass(
       state: state,
       collector: collector,
@@ -179,8 +188,9 @@
       suggestUris: suggestUris,
     );
     pass.computeSuggestions();
-    request.collectorLocationName = collector.completionLocation;
+    state.request.collectorLocationName = collector.completionLocation;
     builder.suggestFromCandidates(collector.suggestions);
+    return pass.notImportedOperations;
   }
 }
 
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 08986fe..e31e789 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
@@ -7,6 +7,7 @@
 import 'package:analysis_server/src/services/completion/dart/candidate_suggestion.dart';
 import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
 import 'package:analysis_server/src/services/completion/dart/completion_state.dart';
+import 'package:analysis_server/src/services/completion/dart/not_imported_completion_pass.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
 import 'package:analysis_server/src/services/completion/dart/visibility_tracker.dart';
 import 'package:analyzer/dart/ast/ast.dart';
@@ -97,6 +98,11 @@
   /// The number of local variables that have already been suggested.
   int _variableDistance = 0;
 
+  /// The operations to be performed in the [NotImportedCompletionPass].
+  ///
+  /// The list will be empty if the pass does not need to be run.
+  List<NotImportedOperation> notImportedOperations = [];
+
   /// Initialize a newly created helper to add suggestions to the [collector]
   /// that are appropriate for the location at the [offset].
   ///
@@ -249,7 +255,7 @@
           !field.isSynthetic &&
           !fieldsToSkip.contains(field) &&
           (!(field.isFinal || field.isConst) || !field.hasInitializer)) {
-        _suggestField(field, containingElement);
+        _suggestField(field: field);
       }
     }
   }
@@ -378,6 +384,7 @@
         if (!skipImports) {
           _addImportedDeclarations(library);
         }
+        _recordOperation(StaticMembersOperation(declarationHelper: this));
       }
     }
     if (topLevelMember != null && !mustBeStatic && !mustBeType) {
@@ -389,16 +396,32 @@
   void addMembersFromExtensionElement(ExtensionElement extension) {
     for (var method in extension.methods) {
       if (!method.isStatic) {
-        _suggestMethod(method, extension);
+        _suggestMethod(method: method);
       }
     }
     for (var accessor in extension.accessors) {
       if (!accessor.isStatic) {
-        _suggestProperty(accessor, extension);
+        _suggestProperty(accessor: accessor);
       }
     }
   }
 
+  /// Add members from all the applicable extensions that are visible in the
+  /// not yet imported [library] that are applicable for the given [type].
+  void addNotImportedExtensionMethods(
+      {required LibraryElement library,
+      required InterfaceType type,
+      required Set<String> excludedGetters,
+      required bool includeMethods,
+      required bool includeSetters}) {}
+
+  /// Adds suggestions for any top-level declarations that are visible within
+  /// the not yet imported [library].
+  void addNotImportedTopLevelDeclarations(LibraryElement library) {
+    // TODO(brianwilkerson): Compute import data for the library and pass it into
+    // _addTopLevelDeclarations(library);
+  }
+
   /// Add any parameters from the super constructor of the constructor
   /// containing the [node] that can be referenced as a super parameter.
   void addParametersFromSuperConstructor(SuperFormalParameter node) {
@@ -636,13 +659,13 @@
       if (includeMethods) {
         for (var method in extension.methods) {
           if (!method.isStatic) {
-            _suggestMethod(method, extension);
+            _suggestMethod(method: method);
           }
         }
       }
       for (var accessor in extension.accessors) {
         if (accessor.isGetter || includeSetters && accessor.isSetter) {
-          _suggestProperty(accessor, extension);
+          _suggestProperty(accessor: accessor);
         }
       }
     }
@@ -760,26 +783,46 @@
       GenericTypeAlias() => containingMember.declaredElement,
       _ => null,
     };
+    if (!mustBeStatic && element is ExtensionElement) {
+      var thisType = element.thisType;
+      if (thisType is InterfaceType) {
+        _addInstanceMembers(
+            type: thisType,
+            excludedGetters: {},
+            includeMethods: true,
+            includeSetters: true);
+      }
+      return;
+    }
     if (element is! InterfaceElement) {
       return;
     }
+    var referencingInterface = _referencingInterfaceFor(element);
     var members = request.inheritanceManager.getInheritedMap2(element);
     for (var member in members.values) {
       switch (member) {
         case MethodElement():
-          _suggestMethod(member, element);
+          _suggestMethod(
+            method: member,
+            referencingInterface: referencingInterface,
+          );
         case PropertyAccessorElement():
-          _suggestProperty(member, element);
+          _suggestProperty(
+            accessor: member,
+            referencingInterface: referencingInterface,
+          );
       }
     }
   }
 
   /// Adds completion suggestions for instance members of the given [type].
   ///
-  /// Suggestions will not be added for any getters whose named are in the set
-  /// of [excludedGetters]. Suggestions for methods will only be added if
-  /// [includeMethods] is `true`. Suggestions for setters will only be added if
-  /// [includeSetters] is `true`.
+  /// Suggestions will not be added for any getters whose names are in the set
+  /// of [excludedGetters].
+  ///
+  /// Suggestions for methods will only be added if [includeMethods] is `true`.
+  ///
+  /// Suggestions for setters will only be added if [includeSetters] is `true`.
   ///
   /// If [onlySuper] is `true`, only valid super members will be suggested.
   void _addInstanceMembers(
@@ -802,6 +845,7 @@
             .add(rawMember);
       }
     }
+    var referencingInterface = _referencingInterfaceFor(type.element);
     for (var entry in membersByName.entries) {
       var members = entry.value;
       var rawMember = members.bestMember;
@@ -809,16 +853,19 @@
         if (includeMethods) {
           // Exclude static methods when completion on an instance.
           var member = ExecutableMember.from2(rawMember, substitution);
-          _suggestMethod(member as MethodElement, member.enclosingElement,
-              ignoreVisibility: true);
+          _suggestMethod(
+            method: member as MethodElement,
+            referencingInterface: referencingInterface,
+          );
         }
       } else if (rawMember is PropertyAccessorElement) {
         if (rawMember.isGetter && !excludedGetters.contains(entry.key) ||
             includeSetters && rawMember.isSetter) {
           var member = ExecutableMember.from2(rawMember, substitution);
           _suggestProperty(
-              member as PropertyAccessorElement, member.enclosingElement,
-              ignoreVisibility: true);
+            accessor: member as PropertyAccessorElement,
+            referencingInterface: referencingInterface,
+          );
         }
       }
     }
@@ -826,12 +873,21 @@
         type.allSupertypes.any((type) => type.isDartCoreFunction)) {
       _suggestFunctionCall(); // from builder
     }
-    // Add members from extensions
+    // Add members from extensions. Members from extensions accessible in the
+    // same library as the completion location are suggested in this pass.
+    // Members from extensions that are not currently imported are suggested in
+    // the second pass.
     _addExtensionMembers(
         type: type,
         excludedGetters: excludedGetters,
         includeMethods: includeMethods,
         includeSetters: includeSetters);
+    _recordOperation(InstanceExtensionMembersOperation(
+        declarationHelper: this,
+        type: type,
+        excludedGetters: excludedGetters,
+        includeMethods: includeMethods,
+        includeSetters: includeSetters));
   }
 
   /// Adds suggestions for any local declarations that are visible at the
@@ -968,25 +1024,35 @@
         includeSetters: true);
   }
 
-  /// Completion is inside the declaration with [element].
+  /// Completion is inside the declaration of the [element].
   void _addMembersOfEnclosingInstance(InstanceElement element) {
     var augmented = element.augmented;
+    var referencingInterface = _referencingInterfaceFor(element);
 
     for (var accessor in augmented.accessors) {
       if (!accessor.isSynthetic && (!mustBeStatic || accessor.isStatic)) {
-        _suggestProperty(accessor, element);
+        _suggestProperty(
+          accessor: accessor,
+          referencingInterface: referencingInterface,
+        );
       }
     }
 
     for (var field in augmented.fields) {
       if (!field.isSynthetic && (!mustBeStatic || field.isStatic)) {
-        _suggestField(field, element);
+        _suggestField(
+          field: field,
+          referencingInterface: referencingInterface,
+        );
       }
     }
 
     for (var method in augmented.methods) {
       if (!mustBeStatic || method.isStatic) {
-        _suggestMethod(method, element);
+        _suggestMethod(
+          method: method,
+          referencingInterface: referencingInterface,
+        );
       }
     }
     _addExtensionMembers(
@@ -1035,7 +1101,7 @@
             _addMembersOfEnclosingInstance(element);
             var fieldElement = declaration.representation.fieldElement;
             if (fieldElement != null) {
-              _suggestField(fieldElement, element);
+              _suggestField(field: fieldElement);
             }
           }
           _suggestTypeParameters(element.typeParameters);
@@ -1073,7 +1139,7 @@
       if (accessor.isStatic &&
           !accessor.isSynthetic &&
           accessor.isVisibleIn(request.libraryElement)) {
-        _suggestProperty(accessor, containingElement);
+        _suggestProperty(accessor: accessor);
       }
     }
     for (var field in fields) {
@@ -1093,7 +1159,7 @@
             collector.addSuggestion(suggestion);
           }
         } else {
-          _suggestField(field, containingElement);
+          _suggestField(field: field);
         }
       }
     }
@@ -1113,7 +1179,7 @@
       }
       for (var method in methods) {
         if (method.isStatic && method.isVisibleIn(request.libraryElement)) {
-          _suggestMethod(method, containingElement);
+          _suggestMethod(method: method);
         }
       }
     }
@@ -1227,6 +1293,25 @@
   /// characters and nothing else.
   bool _isUnused(String identifier) => UnusedIdentifier.hasMatch(identifier);
 
+  /// Record that the given [operation] should be performed in the second pass.
+  void _recordOperation(NotImportedOperation operation) {
+    notImportedOperations.add(operation);
+  }
+
+  /// Returns the interface element for the type of `this` within the
+  /// declaration of the given class-like [element].
+  InterfaceElement? _referencingInterfaceFor(Element element) {
+    if (element is InterfaceElement) {
+      return element;
+    } else if (element is InstanceElement) {
+      var thisElement = element.thisType.element;
+      if (thisElement is InterfaceElement) {
+        return thisElement;
+      }
+    }
+    return null;
+  }
+
   /// Adds a suggestion for the class represented by the [element]. The [prefix]
   /// is the prefix by which the element is imported.
   void _suggestClass(ClassElement element, ImportData? importData) {
@@ -1375,21 +1460,25 @@
     }
   }
 
-  /// Adds a suggestion for the field represented by the [element] contained
-  /// in the [containingElement].
-  void _suggestField(FieldElement element, Element containingElement) {
-    if (visibilityTracker.isVisible(element)) {
-      if ((mustBeAssignable && element.setter == null) ||
-          (mustBeConstant && !element.isConst)) {
+  /// Adds a suggestion for the [field].
+  ///
+  /// The [referencingInterface] is used to compute the inheritance distance to
+  /// an instance member, and should not be provided for static members. If a
+  /// [referencingInterface] is provided, it should be the class in which
+  /// completion was requested.
+  void _suggestField(
+      {required FieldElement field, InterfaceElement? referencingInterface}) {
+    if (visibilityTracker.isVisible(field)) {
+      if ((mustBeAssignable && field.setter == null) ||
+          (mustBeConstant && !field.isConst)) {
         return;
       }
-      var score = state.matcher.score(element.displayName);
+      var score = state.matcher.score(field.displayName);
       if (score != -1) {
         var suggestion = FieldSuggestion(
-            element: element,
+            element: field,
             score: score,
-            referencingClass:
-                (containingElement is ClassElement) ? containingElement : null);
+            referencingInterface: referencingInterface);
         collector.addSuggestion(suggestion);
       }
     }
@@ -1417,28 +1506,33 @@
     collector.addSuggestion(FunctionCall(score: 0));
   }
 
-  /// Adds a suggestion for the method represented by the [element] contained
-  /// in the [containingElement].
+  /// Adds a suggestion for the [method].
   ///
   /// If [ignoreVisibility] is `true` then the visibility tracker will not be
   /// used to determine whether the element is shadowed. This should be used
   /// when suggesting a member accessed through a target.
-  void _suggestMethod(MethodElement element, Element containingElement,
-      {bool ignoreVisibility = false}) {
-    if (visibilityTracker.isVisible(element)) {
+  ///
+  /// The [referencingInterface] is used to compute the inheritance distance to
+  /// an instance member, and should not be provided for static members. If a
+  /// [referencingInterface] is provided, it should be the class in which
+  /// completion was requested.
+  void _suggestMethod(
+      {required MethodElement method,
+      bool ignoreVisibility = false,
+      InterfaceElement? referencingInterface}) {
+    if (ignoreVisibility || visibilityTracker.isVisible(method)) {
       if (mustBeAssignable ||
           mustBeConstant ||
-          (mustBeNonVoid && element.returnType is VoidType)) {
+          (mustBeNonVoid && method.returnType is VoidType)) {
         return;
       }
-      var score = state.matcher.score(element.displayName);
+      var score = state.matcher.score(method.displayName);
       if (score != -1) {
         var suggestion = MethodSuggestion(
             kind: _executableSuggestionKind,
-            element: element,
+            element: method,
             score: score,
-            referencingClass:
-                (containingElement is ClassElement) ? containingElement : null);
+            referencingInterface: referencingInterface);
         collector.addSuggestion(suggestion);
       }
     }
@@ -1484,30 +1578,34 @@
     }
   }
 
-  /// Adds a suggestion for the getter or setter represented by the [element]
-  /// contained in the [containingElement].
+  /// Adds a suggestion for the getter or setter represented by the [accessor].
   ///
   /// If [ignoreVisibility] is `true` then the visibility tracker will not be
   /// used to determine whether the element is shadowed. This should be used
   /// when suggesting a member accessed through a target.
+  ///
+  /// The [referencingInterface] is used to compute the inheritance distance to
+  /// an instance member, and should not be provided for static members. If a
+  /// [referencingInterface] is provided, it should be the class in which
+  /// completion was requested.
   void _suggestProperty(
-      PropertyAccessorElement element, Element containingElement,
-      {bool ignoreVisibility = false}) {
-    if (ignoreVisibility || visibilityTracker.isVisible(element)) {
+      {required PropertyAccessorElement accessor,
+      bool ignoreVisibility = false,
+      InterfaceElement? referencingInterface}) {
+    if (ignoreVisibility || visibilityTracker.isVisible(accessor)) {
       if ((mustBeAssignable &&
-              element.isGetter &&
-              element.correspondingSetter == null) ||
+              accessor.isGetter &&
+              accessor.correspondingSetter == null) ||
           mustBeConstant ||
-          (mustBeNonVoid && element.returnType is VoidType)) {
+          (mustBeNonVoid && accessor.returnType is VoidType)) {
         return;
       }
-      var score = state.matcher.score(element.displayName);
+      var score = state.matcher.score(accessor.displayName);
       if (score != -1) {
         var suggestion = PropertyAccessSuggestion(
-            element: element,
+            element: accessor,
             score: score,
-            referencingClass:
-                (containingElement is ClassElement) ? containingElement : null);
+            referencingInterface: referencingInterface);
         collector.addSuggestion(suggestion);
       }
     }
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
index 156c793..c0bdcce 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/in_scope_completion_pass.dart
@@ -8,6 +8,7 @@
 import 'package:analysis_server/src/services/completion/dart/identifier_helper.dart';
 import 'package:analysis_server/src/services/completion/dart/keyword_helper.dart';
 import 'package:analysis_server/src/services/completion/dart/label_helper.dart';
+import 'package:analysis_server/src/services/completion/dart/not_imported_completion_pass.dart';
 import 'package:analysis_server/src/services/completion/dart/override_helper.dart';
 import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
 import 'package:analysis_server/src/services/completion/dart/uri_helper.dart';
@@ -93,6 +94,12 @@
   /// being computed.
   FeatureSet get featureSet => state.libraryElement.featureSet;
 
+  /// The operation that should be executed in the [NotImportedCompletionPass].
+  ///
+  /// The list will be empty if the pass does not need to be run.
+  List<NotImportedOperation> get notImportedOperations =>
+      _declarationHelper?.notImportedOperations ?? [];
+
   /// The offset at which completion was requested.
   int get offset => state.selection.offset;
 
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
new file mode 100644
index 0000000..3f10132
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_completion_pass.dart
@@ -0,0 +1,172 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:analysis_server/src/services/completion/dart/completion_state.dart';
+import 'package:analysis_server/src/services/completion/dart/declaration_helper.dart';
+import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/analysis/file_state_filter.dart';
+import 'package:analyzer/src/util/performance/operation_performance.dart';
+
+/// An operation run by the [NotImportedCompletionPass] to add the members of
+/// the extensions in a given library that match a known type.
+class InstanceExtensionMembersOperation extends NotImportedOperation {
+  /// The declaration helper to be used to create the candidate suggestions.
+  final DeclarationHelper declarationHelper;
+
+  /// The type that the extensions must extend.
+  final InterfaceType type;
+
+  /// The names of getters that should not be suggested.
+  final Set<String> excludedGetters;
+
+  /// Whether to include suggestions for methods.
+  final bool includeMethods;
+
+  /// Whether to include suggestions for setters.
+  final bool includeSetters;
+
+  InstanceExtensionMembersOperation(
+      {required this.declarationHelper,
+      required this.type,
+      required this.excludedGetters,
+      required this.includeMethods,
+      required this.includeSetters});
+
+  /// Compute any candidate suggestions for elements in the [library].
+  void computeSuggestionsIn(LibraryElement library) {
+    declarationHelper.addNotImportedExtensionMethods(
+        library: library,
+        type: type,
+        excludedGetters: excludedGetters,
+        includeMethods: includeMethods,
+        includeSetters: includeSetters);
+  }
+}
+
+/// A completion pass that will create candidate suggestions based on the
+/// elements that are not in scope in the library containing the selection, but
+/// that could be imported into the scope.
+class NotImportedCompletionPass {
+  /// The state used to compute the candidate suggestions.
+  final CompletionState state;
+
+  /// The suggestion collector to which suggestions will be added.
+  final SuggestionCollector collector;
+
+  /// The operation to be performed for each of the not imported libraries.
+  final List<NotImportedOperation> operations;
+
+  /// Initialize a newly created completion pass.
+  NotImportedCompletionPass(this.state, this.collector, this.operations);
+
+  /// Compute any candidate suggestions for elements in not imported libraries.
+  Future<void> computeSuggestions({
+    required OperationPerformanceImpl performance,
+  }) async {
+    var request = state.request;
+    var budget = state.budget;
+
+    var analysisDriver = request.analysisContext.driver;
+
+    var fsState = analysisDriver.fsState;
+    var filter = FileStateFilter(
+      fsState.getFileForPath(request.path),
+    );
+
+    try {
+      await performance.runAsync('discoverAvailableFiles', (_) async {
+        await analysisDriver.discoverAvailableFiles().timeout(budget.left);
+      });
+    } on TimeoutException {
+      collector.isIncomplete = true;
+      return;
+    }
+
+    var knownFiles = fsState.knownFiles.toList();
+    for (var file in knownFiles) {
+      if (budget.isEmpty) {
+        collector.isIncomplete = true;
+        return;
+      }
+
+      if (!filter.shouldInclude(file)) {
+        continue;
+      }
+
+      var elementResult = await performance.runAsync(
+        'getLibraryByUri',
+        (_) async {
+          return await analysisDriver.getLibraryByUri(file.uriStr);
+        },
+      );
+      if (elementResult is! LibraryElementResult) {
+        continue;
+      }
+
+      for (var operation in operations) {
+        switch (operation) {
+          case InstanceExtensionMembersOperation():
+            performance.run('instanceMembers', (_) {
+              operation.computeSuggestionsIn(elementResult.element);
+            });
+          case StaticMembersOperation():
+            var importedElements = Set<Element>.identity();
+            var importedLibraries = Set<LibraryElement>.identity();
+            for (var import in request.libraryElement.libraryImports) {
+              var importedLibrary = import.importedLibrary;
+              if (importedLibrary != null) {
+                if (import.combinators.isEmpty) {
+                  importedLibraries.add(importedLibrary);
+                } else {
+                  importedElements.addAll(
+                    import.namespace.definedNames.values,
+                  );
+                }
+              }
+            }
+
+            var element = elementResult.element;
+            if (importedLibraries.contains(element)) {
+              continue;
+            }
+
+            var exportNamespace = element.exportNamespace;
+            var exportElements = exportNamespace.definedNames.values.toList();
+
+            performance.run('staticMembers', (_) {
+              operation.computeSuggestionsIn(
+                  elementResult.element, exportElements, importedElements);
+            });
+        }
+      }
+    }
+  }
+}
+
+/// An operation used to process a not imported library in order to add
+/// candidate completion suggestions from the library.
+sealed class NotImportedOperation {}
+
+/// An operation run by the [NotImportedCompletionPass] to add the static
+/// members from a not imported library.
+class StaticMembersOperation extends NotImportedOperation {
+  /// The declaration helper to be used to create the candidate suggestions.
+  final DeclarationHelper declarationHelper;
+
+  /// Initialize a newly created operation to use the [declarationHelper] to add
+  /// the static members from a library.
+  StaticMembersOperation({required this.declarationHelper});
+
+  /// Compute any candidate suggestions for elements in the [library].
+  void computeSuggestionsIn(LibraryElement library,
+      List<Element> exportElements, Set<Element> importedElements) {
+    // TODO(brianwilkerson): Determine whether we need the element parameters.
+    declarationHelper.addNotImportedTopLevelDeclarations(library);
+  }
+}
diff --git a/pkg/analysis_server/test/services/completion/dart/completion_test.dart b/pkg/analysis_server/test/services/completion/dart/completion_test.dart
index e2b6dbf0..f81d097 100644
--- a/pkg/analysis_server/test/services/completion/dart/completion_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/completion_test.dart
@@ -10070,10 +10070,10 @@
 ''');
     assertResponse(r'''
 suggestions
-  fieldA
-    kind: field
   fieldC
     kind: field
+  fieldA
+    kind: field
 ''');
   }
 
@@ -10392,10 +10392,10 @@
 ''');
     assertResponse(r'''
 suggestions
-  fa
-    kind: field
   fb
     kind: field
+  fa
+    kind: field
   ma
     kind: methodInvocation
   mb
diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/extension_member_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/extension_member_test.dart
index d5700b8..413704f 100644
--- a/pkg/analysis_server/test/services/completion/dart/declaration/extension_member_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/declaration/extension_member_test.dart
@@ -222,10 +222,10 @@
 ''');
     assertResponse(r'''
 suggestions
-  b0
-    kind: setter
   a0
     kind: getter
+  b0
+    kind: setter
 ''');
   }
 
diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/imported_reference_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/imported_reference_test.dart
index 5e4c903..dd4a7b6 100644
--- a/pkg/analysis_server/test/services/completion/dart/declaration/imported_reference_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/declaration/imported_reference_test.dart
@@ -5237,18 +5237,18 @@
     kind: field
   _e0
     kind: getter
-  _g0
-    kind: getter
   _s0
     kind: setter
   b0
     kind: field
   d0
     kind: getter
-  f0
-    kind: getter
   s1
     kind: setter
+  _g0
+    kind: getter
+  f0
+    kind: getter
   _n0
     kind: methodInvocation
   m0
@@ -5814,18 +5814,18 @@
     kind: field
   _e0
     kind: getter
-  _g0
-    kind: getter
   _s0
     kind: setter
   b0
     kind: field
   d0
     kind: getter
-  f0
-    kind: getter
   s0
     kind: setter
+  _g0
+    kind: getter
+  f0
+    kind: getter
   _n0
     kind: methodInvocation
   m0
diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart
index ce6eb7a..fed91b4 100644
--- a/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/declaration/local_reference_test.dart
@@ -6493,18 +6493,18 @@
     kind: field
   _e0
     kind: getter
-  _g0
-    kind: getter
   _s0
     kind: setter
   b0
     kind: field
   d0
     kind: getter
-  f0
-    kind: getter
   s1
     kind: setter
+  _g0
+    kind: getter
+  f0
+    kind: getter
   _n0
     kind: methodInvocation
   m0
@@ -7204,18 +7204,18 @@
     kind: field
   _e0
     kind: getter
-  _g0
-    kind: getter
   _s0
     kind: setter
   b0
     kind: field
   d0
     kind: getter
-  f0
-    kind: getter
   s0
     kind: setter
+  _g0
+    kind: getter
+  f0
+    kind: getter
   _n0
     kind: methodInvocation
   m0
diff --git a/pkg/analysis_server/test/services/completion/dart/declaration/type_member_test.dart b/pkg/analysis_server/test/services/completion/dart/declaration/type_member_test.dart
index 1c87573d..4d37608 100644
--- a/pkg/analysis_server/test/services/completion/dart/declaration/type_member_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/declaration/type_member_test.dart
@@ -4394,18 +4394,18 @@
     kind: field
   _e0
     kind: getter
-  _g0
-    kind: getter
   _s0
     kind: setter
   b0
     kind: field
   d0
     kind: getter
-  f0
-    kind: getter
   s1
     kind: setter
+  _g0
+    kind: getter
+  f0
+    kind: getter
   _n0
     kind: methodInvocation
   m0
@@ -5275,10 +5275,10 @@
 ''');
     assertResponse(r'''
 suggestions
-  f2
-    kind: field
   m4
     kind: methodInvocation
+  f2
+    kind: field
   m2
     kind: methodInvocation
 ''');
@@ -5457,18 +5457,18 @@
     kind: field
   _e0
     kind: getter
-  _g0
-    kind: getter
   _s0
     kind: setter
   b0
     kind: field
   d0
     kind: getter
-  f0
-    kind: getter
   s0
     kind: setter
+  _g0
+    kind: getter
+  f0
+    kind: getter
   _n0
     kind: methodInvocation
   m0