Transition DAS to analysis / idle when setAnalysisRoots with no Dart files.

Bug: https://github.com/dart-lang/sdk/issues/40096
Change-Id: I84b83d374e325f1c3c90be30c5c33ed6007b8849
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/136921
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 07829e6..df1d016 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -475,6 +475,7 @@
       throw RequestFailure(Response.unsupportedFeature(requestId, e.message));
     }
     addContextsToDeclarationsTracker();
+    analysisDriverScheduler.transitionToAnalyzingToIdleIfNoFilesToAnalyze();
   }
 
   /// Implementation for `analysis.setSubscriptions`.
diff --git a/pkg/analysis_server/test/analysis/update_content_test.dart b/pkg/analysis_server/test/analysis/update_content_test.dart
index ddfe39d..6e794cd 100644
--- a/pkg/analysis_server/test/analysis/update_content_test.dart
+++ b/pkg/analysis_server/test/analysis/update_content_test.dart
@@ -186,8 +186,8 @@
   @failingTest
   Future<void> test_sendNoticesAfterNopChange() async {
     // The errors are empty on the last line.
-    createProject();
     addTestFile('');
+    createProject();
     await server.onAnalysisComplete;
     // add an overlay
     server.updateContent(
@@ -206,8 +206,8 @@
   @failingTest
   Future<void> test_sendNoticesAfterNopChange_flushedUnit() async {
     // The list of errors is empty on the last line.
-    createProject();
     addTestFile('');
+    createProject();
     await server.onAnalysisComplete;
     // add an overlay
     server.updateContent(
diff --git a/pkg/analysis_server/test/analysis_server_test.dart b/pkg/analysis_server/test/analysis_server_test.dart
index 4640cbb..e966735 100644
--- a/pkg/analysis_server/test/analysis_server_test.dart
+++ b/pkg/analysis_server/test/analysis_server_test.dart
@@ -98,33 +98,64 @@
     });
   }
 
-  Future test_serverStatusNotifications() {
+  Future test_serverStatusNotifications_hasFile() async {
     server.serverServices.add(ServerService.STATUS);
-    var pkgFolder = convertPath('/pkg');
-    newFolder(pkgFolder);
-    newFolder(join(pkgFolder, 'lib'));
-    newFile(join(pkgFolder, 'lib', 'test.dart'), content: 'class C {}');
-    server.setAnalysisRoots('0', [pkgFolder], [], {});
-    // Pump the event queue to make sure the server has finished any
-    // analysis.
-    return pumpEventQueue(times: 5000).then((_) {
-      List<Notification> notifications = channel.notificationsReceived;
-      expect(notifications, isNotEmpty);
-      // expect at least one notification indicating analysis is in progress
-      expect(notifications.any((Notification notification) {
-        if (notification.event == SERVER_NOTIFICATION_STATUS) {
-          var params = ServerStatusParams.fromNotification(notification);
-          if (params.analysis != null) {
-            return params.analysis.isAnalyzing;
-          }
+
+    newFile('/test/lib/a.dart', content: r'''
+class A {}
+''');
+    server.setAnalysisRoots('0', [convertPath('/test')], [], {});
+
+    // Pump the event queue, so that the server has finished any analysis.
+    await pumpEventQueue(times: 5000);
+
+    var notifications = channel.notificationsReceived;
+    expect(notifications, isNotEmpty);
+
+    // At least one notification indicating analysis is in progress.
+    expect(notifications.any((Notification notification) {
+      if (notification.event == SERVER_NOTIFICATION_STATUS) {
+        var params = ServerStatusParams.fromNotification(notification);
+        if (params.analysis != null) {
+          return params.analysis.isAnalyzing;
         }
-        return false;
-      }), isTrue);
-      // the last notification should indicate that analysis is complete
-      Notification notification = notifications[notifications.length - 1];
-      var params = ServerStatusParams.fromNotification(notification);
-      expect(params.analysis.isAnalyzing, isFalse);
-    });
+      }
+      return false;
+    }), isTrue);
+
+    // The last notification should indicate that analysis is complete.
+    var notification = notifications[notifications.length - 1];
+    var params = ServerStatusParams.fromNotification(notification);
+    expect(params.analysis.isAnalyzing, isFalse);
+  }
+
+  Future test_serverStatusNotifications_noFiles() async {
+    server.serverServices.add(ServerService.STATUS);
+
+    newFolder('/test');
+    server.setAnalysisRoots('0', [convertPath('/test')], [], {});
+
+    // Pump the event queue, so that the server has finished any analysis.
+    await pumpEventQueue(times: 5000);
+
+    var notifications = channel.notificationsReceived;
+    expect(notifications, isNotEmpty);
+
+    // At least one notification indicating analysis is in progress.
+    expect(notifications.any((Notification notification) {
+      if (notification.event == SERVER_NOTIFICATION_STATUS) {
+        var params = ServerStatusParams.fromNotification(notification);
+        if (params.analysis != null) {
+          return params.analysis.isAnalyzing;
+        }
+      }
+      return false;
+    }), isTrue);
+
+    // The last notification should indicate that analysis is complete.
+    var notification = notifications[notifications.length - 1];
+    var params = ServerStatusParams.fromNotification(notification);
+    expect(params.analysis.isAnalyzing, isFalse);
   }
 
   Future<void>
@@ -142,7 +173,7 @@
     });
 
     // We respect subscriptions, even for excluded files.
-    await server.onAnalysisComplete;
+    await pumpEventQueue();
     expect(channel.notificationsReceived.any((notification) {
       return notification.event == ANALYSIS_NOTIFICATION_NAVIGATION;
     }), isTrue);
@@ -163,7 +194,7 @@
     });
 
     // We respect subscriptions, even for excluded files.
-    await server.onAnalysisComplete;
+    await pumpEventQueue();
     expect(channel.notificationsReceived.any((notification) {
       return notification.event == ANALYSIS_NOTIFICATION_NAVIGATION;
     }), isTrue);
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index 9064ac6..79b998e 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -1943,6 +1943,17 @@
     _run();
   }
 
+  /// Usually we transition status to analyzing only if there are files to
+  /// analyze. However when used in the server, there are rare cases when
+  /// analysis roots don't have any Dart files, but for consistency we still
+  /// want to get status to transition to analysis, and back to idle.
+  void transitionToAnalyzingToIdleIfNoFilesToAnalyze() {
+    if (!_hasFilesToAnalyze) {
+      _statusSupport.transitionToAnalyzing();
+      _statusSupport.transitionToIdle();
+    }
+  }
+
   /// Return a future that will be completed the next time the status is idle.
   ///
   /// If the status is currently idle, the returned future will be signaled