Version 2.14.0-369.0.dev

Merge commit '2a99af9bc93e8fec4f4746168803fdeaff239c7b' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index 8ccb4bf..9f7152c 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -4,12 +4,11 @@
 
 import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
 import 'package:analysis_server/lsp_protocol/protocol_special.dart';
-import 'package:analysis_server/plugin/edit/assist/assist_core.dart';
-import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
 import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/lsp/handlers/handlers.dart';
 import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
 import 'package:analysis_server/src/lsp/mapping.dart';
+import 'package:analysis_server/src/plugin/plugin_manager.dart';
 import 'package:analysis_server/src/protocol_server.dart' hide Position;
 import 'package:analysis_server/src/services/correction/assist.dart';
 import 'package:analysis_server/src/services/correction/assist_internal.dart';
@@ -22,13 +21,36 @@
 import 'package:analyzer/dart/analysis/session.dart'
     show InconsistentAnalysisException;
 import 'package:analyzer/dart/element/element.dart';
-import 'package:analyzer/error/error.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
+import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 import 'package:collection/collection.dart' show groupBy;
 
 class CodeActionHandler extends MessageHandler<CodeActionParams,
     List<Either2<Command, CodeAction>>> {
+  // Because server+plugin results are different types and we lose
+  // priorites when converting them to CodeActions, store the priorities
+  // against each action in an expando. This avoids wrapping CodeActions in
+  // another wrapper class (since we can't modify the LSP-spec-generated
+  // CodeAction class).
+  final codeActionPriorities = Expando<int>();
+
+  /// A comparator that can be used to sort [CodeActions]s using priorties
+  /// in [codeActionPriorities]. The highest number priority will be sorted
+  /// before lower number priorityies. Items with the same relevance are sorted
+  /// alphabetically by their title.
+  late final Comparator<CodeAction> _codeActionComparator =
+      (CodeAction a, CodeAction b) {
+    // We should never be sorting actions without priorities.
+    final aPriority = codeActionPriorities[a] ?? 0;
+    final bPriority = codeActionPriorities[b] ?? 0;
+    if (aPriority != bPriority) {
+      return bPriority - aPriority;
+    }
+    return a.title.compareTo(b.title);
+  };
+
   CodeActionHandler(LspAnalysisServer server) : super(server);
 
   @override
@@ -147,12 +169,12 @@
   /// version of each document being modified so it's important to call this
   /// immediately after computing edits to ensure the document is not modified
   /// before the version number is read.
-  CodeAction _createAssistAction(Assist assist) {
+  CodeAction _createAssistAction(SourceChange change) {
     return CodeAction(
-      title: assist.change.message,
-      kind: toCodeActionKind(assist.change.id, CodeActionKind.Refactor),
+      title: change.message,
+      kind: toCodeActionKind(change.id, CodeActionKind.Refactor),
       diagnostics: const [],
-      edit: createWorkspaceEdit(server, assist.change),
+      edit: createWorkspaceEdit(server, change),
     );
   }
 
@@ -160,12 +182,12 @@
   /// version of each document being modified so it's important to call this
   /// immediately after computing edits to ensure the document is not modified
   /// before the version number is read.
-  CodeAction _createFixAction(Fix fix, Diagnostic diagnostic) {
+  CodeAction _createFixAction(SourceChange change, Diagnostic diagnostic) {
     return CodeAction(
-      title: fix.change.message,
-      kind: toCodeActionKind(fix.change.id, CodeActionKind.QuickFix),
+      title: change.message,
+      kind: toCodeActionKind(change.id, CodeActionKind.QuickFix),
       diagnostics: [diagnostic],
-      edit: createWorkspaceEdit(server, fix.change),
+      edit: createWorkspaceEdit(server, change),
     );
   }
 
@@ -222,6 +244,7 @@
   Future<List<Either2<Command, CodeAction>>> _getAssistActions(
     bool Function(CodeActionKind?) shouldIncludeKind,
     bool supportsLiteralCodeActions,
+    String path,
     Range range,
     int offset,
     int length,
@@ -236,13 +259,28 @@
         length,
       );
       final processor = AssistProcessor(context);
-      final assists = await processor.compute();
-      assists.sort(Assist.SORT_BY_RELEVANCE);
+      final serverFuture = processor.compute();
+      final pluginFuture = _getPluginAssistChanges(path, offset, length);
 
-      final assistActions =
-          _dedupeActions(assists.map(_createAssistAction), range.start);
+      final assists = await serverFuture;
+      final pluginChanges = await pluginFuture;
 
-      return assistActions
+      final codeActions = <CodeAction>[];
+      codeActions.addAll(assists.map((assist) {
+        final action = _createAssistAction(assist.change);
+        codeActionPriorities[action] = assist.kind.priority;
+        return action;
+      }));
+      codeActions.addAll(pluginChanges.map((change) {
+        final action = _createAssistAction(change.change);
+        codeActionPriorities[action] = change.priority;
+        return action;
+      }));
+
+      final dedupedCodeActions = _dedupeActions(codeActions, range.start);
+      dedupedCodeActions.sort(_codeActionComparator);
+
+      return dedupedCodeActions
           .where((action) => shouldIncludeKind(action.kind))
           .map((action) => Either2<Command, CodeAction>.t2(action))
           .toList();
@@ -268,11 +306,11 @@
     final results = await Future.wait([
       _getSourceActions(shouldIncludeKind, supportsLiterals,
           supportsWorkspaceApplyEdit, path),
-      _getAssistActions(
-          shouldIncludeKind, supportsLiterals, range, offset, length, unit),
+      _getAssistActions(shouldIncludeKind, supportsLiterals, path, range,
+          offset, length, unit),
       _getRefactorActions(
           shouldIncludeKind, supportsLiterals, path, offset, length, unit),
-      _getFixActions(shouldIncludeKind, supportsLiterals,
+      _getFixActions(shouldIncludeKind, supportsLiterals, path, offset,
           supportedDiagnosticTags, range, unit),
     ]);
     final flatResults = results.expand((x) => x).toList();
@@ -283,22 +321,23 @@
   Future<List<Either2<Command, CodeAction>>> _getFixActions(
     bool Function(CodeActionKind?) shouldIncludeKind,
     bool supportsLiteralCodeActions,
+    String path,
+    int offset,
     Set<DiagnosticTag> supportedDiagnosticTags,
     Range range,
     ResolvedUnitResult unit,
   ) async {
+    final clientSupportsCodeDescription =
+        server.clientCapabilities?.diagnosticCodeDescription ?? false;
+    // TODO(dantup): We may be missing fixes for pubspec, analysis_options,
+    //   android manifests (see _computeServerErrorFixes in EditDomainHandler).
     final lineInfo = unit.lineInfo;
     final codeActions = <CodeAction>[];
     final fixContributor = DartFixContributor();
 
-    try {
-      final errorCodeCounts = <ErrorCode, int>{};
-      // Count the errors by code so we know whether to include a fix-all.
-      for (final error in unit.errors) {
-        errorCodeCounts[error.errorCode] =
-            (errorCodeCounts[error.errorCode] ?? 0) + 1;
-      }
+    final pluginFuture = _getPluginFixActions(unit, offset);
 
+    try {
       for (final error in unit.errors) {
         // Server lineNumber is one-based so subtract one.
         var errorLine = lineInfo.getLocation(error.offset).lineNumber - 1;
@@ -317,22 +356,44 @@
         }, extensionCache: server.getExtensionCacheFor(unit));
         final fixes = await fixContributor.computeFixes(context);
         if (fixes.isNotEmpty) {
-          fixes.sort(Fix.SORT_BY_RELEVANCE);
-
           final diagnostic = toDiagnostic(
             unit,
             error,
             supportedTags: supportedDiagnosticTags,
-            clientSupportsCodeDescription:
-                server.clientCapabilities?.diagnosticCodeDescription ?? false,
+            clientSupportsCodeDescription: clientSupportsCodeDescription,
           );
           codeActions.addAll(
-            fixes.map((fix) => _createFixAction(fix, diagnostic)),
+            fixes.map((fix) {
+              final action = _createFixAction(fix.change, diagnostic);
+              codeActionPriorities[action] = fix.kind.priority;
+              return action;
+            }),
           );
         }
       }
 
+      Diagnostic pluginErrorToDiagnostic(AnalysisError error) {
+        return pluginToDiagnostic(
+          (_) => lineInfo,
+          error,
+          supportedTags: supportedDiagnosticTags,
+          clientSupportsCodeDescription: clientSupportsCodeDescription,
+        );
+      }
+
+      final pluginFixes = await pluginFuture;
+      final pluginFixActions = pluginFixes.expand(
+        (fix) => fix.fixes.map((fixChange) {
+          final action = _createFixAction(
+              fixChange.change, pluginErrorToDiagnostic(fix.error));
+          codeActionPriorities[action] = fixChange.priority;
+          return action;
+        }),
+      );
+      codeActions.addAll(pluginFixActions);
+
       final dedupedActions = _dedupeActions(codeActions, range.start);
+      dedupedActions.sort(_codeActionComparator);
 
       return dedupedActions
           .where((action) => shouldIncludeKind(action.kind))
@@ -346,6 +407,61 @@
     }
   }
 
+  Future<Iterable<plugin.PrioritizedSourceChange>> _getPluginAssistChanges(
+      String path, int offset, int length) async {
+    final requestParams = plugin.EditGetAssistsParams(path, offset, length);
+    final driver = server.getAnalysisDriver(path);
+
+    Map<PluginInfo, Future<plugin.Response>> pluginFutures;
+    if (driver == null) {
+      pluginFutures = <PluginInfo, Future<plugin.Response>>{};
+    } else {
+      pluginFutures = server.pluginManager.broadcastRequest(
+        requestParams,
+        contextRoot: driver.analysisContext!.contextRoot,
+      );
+    }
+
+    final pluginChanges = <plugin.PrioritizedSourceChange>[];
+    final responses =
+        await waitForResponses(pluginFutures, requestParameters: requestParams);
+
+    for (final response in responses) {
+      final result = plugin.EditGetAssistsResult.fromResponse(response);
+      pluginChanges.addAll(result.assists);
+    }
+
+    return pluginChanges;
+  }
+
+  Future<Iterable<plugin.AnalysisErrorFixes>> _getPluginFixActions(
+      ResolvedUnitResult unit, int offset) async {
+    final file = unit.path;
+    final requestParams = plugin.EditGetFixesParams(file, offset);
+    final driver = server.getAnalysisDriver(file);
+
+    Map<PluginInfo, Future<plugin.Response>> pluginFutures;
+    if (driver == null) {
+      pluginFutures = <PluginInfo, Future<plugin.Response>>{};
+    } else {
+      pluginFutures = server.pluginManager.broadcastRequest(
+        requestParams,
+        contextRoot: driver.analysisContext!.contextRoot,
+      );
+    }
+
+    final pluginFixes = <plugin.AnalysisErrorFixes>[];
+    final responses =
+        await waitForResponses(pluginFutures, requestParameters: requestParams);
+
+    for (final response in responses) {
+      final result = plugin.EditGetFixesResult.fromResponse(response);
+      pluginFixes.addAll(result.fixes);
+    }
+
+    return pluginFixes;
+  }
+
   Future<List<Either2<Command, CodeAction>>> _getRefactorActions(
     bool Function(CodeActionKind) shouldIncludeKind,
     bool supportsLiteralCodeActions,
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index 429920f..03be64d 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -866,19 +866,30 @@
         range: toRange(lineInfo, label.offset, label.length),
         label: label.label);
 
-lsp.CodeActionKind toCodeActionKind(String? id, lsp.CodeActionKind fallback) {
+/// Converts [id] to a [CodeActionKind] using [fallbackOrPrefix] as a fallback
+/// or a prefix if the ID is not already a fix/refactor.
+lsp.CodeActionKind toCodeActionKind(
+    String? id, lsp.CodeActionKind fallbackOrPrefix) {
   if (id == null) {
-    return fallback;
+    return fallbackOrPrefix;
   }
   // Dart fixes and assists start with "dart.assist." and "dart.fix." but in LSP
   // we want to use the predefined prefixes for CodeActions.
-  final newId = id
+  var newId = id
       .replaceAll('dart.assist', lsp.CodeActionKind.Refactor.toString())
       .replaceAll('dart.fix', lsp.CodeActionKind.QuickFix.toString())
       .replaceAll(
           'analysisOptions.assist', lsp.CodeActionKind.Refactor.toString())
       .replaceAll(
           'analysisOptions.fix', lsp.CodeActionKind.QuickFix.toString());
+
+  // If the ID does not start with either of the kinds above, prefix it as
+  // it will be an unqualified ID from a plugin.
+  if (!newId.startsWith(lsp.CodeActionKind.Refactor.toString()) &&
+      !newId.startsWith(lsp.CodeActionKind.QuickFix.toString())) {
+    newId = '$fallbackOrPrefix.$newId';
+  }
+
   return lsp.CodeActionKind(newId);
 }
 
diff --git a/pkg/analysis_server/lib/src/utilities/mocks.dart b/pkg/analysis_server/lib/src/utilities/mocks.dart
index 5022dcd..9c581cd 100644
--- a/pkg/analysis_server/lib/src/utilities/mocks.dart
+++ b/pkg/analysis_server/lib/src/utilities/mocks.dart
@@ -146,6 +146,8 @@
   plugin.AnalysisUpdateContentParams? analysisUpdateContentParams;
   plugin.RequestParams? broadcastedRequest;
   Map<PluginInfo, Future<plugin.Response>>? broadcastResults;
+  Map<PluginInfo, Future<plugin.Response>>? Function(plugin.RequestParams)?
+      handleRequest;
 
   @override
   List<PluginInfo> plugins = [];
@@ -192,7 +194,9 @@
       plugin.RequestParams params,
       {analyzer.ContextRoot? contextRoot}) {
     broadcastedRequest = params;
-    return broadcastResults ?? <PluginInfo, Future<plugin.Response>>{};
+    return handleRequest?.call(params) ??
+        broadcastResults ??
+        <PluginInfo, Future<plugin.Response>>{};
   }
 
   @override
diff --git a/pkg/analysis_server/test/lsp/code_actions_assists_test.dart b/pkg/analysis_server/test/lsp/code_actions_assists_test.dart
index 67323f9..2797109 100644
--- a/pkg/analysis_server/test/lsp/code_actions_assists_test.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_assists_test.dart
@@ -7,6 +7,8 @@
 import 'package:analysis_server/lsp_protocol/protocol_custom_generated.dart';
 import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
 import 'package:analysis_server/lsp_protocol/protocol_special.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 import 'package:collection/collection.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -166,6 +168,87 @@
     expect(codeActions, isEmpty);
   }
 
+  Future<void> test_plugin() async {
+    // This code should get an assist to replace 'foo' with 'bar'.'
+    const content = '[[foo]]';
+    const expectedContent = 'bar';
+
+    final pluginResult = plugin.EditGetAssistsResult([
+      plugin.PrioritizedSourceChange(
+        0,
+        plugin.SourceChange(
+          "Change 'foo' to 'bar'",
+          edits: [
+            plugin.SourceFileEdit(mainFilePath, 0,
+                edits: [plugin.SourceEdit(0, 3, 'bar')])
+          ],
+          id: 'fooToBar',
+        ),
+      )
+    ]);
+    configureTestPlugin(
+      handler: (request) =>
+          request is plugin.EditGetAssistsParams ? pluginResult : null,
+    );
+
+    newFile(mainFilePath, content: withoutMarkers(content));
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.Refactor]),
+    );
+
+    final codeActions = await getCodeActions(mainFileUri.toString(),
+        range: rangeFromMarkers(content));
+    final assist = findEditAction(codeActions,
+        CodeActionKind('refactor.fooToBar'), "Change 'foo' to 'bar'")!;
+
+    final edit = assist.edit!;
+    expect(edit.changes, isNotNull);
+
+    // Ensure applying the changes will give us the expected content.
+    final contents = {
+      mainFilePath: withoutMarkers(content),
+    };
+    applyChanges(contents, edit.changes!);
+    expect(contents[mainFilePath], equals(expectedContent));
+  }
+
+  Future<void> test_plugin_sortsWithServer() async {
+    // Produces a server assist of "Convert to single quoted string" (with a
+    // priority of 30).
+    const content = 'import "[[dart:async]]";';
+
+    // Provide two plugin results that should sort either side of the server assist.
+    final pluginResult = plugin.EditGetAssistsResult([
+      plugin.PrioritizedSourceChange(10, plugin.SourceChange('Low')),
+      plugin.PrioritizedSourceChange(100, plugin.SourceChange('High')),
+    ]);
+    configureTestPlugin(
+      handler: (request) =>
+          request is plugin.EditGetAssistsParams ? pluginResult : null,
+    );
+
+    newFile(mainFilePath, content: withoutMarkers(content));
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.Refactor]),
+    );
+
+    final codeActions = await getCodeActions(mainFileUri.toString(),
+        range: rangeFromMarkers(content));
+    final codeActionTitles = codeActions.map((action) =>
+        action.map((command) => command.title, (action) => action.title));
+
+    expect(
+      codeActionTitles,
+      containsAllInOrder([
+        'High',
+        'Convert to single quoted string',
+        'Low',
+      ]),
+    );
+  }
+
   Future<void> test_snippetTextEdits_supported() async {
     // This tests experimental support for including Snippets in TextEdits.
     // https://github.com/rust-analyzer/rust-analyzer/blob/b35559a2460e7f0b2b79a7029db0c5d4e0acdb44/docs/dev/lsp-extensions.md#snippet-textedit
diff --git a/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart b/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart
index 2a9184a..925b13b 100644
--- a/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_fixes_test.dart
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin;
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 import 'package:linter/src/rules.dart';
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
@@ -350,6 +352,111 @@
     final codeActions = await getCodeActions(otherFileUri.toString());
     expect(codeActions, isEmpty);
   }
