Always create DeclarationsTracker in AnalysisServer.

R=brianwilkerson@google.com

Change-Id: I1de49423d921da1fdf78ecd480a26e4577efcfe9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/101480
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index c60be02..4eb8f59 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -149,7 +149,9 @@
 
   ByteStore byteStore;
   nd.AnalysisDriverScheduler analysisDriverScheduler;
+
   DeclarationsTracker declarationsTracker;
+  DeclarationsTrackerData declarationsTrackerData;
 
   /// The controller for [onAnalysisSetChanged].
   final StreamController _onAnalysisSetChangedController =
@@ -202,13 +204,20 @@
       }
       _analysisPerformanceLogger = new PerformanceLog(sink);
     }
+
     byteStore = createByteStore(resourceProvider);
+
     analysisDriverScheduler = new nd.AnalysisDriverScheduler(
         _analysisPerformanceLogger,
         driverWatcher: pluginWatcher);
     analysisDriverScheduler.status.listen(sendStatusNotificationNew);
     analysisDriverScheduler.start();
 
+    declarationsTracker = DeclarationsTracker(byteStore, resourceProvider);
+    declarationsTrackerData = DeclarationsTrackerData(declarationsTracker);
+    analysisDriverScheduler.outOfBandWorker =
+        CompletionLibrariesWorker(declarationsTracker);
+
     contextManager = new ContextManagerImpl(
         resourceProvider,
         sdkManager,
@@ -276,27 +285,6 @@
     return _onAnalysisStartedController.stream;
   }
 
-  void createDeclarationsTracker(void Function(LibraryChange) listener) {
-    if (declarationsTracker != null) return;
-
-    declarationsTracker = DeclarationsTracker(byteStore, resourceProvider);
-    declarationsTracker.changes.listen(listener);
-
-    _addContextsToDeclarationsTracker();
-
-    // Configure the scheduler to run the tracker.
-    analysisDriverScheduler.outOfBandWorker =
-        CompletionLibrariesWorker(declarationsTracker);
-
-    // We might have done running drivers work, so ask the scheduler to check.
-    analysisDriverScheduler.notify(null);
-  }
-
-  void disposeDeclarationsTracker() {
-    declarationsTracker = null;
-    analysisDriverScheduler.outOfBandWorker = null;
-  }
-
   /// The socket from which requests are being read has been closed.
   void done() {}
 
@@ -389,10 +377,8 @@
   /// Notify the declarations tracker that the file with the given [path] was
   /// changed - added, updated, or removed.  Schedule processing of the file.
   void notifyDeclarationsTracker(String path) {
-    if (declarationsTracker != null) {
-      declarationsTracker.changeFile(path);
-      analysisDriverScheduler.notify(null);
-    }
+    declarationsTracker.changeFile(path);
+    analysisDriverScheduler.notify(null);
   }
 
   /// Read all files, resolve all URIs, and perform required analysis in
