[analysis_server] Validate server includes all used commands in ServerCapabilities

Change-Id: I03855b7528d8199ceb140b0f0c59dd21c025dc90
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/309260
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/constants.dart b/pkg/analysis_server/lib/src/lsp/constants.dart
index 1516405..24536d6 100644
--- a/pkg/analysis_server/lib/src/lsp/constants.dart
+++ b/pkg/analysis_server/lib/src/lsp/constants.dart
@@ -61,6 +61,7 @@
     fixAll,
     sendWorkspaceEdit,
     performRefactor,
+    validateRefactor,
     logAction,
     // Add commands for each of the new refactorings.
     ...RefactoringProcessor.generators.keys,
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/dart.dart b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/dart.dart
index f73911e..a54d1bc5 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/code_actions/dart.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/code_actions/dart.dart
@@ -56,21 +56,26 @@
     CodeActionKind actionKind,
     String title,
     String command,
-  ) =>
-      _commandOrCodeAction(
-        actionKind,
-        Command(
-          title: title,
-          command: command,
-          arguments: [
-            {
-              'path': path,
-              if (triggerKind == CodeActionTriggerKind.Automatic)
-                'autoTriggered': true,
-            }
-          ],
-        ),
-      );
+  ) {
+    assert(
+      (() => Commands.serverSupportedCommands.contains(command))(),
+      'serverSupportedCommands did not contain $command',
+    );
+    return _commandOrCodeAction(
+      actionKind,
+      Command(
+        title: title,
+        command: command,
+        arguments: [
+          {
+            'path': path,
+            if (triggerKind == CodeActionTriggerKind.Automatic)
+              'autoTriggered': true,
+          }
+        ],
+      ),
+    );
+  }
 
   /// Helper to create refactors that execute commands provided with
   /// the current file, location and document version.
@@ -79,24 +84,31 @@
     String name,
     RefactoringKind refactorKind, [
     Map<String, dynamic>? options,
-  ]) =>
-      _commandOrCodeAction(
-          actionKind,
-          Command(
-            title: name,
-            command: Commands.performRefactor,
-            arguments: [
-              // TODO(dantup): Change this to a single entry that is a Map once
-              //  enough time has passed that old versions of Dart-Code prior to
-              //  to June 2022 need not be supported against newer SDKs.
-              refactorKind.toJson(),
-              path,
-              docIdentifier.version,
-              offset,
-              length,
-              options,
-            ],
-          ));
+  ]) {
+    final command = Commands.performRefactor;
+    assert(
+      (() => Commands.serverSupportedCommands.contains(command))(),
+      'serverSupportedCommands did not contain $command',
+    );
+
+    return _commandOrCodeAction(
+        actionKind,
+        Command(
+          title: name,
+          command: command,
+          arguments: [
+            // TODO(dantup): Change this to a single entry that is a Map once
+            //  enough time has passed that old versions of Dart-Code prior to
+            //  to June 2022 need not be supported against newer SDKs.
+            refactorKind.toJson(),
+            path,
+            docIdentifier.version,
+            offset,
+            length,
+            options,
+          ],
+        ));
+  }
 
   @override
   Future<List<CodeActionWithPriority>> getAssistActions() async {
diff --git a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart
index 1c8ee12..0fe8680 100644
--- a/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart
+++ b/pkg/analysis_server/lib/src/services/refactoring/framework/refactoring_processor.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
+import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/services/refactoring/convert_formal_parameters.dart';
 import 'package:analysis_server/src/services/refactoring/framework/refactoring_context.dart';
 import 'package:analysis_server/src/services/refactoring/framework/refactoring_producer.dart';
@@ -54,12 +55,18 @@
         'that are not supported by the client',
       );
 
+      final command = entry.key;
+      assert(
+        (() => Commands.serverSupportedCommands.contains(command))(),
+        'serverSupportedCommands did not contain $command',
+      );
+
       refactorings.add(
         CodeAction(
             title: producer.title,
             kind: producer.kind,
             command: Command(
-              command: entry.key,
+              command: command,
               title: producer.title,
               arguments: [
                 {
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 7927660..2bfda56 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -726,6 +726,12 @@
   /// null if an initialization request has not yet been sent.
   ClientCapabilities? _clientCapabilities;
 
+  /// The capabilities returned from the server during initialization.
+  ///
+  /// `null` if the server is not initialized, or returned an error during
+  /// initialize.
+  ServerCapabilities? _serverCapabilities;
+
   final validProgressTokens = <ProgressToken>{};
 
   /// Whether to include 'clientRequestTime' fields in outgoing messages.
@@ -986,6 +992,12 @@
     T Function(Map<String, Object?>)? decoder,
     ProgressToken? workDoneToken,
   }) async {
+    final supportedCommands =
+        _serverCapabilities?.executeCommandProvider?.commands ?? [];
+    if (!supportedCommands.contains(command.command)) {
+      throw ArgumentError('Server does not support ${command.command}. '
+          'Is it missing from serverSupportedCommands?');
+    }
     final request = makeRequest(
       Method.workspace_executeCommand,
       ExecuteCommandParams(
@@ -1610,6 +1622,10 @@
 
     final error = response.error;
     if (error == null) {
+      final result =
+          InitializeResult.fromJson(response.result as Map<String, Object?>);
+      _serverCapabilities = result.capabilities;
+
       final notification =
           makeNotification(Method.initialized, InitializedParams());
       await sendNotificationToServer(notification);