+
+  Future<void> test_plugin() async {
+    // This code should get a fix to replace 'foo' with 'bar'.'
+    const content = '[[foo]]';
+    const expectedContent = 'bar';
+
+    final pluginResult = plugin.EditGetFixesResult([
+      plugin.AnalysisErrorFixes(
+        plugin.AnalysisError(
+          plugin.AnalysisErrorSeverity.ERROR,
+          plugin.AnalysisErrorType.HINT,
+          plugin.Location(mainFilePath, 0, 3, 0, 0),
+          "Do not use 'foo'",
+          'do_not_use_foo',
+        ),
+        fixes: [
+          plugin.PrioritizedSourceChange(
+            0,
+            plugin.SourceChange(
+              "Change 'foo' to 'bar'",
+              edits: [
+                plugin.SourceFileEdit(mainFilePath, 0,
+                    edits: [plugin.SourceEdit(0, 3, 'bar')])
+              ],
+              id: 'fooToBar',
+            ),
+          )
+        ],
+      )
+    ]);
+    configureTestPlugin(
+      handler: (request) =>
+          request is plugin.EditGetFixesParams ? pluginResult : null,
+    );
+
+    newFile(mainFilePath, content: withoutMarkers(content));
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.QuickFix]),
+    );
+
+    final codeActions = await getCodeActions(mainFileUri.toString(),
+        range: rangeFromMarkers(content));
+    final assist = findEditAction(codeActions,
+        CodeActionKind('quickfix.fooToBar'), "Change 'foo' to 'bar'")!;
+
+    final edit = assist.edit!;
+    expect(edit.changes, isNotNull);
+
+    // Ensure applying the changes will give us the expected content.
+    final contents = {
+      mainFilePath: withoutMarkers(content),
+    };
+    applyChanges(contents, edit.changes!);
+    expect(contents[mainFilePath], equals(expectedContent));
+  }
+
+  Future<void> test_plugin_sortsWithServer() async {
+    // Produces a server fix for removing unused import with a default
+    // priority of 50.
+    const content = '''
+[[import]] 'dart:convert';
+''';
+
+    // Provide two plugin results that should sort either side of the server fix.
+    final pluginResult = plugin.EditGetFixesResult([
+      plugin.AnalysisErrorFixes(
+        plugin.AnalysisError(
+          plugin.AnalysisErrorSeverity.ERROR,
+          plugin.AnalysisErrorType.HINT,
+          plugin.Location(mainFilePath, 0, 3, 0, 0),
+          'Dummy error',
+          'dummy',
+        ),
+        fixes: [
+          plugin.PrioritizedSourceChange(10, plugin.SourceChange('Low')),
+          plugin.PrioritizedSourceChange(100, plugin.SourceChange('High')),
+        ],
+      )
+    ]);
+    configureTestPlugin(
+      handler: (request) =>
+          request is plugin.EditGetFixesParams ? pluginResult : null,
+    );
+
+    newFile(mainFilePath, content: withoutMarkers(content));
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.QuickFix]),
+    );
+
+    final codeActions = await getCodeActions(mainFileUri.toString(),
+        range: rangeFromMarkers(content));
+    final codeActionTitles = codeActions.map((action) =>
+        action.map((command) => command.title, (action) => action.title));
+
+    expect(
+      codeActionTitles,
+      containsAllInOrder([
+        'High',
+        'Remove unused import',
+        'Low',
+      ]),
+    );
+  }
 }
 
 @reflectiveTest
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index a7a8ea0..963542a 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -62,12 +62,25 @@
   DiscoveredPluginInfo configureTestPlugin({
     plugin.ResponseResult? respondWith,
     plugin.Notification? notification,
+    plugin.ResponseResult? Function(plugin.RequestParams)? handler,
     Duration respondAfter = Duration.zero,
   }) {
     final info = DiscoveredPluginInfo('a', 'b', 'c', server.notificationManager,
         server.instrumentationService);
     pluginManager.plugins.add(info);
 
+    if (handler != null) {
+      pluginManager.handleRequest = (request) {
+        final response = handler(request);
+        return response == null
+            ? null
+            : <PluginInfo, Future<plugin.Response>>{
+                info: Future.delayed(respondAfter)
+                    .then((_) => response.toResponse('-', 1))
+              };
+      };
+    }
+
     if (respondWith != null) {
       pluginManager.broadcastResults = <PluginInfo, Future<plugin.Response>>{
         info: Future.delayed(respondAfter)
diff --git a/pkg/analysis_server/tool/lsp_spec/README.md b/pkg/analysis_server/tool/lsp_spec/README.md
index 5376013..bac9b69 100644
--- a/pkg/analysis_server/tool/lsp_spec/README.md
+++ b/pkg/analysis_server/tool/lsp_spec/README.md
@@ -94,8 +94,8 @@
 | textDocument/codeAction (organiseImports) | ✅ | ✅ | | ✅ | ✅ |
 | textDocument/codeAction (fixAll) | ✅ | ✅ | | ✅ | ✅ |
 | textDocument/codeAction (refactors) | ✅ | ✅ | | ✅ | ✅ |
-| textDocument/codeAction (assists) | ✅ | ✅ | | ✅ | ✅ | Only if the client advertises `codeActionLiteralSupport` with `Refactor`
-| textDocument/codeAction (fixes) | ✅ | ✅ | | ✅ | ✅ | Only if the client advertises `codeActionLiteralSupport` with `QuickFix`
+| textDocument/codeAction (assists) | ✅ | ✅ | ✅ | ✅ | ✅ | Only if the client advertises `codeActionLiteralSupport` with `Refactor`
+| textDocument/codeAction (fixes) | ✅ | ✅ | ✅ | ✅ | ✅ | Only if the client advertises `codeActionLiteralSupport` with `QuickFix`
 | textDocument/codeLens | | | | | |
 | codeLens/resolve | | | | | |
 | textDocument/documentLink | | | | | |
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index 1e448ff..aec3305 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -12,7 +12,6 @@
 import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/analysis/testing_data.dart';
 import 'package:analyzer/src/dart/ast/ast.dart';
-import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/dart/constant/compute.dart';
 import 'package:analyzer/src/dart/constant/constant_verifier.dart';
 import 'package:analyzer/src/dart/constant/evaluation.dart';
@@ -332,7 +331,6 @@
 
     var enableTiming = _analysisOptions.enableTiming;
     var nodeRegistry = NodeLintRegistry(enableTiming);
-    var visitors = <AstVisitor>[];
 
     var context = LinterContextImpl(
       allUnits,
@@ -354,13 +352,6 @@
 
     // Run lints that handle specific node types.
     unit.accept(LinterVisitor(nodeRegistry));
-
-    // Run visitor based lints.
-    if (visitors.isNotEmpty) {
-      AstVisitor visitor = ExceptionHandlingDelegatingAstVisitor(
-          visitors, ExceptionHandlingDelegatingAstVisitor.logException);
-      unit.accept(visitor);
-    }
   }
 
   void _computeVerifyErrors(FileState file, CompilationUnit unit) {
diff --git a/pkg/analyzer/lib/src/dart/ast/utilities.dart b/pkg/analyzer/lib/src/dart/ast/utilities.dart
index 605628f..a068ad7 100644
--- a/pkg/analyzer/lib/src/dart/ast/utilities.dart
+++ b/pkg/analyzer/lib/src/dart/ast/utilities.dart
@@ -1302,38 +1302,13 @@
   }
 }
 
-/// A [DelegatingAstVisitor] that will additionally catch all exceptions from
-/// the delegates without stopping the visiting. A function must be provided
-/// that will be invoked for each such exception.
+/// Class capable of handling exceptions generated during linting.
 ///
 /// Clients may not extend, implement or mix-in this class.
-class ExceptionHandlingDelegatingAstVisitor<T> extends DelegatingAstVisitor<T> {
-  /// The function that will be executed for each exception that is thrown by
-  /// one of the visit methods on the delegate.
-  final ExceptionInDelegateHandler handler;
-
-  /// Initialize a newly created visitor to use each of the given delegate
-  /// visitors to visit the nodes of an AST structure.
-  ExceptionHandlingDelegatingAstVisitor(
-      Iterable<AstVisitor<T>> delegates, this.handler)
-      : super(delegates);
-
-  @override
-  T? visitNode(AstNode node) {
-    delegates.forEach((delegate) {
-      try {
-        node.accept(delegate);
-      } catch (exception, stackTrace) {
-        handler(node, delegate, exception, stackTrace);
-      }
-    });
-    node.visitChildren(this);
-    return null;
-  }
-
-  /// A function that can be used with instances of this class to log and then
-  /// ignore any exceptions that are thrown by any of the delegates.
-  static void logException(
+class LinterExceptionHandler {
+  /// A method that can be passed to the `LinterVisitor` constructor to handle
+  /// exceptions that occur during linting.
+  void logException(
       AstNode node, Object visitor, dynamic exception, StackTrace stackTrace) {
     StringBuffer buffer = StringBuffer();
     buffer.write('Exception while using a ${visitor.runtimeType} to visit a ');
diff --git a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
index cebd705..0b1635c 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_analyzer.dart
@@ -332,7 +332,6 @@
     ErrorReporter errorReporter = _getErrorReporter(file);
 
     var nodeRegistry = NodeLintRegistry(_analysisOptions.enableTiming);
-    var visitors = <AstVisitor>[];
     final workspacePackage = _getPackage(currentUnit.unit);
     var context = LinterContextImpl(
         allUnits,
@@ -350,13 +349,6 @@
 
     // Run lints that handle specific node types.
     unit.accept(LinterVisitor(nodeRegistry));
-
-    // Run visitor based lints.
-    if (visitors.isNotEmpty) {
-      AstVisitor visitor = ExceptionHandlingDelegatingAstVisitor(
-          visitors, ExceptionHandlingDelegatingAstVisitor.logException);
-      unit.accept(visitor);
-    }
   }
 
   void _computeVerifyErrors(FileState file, CompilationUnit unit) {
diff --git a/pkg/analyzer/lib/src/lint/linter_visitor.dart b/pkg/analyzer/lib/src/lint/linter_visitor.dart
index 05926b0..25afec4 100644
--- a/pkg/analyzer/lib/src/lint/linter_visitor.dart
+++ b/pkg/analyzer/lib/src/lint/linter_visitor.dart
@@ -17,9 +17,9 @@
   final NodeLintRegistry registry;
   final LintRuleExceptionHandler exceptionHandler;
 
-  LinterVisitor(this.registry,
-      [this.exceptionHandler =
-          ExceptionHandlingDelegatingAstVisitor.logException]);
+  LinterVisitor(this.registry, [LintRuleExceptionHandler? exceptionHandler])
+      : exceptionHandler =
+            exceptionHandler ?? LinterExceptionHandler().logException;
 
   @override
   void visitAdjacentStrings(AdjacentStrings node) {
diff --git a/pkg/analyzer/test/generated/utilities_test.dart b/pkg/analyzer/test/generated/utilities_test.dart
index 589c79a..b6d2157 100644
--- a/pkg/analyzer/test/generated/utilities_test.dart
+++ b/pkg/analyzer/test/generated/utilities_test.dart
@@ -5,13 +5,11 @@
 import 'package:analyzer/dart/analysis/utilities.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/token.dart';
-import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/src/dart/ast/ast_factory.dart';
 import 'package:analyzer/src/dart/ast/utilities.dart';
 import 'package:analyzer/src/generated/java_engine.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/generated/testing/ast_test_factory.dart';
-import 'package:analyzer/src/generated/testing/token_factory.dart';
 import 'package:analyzer/src/generated/utilities_collection.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -19,7 +17,6 @@
 main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(BooleanArrayTest);
-    defineReflectiveTests(ExceptionHandlingDelegatingAstVisitorTest);
     defineReflectiveTests(LineInfoTest);
     defineReflectiveTests(NodeReplacerTest);
     defineReflectiveTests(SourceRangeTest);
@@ -116,23 +113,6 @@
   }
 }
 
-@reflectiveTest
-class ExceptionHandlingDelegatingAstVisitorTest {
-  void test_handlerIsCalled() {
-    AstVisitor exceptionThrowingVisitor = _ExceptionThrowingVisitor();
-    bool handlerInvoked = false;
-    AstVisitor visitor = ExceptionHandlingDelegatingAstVisitor(
-        [exceptionThrowingVisitor], (AstNode node, AstVisitor visitor,
-            dynamic exception, StackTrace stackTrace) {
-      handlerInvoked = true;
-    });
-    astFactory
-        .nullLiteral(TokenFactory.tokenFromKeyword(Keyword.NULL))
-        .accept(visitor);
-    expect(handlerInvoked, isTrue);
-  }
-}
-
 class Getter_NodeReplacerTest_test_annotation
     implements NodeReplacerTest_Getter<Annotation, ArgumentList> {
   @override
@@ -2740,10 +2720,3 @@
     expect(StringUtilities.substringBeforeChar("abc", 0x64), "abc");
   }
 }
-
-class _ExceptionThrowingVisitor extends SimpleAstVisitor {
-  @override
-  visitNullLiteral(NullLiteral node) {
-    throw ArgumentError('');
-  }
-}
diff --git a/runtime/vm/message_snapshot.cc b/runtime/vm/message_snapshot.cc
index ae7a6ad..37bb767 100644
--- a/runtime/vm/message_snapshot.cc
+++ b/runtime/vm/message_snapshot.cc
@@ -1636,10 +1636,9 @@
       if (length == 0) {
         data->value.as_typed_data.values = NULL;
       } else {
-        const intptr_t length_in_bytes = length * element_size;
-        uint8_t* cdata = d->zone()->Alloc<uint8_t>(length_in_bytes);
-        data->value.as_typed_data.values = cdata;
-        d->ReadBytes(cdata, length_in_bytes);
+        data->value.as_typed_data.values =
+            const_cast<uint8_t*>(d->CurrentBufferAddress());
+        d->Advance(length * element_size);
       }
       d->AssignRef(data);
     }
diff --git a/tools/VERSION b/tools/VERSION
index 79fac53..60bf783 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 368
+PRERELEASE 369
 PRERELEASE_PATCH 0
\ No newline at end of file