Version 3.6.0-237.0.dev

Merge e06d0f3c407843ce708e8abc36d46a8a1e5edb76 into dev
diff --git a/DEPS b/DEPS
index 2e88cdf..16e9183 100644
--- a/DEPS
+++ b/DEPS
@@ -54,7 +54,7 @@
 
   # co19 is a cipd package automatically generated for each co19 commit.
   # Use tests/co19/update.sh to update this hash.
-  "co19_rev": "767bb1f623a4f005072224cd7a49726a5b644296",
+  "co19_rev": "a572236bc7d760dc986fd34abe6d0b8ae56254d7",
 
   # The internal benchmarks to use. See go/dart-benchmarks-internal
   "benchmarks_internal_rev": "3bd6bc6d207dfb7cf687537e819863cf9a8f2470",
@@ -188,7 +188,7 @@
   "typed_data_rev": "365468a74251c930a463daf5b8f13227e269111a",
   "vector_math_rev": "2cfbe2c115a57b368ccbc3c89ebd38a06764d3d1",
   "watcher_rev": "0484625589d8512b36a7ad898a6cc6351d24c556",
-  "web_rev": "4996dc2acd30ff06f7e500ec76fde8a66db82c0c",
+  "web_rev": "fb3019264edbed87b40c29d7777b2f98ae562008",
   "web_socket_channel_rev": "0e1d6e2eb5a0bfd62e45b772ac7107d796176cf6",
   "webdev_rev": "5f30c560dc4e3df341356c43ec1a766ee6b74a7c",
   "webdriver_rev": "b181c9e5eca657ea4a12621332f47d9579106fda",
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 067f441..6d4d1f7 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -12,12 +12,14 @@
 import 'package:analysis_server/src/context_manager.dart';
 import 'package:analysis_server/src/domains/completion/available_suggestions.dart';
 import 'package:analysis_server/src/legacy_analysis_server.dart';
-import 'package:analysis_server/src/lsp/client_capabilities.dart';
-import 'package:analysis_server/src/lsp/client_configuration.dart';
+import 'package:analysis_server/src/lsp/client_capabilities.dart' as lsp;
+import 'package:analysis_server/src/lsp/client_configuration.dart' as lsp;
 import 'package:analysis_server/src/lsp/constants.dart' as lsp;
-import 'package:analysis_server/src/lsp/error_or.dart';
-import 'package:analysis_server/src/lsp/handlers/handler_execute_command.dart';
-import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
+import 'package:analysis_server/src/lsp/error_or.dart' as lsp;
+import 'package:analysis_server/src/lsp/handlers/handler_execute_command.dart'
+    as lsp;
+import 'package:analysis_server/src/lsp/handlers/handler_states.dart' as lsp;
+import 'package:analysis_server/src/lsp/handlers/handlers.dart' as lsp;
 import 'package:analysis_server/src/plugin/notification_manager.dart';
 import 'package:analysis_server/src/plugin/plugin_manager.dart';
 import 'package:analysis_server/src/plugin/plugin_watcher.dart';
@@ -136,7 +138,7 @@
   ///
   /// This allows the server to call commands itself, such as "Fix All in
   /// Workspace" triggered from the [DartFixPromptManager].
-  ExecuteCommandHandler? executeCommandHandler;
+  lsp.ExecuteCommandHandler? executeCommandHandler;
 
   /// The object used to manage the execution of plugins.
   late PluginManager pluginManager;
@@ -405,13 +407,13 @@
   /// For the legacy server, this set may be a fixed set that is not actually
   /// configured by the client, but matches what legacy protocol editors expect
   /// when using LSP-over-Legacy.
-  LspClientCapabilities? get editorClientCapabilities;
+  lsp.LspClientCapabilities? get editorClientCapabilities;
 
   /// The configuration (user/workspace settings) from the LSP client.
   ///
   /// For the legacy server, this set may be a fixed set that is not controlled
   /// by the client.
-  LspClientConfiguration get lspClientConfiguration;
+  lsp.LspClientConfiguration get lspClientConfiguration;
 
   /// A [Future] that completes when the LSP server moves into the initialized
   /// state and can handle normal LSP requests.
@@ -420,7 +422,7 @@
   ///
   /// When the server leaves the initialized state, [lspUninitialized] will
   /// complete.
-  FutureOr<InitializedStateMessageHandler> get lspInitialized;
+  FutureOr<lsp.InitializedStateMessageHandler> get lspInitialized;
 
   /// A [Future] that completes once the server transitions out of an
   /// initialized state.
@@ -517,10 +519,10 @@
   ///
   /// If there is already an active connection to DTD or there is an error
   /// connecting, returns an error, otherwise returns `null`.
-  Future<ErrorOr<Null>> connectToDtd(Uri dtdUri) async {
+  Future<lsp.ErrorOr<Null>> connectToDtd(Uri dtdUri) async {
     switch (dtd?.state) {
       case DtdConnectionState.Connecting || DtdConnectionState.Connected:
-        return error(
+        return lsp.error(
           lsp.ServerErrorCodes.StateError,
           'Server is already connected to DTD',
         );
@@ -528,7 +530,7 @@
         var connectResult = await DtdServices.connect(this, dtdUri);
         return connectResult.mapResultSync((dtd) {
           this.dtd = dtd;
-          return success(null);
+          return lsp.success(null);
         });
     }
   }
@@ -795,6 +797,28 @@
     }
   }
 
+  /// Immediately handles an LSP message by delegating to the
+  /// [lsp.InitializedStateMessageHandler]. This method does not schedule the
+  /// message and is intended to be used by the scheduler (or LSP-over-Legacy
+  /// where the original legacy wrapper was already scheduled).
+  ///
+  /// If the LSP server/support is not yet initialized, will wait until it is.
+  FutureOr<lsp.ErrorOr<Object?>> immediatelyHandleLspMessage(
+    lsp.IncomingMessage message,
+    lsp.MessageInfo messageInfo, {
+    lsp.CancellationToken? cancellationToken,
+  }) async {
+    // This is FutureOr<> because for the legacy server it's never a future, so
+    // we can skip the await.
+    var initializedLspHandler = lspInitialized;
+    var handler = initializedLspHandler is lsp.InitializedStateMessageHandler
+        ? initializedLspHandler
+        : await initializedLspHandler;
+
+    return handler.handleMessage(message, messageInfo,
+        cancellationToken: cancellationToken);
+  }
+
   /// Return `true` if the file or directory with the given [path] will be
   /// analyzed in one of the analysis contexts.
   bool isAnalyzed(String path) {
diff --git a/pkg/analysis_server/lib/src/handler/legacy/lsp_over_legacy_handler.dart b/pkg/analysis_server/lib/src/handler/legacy/lsp_over_legacy_handler.dart
index dce114c..f5f1c8b 100644
--- a/pkg/analysis_server/lib/src/handler/legacy/lsp_over_legacy_handler.dart
+++ b/pkg/analysis_server/lib/src/handler/legacy/lsp_over_legacy_handler.dart
@@ -9,7 +9,6 @@
 import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
 import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/lsp/error_or.dart';
-import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
 import 'package:analysis_server/src/lsp/handlers/handlers.dart' as lsp;
 import 'package:analyzer/dart/analysis/session.dart';
 import 'package:language_server_protocol/json_parsing.dart';
@@ -43,20 +42,10 @@
           })
         : null;
 
-    // Get the handler for LSP requests from the server.
-    // The value is a `FutureOr<>` because for the real LSP server it can be
-    // delayed (the client influences when we're in the initialized state) but
-    // since it's never a `Future` for the legacy server and we want to maintain
-    // request order here, skip the `await`.
-    var initializedLspHandler = server.lspInitialized;
-    var handler = initializedLspHandler is InitializedStateMessageHandler
-        ? initializedLspHandler
-        : await server.lspInitialized;
-
     if (lspMessage != null) {
       server.analyticsManager.startedRequestMessage(
           request: lspMessage, startTime: DateTime.now());
-      await handleRequest(handler, lspMessage);
+      await handleRequest(lspMessage);
     } else {
       var message = "The 'lspMessage' parameter was not a valid LSP request:\n"
           "${reporter.errors.join('\n')}";
@@ -65,10 +54,7 @@
     }
   }
 
