Abort not-yet-imported suggestions on a new completion request.

Change-Id: Id08b7ca328253ea5190038d2204cbfb2015aac01
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/218541
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 85c3ac9..9936e74 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -97,13 +97,9 @@
         librariesToImport: librariesToImport,
       );
 
-      try {
-        suggestions.addAll(
-          await manager.computeSuggestions(request, performance),
-        );
-      } on AbortCompletion {
-        suggestions.clear();
-      }
+      suggestions.addAll(
+        await manager.computeSuggestions(request, performance),
+      );
     });
     // TODO (danrubel) if request is obsolete (processAnalysisRequest returns
     // false) then send empty results
@@ -261,14 +257,28 @@
         ),
         documentationCache: server.getDocumentationCacheFor(resolvedUnit),
       );
+      setNewRequest(completionRequest);
 
       var librariesToImport = <Uri>[];
-      var suggestions = await computeSuggestions(
-        budget: budget,
-        performance: performance,
-        request: completionRequest,
-        librariesToImport: librariesToImport,
-      );
+      var suggestions = <CompletionSuggestion>[];
+      try {
+        suggestions = await computeSuggestions(
+          budget: budget,
+          performance: performance,
+          request: completionRequest,
+          librariesToImport: librariesToImport,
+        );
+      } on AbortCompletion {
+        return server.sendResponse(
+          CompletionGetSuggestions2Result(
+            completionRequest.replacementOffset,
+            completionRequest.replacementLength,
+            [],
+            [],
+            true,
+          ).toResponse(request.id),
+        );
+      }
 
       performance.run('filter', (performance) {
         performance.getDataInt('count').add(suggestions.length);
@@ -436,14 +446,19 @@
 
       // Compute suggestions in the background
       try {
-        var suggestions = await computeSuggestions(
-          budget: budget,
-          performance: perf,
-          request: completionRequest,
-          includedElementKinds: includedElementKinds,
-          includedElementNames: includedElementNames,
-          includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
-        );
+        var suggestions = <CompletionSuggestion>[];
+        try {
+          suggestions = await computeSuggestions(
+            budget: budget,
+            performance: perf,
+            request: completionRequest,
+            includedElementKinds: includedElementKinds,
+            includedElementNames: includedElementNames,
+            includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
+          );
+        } on AbortCompletion {
+          // Continue with empty suggestions list.
+        }
         String? libraryFile;
         var includedSuggestionSets = <IncludedSuggestionSet>[];
         if (includedElementKinds != null && includedElementNames != null) {
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 4fba896..938228c 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
@@ -16,9 +16,14 @@
 import 'package:analyzer/src/lint/pub.dart';
 import 'package:analyzer/src/workspace/pub.dart';
 import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
 
 /// A contributor of suggestions from not yet imported libraries.
 class NotImportedContributor extends DartCompletionContributor {
+  /// Tests set this function to abort the current request.
+  @visibleForTesting
+  static void Function(FileState)? onFile;
+
   final CompletionBudget budget;
   final List<Uri> librariesToImport;
 
@@ -49,6 +54,9 @@
 
     var knownFiles = fsState.knownFiles.toList();
     for (var file in knownFiles) {
+      onFile?.call(file);
+      request.checkAborted();
+
       if (budget.isEmpty) {
         return;
       }
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index ca7daef..5021221 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.dart
@@ -2,6 +2,8 @@
 // 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/analysis_server.dart';
 import 'package:analysis_server/src/domain_analysis.dart';
 import 'package:analysis_server/src/domain_completion.dart';
@@ -9,6 +11,7 @@
 import 'package:analysis_server/src/protocol_server.dart';
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
 import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
+import 'package:analysis_server/src/services/completion/dart/not_imported_contributor.dart';
 import 'package:analysis_server/src/utilities/mocks.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/instrumentation/service.dart';
@@ -95,6 +98,42 @@
     completionDomain.budgetDuration = const Duration(seconds: 30);
   }
 
+  void tearDown() {
+    NotImportedContributor.onFile = null;
+  }
+
+  Future<void> test_notImported_abort() async {
+    await _configureWithWorkspaceRoot();
+
+    NotImportedContributor.onFile = (file) {
+      if (file.uriStr == 'dart:math') {
+        unawaited(
+          _getSuggestions(
+            path: convertPath(testFilePath),
+            completionOffset: 0,
+            maxResults: 100,
+          ),
+        );
+      }
+    };
+
+    var responseValidator = await _getTestCodeSuggestions('''
+void f() {
+  Rand^
+}
+''');
+
+    responseValidator
+      ..assertIncomplete()
+      ..assertReplacementBack(4)
+      ..assertLibrariesToImport(includes: [], excludes: [
+        'dart:core',
+        'dart:math',
+      ]);
+
+    responseValidator.suggestions.assertEmpty();
+  }
+
   Future<void> test_notImported_emptyBudget() async {
     await _configureWithWorkspaceRoot();