@@ -513,7 +499,7 @@
   /// projects/contexts support.
   void setAnalysisRoots(String requestId, List<String> includedPaths,
       List<String> excludedPaths, Map<String, String> packageRoots) {
-    declarationsTracker?.discardContexts();
+    declarationsTracker.discardContexts();
     if (notificationManager != null) {
       notificationManager.setAnalysisRoots(includedPaths, excludedPaths);
     }
@@ -702,11 +688,9 @@
   }
 
   void _addContextsToDeclarationsTracker() {
-    if (declarationsTracker != null) {
-      for (var driver in driverMap.values) {
-        declarationsTracker.addContext(driver.analysisContext);
-        driver.resetUriResolution();
-      }
+    for (var driver in driverMap.values) {
+      declarationsTracker.addContext(driver.analysisContext);
+      driver.resetUriResolution();
     }
   }
 
diff --git a/pkg/analysis_server/lib/src/domain_analysis.dart b/pkg/analysis_server/lib/src/domain_analysis.dart
index 53a2d7f..e967188 100644
--- a/pkg/analysis_server/lib/src/domain_analysis.dart
+++ b/pkg/analysis_server/lib/src/domain_analysis.dart
@@ -513,7 +513,7 @@
   DartdocDirectiveInfo _getDartdocDirectiveInfoFor(ResolvedUnitResult result) {
     // TODO(brianwilkerson) Consider moving this to AnalysisServer.
     return server.declarationsTracker
-            ?.getContext(result.session.analysisContext)
+            .getContext(result.session.analysisContext)
             ?.dartdocDirectiveInfo ??
         new DartdocDirectiveInfo();
   }
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index ad3b6d2..26461fc 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -477,13 +477,23 @@
     subscriptions.addAll(params.subscriptions);
 
     if (subscriptions.contains(CompletionService.AVAILABLE_SUGGESTION_SETS)) {
-      server.createDeclarationsTracker((change) {
+      var data = server.declarationsTrackerData;
+      var soFarLibraries = data.startListening((change) {
         server.sendNotification(
-          createCompletionAvailableSuggestionsNotification(change),
+          createCompletionAvailableSuggestionsNotification(
+            change.changed,
+            change.removed,
+          ),
         );
       });
+      server.sendNotification(
+        createCompletionAvailableSuggestionsNotification(
+          soFarLibraries,
+          [],
+        ),
+      );
     } else {
-      server.disposeDeclarationsTracker();
+      server.declarationsTrackerData.stopListening();
     }
 
     return CompletionSetSubscriptionsResult().toResponse(request.id);
diff --git a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
index bbe879f..bee0613 100644
--- a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
+++ b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
@@ -68,13 +68,14 @@
 
 /// Convert the [LibraryChange] into the corresponding protocol notification.
 protocol.Notification createCompletionAvailableSuggestionsNotification(
-  LibraryChange change,
+  List<Library> changed,
+  List<int> removed,
 ) {
   return protocol.CompletionAvailableSuggestionsParams(
-    changedLibraries: change.changed.map((library) {
+    changedLibraries: changed.map((library) {
       return _protocolAvailableSuggestionSet(library);
     }).toList(),
-    removedLibraries: change.removed,
+    removedLibraries: removed,
   ).toNotification();
 }
 
@@ -219,3 +220,55 @@
     tracker.doWork();
   }
 }
+
+class DeclarationsTrackerData {
+  final DeclarationsTracker _tracker;
+
+  /// The set of libraries reported by [_tracker] so far.
+  ///
+  /// We create [_tracker] at the server start, but the completion domain
+  /// should send available declarations only when the corresponding
+  /// subscription is done. OTOH, we don't want the changes stream grow
+  /// infinitely as the same libraries are changed multiple times. So, we drain
+  /// the changes stream in this map, and send it at subscription.
+  final Map<int, Library> _idToLibrary = {};
+
+  /// When the completion domain subscribes for changes, we start redirecting
+  /// changes to this listener.
+  void Function(LibraryChange) _listener = null;
+
+  DeclarationsTrackerData(this._tracker) {
+    _tracker.changes.listen((change) {
+      if (_listener != null) {
+        _listener(change);
+      } else {
+        for (var library in change.changed) {
+          _idToLibrary[library.id] = library;
+        }
+        for (var id in change.removed) {
+          _idToLibrary.remove(id);
+        }
+      }
+    });
+  }
+
+  /// Start listening for available libraries, and return the libraries that
+  /// were accumulated so far.
+  List<Library> startListening(void Function(LibraryChange) listener) {
+    if (_listener != null) {
+      throw StateError('Already listening.');
+    }
+    _listener = listener;
+
+    var accumulatedLibraries = _idToLibrary.values.toList();
+    _idToLibrary.clear();
+    return accumulatedLibraries;
+  }
+
+  void stopListening() {
+    if (_listener == null) {
+      throw StateError('Not listening.');
+    }
+    _listener = null;
+  }
+}