-  Future<void> handleRequest(
-    InitializedStateMessageHandler handler,
-    RequestMessage message,
-  ) async {
+  Future<void> handleRequest(RequestMessage message) async {
     var messageInfo = lsp.MessageInfo(
       performance: performance,
       clientCapabilities: server.editorClientCapabilities,
@@ -77,7 +63,9 @@
 
     ErrorOr<Object?> result;
     try {
-      result = await handler.handleMessage(message, messageInfo,
+      // Since this (legacy) request was already scheduled, we immediately
+      // execute the wrapped LSP request without additional scheduling.
+      result = await server.immediatelyHandleLspMessage(message, messageInfo,
           cancellationToken: cancellationToken);
     } on InconsistentAnalysisException {
       result = error(
diff --git a/pkg/analysis_server/lib/src/server/message_scheduler.dart b/pkg/analysis_server/lib/src/server/message_scheduler.dart
index 7d9948b..7e52577 100644
--- a/pkg/analysis_server/lib/src/server/message_scheduler.dart
+++ b/pkg/analysis_server/lib/src/server/message_scheduler.dart
@@ -44,10 +44,10 @@
 
 /// The [MessageScheduler] receives messages from all clients of the
 /// [AnalysisServer]. Clients can include IDE's (LSP and Legacy protocol), DTD,
-/// and the Diagnostic server. The [MessageSchedular] acts as a hub for all
+/// and the Diagnostic server. The [MessageScheduler] acts as a hub for all
 /// incoming messages and forwards the messages to the appropriate handlers.
 final class MessageScheduler {
-  /// The [AnalaysisServer] associated with the schedular.
+  /// The [AnalysisServer] associated with the scheduler.
   late final AnalysisServer server;
 
   /// A queue of incoming messages from all the clients of the [AnalysisServer].
@@ -61,7 +61,7 @@
     _incomingMessages.addLast(message);
   }
 
-  /// Notify the [MessageSchedular] to process the messages queue.
+  /// Notify the [MessageScheduler] to process the messages queue.
   void notify() async {
     processMessages();
   }
diff --git a/pkg/analysis_server/lib/src/services/dart_tooling_daemon/dtd_services.dart b/pkg/analysis_server/lib/src/services/dart_tooling_daemon/dtd_services.dart
index b58af9b..057e47a 100644
--- a/pkg/analysis_server/lib/src/services/dart_tooling_daemon/dtd_services.dart
+++ b/pkg/analysis_server/lib/src/services/dart_tooling_daemon/dtd_services.dart
@@ -6,6 +6,7 @@
 
 import 'package:analysis_server/lsp_protocol/protocol.dart';
 import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/lsp/client_capabilities.dart';
 import 'package:analysis_server/src/lsp/error_or.dart';
 import 'package:analysis_server/src/lsp/handlers/handler_states.dart';
 import 'package:analysis_server/src/lsp/handlers/handlers.dart';
@@ -63,52 +64,42 @@
 
   DtdConnectionState get state => _state;
 
-  /// Executes the LSP handler [messageHandler] with [params] and returns the
-  /// results as a map to provide back to DTD.
-  ///
-  /// If the handler fails, throws an [RpcException] to be propagated to the
-  /// client.
+  /// Executes the LSP handler for [message] and completes [completer] with the
+  /// result or an [RpcException].
   void processMessage(
       IncomingMessage message,
       OperationPerformanceImpl performance,
       Completer<Map<String, Object?>> completer) async {
-    // (TODO:keertip) Lookup the right handler, execute and return results.
-    // For now, complete with exception.
-    completer.completeError(RpcException(
-      ErrorCodes.InvalidRequest.toJson(),
-      'DTD requests are not yet supported',
-    ));
+    var info = MessageInfo(
+      performance: performance,
+      // DTD clients requests are always executed with a fixed set of
+      // capabilities so that the responses don't change in format based on the
+      // owning editor.
+      clientCapabilities: fixedBasicLspClientCapabilities,
+    );
+    var token = NotCancelableToken(); // We don't currently support cancel.
 
-    // (TODO:keertip) Uncomment when lookup has been implemented
-    // var info = MessageInfo(
-    //   performance: performance,
-    //   // DTD clients requests are always executed with a fixed set of
-    //   // capabilities so that the responses don't change in format based on the
-    //   // owning editor.
-    //   clientCapabilities: fixedBasicLspClientCapabilities,
-    // );
-    // var token = NotCancelableToken(); // We don't currently support cancel.
+    // Execute the handler.
+    var result = await _server.immediatelyHandleLspMessage(message, info,
+        cancellationToken: token);
 
-    // // Execute the handler.
-    // var result = await messageHandler.handleMessage(message, info, token);
-
-    // // Map the result (or error) on to what a DTD handler needs to return.
-    // return result.map(
-    //   // Map LSP errors on to equiv JSON-RPC errors for DTD.
-    //   (error) => throw RpcException(
-    //     error.code.toJson(),
-    //     error.message,
-    //     data: error.data,
-    //   ),
-    //   // DTD requires that all results are a Map and that they contain a
-    //   // 'type' field. This differs slightly from LSP where we could return a
-    //   // boolean (for example). This means we need to put the result in a
-    //   // field, which we're calling 'result'.
-    //   (result) => {
-    //     'type': result?.runtimeType.toString(),
-    //     'result': result,
-    //   },
-    // );
+    // Complete with the result or error.
+    result.map(
+      // Map LSP errors on to equiv JSON-RPC errors for DTD.
+      (error) => completer.completeError(RpcException(
+        error.code.toJson(),
+        error.message,
+        data: error.data,
+      )),
+      // DTD requires that all results are a Map and that they contain a
+      // 'type' field. This differs slightly from LSP where we could return a
+      // boolean (for example). This means we need to put the result in a
+      // field, which we're calling 'result'.
+      (result) => completer.complete({
+        'type': result?.runtimeType.toString(),
+        'result': result,
+      }),
+    );
   }
 
   /// Closes the connection to DTD and cleans up.
@@ -174,14 +165,14 @@
   /// A completer is returned which will be completed with the result of the
   /// execution of the request by the corresponding [MessageHandler].
   Future<Map<String, Object?>> _executeLspHandler(
-    MessageHandler<Object?, Object?, AnalysisServer> messageHandler,
+    Method method,
     Parameters params,
     OperationPerformanceImpl performance,
   ) async {
     // Map the incoming request into types we use for LSP request handling.
     var message = IncomingMessage(
       jsonrpc: jsonRpcVersion,
-      method: messageHandler.handlesMessage,
+      method: method,
       params: params.asMap,
     );
     var scheduler = _server.messageScheduler;
@@ -227,9 +218,10 @@
     DartToolingDaemon dtd,
   ) async {
     if (messageHandler.requiresTrustedCaller) return;
+    var method = messageHandler.handlesMessage;
     await dtd.registerService(
       _lspServiceName,
-      messageHandler.handlesMessage.toString(),
+      method.toString(),
       (Parameters params) async {
         var rootPerformance = OperationPerformanceImpl('<root>');
         RequestPerformance? requestPerformance;
@@ -237,12 +229,12 @@
           // Record request performance so DTD requests show up in the
           // server diagnostic pages.
           requestPerformance = RequestPerformance(
-            operation: '${messageHandler.handlesMessage} (DTD)',
+            operation: '$method (DTD)',
             performance: performance,
           );
           _server.recentPerformance.requests.add(requestPerformance!);
 
-          return await _executeLspHandler(messageHandler, params, performance);
+          return await _executeLspHandler(method, params, performance);
         });
       },
     );
diff --git a/pkg/analysis_server/test/services/correction/sort_members_test.dart b/pkg/analysis_server/test/services/correction/sort_members_test.dart
index 8660eb0..1b4c116 100644
--- a/pkg/analysis_server/test/services/correction/sort_members_test.dart
+++ b/pkg/analysis_server/test/services/correction/sort_members_test.dart
@@ -1317,6 +1317,33 @@
 ''');
   }
 
+  Future<void> test_partFile() async {
+    newFile('$testPackageLibPath/part.dart', r'''
+part 'test.dart';
+''');
+
+    await _parseTestUnit(r'''
+part of 'part.dart';
+
+import 'dart:io';
+import 'dart:async';
+
+void f() {}
+
+Future? a;
+''');
+    _assertSort(r'''
+part of 'part.dart';
+
+import 'dart:async';
+import 'dart:io';
+
+Future? a;
+
+void f() {}
+''');
+  }
+
   Future<void> test_unit_class() async {
     await _parseTestUnit(r'''
 class C {}
@@ -1713,6 +1740,33 @@
 ''');
   }
 
+  Future<void> test_withParts() async {
+    newFile('$testPackageLibPath/part.dart', r'''
+part of 'test.dart';
+''');
+
+    await _parseTestUnit(r'''
+import 'dart:io';
+import 'dart:async';
+
+part 'part.dart';
+
+void f() {}
+
+Future? a;
+''');
+    _assertSort(r'''
+import 'dart:async';
+import 'dart:io';
+
+part 'part.dart';
+
+Future? a;
+
+void f() {}
+''');
+  }
+
   void _assertSort(String expectedCode) {
     var sorter = MemberSorter(testCode, testUnit, lineInfo!);
     var edits = sorter.sort();
diff --git a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
index a101d9b..bc2cbc3 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/fix_processor.dart
@@ -17,6 +17,7 @@
 import 'package:analyzer_plugin/protocol/protocol_common.dart'
     hide AnalysisError;
 import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:analyzer_utilities/test/experiments/experiments.dart';
 import 'package:meta/meta.dart';
 import 'package:test/test.dart';
 
@@ -89,7 +90,7 @@
   late BulkFixProcessor processor;
 
   @override
-  List<String> get experiments => const [];
+  List<String> get experiments => experimentsForTests;
 
   /// The name of the lint code being tested.
   String? get lintCode => null;
diff --git a/pkg/analysis_server/test/src/services/correction/fix/organize_imports_test.dart b/pkg/analysis_server/test/src/services/correction/fix/organize_imports_test.dart
index 286057a..d13772f 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/organize_imports_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/organize_imports_test.dart
@@ -12,31 +12,79 @@
 void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(OrganizeImportsBulkTest);
-    defineReflectiveTests(OrganizeImportsTest);
+    defineReflectiveTests(OrganizeImportsDirectivesOrderingTest);
   });
 }
 
 @reflectiveTest
 class OrganizeImportsBulkTest extends BulkFixProcessorTest {
+  Future<void> test_partFile() async {
+    newFile('$testPackageLibPath/a.dart', r'''
+part 'test.dart';
+''');
+
+    await resolveTestCode('''
+part of 'a.dart';
+
+import 'dart:io';
+import 'dart:async';
+
+Future? a;
+''');
+
+    await assertOrganize('''
+part of 'a.dart';
+
+import 'dart:async';
+import 'dart:io';
+
+Future? a;
+''');
+  }
+
   Future<void> test_single_file() async {
     await parseTestCode('''
 import 'dart:io';
 import 'dart:async';
 
-Future a;
+Future? a;
 ''');
 
     await assertOrganize('''
 import 'dart:async';
 import 'dart:io';
 
-Future a;
+Future? a;
+''');
+  }
+
+  Future<void> test_withParts() async {
+    newFile('$testPackageLibPath/a.dart', r'''
+part of 'test.dart';
+''');
+
+    await parseTestCode('''
+import 'dart:io';
+import 'dart:async';
+
+part 'a.dart';
+
+Future? a;
+''');
+
+    await assertOrganize('''
+import 'dart:async';
+import 'dart:io';
+
+part 'a.dart';
+
+Future? a;
 ''');
   }
 }
 
 @reflectiveTest
-class OrganizeImportsTest extends FixProcessorLintTest {
+class OrganizeImportsDirectivesOrderingTest extends FixProcessorLintTest {
   @override
   FixKind get kind => DartFixKind.ORGANIZE_IMPORTS;
 
diff --git a/pkg/analyzer/lib/src/dart/element/scope.dart b/pkg/analyzer/lib/src/dart/element/scope.dart
index 7ab6862..310d092 100644
--- a/pkg/analyzer/lib/src/dart/element/scope.dart
+++ b/pkg/analyzer/lib/src/dart/element/scope.dart
@@ -396,6 +396,7 @@
     for (var prefixElement in _prefixElements.values) {
       prefixElement.scope.importsTrackingDestroy();
     }
+    _importsTracking = null;
   }
 
   ImportsTracking importsTrackingInit() {
diff --git a/pkg/compiler/test/tool/graph_isomorphizer/graph_isomorphizer_test.dart b/pkg/compiler/test/tool/graph_isomorphizer/graph_isomorphizer_test.dart
index 0bf93e2..87497d9 100644
--- a/pkg/compiler/test/tool/graph_isomorphizer/graph_isomorphizer_test.dart
+++ b/pkg/compiler/test/tool/graph_isomorphizer/graph_isomorphizer_test.dart
@@ -42,7 +42,9 @@
 void verifyGeneratedFile(
     String filename, StringBuffer contents, Map<String, String> expectations) {
   Expect.stringEquals(
-      DartFormatter().format(contents.toString()), expectations[filename]!);
+      DartFormatter(languageVersion: DartFormatter.latestLanguageVersion)
+          .format(contents.toString()),
+      expectations[filename]!);
 }
 
 GraphIsomorphizer generateFiles(List<String> graphFileLines,
diff --git a/pkg/compiler/tool/graph_isomorphizer.dart b/pkg/compiler/tool/graph_isomorphizer.dart
index 7a4da5e..ff4e76c 100644
--- a/pkg/compiler/tool/graph_isomorphizer.dart
+++ b/pkg/compiler/tool/graph_isomorphizer.dart
@@ -458,7 +458,9 @@
     var file = File(this.outDirectory + '/' + filename);
     file.createSync(recursive: true);
     var sink = file.openWrite();
-    sink.write(DartFormatter().format(contents.toString()));
+    sink.write(
+        DartFormatter(languageVersion: DartFormatter.latestLanguageVersion)
+            .format(contents.toString()));
     sink.close();
   }
 
diff --git a/pkg/dart2wasm/bin/run_wasm.js b/pkg/dart2wasm/bin/run_wasm.js
index d290d84..4c20888 100644
--- a/pkg/dart2wasm/bin/run_wasm.js
+++ b/pkg/dart2wasm/bin/run_wasm.js
@@ -359,6 +359,10 @@
   self.clearInterval = cancelTimer;
   self.queueMicrotask = addTask;
 
+  // Constructor function for JS `Response` objects, allows us to test for it
+  // via `instanceof`.
+  self.Response = function() {}
+
   self.location = {}
   self.location.href = 'file://' + args[wasmArg];
 
@@ -402,7 +406,7 @@
   const appInstance = await compiledApp.instantiate(importObject, {
     loadDeferredWasm: async (moduleName) => {
       let filename = wasmFilename.replace('.wasm', `_${moduleName}.wasm`);
-      return await dart2wasm.compile(readBytes(filename));
+      return readBytes(filename);
     }
   });
 
diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart
index b2cac0a..36ccfc1 100644
--- a/pkg/dart2wasm/lib/js/runtime_blob.dart
+++ b/pkg/dart2wasm/lib/js/runtime_blob.dart
@@ -14,27 +14,24 @@
     new Uint8Array(bytes), {builtins: ['js-string']});
 }
 
-// Compiles a dart2wasm-generated wasm modules from `source` which is then
+// Compiles a dart2wasm-generated main module from `source` which can then
 // instantiatable via the `instantiate` method.
 //
 // `source` needs to be a `Response` object (or promise thereof) e.g. created
 // via the `fetch()` JS API.
 export async function compileStreaming(source) {
-  const module = await WebAssembly.compileStreaming(
-    source,
-    detectJsStringBuiltins() ? {builtins: ['js-string']} : {}
-  );
-  return new CompiledApp(module);
+  const builtins = detectJsStringBuiltins()
+      ? {builtins: ['js-string']} : {};
+  return new CompiledApp(
+      await WebAssembly.compileStreaming(source, builtins), builtins);
 }
 
 // Compiles a dart2wasm-generated wasm modules from `bytes` which is then
 // instantiatable via the `instantiate` method.
 export async function compile(bytes) {
-  const module = await WebAssembly.compile(
-    bytes,
-    detectJsStringBuiltins() ? {builtins: ['js-string']} : {}
-  );
-  return new CompiledApp(module);
+  const builtins = detectJsStringBuiltins()
+      ? {builtins: ['js-string']} : {};
+  return new CompiledApp(await WebAssembly.compile(bytes, builtins), builtins);
 }
 
 // DEPRECATED: Please use `compile` or `compileStreaming` to get a compiled app,
@@ -57,8 +54,9 @@
 }
 
 class CompiledApp {
-  constructor(module) {
+  constructor(module, builtins) {
     this.module = module;
+    this.builtins = builtins;
   }
 
   // The second argument is an options object containing:
@@ -150,8 +148,13 @@
         if (!loadDeferredWasm) {
           throw "No implementation of loadDeferredWasm provided.";
         }
-        const compiledWasm = await loadDeferredWasm(moduleName);
-        return await compiledWasm.instantiate({"module0": dartInstance.exports});
+        const source = await Promise.resolve(loadDeferredWasm(moduleName));
+        const module = await ((source instanceof Response)
+            ? WebAssembly.compileStreaming(source, this.builtins)
+            : WebAssembly.compile(source, this.builtins));
+        return await WebAssembly.instantiate(module, {
+          "module0": dartInstance.exports,
+        });
       },
     };
 
diff --git a/pkg/dtd_impl/dtd_common_services_editor.md b/pkg/dtd_impl/dtd_common_services_editor.md
index f413c38..8a590d9 100644
--- a/pkg/dtd_impl/dtd_common_services_editor.md
+++ b/pkg/dtd_impl/dtd_common_services_editor.md
@@ -133,10 +133,18 @@
 
 An event sent by an editor when a debug session is changed.
 
-This could be happen when a VM Service URI becomes available for a session
+This could happen when a VM Service URI becomes available for a session
 launched in debug mode, for example.
 
 
+## themeChanged
+`ThemeChangedEvent`
+
+An event sent by an editor when its theme has changed.
+
+This could happen when a user changes their settings to toggle between light
+and dark mode or increase/decrease font size.
+
 # Type Definitions
 
 ```dart
@@ -188,6 +196,11 @@
   String? deviceId;
 }
 
+/// An event sent by an editor when theme settings have changed.
+class ThemeChangedEvent {
+  Theme theme;
+}
+
 /// A debug session running in the editor.
 class EditorDebugSession {
   String id;
@@ -218,6 +231,14 @@
   bool supported;
 }
 
+/// The description of an editor's theme.
+class Theme {
+  bool isDarkMode;
+  String? backgroundColor;
+  String? foregroundColor;
+  int? fontSize;
+}
+
 /// Parameters for the `enablePlatformTypeParams` request.
 class EnablePlatformTypeParams {
   /// The `platformType` to enable.
diff --git a/pkg/test_runner/lib/src/android.dart b/pkg/test_runner/lib/src/android.dart
index 417d643..3bc3bb4 100644
--- a/pkg/test_runner/lib/src/android.dart
+++ b/pkg/test_runner/lib/src/android.dart
@@ -341,7 +341,7 @@
 /// Helper to list all adb devices available.
 class AdbHelper {
   static final RegExp _deviceLineRegexp =
-      RegExp(r'^(([a-zA-Z0-9:_-]|\.)+)[ \t]+device$', multiLine: true);
+      RegExp(r'^([a-zA-Z0-9:_.\-]+)[ \t]+device$', multiLine: true);
 
   static Future<List<String>> listDevices() {
     return Process.run('adb', ['devices']).then((ProcessResult result) {
@@ -351,7 +351,7 @@
       }
       return _deviceLineRegexp
           .allMatches(result.stdout as String)
-          .map((Match m) => m.group(1)!)
+          .map((Match m) => m[1]!)
           .toList();
     });
   }
diff --git a/pkg/test_runner/lib/src/browser.dart b/pkg/test_runner/lib/src/browser.dart
index 32316af..c148b47 100644
--- a/pkg/test_runner/lib/src/browser.dart
+++ b/pkg/test_runner/lib/src/browser.dart
@@ -60,24 +60,26 @@
       .replaceAll('-', '_'));
 }
 
+final _digitPattern = RegExp(r'\d');
+
 /// Escape [name] to make it into a valid identifier.
 String _toJSIdentifier(String name) {
   if (name.isEmpty) return r'$';
 
   // Escape any invalid characters
-  var result = name.replaceAllMapped(_invalidCharInIdentifier,
-      (match) => '\$${match.group(0)!.codeUnits.join("")}');
+  var result = name.replaceAllMapped(
+      _invalidCharInIdentifier, (match) => '\$${match[0]!.codeUnits.join("")}');
 
   // Ensure the identifier first character is not numeric and that the whole
   // identifier is not a keyword.
-  if (result.startsWith(RegExp('[0-9]')) || _invalidVariableName(result)) {
+  if (result.startsWith(_digitPattern) || _invalidVariableName(result)) {
     return '\$$result';
   }
   return result;
 }
 
 // Invalid characters for identifiers, which would need to be escaped.
-final _invalidCharInIdentifier = RegExp(r'[^A-Za-z_0-9]');
+final _invalidCharInIdentifier = RegExp(r'[^A-Za-z_\d]');
 
 bool _invalidVariableName(String keyword, {bool strictMode = true}) {
   switch (keyword) {
@@ -334,7 +336,7 @@
     const appInstance = await compiledApp.instantiate({}, {
       loadDeferredWasm: (moduleName) => {
         const moduleFile = '$wasmPath'.replace('.wasm', `_\${moduleName}.wasm`);
-        return mjs.compileStreaming(fetch(moduleFile));
+        return fetch(moduleFile);
       }
     });
     dartMainRunner(() => {
diff --git a/pkg/test_runner/lib/src/co19_test_config.dart b/pkg/test_runner/lib/src/co19_test_config.dart
index 68042956..66cc6d5 100644
--- a/pkg/test_runner/lib/src/co19_test_config.dart
+++ b/pkg/test_runner/lib/src/co19_test_config.dart
@@ -7,7 +7,7 @@
 import 'test_suite.dart';
 
 class Co19TestSuite extends StandardTestSuite {
-  static final _testRegExp = RegExp(r"t[0-9]{2,3}.dart$");
+  static final _testRegExp = RegExp(r"t\d{2,3}.dart$");
 
   Co19TestSuite(TestConfiguration configuration, String selector)
       : super(configuration, selector, Path("tests/$selector/src"), [
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index 7a100aa..adc4a4a 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -339,7 +339,7 @@
   @override
   void describe(TestCase testCase, Progress progress, OutputWriter output) {
     if (_jsonResult != null) {
-      _describeEvents(progress, output);
+      _describeEvents(progress, output, _jsonResult);
     } else {
       // We couldn't parse the events, so fallback to showing the last message.
       output.section("Last message");
@@ -359,7 +359,8 @@
     }
   }
 
-  void _describeEvents(Progress progress, OutputWriter output) {
+  void _describeEvents(Progress progress, OutputWriter output,
+      BrowserTestJsonResult jsonResult) {
     // Always show the error events since those are most useful.
     var errorShown = false;
 
@@ -374,19 +375,19 @@
 
       // Skip deobfuscation if there is no indication that there is a stack
       // trace in the string value.
-      if (!value!.contains(RegExp('\\.js:'))) return;
+      if (!value!.contains('.js:')) return;
       var stringStack = value
           // Convert `http:` URIs to relative `file:` URIs.
-          .replaceAll(RegExp('http://[^/]*/root_build/'), '$_buildDirectory/')
-          .replaceAll(RegExp('http://[^/]*/root_dart/'), '')
+          .replaceAll(RegExp(r'http://[^/]*/root_build/'), '$_buildDirectory/')
+          .replaceAll(RegExp(r'http://[^/]*/root_dart/'), '')
           // Remove query parameters (seen in .html URIs).
-          .replaceAll(RegExp('\\?[^:\n]*:'), ':');
+          .replaceAll(RegExp(r'\?[^:\n]*:'), ':');
       // TODO(sigmund): change internal deobfuscation code to avoid spurious
       // error messages when files do not have a corresponding source-map.
       _deobfuscateAndWriteStack(stringStack, output);
     }
 
-    for (var event in _jsonResult!.events) {
+    for (var event in jsonResult.events) {
       if (event["type"] == "sync_exception") {
         showError("Runtime error", event);
       } else if (event["type"] == "window_onerror") {
@@ -401,7 +402,7 @@
     }
 
     output.subsection("Events");
-    for (var event in _jsonResult!.events) {
+    for (var event in jsonResult.events) {
       switch (event["type"] as String?) {
         case "debug":
           output.write('- debug "${event["value"]}"');
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index 0278dcd..9ebb610 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -801,7 +801,7 @@
           .replaceAll('/', '__')
           .replaceAll('-', '_')
           .replaceAll('.dart', '')
-          .replaceAllMapped(RegExp(r'[^A-Za-z_$0-9]'),
+          .replaceAllMapped(RegExp(r'[^A-Za-z_$\d]'),
               (Match m) => '\$${m[0]!.codeUnits.join('')}');
       var testPackageLoadStatements = [
         for (var package in testPackages) 'load("$pkgJsDir/$package.js");'
@@ -1421,7 +1421,7 @@
   }
 }
 
-abstract class VMKernelCompilerMixin {
+abstract mixin class VMKernelCompilerMixin {
   TestConfiguration get _configuration;
 
   bool get _useSdk;
diff --git a/pkg/test_runner/lib/src/multitest.dart b/pkg/test_runner/lib/src/multitest.dart
index cae39d3..f81bb49 100644
--- a/pkg/test_runner/lib/src/multitest.dart
+++ b/pkg/test_runner/lib/src/multitest.dart
@@ -92,7 +92,7 @@
   var lineSeparator =
       (firstNewline == 0 || contents[firstNewline - 1] != '\r') ? '\n' : '\r\n';
   var lines = contents.split(lineSeparator);
-  if (lines.last == '') lines.removeLast();
+  if (lines.last.isEmpty) lines.removeLast();
 
   // Create the set of multitests, which will have a new test added each
   // time we see a multitest line with a new key.
@@ -108,16 +108,16 @@
     var annotation = Annotation.tryParse(line);
     if (annotation != null) {
       testsAsLines.putIfAbsent(
-          annotation.key, () => List<String>.from(testsAsLines["none"]!));
+          annotation.key, () => List<String>.of(testsAsLines["none"]!));
       // Add line to test with annotation.key as key, empty line to the rest.
       for (var entry in testsAsLines.entries) {
         entry.value.add(annotation.key == entry.key ? line : "");
       }
-      outcomes.putIfAbsent(annotation.key, () => <String>{});
+      var outcome = outcomes.putIfAbsent(annotation.key, () => <String>{});
       if (annotation.rest != 'continued') {
         for (var nextOutcome in annotation.outcomes) {
           if (_multitestOutcomes.contains(nextOutcome)) {
-            outcomes[annotation.key]!.add(nextOutcome);
+            outcome.add(nextOutcome);
           } else {
             DebugLogger.warning(
                 "${filePath.toNativePath()}: Invalid expectation "
@@ -277,20 +277,20 @@
 Set<String> _findAllRelativeImports(Path topLibrary) {
   var found = <String>{};
   var libraryDir = topLibrary.directoryPath;
-  var relativeImportRegExp = RegExp(
-      '^(?:@.*\\s+)?' // Allow for a meta-data annotation.
-      '(import|part)'
-      '\\s+["\']'
-      '(?!(dart:|dart-ext:|data:|package:|/))' // Look-ahead: not in package.
-      '([^"\']*)' // The path to the imported file.
-      '["\']');
+  var relativeImportRegExp =
+      RegExp(r'^(?:@.*\s+)?' // Allow for a meta-data annotation.
+          r'(?:import|part)\s+'
+          r'''["']'''
+          r'(?!dart:|dart-ext:|data:|package:|/)' // Look-ahead: not in package.
+          r'([^]*?)' // The path to the imported file.
+          r'''["']''');
 
   processFile(Path filePath) {
     var file = File(filePath.toNativePath());
     for (var line in file.readAsLinesSync()) {
       var match = relativeImportRegExp.firstMatch(line);
       if (match == null) continue;
-      var relativePath = match.group(3)!;
+      var relativePath = match[1]!;
 
       // If a multitest deliberately imports a nonexistent file, don't try to
       // include it.
diff --git a/pkg/test_runner/lib/src/options.dart b/pkg/test_runner/lib/src/options.dart
index 35f1034..1733112 100644
--- a/pkg/test_runner/lib/src/options.dart
+++ b/pkg/test_runner/lib/src/options.dart
@@ -509,7 +509,7 @@
           // Remove the `src/` subdirectories from the co19 directories that do
           // not appear in the test names.
           if (selector.startsWith('co19')) {
-            selector = selector.replaceFirst(RegExp('src/'), '');
+            selector = selector.replaceFirst('src/', '');
           }
           break;
         }
@@ -858,7 +858,7 @@
       var pattern = selectors[i];
       var suite = pattern;
       var slashLocation = pattern.indexOf('/');
-      if (slashLocation != -1) {
+      if (slashLocation >= 0) {
         suite = pattern.substring(0, slashLocation);
         pattern = pattern.substring(slashLocation + 1);
         pattern = pattern.replaceAll('*', '.*');
diff --git a/pkg/test_runner/lib/src/static_error.dart b/pkg/test_runner/lib/src/static_error.dart
index ef8b5ea..dd30e53 100644
--- a/pkg/test_runner/lib/src/static_error.dart
+++ b/pkg/test_runner/lib/src/static_error.dart
@@ -13,7 +13,7 @@
 /// A front end that can report static errors.
 class ErrorSource {
   static const analyzer = ErrorSource._("analyzer");
-  static const cfe = ErrorSource._("CFE");
+  static const cfe = ErrorSource._("CFE", marker: "cfe");
   static const web = ErrorSource._("web");
 
   /// Pseudo-front end for context messages.
@@ -41,9 +41,16 @@
   final String name;
 
   /// The string used to mark errors from this source in test files.
-  String get marker => name.toLowerCase();
+  ///
+  /// Always lower-case.
+  final String marker;
 
-  const ErrorSource._(this.name);
+  /// Creates error source.
+  ///
+  /// If [name] is not all lower-case, then a [marker] must be passed with
+  /// an all lower-case name. If `name` is lower-case, `marker` can be omitted
+  /// and then defaults to `name`.
+  const ErrorSource._(this.name, {String? marker}) : marker = marker ?? name;
 }
 
 /// Describes a single static error reported by a single front end at a specific
@@ -465,7 +472,7 @@
   ///     //      ^^^
   ///
   /// We look for a line that starts with a line comment followed by spaces and
-  /// carets.
+  /// carets. Only used on single lines.
   static final _caretLocationRegExp = RegExp(r"^\s*//\s*(\^+)\s*$");
 
   /// Matches an explicit error location with a length, like:
@@ -475,29 +482,26 @@
   /// or implicitly on the previous line
   ///
   ///     // [error column 17, length 3]
-  static final _explicitLocationAndLengthRegExp = RegExp(
-      r"^\s*//\s*\[\s*error (?:line\s+(\d+)\s*,)?\s*column\s+(\d+)\s*,\s*"
-      r"length\s+(\d+)\s*\]\s*$");
-
-  /// Matches an explicit error location without a length, like:
+  ///
+  /// or either without a length:
   ///
   ///     // [error line 1, column 17]
-  ///
-  /// or implicitly on the previous line.
-  ///
   ///     // [error column 17]
+  ///
+  ///  Only used on single lines.
   static final _explicitLocationRegExp = RegExp(
-      r"^\s*//\s*\[\s*error (?:line\s+(\d+)\s*,)?\s*column\s+(\d+)\s*\]\s*$");
+      r"^\s*//\s*\[\s*error (?:line\s+(\d+)\s*,)?\s*column\s+(\d+)\s*(?:,\s*"
+      r"length\s+(\d+)\s*)?\]\s*$");
 
   /// Matches the beginning of an error message, like `// [analyzer]`.
   ///
   /// May have an optional number like `// [cfe 32]`.
   static final _errorMessageRegExp =
-      RegExp(r"^\s*// \[(\w+)(\s+\d+)?\]\s*(.*)");
+      RegExp(r"^\s*// \[(\w+)(?:\s+(\d+))?\]\s*(.*)");
 
   /// An analyzer error code is a dotted identifier or the magic string
   /// "unspecified".
-  static final _errorCodeRegExp = RegExp(r"^\w+\.\w+|unspecified$");
+  static final _errorCodeRegExp = RegExp(r"^(?:\w+\.\w+|unspecified)$");
 
   /// Any line-comment-only lines after the first line of a CFE error message
   /// are part of it.
@@ -535,40 +539,29 @@
     while (_canPeek(0)) {
       var sourceLine = _peek(0);
 
-      var match = _caretLocationRegExp.firstMatch(sourceLine);
-      if (match != null) {
+      if (_caretLocationRegExp.firstMatch(sourceLine) case var match?) {
         if (_lastRealLine == -1) {
           _fail("An error expectation must follow some code.");
         }
-
+        var markerMatch = match[1]!;
         _parseErrors(
             path: path,
             line: _lastRealLine,
             column: sourceLine.indexOf("^") + 1,
-            length: match[1]!.length);
+            length: markerMatch.length);
         _advance();
         continue;
       }
 
-      match = _explicitLocationAndLengthRegExp.firstMatch(sourceLine);
-      if (match != null) {
+      if (_explicitLocationRegExp.firstMatch(sourceLine) case var match?) {
         var lineCapture = match[1];
+        var columnCapture = match[2]!;
+        var lengthCapture = match[3];
         _parseErrors(
             path: path,
             line: lineCapture == null ? _lastRealLine : int.parse(lineCapture),
-            column: int.parse(match[2]!),
-            length: int.parse(match[3]!));
-        _advance();
-        continue;
-      }
-
-      match = _explicitLocationRegExp.firstMatch(sourceLine);
-      if (match != null) {
-        var lineCapture = match[1];
-        _parseErrors(
-            path: path,
-            line: lineCapture == null ? _lastRealLine : int.parse(lineCapture),
-            column: int.parse(match[2]!));
+            column: int.parse(columnCapture),
+            length: lengthCapture == null ? 0 : int.parse(lengthCapture));
         _advance();
         continue;
       }
@@ -604,7 +597,7 @@
         _fail("Context messages must have an error number.");
       }
 
-      var message = match[3]!;
+      var message = StringBuffer(match[3]!);
       _advance();
       var sourceLines = {locationLine, _currentLine};
 
@@ -614,7 +607,6 @@
 
         // A location line shouldn't be treated as part of the message.
         if (_caretLocationRegExp.hasMatch(nextLine)) break;
-        if (_explicitLocationAndLengthRegExp.hasMatch(nextLine)) break;
         if (_explicitLocationRegExp.hasMatch(nextLine)) break;
 
         // The next source should not be treated as part of the message.
@@ -623,13 +615,15 @@
         var messageMatch = _errorMessageRestRegExp.firstMatch(nextLine);
         if (messageMatch == null) break;
 
-        message += "\n${messageMatch[1]!}";
+        message
+          ..write("\n")
+          ..write(messageMatch[1]!);
         _advance();
         sourceLines.add(_currentLine);
       }
 
       if (source == ErrorSource.analyzer &&
-          !_errorCodeRegExp.hasMatch(message)) {
+          !_errorCodeRegExp.hasMatch(message.toString())) {
         _fail("An analyzer error expectation should be a dotted identifier.");
       }
 
@@ -644,7 +638,7 @@
         errorLength = 0;
       }
 
-      var error = StaticError(source, message,
+      var error = StaticError(source, message.toString(),
           path: path,
           line: line,
           column: column,
@@ -654,9 +648,9 @@
       if (number != null) {
         // Make sure two errors don't claim the same number.
         if (source != ErrorSource.context) {
-          var existingError = _errors
-              .firstWhereOrNull((error) => _errorNumbers[error] == number);
-          if (existingError != null) {
+          var existingError =
+              _errors.any((error) => _errorNumbers[error] == number);
+          if (existingError) {
             _fail("Already have an error with number $number.");
           }
         }
@@ -700,9 +694,9 @@
       var number = _errorNumbers[error];
       if (number == null) continue;
 
-      var context = _contextMessages
-          .firstWhereOrNull((context) => _errorNumbers[context] == number);
-      if (context == null) {
+      var hasContext =
+          _contextMessages.any((context) => _errorNumbers[context] == number);
+      if (!hasContext) {
         throw FormatException("Missing context for numbered error $number "
             "'${error.message}'.");
       }
diff --git a/pkg/test_runner/lib/src/test_file.dart b/pkg/test_runner/lib/src/test_file.dart
index e5706c7..f9a90e1 100644
--- a/pkg/test_runner/lib/src/test_file.dart
+++ b/pkg/test_runner/lib/src/test_file.dart
@@ -6,28 +6,33 @@
 import 'feature.dart';
 import 'path.dart';
 import 'static_error.dart';
+import 'utils.dart';
 
-final _multitestRegExp = RegExp(r"//# \w+:(.*)");
+final _multitestRegExp = RegExp(r"//# \w+:");
 
-final _vmOptionsRegExp = RegExp(r"// VMOptions=(.*)");
-final _environmentRegExp = RegExp(r"// Environment=(.*)");
-final _packagesRegExp = RegExp(r"// Packages=(.*)");
+final _vmOptionsRegExp = RegExp(r"^[ \t]*// VMOptions=(.*)", multiLine: true);
+final _environmentRegExp =
+    RegExp(r"^[ \t]*// Environment=(.*)", multiLine: true);
+final _packagesRegExp = RegExp(r"^[ \t]*// Packages=(.*)", multiLine: true);
 final _experimentRegExp = RegExp(r"^--enable-experiment=([a-z0-9,-]+)$");
 final _localFileRegExp = RegExp(
-    r"""^\s*(?:import(?: augment)?|part) """
-    r"""['"](?!package:|dart:)(.*)['"]"""
-    r"""(?: deferred as \w+)?;""",
+    r"""^[ \t]*(?:import(?: augment)?|part)\s*"""
+    r"""['"](?!package:|dart:)(.*?)['"]\s*"""
+    r"""(?:(?:deferred\s+)?as\s+\w+\s*)?"""
+    r"""(?:(?:show|hide)\s+\w+\s*(?:,\s*\w+\s*))*;""",
     multiLine: true);
 
 List<String> _splitWords(String s) =>
-    s.split(' ').where((e) => e != '').toList();
+    s.split(' ')..removeWhere((s) => s.isEmpty);
 
 List<T> _parseOption<T>(
     String filePath, String contents, String name, T Function(String) convert,
     {bool allowMultiple = false}) {
-  var matches = RegExp('// $name=(.*)').allMatches(contents);
+  var matches = RegExp('^[ \t]*// $name=(.*)', multiLine: true)
+      .allMatches(contents)
+      .toList();
   if (!allowMultiple && matches.length > 1) {
-    throw Exception('More than one "// $name=" line in test $filePath');
+    throw FormatException('More than one "// $name=" line in test $filePath');
   }
 
   var options = <T>[];
@@ -99,7 +104,7 @@
       return path.toString().hashCode;
     }
 
-    return originPath.relativeTo(_suiteDirectory!).toString().hashCode;
+    return originPath.relativeTo(_suiteDirectory).toString().hashCode;
   }
 
   _TestFileBase(this._suiteDirectory, this.path, this.expectedErrors) {
@@ -117,15 +122,15 @@
     var directory = testNamePath.directoryPath;
     var filenameWithoutExt = testNamePath.filenameWithoutExtension;
 
-    String concat(String base, String part) {
-      if (base == "") return part;
-      if (part == "") return base;
+    String join(String base, String part) {
+      if (base.isEmpty) return part;
+      if (part.isEmpty) return base;
       return "$base/$part";
     }
 
     var result = "$directory";
-    result = concat(result, filenameWithoutExt);
-    result = concat(result, multitestKey);
+    result = join(result, filenameWithoutExt);
+    result = join(result, multitestKey);
     return result;
   }
 }
@@ -255,7 +260,7 @@
               "flags. Was:\n$sharedOption");
         }
 
-        experiments.addAll(match.group(1)!.split(","));
+        experiments.addAll(match[1]!.split(","));
         sharedOptions.removeAt(i);
         i--;
       }
@@ -266,9 +271,13 @@
     matches = _environmentRegExp.allMatches(contents);
     for (var match in matches) {
       var envDef = match[1]!;
+      var name = envDef;
+      var value = '';
       var pos = envDef.indexOf('=');
-      var name = (pos < 0) ? envDef : envDef.substring(0, pos);
-      var value = (pos < 0) ? '' : envDef.substring(pos + 1);
+      if (pos >= 0) {
+        name = envDef.substring(0, pos);
+        value = envDef.substring(pos + 1);
+      }
       environment[name] = value;
     }
 
@@ -278,18 +287,23 @@
     matches = _packagesRegExp.allMatches(contents);
     for (var match in matches) {
       if (packages != null) {
-        throw Exception('More than one "// Package..." line in test $filePath');
+        throw FormatException(
+            'More than one "// Package..." line in test $filePath');
       }
-      packages = match[1];
+      packages = match[1]!;
       if (packages != 'none') {
         // Packages=none means that no packages option should be given. Any
         // other value overrides packages.
         packages =
-            Uri.file(filePath).resolveUri(Uri.file(packages!)).toFilePath();
+            Uri.file(filePath).resolveUri(Uri.file(packages)).toFilePath();
       }
     }
 
     var isMultitest = _multitestRegExp.hasMatch(contents);
+    if (isMultitest) {
+      DebugLogger.warning(
+          "${Path(filePath).toNativePath()} is a legacy multi-test file.");
+    }
 
     var errorExpectations = <StaticError>[];
     try {
@@ -323,12 +337,12 @@
       {Set<String>? alreadyParsed}) {
     alreadyParsed ??= {};
     var file = File(path);
-
+    var pathUri = Uri.parse(path);
     // Missing files set no expectations.
     if (!file.existsSync()) return [];
 
     // Catch import loops.
-    if (!alreadyParsed.add(Uri.parse(path).toString())) return [];
+    if (!alreadyParsed.add(pathUri.toString())) return [];
 
     // Parse one file.
     var contents = File(path).readAsStringSync();
@@ -340,7 +354,7 @@
       var localPath = Uri.tryParse(match[1]!);
       // Broken import paths set no expectations.
       if (localPath == null) continue;
-      var uriString = Uri.parse(path).resolve(localPath.path).toString();
+      var uriString = pathUri.resolve(localPath.path).toString();
       result
           .addAll(_parseExpectations(uriString, alreadyParsed: alreadyParsed));
     }
diff --git a/pkg/test_runner/lib/src/update_errors.dart b/pkg/test_runner/lib/src/update_errors.dart
index bcff57e..f8068c4 100644
--- a/pkg/test_runner/lib/src/update_errors.dart
+++ b/pkg/test_runner/lib/src/update_errors.dart
@@ -3,10 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 import 'static_error.dart';
 
-/// Matches leading indentation in a string.
-final _indentationRegExp = RegExp(r"^(\s*)");
+/// Matches end of leading indentation in a line.
+///
+/// Only used on single lines.
+final _indentationRegExp = RegExp(r"(?=\S|$)");
 
 /// Matches a line that contains only a line comment.
+///
+/// Only used on single lines.
 final _lineCommentRegExp = RegExp(r"^\s*//");
 
 /// Removes existing static error marker comments in [source] and adds markers
@@ -190,5 +194,5 @@
 /// Returns the number of characters of leading spaces in [line].
 int _countIndentation(String line) {
   var match = _indentationRegExp.firstMatch(line)!;
-  return match.group(1)!.length;
+  return match.start;
 }
diff --git a/pkg/test_runner/lib/test_runner.dart b/pkg/test_runner/lib/test_runner.dart
index 2e68d78..028a7ab 100644
--- a/pkg/test_runner/lib/test_runner.dart
+++ b/pkg/test_runner/lib/test_runner.dart
@@ -34,7 +34,7 @@
 ///   bar becomes 'foo
 ///   bar'
 String shellSingleQuote(String string) {
-  return "'${string.replaceAll("'", "'\\''")}'";
+  return "'${string.replaceAll("'", r"'\''")}'";
 }
 
 /// Like [shellSingleQuote], but if the string only contains safe ASCII
@@ -43,7 +43,7 @@
 /// a shell keyword or a shell builtin in the first argument in a command. It
 /// should be safe to use this for the second argument onwards in a command.
 String simpleShellSingleQuote(String string) {
-  return RegExp(r"^[a-zA-Z0-9%+,./:_-]*$").hasMatch(string)
+  return RegExp(r"^[a-zA-Z\d%+,./:_\-]*$").hasMatch(string)
       ? string
       : shellSingleQuote(string);
 }
diff --git a/pkg/test_runner/pubspec.yaml b/pkg/test_runner/pubspec.yaml
index 31396d4..3ce5bf7 100644
--- a/pkg/test_runner/pubspec.yaml
+++ b/pkg/test_runner/pubspec.yaml
@@ -7,7 +7,7 @@
 publish_to: none
 
 environment:
-  sdk: '>=2.19.0 <3.0.0'
+  sdk: '>=3.4.0 <4.0.0'
 
 # Use 'any' constraints here; we get our versions from the DEPS file.
 dependencies:
diff --git a/runtime/tests/vm/dart/regress_46790_test.dart b/runtime/tests/vm/dart/regress_46790_test.dart
index ba2cfa5..8846059 100644
--- a/runtime/tests/vm/dart/regress_46790_test.dart
+++ b/runtime/tests/vm/dart/regress_46790_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// VMOptions=--stacktrace_every=137 --deterministic
+// VMOptions=--stacktrace_every=137 --deterministic
 
 // Reduced from
 // The Dart Project Fuzz Tester (1.91).
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc
index 504880f..e8ab786 100644
--- a/runtime/vm/isolate_reload.cc
+++ b/runtime/vm/isolate_reload.cc
@@ -1626,15 +1626,14 @@
   saved_libraries_ = GrowableObjectArray::null();
 }
 
-#ifdef DEBUG
 void ProgramReloadContext::VerifyMaps() {
   TIMELINE_SCOPE(VerifyMaps);
+
+  // Verify that two old classes aren't both mapped to the same new
+  // class. This could happen if the IsSameClass function is broken.
   Class& cls = Class::Handle();
   Class& new_cls = Class::Handle();
   Class& cls2 = Class::Handle();
-
-  // Verify that two old classes aren't both mapped to the same new
-  // class. This could happen is the IsSameClass function is broken.
   UnorderedHashMap<ClassMapTraits> class_map(class_map_storage_);
   UnorderedHashMap<ClassMapTraits> reverse_class_map(
       HashTables::New<UnorderedHashMap<ClassMapTraits> >(
@@ -1647,11 +1646,10 @@
       cls = Class::RawCast(class_map.GetPayload(entry, 0));
       cls2 ^= reverse_class_map.GetOrNull(new_cls);
       if (!cls2.IsNull()) {
-        OS::PrintErr(
+        FATAL(
             "Classes '%s' and '%s' are distinct classes but both map "
             " to class '%s'\n",
             cls.ToCString(), cls2.ToCString(), new_cls.ToCString());
-        UNREACHABLE();
       }
       bool update = reverse_class_map.UpdateOrInsert(cls, new_cls);
       ASSERT(!update);
@@ -1659,15 +1657,41 @@
   }
   class_map.Release();
   reverse_class_map.Release();
+
+  // Verify that two old libraries aren't both mapped to the same new
+  // library. This could happen if the IsSameLibrary function is broken.
+  Library& lib = Library::Handle();
+  Library& new_lib = Library::Handle();
+  Library& lib2 = Library::Handle();
+  UnorderedHashMap<LibraryMapTraits> library_map(library_map_storage_);
+  UnorderedHashMap<LibraryMapTraits> reverse_library_map(
+      HashTables::New<UnorderedHashMap<LibraryMapTraits> >(
+          library_map.NumOccupied()));
+  {
+    UnorderedHashMap<LibraryMapTraits>::Iterator it(&library_map);
+    while (it.MoveNext()) {
+      const intptr_t entry = it.Current();
+      new_lib = Library::RawCast(library_map.GetKey(entry));
+      lib = Library::RawCast(library_map.GetPayload(entry, 0));
+      lib2 ^= reverse_library_map.GetOrNull(new_lib);
+      if (!lib2.IsNull()) {
+        FATAL(
+            "Libraries '%s' and '%s' are distinct libraries but both map "
+            " to library '%s'\n",
+            lib.ToCString(), lib2.ToCString(), new_lib.ToCString());
+      }
+      bool update = reverse_library_map.UpdateOrInsert(lib, new_lib);
+      ASSERT(!update);
+    }
+  }
+  library_map.Release();
+  reverse_library_map.Release();
 }
-#endif
 
 void ProgramReloadContext::CommitBeforeInstanceMorphing() {
   TIMELINE_SCOPE(Commit);
 
-#ifdef DEBUG
   VerifyMaps();
-#endif
 
   // Copy over certain properties of libraries, e.g. is the library
   // debuggable?
diff --git a/runtime/vm/isolate_reload.h b/runtime/vm/isolate_reload.h
index 263b486..43e22ba 100644
--- a/runtime/vm/isolate_reload.h
+++ b/runtime/vm/isolate_reload.h
@@ -335,9 +335,7 @@
 
   void RollbackLibraries();
 
-#ifdef DEBUG
   void VerifyMaps();
-#endif
 
   void CommitBeforeInstanceMorphing();
   void CommitAfterInstanceMorphing();
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index 7ab08d8..e03cf3e 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -255,7 +255,7 @@
     ]
     source = "lib/$library"
     dest = "$root_out_dir/$dart_sdk_output/lib/$library"
-    exclude = "*.svn,doc,*.py,*.gypi,*.sh,.gitignore"
+    exclude = "*.svn,doc,*.py,*.gypi,*.sh,.git*,*.gn,*.gni"
   }
 }
 
diff --git a/tests/language/deferred/prefix_importer_tree_shaken_test.dart b/tests/language/deferred/prefix_importer_tree_shaken_test.dart
index ccb0246..242302e 100644
--- a/tests/language/deferred/prefix_importer_tree_shaken_test.dart
+++ b/tests/language/deferred/prefix_importer_tree_shaken_test.dart
@@ -2,8 +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.
 
-/// VMOptions=--dwarf_stack_traces=true
-/// VMOptions=--dwarf_stack_traces=false
+// VMOptions=--dwarf_stack_traces=true
+// VMOptions=--dwarf_stack_traces=false
 
 import "prefix_importer_tree_shaken_immediate.dart" as i;
 
diff --git a/tests/language/deferred/regression_22995_test.dart b/tests/language/deferred/regression_22995_test.dart
index fdd36c1..daf72a0 100644
--- a/tests/language/deferred/regression_22995_test.dart
+++ b/tests/language/deferred/regression_22995_test.dart
@@ -4,6 +4,8 @@
 
 // Test that closurizing a function implies a dependency on its type.
 
+// dart2wasmOptions=--extra-compiler-option=--enable-deferred-loading
+
 import "package:expect/expect.dart";
 
 import 'regression_22995_lib.dart' deferred as lib;
diff --git a/tests/language/vm/fuzzer_unsigned_shift_right_test.dart b/tests/language/vm/fuzzer_unsigned_shift_right_test.dart
index 01e935a..42660ed 100644
--- a/tests/language/vm/fuzzer_unsigned_shift_right_test.dart
+++ b/tests/language/vm/fuzzer_unsigned_shift_right_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// VMOptions=--deterministic
+// VMOptions=--deterministic
 
 // The Dart Project Fuzz Tester (1.93).
 // Program generated as:
diff --git a/tests/lib/developer/timeline_recorders_test.dart b/tests/lib/developer/timeline_recorders_test.dart
index 349cbba..2656f30 100644
--- a/tests/lib/developer/timeline_recorders_test.dart
+++ b/tests/lib/developer/timeline_recorders_test.dart
@@ -2,10 +2,10 @@
 // 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.
 
-/// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=endless
-/// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=ring
-/// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=startup
-/// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=systrace
+// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=endless
+// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=ring
+// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=startup
+// VMOptions=--timeline_streams=VM,Isolate,GC,Dart --timeline_recorder=systrace
 
 import 'dart:developer';
 
diff --git a/tests/lib/js/static_interop_test/external_dart_reference_test.dart b/tests/lib/js/static_interop_test/external_dart_reference_test.dart
index 33108e4..98bce34 100644
--- a/tests/lib/js/static_interop_test/external_dart_reference_test.dart
+++ b/tests/lib/js/static_interop_test/external_dart_reference_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// Requirements=checked-implicit-downcasts
+// Requirements=checked-implicit-downcasts
 
 import 'dart:js_interop';
 
diff --git a/tests/lib/js/static_interop_test/js_function_arity_test.dart b/tests/lib/js/static_interop_test/js_function_arity_test.dart
index a7ee811..5befc7e 100644
--- a/tests/lib/js/static_interop_test/js_function_arity_test.dart
+++ b/tests/lib/js/static_interop_test/js_function_arity_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// Requirements=checked-implicit-downcasts
+// Requirements=checked-implicit-downcasts
 
 import 'dart:js_interop';
 
diff --git a/tests/lib/js/static_interop_test/js_function_conversions_test.dart b/tests/lib/js/static_interop_test/js_function_conversions_test.dart
index 9719735..f800fbe 100644
--- a/tests/lib/js/static_interop_test/js_function_conversions_test.dart
+++ b/tests/lib/js/static_interop_test/js_function_conversions_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// Requirements=checked-implicit-downcasts
+// Requirements=checked-implicit-downcasts
 
 // Test that Function.toJS properly converts/casts arguments and return values
 // when using non-JS types.
diff --git a/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart b/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
index cb155bb..da91c2e 100644
--- a/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
+++ b/tests/standalone/dwarf_stack_trace_invisible_functions_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf_invisible_functions.so
+// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf_invisible_functions.so
 
 import 'dart:io';
 
diff --git a/tests/standalone/dwarf_stack_trace_obfuscate_test.dart b/tests/standalone/dwarf_stack_trace_obfuscate_test.dart
index 01b5515..a6f09a7 100644
--- a/tests/standalone/dwarf_stack_trace_obfuscate_test.dart
+++ b/tests/standalone/dwarf_stack_trace_obfuscate_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf_obfuscate.so --obfuscate
+// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf_obfuscate.so --obfuscate
 
 import 'dart:io';
 
diff --git a/tests/standalone/dwarf_stack_trace_test.dart b/tests/standalone/dwarf_stack_trace_test.dart
index 53f35db..f6bfad8 100644
--- a/tests/standalone/dwarf_stack_trace_test.dart
+++ b/tests/standalone/dwarf_stack_trace_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf.so
+// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf.so
 
 import 'dart:convert';
 import 'dart:io';
diff --git a/tests/standalone/regress31114_test.dart b/tests/standalone/regress31114_test.dart
index 2977a21..8f822ef 100644
--- a/tests/standalone/regress31114_test.dart
+++ b/tests/standalone/regress31114_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// VMOptions=--background-compilation=false --optimization-counter-threshold=20
+// VMOptions=--background-compilation=false --optimization-counter-threshold=20
 
 import 'pow_test.dart' as test;
 
diff --git a/tests/web/consistent_subtract_error_test.dart b/tests/web/consistent_subtract_error_test.dart
index a34f50f..2a131a2 100644
--- a/tests/web/consistent_subtract_error_test.dart
+++ b/tests/web/consistent_subtract_error_test.dart
@@ -2,7 +2,7 @@
 // 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.
 
-/// dart2jsOptions=--omit-implicit-checks
+// dart2jsOptions=--omit-implicit-checks
 
 import "package:expect/expect.dart";
 
diff --git a/tools/VERSION b/tools/VERSION
index d74ebd2..286335e 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 3
 MINOR 6
 PATCH 0
-PRERELEASE 236
+PRERELEASE 237
 PRERELEASE_PATCH 0
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index ef7d845..9b0b50f 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -582,7 +582,7 @@
         "use-sdk": true
       }
     },
-    "ddc-mac-chrome": {
+    "ddc-mac-(chrome|safari)": {
       "options": {
         "architecture": "arm64",
         "checked": true,
@@ -2022,6 +2022,49 @@
     },
     {
       "builders": [
+        "ddc-mac-safari"
+      ],
+      "meta": {
+        "description": "DDC running in Safari on Mac."
+      },
+      "steps": [
+        {
+          "name": "build dart",
+          "script": "tools/build.py",
+          "arguments": [
+            "--arch=arm64",
+            "dart2js_bot",
+            "ddc_stable_test"
+          ]
+        },
+        {
+          "name": "ddc sdk tests",
+          "arguments": [
+            "-nddc-mac-safari",
+            "--arch=arm64",
+            "corelib",
+            "dartdevc",
+            "language",
+            "lib",
+            "web"
+          ],
+          "shards": 6,
+          "fileset": "js_platform"
+        },
+        {
+          "name": "ddc co19 tests",
+          "arguments": [
+            "-nddc-mac-safari",
+            "--arch=arm64",
+            "co19"
+          ],
+          "shards": 6,
+          "fileset": "js_platform"
+        }
+      ]
+    },
+    {
+      "builders": [
         "ddc-linux-firefox"
       ],
       "meta": {