Version 2.16.0-73.0.dev

Merge commit 'ebe06b7ccd4222263aee88bdfac0055c9f911a7c' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
index 96a2cae..435013c 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_workspace_symbols.dart
@@ -7,7 +7,7 @@
 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/search/workspace_symbols.dart' as search;
+import 'package:analyzer/src/dart/analysis/search.dart' as search;
 
 class WorkspaceSymbolHandler
     extends MessageHandler<WorkspaceSymbolParams, List<SymbolInformation>> {
@@ -52,24 +52,19 @@
     // huge numbers on large projects.
     var remainingResults = 500;
 
-    final filePathsHashSet = <String>{};
-    final tracker = server.declarationsTracker!;
-    final declarations = search.WorkspaceSymbols(tracker).declarations(
-      regex,
-      remainingResults,
-      filePathsHashSet,
-    );
-
-    // Convert the file paths to something we can quickly index into since
-    // we'll be looking things up by index a lot.
-    final filePaths = filePathsHashSet.toList();
+    var workspaceSymbols = search.WorkspaceSymbols();
+    var analysisDrivers = server.driverMap.values.toList();
+    for (var analysisDriver in analysisDrivers) {
+      await analysisDriver.search
+          .declarations(workspaceSymbols, regex, remainingResults);
+    }
 
     // Map the results to SymbolInformations and flatten the list of lists.
-    final symbols = declarations
+    final symbols = workspaceSymbols.declarations
         .map((declaration) => _asSymbolInformation(
               declaration,
               supportedSymbolKinds,
-              filePaths,
+              workspaceSymbols.files,
             ))
         .toList();
 
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index 3a3e421..36d72cc 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -20,12 +20,12 @@
 import 'package:analysis_server/src/lsp/source_edits.dart';
 import 'package:analysis_server/src/protocol_server.dart' as server
     hide AnalysisError;
-import 'package:analysis_server/src/search/workspace_symbols.dart' as server
-    show DeclarationKind;
 import 'package:analyzer/dart/analysis/results.dart' as server;
 import 'package:analyzer/error/error.dart' as server;
 import 'package:analyzer/source/line_info.dart' as server;
 import 'package:analyzer/source/source_range.dart' as server;
+import 'package:analyzer/src/dart/analysis/search.dart' as server
+    show DeclarationKind;
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/services/available_declarations.dart';
 import 'package:analyzer/src/services/available_declarations.dart' as dec;
diff --git a/pkg/analysis_server/lib/src/search/search_domain.dart b/pkg/analysis_server/lib/src/search/search_domain.dart
index 632f7f8..3336e28 100644
--- a/pkg/analysis_server/lib/src/search/search_domain.dart
+++ b/pkg/analysis_server/lib/src/search/search_domain.dart
@@ -8,9 +8,9 @@
 import 'package:analysis_server/src/protocol_server.dart' as protocol;
 import 'package:analysis_server/src/search/element_references.dart';
 import 'package:analysis_server/src/search/type_hierarchy.dart';
-import 'package:analysis_server/src/search/workspace_symbols.dart' as search;
 import 'package:analysis_server/src/services/search/search_engine.dart';
 import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/analysis/search.dart' as search;
 
 /// Instances of the class [SearchDomainHandler] implement a [RequestHandler]
 /// that handles requests in the search domain.
@@ -161,21 +161,21 @@
       }
     }
 
-    var tracker = server.declarationsTracker;
-    if (tracker == null) {
+    if (!server.options.featureSet.completion) {
       server.sendResponse(Response.unsupportedFeature(
           request.id, 'Completion is not enabled.'));
       return;
     }
-    var files = <String>{};
-    var remainingMaxResults = params.maxResults;
-    var declarations = search.WorkspaceSymbols(tracker).declarations(
-      regExp,
-      remainingMaxResults,
-      files,
-      onlyForFile: params.file,
-    );
 
+    var workspaceSymbols = search.WorkspaceSymbols();
+    var analysisDrivers = server.driverMap.values.toList();
+    for (var analysisDriver in analysisDrivers) {
+      await analysisDriver.search.declarations(
+          workspaceSymbols, regExp, params.maxResults,
+          onlyForFile: params.file);
+    }
+
+    var declarations = workspaceSymbols.declarations;
     var elementDeclarations = declarations.map((declaration) {
       return protocol.ElementDeclaration(
           declaration.name,
@@ -192,7 +192,7 @@
     }).toList();
 
     server.sendResponse(protocol.SearchGetElementDeclarationsResult(
-            elementDeclarations, files.toList())
+            elementDeclarations, workspaceSymbols.files)
         .toResponse(request.id));
   }
 
diff --git a/pkg/analysis_server/lib/src/search/workspace_symbols.dart b/pkg/analysis_server/lib/src/search/workspace_symbols.dart
deleted file mode 100644
index bbcf5ac..0000000
--- a/pkg/analysis_server/lib/src/search/workspace_symbols.dart
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
-// 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.
-
-import 'package:analyzer/source/line_info.dart';
-import 'package:analyzer/src/services/available_declarations.dart';
-import 'package:analyzer/src/services/available_declarations.dart' as ad;
-
-class Declaration {
-  final int fileIndex;
-  final LineInfo lineInfo;
-  final String name;
-  final DeclarationKind kind;
-  final int offset;
-  final int line;
-  final int column;
-  final int codeOffset;
-  final int codeLength;
-  final String? className;
-  final String? mixinName;
-  final String? parameters;
-
-  Declaration(
-    this.fileIndex,
-    this.lineInfo,
-    this.name,
-    this.kind,
-    this.offset,
-    this.line,
-    this.column,
-    this.codeOffset,
-    this.codeLength,
-    this.className,
-    this.mixinName,
-    this.parameters,
-  );
-}
-
-enum DeclarationKind {
-  CLASS,
-  CLASS_TYPE_ALIAS,
-  CONSTRUCTOR,
-  ENUM,
-  ENUM_CONSTANT,
-  EXTENSION,
-  FIELD,
-  FUNCTION,
-  FUNCTION_TYPE_ALIAS,
-  GETTER,
-  METHOD,
-  MIXIN,
-  SETTER,
-  TYPE_ALIAS,
-  VARIABLE
-}
-
-class WorkspaceSymbols {
-  final DeclarationsTracker tracker;
-
-  WorkspaceSymbols(this.tracker);
-
-  List<Declaration> declarations(
-      RegExp? regExp, int? maxResults, Set<String> files,
-      {String? onlyForFile}) {
-    _doTrackerWork();
-
-    var declarations = <Declaration>[];
-
-    var pathToIndex = <String, int>{};
-
-    int getPathIndex(String path) {
-      var index = pathToIndex[path];
-      if (index == null) {
-        index = files.length;
-        files.add(path);
-        pathToIndex[path] = index;
-      }
-      return index;
-    }
-
-    if (maxResults != null && declarations.length >= maxResults) {
-      throw const _MaxNumberOfDeclarationsError();
-    }
-
-    void addDeclaration(ad.Declaration declaration) {
-      if (maxResults != null && declarations.length >= maxResults) {
-        throw const _MaxNumberOfDeclarationsError();
-      }
-
-      var path = declaration.locationPath;
-      if (onlyForFile != null && path != onlyForFile) {
-        return;
-      }
-
-      declaration.children.forEach(addDeclaration);
-
-      var name = declaration.name;
-      if (name.isEmpty) {
-        return;
-      }
-      if (name.endsWith('=')) {
-        name = name.substring(0, name.length - 1);
-      }
-      if (regExp != null && !regExp.hasMatch(name)) {
-        return;
-      }
-
-      var parent = declaration.parent;
-
-      String? className;
-      if (parent != null && parent.kind == ad.DeclarationKind.CLASS) {
-        className = parent.name;
-      }
-
-      String? mixinName;
-      if (parent != null && parent.kind == ad.DeclarationKind.MIXIN) {
-        mixinName = parent.name;
-      }
-
-      var topKind = _getTopKind(declaration.kind);
-      if (topKind == null) {
-        return;
-      }
-
-      declarations.add(
-        Declaration(
-          getPathIndex(path),
-          declaration.lineInfo,
-          name,
-          topKind,
-          declaration.locationOffset,
-          declaration.locationStartLine,
-          declaration.locationStartColumn,
-          declaration.codeOffset,
-          declaration.codeLength,
-          className,
-          mixinName,
-          declaration.parameters,
-        ),
-      );
-    }
-
-    var libraries = tracker.allLibraries;
-    try {
-      for (var library in libraries) {
-        library.declarations.forEach(addDeclaration);
-      }
-    } on _MaxNumberOfDeclarationsError {
-      // Uses an exception to short circuit the recursion when there are too
-      // many declarations.
-    }
-
-    return declarations;
-  }
-
-  void _doTrackerWork() {
-    while (tracker.hasWork) {
-      tracker.doWork();
-    }
-  }
-
-  static DeclarationKind? _getTopKind(ad.DeclarationKind kind) {
-    switch (kind) {
-      case ad.DeclarationKind.CLASS:
-        return DeclarationKind.CLASS;
-      case ad.DeclarationKind.CLASS_TYPE_ALIAS:
-        return DeclarationKind.CLASS_TYPE_ALIAS;
-      case ad.DeclarationKind.CONSTRUCTOR:
-        return DeclarationKind.CONSTRUCTOR;
-      case ad.DeclarationKind.ENUM:
-        return DeclarationKind.ENUM;
-      case ad.DeclarationKind.ENUM_CONSTANT:
-        return DeclarationKind.ENUM_CONSTANT;
-      case ad.DeclarationKind.EXTENSION:
-        return DeclarationKind.EXTENSION;
-      case ad.DeclarationKind.FIELD:
-        return DeclarationKind.FIELD;
-      case ad.DeclarationKind.FUNCTION_TYPE_ALIAS:
-        return DeclarationKind.FUNCTION_TYPE_ALIAS;
-      case ad.DeclarationKind.METHOD:
-        return DeclarationKind.METHOD;
-      case ad.DeclarationKind.MIXIN:
-        return DeclarationKind.MIXIN;
-      case ad.DeclarationKind.FUNCTION:
-        return DeclarationKind.FUNCTION;
-      case ad.DeclarationKind.GETTER:
-        return DeclarationKind.GETTER;
-      case ad.DeclarationKind.SETTER:
-        return DeclarationKind.SETTER;
-      case ad.DeclarationKind.TYPE_ALIAS:
-        return DeclarationKind.TYPE_ALIAS;
-      case ad.DeclarationKind.VARIABLE:
-        return DeclarationKind.VARIABLE;
-      default:
-        return null;
-    }
-  }
-}
-
-/// The marker class that is thrown to stop adding declarations.
-class _MaxNumberOfDeclarationsError {
-  const _MaxNumberOfDeclarationsError();
-}
diff --git a/pkg/analysis_server/test/search/declarations_test.dart b/pkg/analysis_server/test/search/declarations_test.dart
index 3d64012..3c0a02f 100644
--- a/pkg/analysis_server/test/search/declarations_test.dart
+++ b/pkg/analysis_server/test/search/declarations_test.dart
@@ -208,8 +208,8 @@
     assertHas('s', ElementKind.SETTER);
     assertHas('f', ElementKind.FUNCTION);
     assertHas('v', ElementKind.TOP_LEVEL_VARIABLE);
-    assertHas('tf1', ElementKind.FUNCTION_TYPE_ALIAS);
-    assertHas('tf2', ElementKind.FUNCTION_TYPE_ALIAS);
+    assertHas('tf1', ElementKind.TYPE_ALIAS);
+    assertHas('tf2', ElementKind.TYPE_ALIAS);
     assertHas('td3', ElementKind.TYPE_ALIAS);
   }
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/search.dart b/pkg/analyzer/lib/src/dart/analysis/search.dart
index 4e0449b..651e0f9 100644
--- a/pkg/analyzer/lib/src/dart/analysis/search.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/search.dart
@@ -7,6 +7,7 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/visitor.dart';
+import 'package:analyzer/source/line_info.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/dart/analysis/index.dart';
@@ -28,6 +29,102 @@
   return element;
 }
 
+DeclarationKind? _getSearchElementKind(Element element) {
+  if (element is ClassElement) {
+    if (element.isEnum) {
+      return DeclarationKind.ENUM;
+    }
+    if (element.isMixin) return DeclarationKind.MIXIN;
+    if (element.isMixinApplication) return DeclarationKind.CLASS_TYPE_ALIAS;
+    return DeclarationKind.CLASS;
+  }
+
+  if (element is ConstructorElement) {
+    return DeclarationKind.CONSTRUCTOR;
+  }
+
+  if (element is ExtensionElement) {
+    return DeclarationKind.EXTENSION;
+  }
+
+  if (element is FieldElement) {
+    if (element.isEnumConstant) return DeclarationKind.ENUM_CONSTANT;
+    return DeclarationKind.FIELD;
+  }
+
+  if (element is FunctionElement) {
+    return DeclarationKind.FUNCTION;
+  }
+
+  if (element is MethodElement) {
+    return DeclarationKind.METHOD;
+  }
+
+  if (element is PropertyAccessorElement) {
+    return element.isGetter ? DeclarationKind.GETTER : DeclarationKind.SETTER;
+  }
+
+  if (element is TypeAliasElement) {
+    return DeclarationKind.TYPE_ALIAS;
+  }
+
+  if (element is VariableElement) {
+    return DeclarationKind.VARIABLE;
+  }
+
+  return null;
+}
+
+/// An element declaration.
+class Declaration {
+  final int fileIndex;
+  final LineInfo lineInfo;
+  final String name;
+  final DeclarationKind kind;
+  final int offset;
+  final int line;
+  final int column;
+  final int codeOffset;
+  final int codeLength;
+  final String? className;
+  final String? mixinName;
+  final String? parameters;
+
+  Declaration(
+    this.fileIndex,
+    this.lineInfo,
+    this.name,
+    this.kind,
+    this.offset,
+    this.line,
+    this.column,
+    this.codeOffset,
+    this.codeLength,
+    this.className,
+    this.mixinName,
+    this.parameters,
+  );
+}
+
+/// The kind of a [Declaration].
+enum DeclarationKind {
+  CLASS,
+  CLASS_TYPE_ALIAS,
+  CONSTRUCTOR,
+  ENUM,
+  ENUM_CONSTANT,
+  EXTENSION,
+  FIELD,
+  FUNCTION,
+  FUNCTION_TYPE_ALIAS,
+  GETTER,
+  METHOD,
+  MIXIN,
+  SETTER,
+  TYPE_ALIAS,
+  VARIABLE
+}
+
 /// Search support for an [AnalysisDriver].
 class Search {
   final AnalysisDriver _driver;
@@ -64,6 +161,114 @@
     return elements;
   }
 
+  /// Add matching declarations to the [result].
+  Future<void> declarations(
+      WorkspaceSymbols result, RegExp? regExp, int? maxResults,
+      {String? onlyForFile}) async {
+    if (result.hasMoreDeclarationsThan(maxResults)) {
+      return;
+    }
+
+    void addDeclaration(LineInfo lineInfo, Element element) {
+      if (result.hasMoreDeclarationsThan(maxResults)) {
+        throw const _MaxNumberOfDeclarationsError();
+      }
+
+      if (element.isSynthetic) {
+        return;
+      }
+
+      var source = element.source;
+      if (source == null) {
+        return;
+      }
+
+      var path = source.fullName;
+      if (onlyForFile != null && path != onlyForFile) {
+        return;
+      }
+
+      var name = element.name;
+      if (name == null || name.isEmpty) {
+        return;
+      }
+      if (name.endsWith('=')) {
+        name = name.substring(0, name.length - 1);
+      }
+      if (regExp != null && !regExp.hasMatch(name)) {
+        return;
+      }
+
+      var enclosing = element.enclosingElement;
+
+      String? className;
+      String? mixinName;
+      if (enclosing is ClassElement) {
+        if (enclosing.isEnum) {
+          // skip
+        } else if (enclosing.isMixin) {
+          mixinName = enclosing.name;
+        } else {
+          className = enclosing.name;
+        }
+      }
+
+      var kind = _getSearchElementKind(element);
+      if (kind == null) {
+        return;
+      }
+
+      String? parameters;
+      if (element is ExecutableElement) {
+        var displayString = element.getDisplayString(withNullability: true);
+        var parameterIndex = displayString.indexOf('(');
+        if (parameterIndex > 0) {
+          parameters = displayString.substring(parameterIndex);
+        }
+      }
+
+      element as ElementImpl; // to access codeOffset/codeLength
+      var locationOffset = element.nameOffset;
+      var locationStart = lineInfo.getLocation(locationOffset);
+
+      result.declarations.add(
+        Declaration(
+          result._getPathIndex(path),
+          lineInfo,
+          name,
+          kind,
+          locationOffset,
+          locationStart.lineNumber,
+          locationStart.columnNumber,
+          element.codeOffset ?? 0,
+          element.codeLength ?? 0,
+          className,
+          mixinName,
+          parameters,
+        ),
+      );
+    }
+
+    await _driver.discoverAvailableFiles();
+    var knownFiles = _driver.fsState.knownFiles.toList();
+    for (var file in knownFiles) {
+      var libraryElement = _driver.getLibraryByFile(file);
+      if (libraryElement != null) {
+        for (var unitElement in libraryElement.units) {
+          try {
+            unitElement.accept(
+              _FunctionElementVisitor((element) {
+                addDeclaration(unitElement.lineInfo!, element);
+              }),
+            );
+          } on _MaxNumberOfDeclarationsError {
+            return;
+          }
+        }
+      }
+    }
+  }
+
   /// Returns references to the [element].
   Future<List<SearchResult>> references(
       Element? element, SearchedFiles searchedFiles) async {
@@ -604,6 +809,26 @@
   String toString() => id;
 }
 
+class WorkspaceSymbols {
+  final List<Declaration> declarations = [];
+  final List<String> files = [];
+  final Map<String, int> _pathToIndex = {};
+
+  bool hasMoreDeclarationsThan(int? maxResults) {
+    return maxResults != null && declarations.length >= maxResults;
+  }
+
+  int _getPathIndex(String path) {
+    var index = _pathToIndex[path];
+    if (index == null) {
+      index = files.length;
+      files.add(path);
+      _pathToIndex[path] = index;
+    }
+    return index;
+  }
+}
+
 /// A visitor that finds the deep-most [Element] that contains the [offset].
 class _ContainingElementFinder extends GeneralizingElementVisitor<void> {
   final int offset;
@@ -624,6 +849,19 @@
   }
 }
 
+/// A visitor that handles any element with a function.
+class _FunctionElementVisitor extends GeneralizingElementVisitor<void> {
+  final void Function(Element element) process;
+
+  _FunctionElementVisitor(this.process);
+
+  @override
+  void visitElement(Element element) {
+    process(element);
+    super.visitElement(element);
+  }
+}
+
 /// Visitor that adds [SearchResult]s for references to the [importElement].
 class _ImportElementReferencesVisitor extends RecursiveAstVisitor<void> {
   final List<SearchResult> results = <SearchResult>[];
@@ -950,3 +1188,8 @@
         enclosingElement, kind, node.offset, node.length, true, isQualified));
   }
 }
+
+/// The marker class that is thrown to stop adding declarations.
+class _MaxNumberOfDeclarationsError {
+  const _MaxNumberOfDeclarationsError();
+}
diff --git a/pkg/analyzer/test/src/dart/analysis/search_test.dart b/pkg/analyzer/test/src/dart/analysis/search_test.dart
index 700ec3d..2140a33 100644
--- a/pkg/analyzer/test/src/dart/analysis/search_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/search_test.dart
@@ -114,6 +114,259 @@
         unorderedEquals([a.methods[0], b.fields[0]]));
   }
 
+  test_declarations_class() async {
+    await resolveTestCode('''
+class C {
+  int f;
+  C();
+  C.named();
+  int get g => 0;
+  void set s(_) {}
+  void m() {}
+}
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+    declarations.assertHas('C', DeclarationKind.CLASS,
+        offset: 6, codeOffset: 0, codeLength: 91);
+    declarations.assertHas('f', DeclarationKind.FIELD,
+        offset: 16, codeOffset: 12, codeLength: 5, className: 'C');
+    declarations.assertHas('named', DeclarationKind.CONSTRUCTOR,
+        offset: 30, codeOffset: 28, codeLength: 10, className: 'C');
+    declarations.assertHas('g', DeclarationKind.GETTER,
+        offset: 49, codeOffset: 41, codeLength: 15, className: 'C');
+    declarations.assertHas('s', DeclarationKind.SETTER,
+        offset: 68, codeOffset: 59, codeLength: 16, className: 'C');
+    declarations.assertHas('m', DeclarationKind.METHOD,
+        offset: 83, codeOffset: 78, codeLength: 11, className: 'C');
+  }
+
+  test_declarations_discover() async {
+    var aaaPackageRootPath = '$packagesRootPath/aaa';
+    var bbbPackageRootPath = '$packagesRootPath/bbb';
+    var cccPackageRootPath = '$packagesRootPath/ccc';
+    var aaaFilePath = convertPath('$aaaPackageRootPath/lib/a.dart');
+    var bbbFilePath = convertPath('$bbbPackageRootPath/lib/b.dart');
+    var cccFilePath = convertPath('$cccPackageRootPath/lib/c.dart');
+
+    writeTestPackageConfig(
+      PackageConfigFileBuilder()
+        ..add(name: 'aaa', rootPath: aaaPackageRootPath)
+        ..add(name: 'bbb', rootPath: bbbPackageRootPath),
+    );
+
+    newFile(aaaFilePath, content: 'class A {}');
+    newFile(bbbFilePath, content: 'class B {}');
+    newFile(cccFilePath, content: 'class C {}');
+
+    await resolveTestCode('class T {}');
+
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+
+    declarations.assertHas('T', DeclarationKind.CLASS);
+    declarations.assertHas('A', DeclarationKind.CLASS);
+    declarations.assertHas('B', DeclarationKind.CLASS);
+    declarations.assertNo('C');
+  }
+
+  test_declarations_enum() async {
+    await resolveTestCode('''
+enum E {
+  a, bb, ccc
+}
+''');
+
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+
+    declarations.assertHas('E', DeclarationKind.ENUM,
+        offset: 5, codeOffset: 0, codeLength: 23);
+    declarations.assertHas('a', DeclarationKind.ENUM_CONSTANT,
+        offset: 11, codeOffset: 11, codeLength: 1);
+    declarations.assertHas('bb', DeclarationKind.ENUM_CONSTANT,
+        offset: 14, codeOffset: 14, codeLength: 2);
+    declarations.assertHas('ccc', DeclarationKind.ENUM_CONSTANT,
+        offset: 18, codeOffset: 18, codeLength: 3);
+  }
+
+  test_declarations_maxResults() async {
+    await resolveTestCode('''
+class A {}
+class B {}
+class C {}
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, 2);
+    expect(results.declarations, hasLength(2));
+  }
+
+  test_declarations_mixin() async {
+    await resolveTestCode('''
+mixin M {
+  int f;
+  int get g => 0;
+  void set s(_) {}
+  void m() {}
+}
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+    declarations.assertHas('M', DeclarationKind.MIXIN,
+        offset: 6, codeOffset: 0, codeLength: 71);
+    declarations.assertHas('f', DeclarationKind.FIELD,
+        offset: 16, codeOffset: 12, codeLength: 5, mixinName: 'M');
+    declarations.assertHas('g', DeclarationKind.GETTER,
+        offset: 29, codeOffset: 21, codeLength: 15, mixinName: 'M');
+    declarations.assertHas('s', DeclarationKind.SETTER,
+        offset: 48, codeOffset: 39, codeLength: 16, mixinName: 'M');
+    declarations.assertHas('m', DeclarationKind.METHOD,
+        offset: 63, codeOffset: 58, codeLength: 11, mixinName: 'M');
+  }
+
+  test_declarations_onlyForFile() async {
+    newFile('$testPackageLibPath/a.dart', content: 'class A {}');
+    var b = newFile('$testPackageLibPath/b.dart', content: 'class B {}').path;
+
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null, onlyForFile: b);
+    var declarations = results.declarations;
+
+    expect(results.files, [b]);
+
+    declarations.assertNo('A');
+    declarations.assertHas('B', DeclarationKind.CLASS);
+  }
+
+  test_declarations_parameters() async {
+    await resolveTestCode('''
+class C {
+  int get g => 0;
+  void m(int a, double b) {}
+}
+void f(bool a, String b) {}
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+
+    var declaration = declarations.assertHas('C', DeclarationKind.CLASS);
+    expect(declaration.parameters, isNull);
+
+    declaration =
+        declarations.assertHas('g', DeclarationKind.GETTER, className: 'C');
+    expect(declaration.parameters, isNull);
+
+    declaration =
+        declarations.assertHas('m', DeclarationKind.METHOD, className: 'C');
+    expect(declaration.parameters, '(int a, double b)');
+
+    declaration = declarations.assertHas('f', DeclarationKind.FUNCTION);
+    expect(declaration.parameters, '(bool a, String b)');
+  }
+
+  test_declarations_parameters_functionTyped() async {
+    await resolveTestCode('''
+void f1(bool a(int b, String c)) {}
+void f2(a(b, c)) {}
+void f3(bool Function(int a, String b) c) {}
+void f4(bool Function(int, String) a) {}
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+
+    var declaration = declarations.assertHas('f1', DeclarationKind.FUNCTION);
+    expect(declaration.parameters, '(bool Function(int, String) a)');
+
+    declaration = declarations.assertHas('f2', DeclarationKind.FUNCTION);
+    expect(declaration.parameters, '(dynamic Function(dynamic, dynamic) a)');
+
+    declaration = declarations.assertHas('f3', DeclarationKind.FUNCTION);
+    expect(declaration.parameters, '(bool Function(int, String) c)');
+
+    declaration = declarations.assertHas('f4', DeclarationKind.FUNCTION);
+    expect(declaration.parameters, '(bool Function(int, String) a)');
+  }
+
+  test_declarations_parameters_typeArguments() async {
+    await resolveTestCode('''
+class A<T, T2> {
+  void m1(Map<int, String> a) {}
+  void m2<U>(Map<T, U> a) {}
+  void m3<U1, U2>(Map<Map<T2, U2>, Map<U1, T>> a) {}
+}
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+
+    var declaration =
+        declarations.assertHas('m1', DeclarationKind.METHOD, className: 'A');
+    expect(declaration.parameters, '(Map<int, String> a)');
+
+    declaration =
+        declarations.assertHas('m2', DeclarationKind.METHOD, className: 'A');
+    expect(declaration.parameters, '(Map<T, U> a)');
+
+    declaration =
+        declarations.assertHas('m3', DeclarationKind.METHOD, className: 'A');
+    expect(declaration.parameters, '(Map<Map<T2, U2>, Map<U1, T>> a)');
+  }
+
+  test_declarations_regExp() async {
+    await resolveTestCode('''
+class A {}
+class B {}
+class C {}
+class D {}
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, RegExp(r'[A-C]'), null);
+    var declarations = results.declarations;
+
+    declarations.assertHas('A', DeclarationKind.CLASS);
+    declarations.assertHas('B', DeclarationKind.CLASS);
+    declarations.assertHas('C', DeclarationKind.CLASS);
+    declarations.assertNo('D');
+  }
+
+  test_declarations_top() async {
+    await resolveTestCode('''
+int get g => 0;
+void set s(_) {}
+void f(int p) {}
+int v;
+typedef void tf1();
+typedef tf2<T> = int Function<S>(T tp, S sp);
+''');
+    var results = WorkspaceSymbols();
+    await driver.search.declarations(results, null, null);
+    var declarations = results.declarations;
+
+    declarations.assertHas('g', DeclarationKind.GETTER,
+        offset: 8, codeOffset: 0, codeLength: 15);
+    declarations.assertHas('s', DeclarationKind.SETTER,
+        offset: 25, codeOffset: 16, codeLength: 16);
+    declarations.assertHas(
+      'f',
+      DeclarationKind.FUNCTION,
+      offset: 38,
+      codeOffset: 33,
+      codeLength: 16,
+    );
+    declarations.assertHas('v', DeclarationKind.VARIABLE,
+        offset: 54, codeOffset: 50, codeLength: 5);
+    declarations.assertHas('tf1', DeclarationKind.TYPE_ALIAS,
+        offset: 70, codeOffset: 57, codeLength: 19);
+    declarations.assertHas('tf2', DeclarationKind.TYPE_ALIAS,
+        offset: 85, codeOffset: 77, codeLength: 45);
+  }
+
   test_searchMemberReferences_qualified_resolved() async {
     await resolveTestCode('''
 class C {
@@ -2126,3 +2379,38 @@
     expect(matches, unorderedEquals(expectedMatches));
   }
 }
+
+extension on List<Declaration> {
+  void assertNo(String name) {
+    for (var declaration in this) {
+      if (declaration.name == name) {
+        fail('Unexpected declaration $name');
+      }
+    }
+  }
+
+  Declaration assertHas(String name, DeclarationKind kind,
+      {int? offset,
+      int? codeOffset,
+      int? codeLength,
+      String? className,
+      String? mixinName}) {
+    for (var declaration in this) {
+      if (declaration.name == name &&
+          declaration.kind == kind &&
+          (offset == null || declaration.offset == offset) &&
+          (codeOffset == null || declaration.codeOffset == codeOffset) &&
+          (codeLength == null || declaration.codeLength == codeLength) &&
+          declaration.className == className &&
+          declaration.mixinName == mixinName) {
+        return declaration;
+      }
+    }
+    var actual =
+        map((d) => '(name=${d.name}, kind=${d.kind}, offset=${d.offset}, '
+            'codeOffset=${d.codeOffset}, codeLength=${d.codeLength}, '
+            'className=${d.className}, mixinName=${d.mixinName})').join('\n');
+    fail('Expected to find (name=$name, kind=$kind, offset=$offset, '
+        'codeOffset=$codeOffset, codeLength=$codeLength) in\n$actual');
+  }
+}
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index a703267..523cadf 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -149,6 +149,9 @@
   final String? name;
   final String? cwd;
 
+  /// Environment variables to pass to the launched process.
+  final Map<String, String>? env;
+
   /// Paths that should be considered the users local code.
   ///
   /// These paths will generally be all of the open folders in the users editor
@@ -199,6 +202,8 @@
     required this.restart,
     required this.name,
     required this.cwd,
+    // TODO(dantup): This can be made required after Flutter DAP is passing it.
+    this.env,
     required this.additionalProjectPaths,
     required this.debugSdkLibraries,
     required this.debugExternalPackageLibraries,
@@ -211,6 +216,7 @@
       : restart = obj['restart'],
         name = obj['name'] as String?,
         cwd = obj['cwd'] as String?,
+        env = obj['env'] as Map<String, String>?,
         additionalProjectPaths =
             (obj['additionalProjectPaths'] as List?)?.cast<String>(),
         debugSdkLibraries = obj['debugSdkLibraries'] as bool?,
@@ -226,6 +232,7 @@
         if (restart != null) 'restart': restart,
         if (name != null) 'name': name,
         if (cwd != null) 'cwd': cwd,
+        if (env != null) 'env': env,
         if (additionalProjectPaths != null)
           'additionalProjectPaths': additionalProjectPaths,
         if (debugSdkLibraries != null) 'debugSdkLibraries': debugSdkLibraries,
@@ -1881,6 +1888,15 @@
   /// the VM or Flutter tool).
   final List<String>? toolArgs;
 
+  /// Arguments to be passed directly to the Dart VM that will run [program].
+  ///
+  /// Unlike [toolArgs] which always go after the complete tool, these args
+  /// always go directly after `dart`:
+  ///
+  ///   - dart {vmAdditionalArgs} {toolArgs}
+  ///   - dart {vmAdditionalArgs} run test:test {toolArgs}
+  final List<String>? vmAdditionalArgs;
+
   final int? vmServicePort;
 
   final bool? enableAsserts;
@@ -1921,6 +1937,7 @@
     this.args,
     this.vmServicePort,
     this.toolArgs,
+    this.vmAdditionalArgs,
     this.console,
     this.enableAsserts,
     this.customTool,
@@ -1928,6 +1945,7 @@
     Object? restart,
     String? name,
     String? cwd,
+    Map<String, String>? env,
     List<String>? additionalProjectPaths,
     bool? debugSdkLibraries,
     bool? debugExternalPackageLibraries,
@@ -1938,6 +1956,7 @@
           restart: restart,
           name: name,
           cwd: cwd,
+          env: env,
           additionalProjectPaths: additionalProjectPaths,
           debugSdkLibraries: debugSdkLibraries,
           debugExternalPackageLibraries: debugExternalPackageLibraries,
@@ -1951,6 +1970,7 @@
         program = obj['program'] as String,
         args = (obj['args'] as List?)?.cast<String>(),
         toolArgs = (obj['toolArgs'] as List?)?.cast<String>(),
+        vmAdditionalArgs = (obj['vmAdditionalArgs'] as List?)?.cast<String>(),
         vmServicePort = obj['vmServicePort'] as int?,
         console = obj['console'] as String?,
         enableAsserts = obj['enableAsserts'] as bool?,
@@ -1965,6 +1985,7 @@
         'program': program,
         if (args != null) 'args': args,
         if (toolArgs != null) 'toolArgs': toolArgs,
+        if (vmAdditionalArgs != null) 'vmAdditionalArgs': vmAdditionalArgs,
         if (vmServicePort != null) 'vmServicePort': vmServicePort,
         if (console != null) 'console': console,
         if (enableAsserts != null) 'enableAsserts': enableAsserts,
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index 9beae08..46e28e3 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -87,6 +87,7 @@
     }
 
     final vmArgs = <String>[
+      ...?args.vmAdditionalArgs,
       if (debug) ...[
         '--enable-vm-service=${args.vmServicePort ?? 0}${ipv6 ? '/::1' : ''}',
         '--pause_isolates_on_start',
@@ -132,17 +133,22 @@
                 : null
         : null;
 
-    // TODO(dantup): Support passing env to both of these.
-
     if (terminalKind != null) {
       await launchInEditorTerminal(
         debug,
         terminalKind,
         executable,
         processArgs,
+        workingDirectory: args.cwd,
+        env: args.env,
       );
     } else {
-      await launchAsProcess(executable, processArgs);
+      await launchAsProcess(
+        executable,
+        processArgs,
+        workingDirectory: args.cwd,
+        env: args.env,
+      );
     }
 
     // Delay responding until the debugger is connected.
@@ -180,10 +186,12 @@
     bool debug,
     String terminalKind,
     String executable,
-    List<String> processArgs,
-  ) async {
+    List<String> processArgs, {
+    required String? workingDirectory,
+    required Map<String, String>? env,
+  }) async {
     final args = this.args as DartLaunchRequestArguments;
-    logger?.call('Spawning $executable with $processArgs in ${args.cwd}'
+    logger?.call('Spawning $executable with $processArgs in $workingDirectory'
         ' via client ${terminalKind} terminal');
 
     // runInTerminal is a DAP request that goes from server-to-client that
@@ -193,7 +201,8 @@
     // we can detect with the normal watching code.
     final requestArgs = RunInTerminalRequestArguments(
       args: [executable, ...processArgs],
-      cwd: args.cwd ?? path.dirname(args.program),
+      cwd: workingDirectory ?? path.dirname(args.program),
+      env: env,
       kind: terminalKind,
       title: args.name ?? 'Dart',
     );
@@ -225,13 +234,16 @@
   /// [OutputEvent]s.
   Future<void> launchAsProcess(
     String executable,
-    List<String> processArgs,
-  ) async {
-    logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
+    List<String> processArgs, {
+    required String? workingDirectory,
+    required Map<String, String>? env,
+  }) async {
+    logger?.call('Spawning $executable with $processArgs in $workingDirectory');
     final process = await Process.start(
       executable,
       processArgs,
-      workingDirectory: args.cwd,
+      workingDirectory: workingDirectory,
+      environment: env,
     );
     _process = process;
     pidsToTerminate.add(process.pid);
diff --git a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
index 5b0ebfd..0f67af4 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -83,6 +83,7 @@
     }
 
     final vmArgs = <String>[
+      ...?args.vmAdditionalArgs,
       if (debug) ...[
         '--enable-vm-service=${args.vmServicePort ?? 0}${ipv6 ? '/::1' : ''}',
         '--pause_isolates_on_start',
@@ -125,21 +126,27 @@
       ...?args.args,
     ];
 
-    // TODO(dantup): Support passing env.
-
-    await launchAsProcess(executable, processArgs);
+    await launchAsProcess(
+      executable,
+      processArgs,
+      workingDirectory: args.cwd,
+      env: args.env,
+    );
   }
 
   /// Launches the test script as a process controlled by the debug adapter.
   Future<void> launchAsProcess(
     String executable,
-    List<String> processArgs,
-  ) async {
-    logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
+    List<String> processArgs, {
+    required String? workingDirectory,
+    required Map<String, String>? env,
+  }) async {
+    logger?.call('Spawning $executable with $processArgs in $workingDirectory');
     final process = await Process.start(
       executable,
       processArgs,
-      workingDirectory: args.cwd,
+      workingDirectory: workingDirectory,
+      environment: env,
     );
     _process = process;
     pidsToTerminate.add(process.pid);
diff --git a/pkg/dds/test/dap/dart_cli_test.dart b/pkg/dds/test/dap/dart_cli_test.dart
index 26e56c5..d07dc37 100644
--- a/pkg/dds/test/dap/dart_cli_test.dart
+++ b/pkg/dds/test/dap/dart_cli_test.dart
@@ -12,6 +12,24 @@
 
 main() {
   group('dart cli adapter', () {
+    test('includes vmAdditionalArgs', () async {
+      final adapter = MockDartCliDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        vmAdditionalArgs: ['vm_arg'],
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(adapter.processArgs, containsAllInOrder(['vm_arg', 'foo.dart']));
+    });
+
     test('includes toolArgs', () async {
       final adapter = MockDartCliDebugAdapter();
       final responseCompleter = Completer<void>();
@@ -27,7 +45,29 @@
       await responseCompleter.future;
 
       expect(adapter.executable, equals(Platform.resolvedExecutable));
-      expect(adapter.processArgs, contains('tool_arg'));
+      expect(adapter.processArgs, containsAllInOrder(['tool_arg', 'foo.dart']));
+    });
+
+    test('includes env', () async {
+      final adapter = MockDartCliDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        env: {
+          'ENV1': 'VAL1',
+          'ENV2': 'VAL2',
+        },
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(adapter.env!['ENV1'], 'VAL1');
+      expect(adapter.env!['ENV2'], 'VAL2');
     });
 
     group('includes customTool', () {
diff --git a/pkg/dds/test/dap/dart_test_test.dart b/pkg/dds/test/dap/dart_test_test.dart
index eac0a34..defba7d 100644
--- a/pkg/dds/test/dap/dart_test_test.dart
+++ b/pkg/dds/test/dap/dart_test_test.dart
@@ -12,7 +12,28 @@
 
 main() {
   group('dart test adapter', () {
-    test('includes toolArgs', () async {
+    test('includes vmAdditionalArgs before run test:test', () async {
+      final adapter = MockDartTestDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        vmAdditionalArgs: ['vm_arg'],
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(
+        adapter.processArgs,
+        containsAllInOrder(['vm_arg', 'run', 'test:test', 'foo.dart']),
+      );
+    });
+
+    test('includes toolArgs after run test:test', () async {
       final adapter = MockDartTestDebugAdapter();
       final responseCompleter = Completer<void>();
       final request = MockRequest();
@@ -27,8 +48,32 @@
       await responseCompleter.future;
 
       expect(adapter.executable, equals(Platform.resolvedExecutable));
-      expect(adapter.processArgs, containsAllInOrder(['run', 'test:test']));
-      expect(adapter.processArgs, contains('tool_arg'));
+      expect(
+        adapter.processArgs,
+        containsAllInOrder(['run', 'test:test', 'tool_arg', 'foo.dart']),
+      );
+    });
+
+    test('includes env', () async {
+      final adapter = MockDartTestDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        env: {
+          'ENV1': 'VAL1',
+          'ENV2': 'VAL2',
+        },
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(adapter.env!['ENV1'], 'VAL1');
+      expect(adapter.env!['ENV2'], 'VAL2');
     });
 
     group('includes customTool', () {
diff --git a/pkg/dds/test/dap/mocks.dart b/pkg/dds/test/dap/mocks.dart
index 954f25d..9afe079 100644
--- a/pkg/dds/test/dap/mocks.dart
+++ b/pkg/dds/test/dap/mocks.dart
@@ -8,7 +8,8 @@
 import 'package:dds/src/dap/adapters/dart_cli_adapter.dart';
 import 'package:dds/src/dap/adapters/dart_test_adapter.dart';
 
-/// A [DartCliDebugAdapter] that captures what process/args will be launched.
+/// A [DartCliDebugAdapter] that captures information about the process that
+/// will be launched.
 class MockDartCliDebugAdapter extends DartCliDebugAdapter {
   final StreamSink<List<int>> stdin;
   final Stream<List<int>> stdout;
@@ -16,6 +17,8 @@
   late bool launchedInTerminal;
   late String executable;
   late List<String> processArgs;
+  late String? workingDirectory;
+  late Map<String, String>? env;
 
   factory MockDartCliDebugAdapter() {
     final stdinController = StreamController<List<int>>();
@@ -33,32 +36,43 @@
 
   Future<void> launchAsProcess(
     String executable,
-    List<String> processArgs,
-  ) async {
+    List<String> processArgs, {
+    required String? workingDirectory,
+    required Map<String, String>? env,
+  }) async {
     this.launchedInTerminal = false;
     this.executable = executable;
     this.processArgs = processArgs;
+    this.workingDirectory = workingDirectory;
+    this.env = env;
   }
 
   Future<void> launchInEditorTerminal(
     bool debug,
     String terminalKind,
     String executable,
-    List<String> processArgs,
-  ) async {
+    List<String> processArgs, {
+    required String? workingDirectory,
+    required Map<String, String>? env,
+  }) async {
     this.launchedInTerminal = true;
     this.executable = executable;
     this.processArgs = processArgs;
+    this.workingDirectory = workingDirectory;
+    this.env = env;
   }
 }
 
-/// A [DartTestDebugAdapter] that captures what process/args will be launched.
+/// A [DartTestDebugAdapter] that captures information about the process that
+/// will be launched.
 class MockDartTestDebugAdapter extends DartTestDebugAdapter {
   final StreamSink<List<int>> stdin;
   final Stream<List<int>> stdout;
 
   late String executable;
   late List<String> processArgs;
+  late String? workingDirectory;
+  late Map<String, String>? env;
 
   factory MockDartTestDebugAdapter() {
     final stdinController = StreamController<List<int>>();
@@ -79,10 +93,14 @@
 
   Future<void> launchAsProcess(
     String executable,
-    List<String> processArgs,
-  ) async {
+    List<String> processArgs, {
+    required String? workingDirectory,
+    required Map<String, String>? env,
+  }) async {
     this.executable = executable;
     this.processArgs = processArgs;
+    this.workingDirectory = workingDirectory;
+    this.env = env;
   }
 }
 
diff --git a/pkg/dds/tool/dap/README.md b/pkg/dds/tool/dap/README.md
index 5dd4e54..26bbeb3 100644
--- a/pkg/dds/tool/dap/README.md
+++ b/pkg/dds/tool/dap/README.md
@@ -29,13 +29,15 @@
 - `int? vmServicePort` - the port to bind the VM Service too
 - `List<String>? additionalProjectPaths` - paths of any projects (outside of `cwd`) that are open in the users workspace
 - `String? cwd` - the working directory for the Dart process to be spawned in
+- `Map<String, String>? env` - environment variables to be passed to any spawned process
 
 Arguments specific to `launchRequest` are:
 
 - `bool? noDebug` - whether to run in debug or noDebug mode (if not supplied, defaults to debug)
 - `String program` - the path of the Dart program to run
-- `List<String>? args` - arguments to be passed to the Dart program
-- `List<String>? toolArgs` - arguments for the Dart VM
+- `List<String>? args` - arguments to be passed to the Dart program (after the `program` on the command line)
+- `List<String>? toolArgs` - arguments passed after the tool that will run `program` (after `dart` for CLI scripts and after `dart run test:test` for test scripts)
+- `List<String>? vmAdditionalArgs` - arguments passed directly to the Dart VM (after `dart` for both CLI scripts and test scripts)
 - `String? console` - if set to `"terminal"` or `"externalTerminal"` will be run using the `runInTerminal` reverse-request; otherwise the debug adapter spawns the Dart process
 - `bool? enableAsserts` - whether to enable asserts (if not supplied, defaults to enabled)
 - `String? customTool` - an optional tool to run instead of `dart` - the custom tool must be completely compatible with the tool/command it is replacing
diff --git a/pkg/front_end/lib/src/fasta/incremental_compiler.dart b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
index 862e7dd..9eba06d 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -153,20 +153,20 @@
   DillTarget? _dillLoadedData;
   List<LibraryBuilder>? _platformBuilders;
   Map<Uri, LibraryBuilder>? _userBuilders;
-  final Uri? _initializeFromDillUri;
-  Component? _componentToInitializeFrom;
-  bool _initializedFromDill = false;
-  bool _initializedIncrementalSerializer = false;
+
+  final _InitializationStrategy _initializationStrategy;
+
   Uri? _previousPackagesUri;
   Map<String, Package>? _previousPackagesMap;
   Map<String, Package>? _currentPackagesMap;
   bool _hasToCheckPackageUris = false;
   final bool _initializedForExpressionCompilationOnly;
   bool _computeDeltaRunOnce = false;
-  Map<Uri, List<DiagnosticMessageFromJson>> _remainingComponentProblems =
-      new Map<Uri, List<DiagnosticMessageFromJson>>();
   List<Component>? _modulesToLoad;
-  IncrementalSerializer? _incrementalSerializer;
+  final IncrementalSerializer? _incrementalSerializer;
+  final _ComponentProblems _componentProblems = new _ComponentProblems();
+
+  RecorderForTesting? get recorderForTesting => null;
 
   static final Uri debugExprUri =
       new Uri(scheme: "org-dartlang-debug", path: "synthetic_debug_expression");
@@ -179,44 +179,48 @@
   Completer<dynamic>? _currentlyCompiling;
 
   IncrementalCompiler.fromComponent(
-      this.context, this._componentToInitializeFrom,
+      this.context, Component? _componentToInitializeFrom,
       [bool? outlineOnly, this._incrementalSerializer])
       : _ticker = context.options.ticker,
         _resetTicker = true,
-        _initializeFromDillUri = null,
+        _initializationStrategy = new _InitializationStrategy.fromComponent(
+            _componentToInitializeFrom),
         this.outlineOnly = outlineOnly ?? false,
         this._initializedForExpressionCompilationOnly = false {
     _enableExperimentsBasedOnEnvironment();
   }
 
   IncrementalCompiler(this.context,
-      [this._initializeFromDillUri,
+      [Uri? _initializeFromDillUri,
       bool? outlineOnly,
       this._incrementalSerializer])
       : _ticker = context.options.ticker,
         _resetTicker = true,
-        _componentToInitializeFrom = null,
+        _initializationStrategy =
+            new _InitializationStrategy.fromUri(_initializeFromDillUri),
         this.outlineOnly = outlineOnly ?? false,
         this._initializedForExpressionCompilationOnly = false {
     _enableExperimentsBasedOnEnvironment();
   }
 
   IncrementalCompiler.forExpressionCompilationOnly(
-      this.context, this._componentToInitializeFrom,
+      this.context, Component? _componentToInitializeFrom,
       [bool? resetTicker])
       : _ticker = context.options.ticker,
         this._resetTicker = resetTicker ?? true,
-        _initializeFromDillUri = null,
+        _initializationStrategy = new _InitializationStrategy.fromComponent(
+            _componentToInitializeFrom),
         this.outlineOnly = false,
         this._incrementalSerializer = null,
         this._initializedForExpressionCompilationOnly = true {
     _enableExperimentsBasedOnEnvironment();
   }
 
-  bool get initializedFromDill => _initializedFromDill;
+  bool get initializedFromDillForTesting =>
+      _initializationStrategy.initializedFromDillForTesting;
 
-  bool get initializedIncrementalSerializer =>
-      _initializedIncrementalSerializer;
+  bool get initializedIncrementalSerializerForTesting =>
+      _initializationStrategy.initializedIncrementalSerializerForTesting;
 
   DillTarget? get dillTargetForTesting => _dillLoadedData;
 
@@ -289,12 +293,12 @@
       // Experimental invalidation initialization (e.g. figure out if we can).
       ExperimentalInvalidation? experimentalInvalidation =
           await _initializeExperimentalInvalidation(reusedResult, c);
-      recordRebuildBodiesCountForTesting(
+      recorderForTesting?.recordRebuildBodiesCount(
           experimentalInvalidation?.missingSources.length ?? 0);
 
       // Cleanup: After (potentially) removing builders we have stuff to cleanup
       // to not leak, and we might need to re-create the dill target.
-      _cleanupRemovedBuilders(reusedResult, uriTranslator);
+      _cleanupRemovedBuilders(_userCode, reusedResult, uriTranslator);
       _recreateDillTargetIfPackageWasUpdated(uriTranslator, c);
       ClassHierarchy? hierarchy = _userCode?.loader.hierarchy;
       _cleanupHierarchy(hierarchy, experimentalInvalidation, reusedResult);
@@ -319,13 +323,18 @@
       // to be setup, and in the case of experimental invalidation some of the
       // builders needs to be patched up.
       IncrementalKernelTarget? userCodeOld = _userCode;
-      _setupNewUserCode(c, uriTranslator, hierarchy, reusedLibraries,
-          experimentalInvalidation, entryPoints!.first);
+      IncrementalKernelTarget userCode = _userCode = _setupNewUserCode(
+          c,
+          uriTranslator,
+          hierarchy,
+          reusedLibraries,
+          experimentalInvalidation,
+          entryPoints!.first);
       Map<LibraryBuilder, List<LibraryBuilder>>? rebuildBodiesMap =
           _experimentalInvalidationCreateRebuildBodiesBuilders(
-              experimentalInvalidation, uriTranslator);
-      entryPoints = _userCode!.setEntryPoints(entryPoints!);
-      await _userCode!.loader.buildOutlines();
+              userCode, experimentalInvalidation, uriTranslator);
+      entryPoints = userCode.setEntryPoints(entryPoints!);
+      await userCode.loader.buildOutlines();
       _experimentalInvalidationPatchUpScopes(
           experimentalInvalidation, rebuildBodiesMap);
       rebuildBodiesMap = null;
@@ -336,35 +345,31 @@
       // libraries loaded from .dill files or directly from components.
       // Technically, it's the combination of userCode.loader.libraries and
       // dillLoadedData.loader.libraries.
-      Component? componentWithDill = await _userCode!.buildOutlines();
+      Component? componentWithDill = await userCode.buildOutlines();
 
       if (!outlineOnly) {
         // Checkpoint: Build the actual bodies.
         componentWithDill =
-            await _userCode!.buildComponent(verify: c.options.verify);
+            await userCode.buildComponent(verify: c.options.verify);
       }
-      hierarchy ??= _userCode!.loader.hierarchy;
-      // ignore: unnecessary_null_comparison
-      if (hierarchy != null) {
-        if (_userCode!.classHierarchyChanges != null) {
-          hierarchy.applyTreeChanges([], [], _userCode!.classHierarchyChanges!);
-        }
-        if (_userCode!.classMemberChanges != null) {
-          hierarchy.applyMemberChanges(_userCode!.classMemberChanges!,
-              findDescendants: true);
-        }
+      hierarchy ??= userCode.loader.hierarchy;
+      if (userCode.classHierarchyChanges != null) {
+        hierarchy.applyTreeChanges([], [], userCode.classHierarchyChanges!);
       }
-      recordNonFullComponentForTesting(componentWithDill!);
+      if (userCode.classMemberChanges != null) {
+        hierarchy.applyMemberChanges(userCode.classMemberChanges!,
+            findDescendants: true);
+      }
+      recorderForTesting?.recordNonFullComponent(componentWithDill!);
 
       Set<Library>? neededDillLibraries;
       if (trackNeededDillLibraries) {
         // Perform actual dill usage tracking.
-        neededDillLibraries = _performDillUsageTracking(hierarchy);
+        neededDillLibraries = _performDillUsageTracking(userCode, hierarchy);
       }
 
       // If we actually got a result we can throw away the old userCode and the
       // list of invalidated uris.
-      // ignore: unnecessary_null_comparison
       if (componentWithDill != null) {
         this._invalidatedUris.clear();
         _hasToCheckPackageUris = false;
@@ -375,12 +380,13 @@
       // Compute which libraries to output and which (previous) errors/warnings
       // we have to reissue. In the process do some cleanup too.
       List<Library> compiledLibraries =
-          new List<Library>.from(_userCode!.loader.libraries);
-      Map<Uri, Source> uriToSource = componentWithDill.uriToSource;
+          new List<Library>.from(userCode.loader.libraries);
+      Map<Uri, Source> uriToSource = componentWithDill!.uriToSource;
       _experimentalCompilationPostCompilePatchup(
           experimentalInvalidation, compiledLibraries, uriToSource);
       List<Library> outputLibraries =
           _calculateOutputLibrariesAndIssueLibraryProblems(
+              userCode,
               data.component != null || fullComponent,
               compiledLibraries,
               entryPoints!,
@@ -389,19 +395,19 @@
               uriTranslator,
               uriToSource,
               c);
-      List<String> problemsAsJson =
-          _reissueComponentProblems(componentWithDill);
+      List<String> problemsAsJson = _componentProblems.reissueProblems(
+          context, userCode, componentWithDill);
 
       // If we didn't get a result, go back to the previous one so expression
       // calculation has the potential to work.
       // ignore: unnecessary_null_comparison
       if (componentWithDill == null) {
-        _userCode!.loader.clearLibraryBuilders();
-        _userCode = userCodeOld;
-        _dillLoadedData!.loader.currentSourceLoader = _userCode!.loader;
+        userCode.loader.clearLibraryBuilders();
+        userCode = _userCode = userCodeOld!;
+        _dillLoadedData!.loader.currentSourceLoader = userCode.loader;
       } else {
-        _previousSourceBuilders =
-            _convertSourceLibraryBuildersToDill(experimentalInvalidation);
+        _previousSourceBuilders = _convertSourceLibraryBuildersToDill(
+            userCode, experimentalInvalidation);
       }
 
       experimentalInvalidation = null;
@@ -441,16 +447,17 @@
   ///
   /// Returns the set of Libraries that now has new (dill) builders.
   Set<Library> _convertSourceLibraryBuildersToDill(
+      IncrementalKernelTarget userCode,
       ExperimentalInvalidation? experimentalInvalidation) {
     bool changed = false;
     Set<Library> newDillLibraryBuilders = new Set<Library>();
     _userBuilders ??= <Uri, LibraryBuilder>{};
     Map<LibraryBuilder, List<LibraryBuilder>>? convertedLibraries;
-    for (LibraryBuilder builder in _userCode!.loader.libraryBuilders) {
+    for (LibraryBuilder builder in userCode.loader.libraryBuilders) {
       if (builder is SourceLibraryBuilder) {
         DillLibraryBuilder dillBuilder =
             _dillLoadedData!.loader.appendLibrary(builder.library);
-        _userCode!.loader.registerLibraryBuilder(
+        userCode.loader.registerLibraryBuilder(
             // TODO(johnniwinther): Why do we need to create
             //  [DillLibraryBuilder]s for the patch library file uris?
             dillBuilder,
@@ -469,8 +476,7 @@
       // We suppress finalization errors because they have already been
       // reported.
       _dillLoadedData!.buildOutlines(suppressFinalizationErrors: true);
-      assert(
-          _checkEquivalentScopes(_userCode!.loader, _dillLoadedData!.loader));
+      assert(_checkEquivalentScopes(userCode.loader, _dillLoadedData!.loader));
 
       if (experimentalInvalidation != null) {
         /// If doing experimental invalidation that means that some of the old
@@ -506,9 +512,9 @@
         replacementSettersMap = null;
       }
     }
-    _userCode!.loader.buildersCreatedWithReferences.clear();
-    _userCode!.loader.builderHierarchy.clear();
-    _userCode!.loader.referenceFromIndex = null;
+    userCode.loader.buildersCreatedWithReferences.clear();
+    userCode.loader.builderHierarchy.clear();
+    userCode.loader.referenceFromIndex = null;
     convertedLibraries = null;
     experimentalInvalidation = null;
     if (_userBuilders!.isEmpty) _userBuilders = null;
@@ -594,6 +600,7 @@
   /// Compute which libraries to output and which (previous) errors/warnings we
   /// have to reissue. In the process do some cleanup too.
   List<Library> _calculateOutputLibrariesAndIssueLibraryProblems(
+      IncrementalKernelTarget userCode,
       bool fullComponent,
       List<Library> compiledLibraries,
       List<Uri> entryPoints,
@@ -605,7 +612,7 @@
     List<Library> outputLibraries;
     Set<Library> allLibraries;
     if (fullComponent) {
-      outputLibraries = _computeTransitiveClosure(compiledLibraries,
+      outputLibraries = _computeTransitiveClosure(userCode, compiledLibraries,
           entryPoints, reusedLibraries, hierarchy, uriTranslator, uriToSource);
       allLibraries = outputLibraries.toSet();
       if (!c.options.omitPlatform) {
@@ -617,6 +624,7 @@
     } else {
       outputLibraries = <Library>[];
       allLibraries = _computeTransitiveClosure(
+              userCode,
               compiledLibraries,
               entryPoints,
               reusedLibraries,
@@ -656,13 +664,14 @@
   /// Perform dill usage tracking if asked. Use the marking on dill builders as
   /// well as the class hierarchy to figure out which dill libraries was
   /// actually used by the compilation.
-  Set<Library> _performDillUsageTracking(ClassHierarchy hierarchy) {
+  Set<Library> _performDillUsageTracking(
+      IncrementalKernelTarget target, ClassHierarchy hierarchy) {
     // Which dill builders were built?
     Set<Library> neededDillLibraries = {};
 
     // Propagate data from constant evaluator: Libraries used in the constant
     // evaluator - that comes from dill - are marked.
-    Set<Library> librariesUsedByConstantEvaluator = _userCode!.librariesUsed;
+    Set<Library> librariesUsedByConstantEvaluator = target.librariesUsed;
 
     for (LibraryBuilder builder in _dillLoadedData!.loader.libraryBuilders) {
       if (builder is DillLibraryBuilder) {
@@ -674,7 +683,7 @@
     }
 
     updateNeededDillLibrariesWithHierarchy(
-        neededDillLibraries, hierarchy, _userCode!.loader.builderHierarchy);
+        neededDillLibraries, hierarchy, target.loader.builderHierarchy);
 
     return neededDillLibraries;
   }
@@ -719,6 +728,7 @@
   /// references from the original [Library] for things to work.
   Map<LibraryBuilder, List<LibraryBuilder>>
       _experimentalInvalidationCreateRebuildBodiesBuilders(
+          IncrementalKernelTarget userCode,
           ExperimentalInvalidation? experimentalInvalidation,
           UriTranslator uriTranslator) {
     // Any builder(s) in [rebuildBodies] should be semi-reused: Create source
@@ -728,9 +738,8 @@
         new Map<LibraryBuilder, List<LibraryBuilder>>.identity();
     if (experimentalInvalidation != null) {
       for (LibraryBuilder library in experimentalInvalidation.rebuildBodies) {
-        LibraryBuilder newBuilder = _userCode!.loader.read(
-            library.importUri, -1,
-            accessorUri: _userCode!.loader.firstUri,
+        LibraryBuilder newBuilder = userCode.loader.read(library.importUri, -1,
+            accessorUri: userCode.loader.firstUri,
             fileUri: library.fileUri,
             referencesFrom: library.library);
         List<LibraryBuilder> builders = [newBuilder];
@@ -742,8 +751,8 @@
           // libraries.
           Uri partUri = getPartUri(library.importUri, part);
           Uri? fileUri =
-              _getPartFileUri(library.library.fileUri, part, uriTranslator);
-          LibraryBuilder newPartBuilder = _userCode!.loader.read(partUri, -1,
+              uriTranslator.getPartFileUri(library.library.fileUri, part);
+          LibraryBuilder newPartBuilder = userCode.loader.read(partUri, -1,
               accessor: library,
               fileUri: fileUri,
               referencesFrom: library.library,
@@ -850,14 +859,14 @@
   }
 
   /// Create a new [_userCode] object, and add the reused builders to it.
-  void _setupNewUserCode(
+  IncrementalKernelTarget _setupNewUserCode(
       CompilerContext c,
       UriTranslator uriTranslator,
       ClassHierarchy? hierarchy,
       List<LibraryBuilder> reusedLibraries,
       ExperimentalInvalidation? experimentalInvalidation,
       Uri firstEntryPoint) {
-    _userCode = createIncrementalKernelTarget(
+    IncrementalKernelTarget userCode = createIncrementalKernelTarget(
         new HybridFileSystem(
             new MemoryFileSystem(
                 new Uri(scheme: "org-dartlang-debug", path: "/")),
@@ -865,14 +874,14 @@
         false,
         _dillLoadedData!,
         uriTranslator);
-    _userCode!.loader.hierarchy = hierarchy;
-    _dillLoadedData!.loader.currentSourceLoader = _userCode!.loader;
+    userCode.loader.hierarchy = hierarchy;
+    _dillLoadedData!.loader.currentSourceLoader = userCode.loader;
 
     // Re-use the libraries we've deemed re-usable.
     List<bool> seenModes = [false, false, false, false];
     for (LibraryBuilder library in reusedLibraries) {
       seenModes[library.library.nonNullableByDefaultCompiledMode.index] = true;
-      _userCode!.loader.registerLibraryBuilder(library);
+      userCode.loader.registerLibraryBuilder(library);
     }
     // Check compilation mode up against what we've seen here and set
     // `hasInvalidNnbdModeLibrary` accordingly.
@@ -882,14 +891,14 @@
           // Don't expect strong or invalid.
           if (seenModes[NonNullableByDefaultCompiledMode.Strong.index] ||
               seenModes[NonNullableByDefaultCompiledMode.Invalid.index]) {
-            _userCode!.loader.hasInvalidNnbdModeLibrary = true;
+            userCode.loader.hasInvalidNnbdModeLibrary = true;
           }
           break;
         case NnbdMode.Strong:
           // Don't expect weak or invalid.
           if (seenModes[NonNullableByDefaultCompiledMode.Weak.index] ||
               seenModes[NonNullableByDefaultCompiledMode.Invalid.index]) {
-            _userCode!.loader.hasInvalidNnbdModeLibrary = true;
+            userCode.loader.hasInvalidNnbdModeLibrary = true;
           }
           break;
         case NnbdMode.Agnostic:
@@ -897,7 +906,7 @@
           if (seenModes[NonNullableByDefaultCompiledMode.Strong.index] ||
               seenModes[NonNullableByDefaultCompiledMode.Weak.index] ||
               seenModes[NonNullableByDefaultCompiledMode.Invalid.index]) {
-            _userCode!.loader.hasInvalidNnbdModeLibrary = true;
+            userCode.loader.hasInvalidNnbdModeLibrary = true;
           }
           break;
       }
@@ -905,14 +914,15 @@
       // Don't expect strong or invalid.
       if (seenModes[NonNullableByDefaultCompiledMode.Strong.index] ||
           seenModes[NonNullableByDefaultCompiledMode.Invalid.index]) {
-        _userCode!.loader.hasInvalidNnbdModeLibrary = true;
+        userCode.loader.hasInvalidNnbdModeLibrary = true;
       }
     }
 
     // The entry point(s) has to be set first for loader.firstUri to be setup
     // correctly.
-    _userCode!.loader.firstUri =
-        _userCode!.getEntryPointUri(firstEntryPoint, issueProblem: false);
+    userCode.loader.firstUri =
+        userCode.getEntryPointUri(firstEntryPoint, issueProblem: false);
+    return userCode;
   }
 
   /// When tracking used libraries we mark them when we use them. To track
@@ -994,11 +1004,11 @@
   /// We also have to remove any component problems beloning to any such
   /// no-longer-used library (to avoid re-issuing errors about no longer
   /// relevant stuff).
-  void _cleanupRemovedBuilders(
+  void _cleanupRemovedBuilders(IncrementalKernelTarget? userCode,
       ReusageResult reusedResult, UriTranslator uriTranslator) {
     bool removedDillBuilders = false;
     for (LibraryBuilder builder in reusedResult.notReusedLibraries) {
-      _cleanupSourcesForBuilder(reusedResult, builder, uriTranslator,
+      _cleanupSourcesForBuilder(userCode, reusedResult, builder, uriTranslator,
           CompilerContext.current.uriToSource);
       _incrementalSerializer?.invalidate(builder.fileUri);
 
@@ -1010,10 +1020,7 @@
       }
 
       // Remove component problems for libraries we don't reuse.
-      if (_remainingComponentProblems.isNotEmpty) {
-        Library lib = builder.library;
-        _removeLibraryFromRemainingComponentProblems(lib, uriTranslator);
-      }
+      _componentProblems.removeLibrary(builder.library, uriTranslator);
     }
 
     if (removedDillBuilders) {
@@ -1219,76 +1226,28 @@
   /// Load platform and (potentially) initialize from dill,
   /// or initialize from component.
   Future<IncrementalCompilerData> _ensurePlatformAndInitialize(
-      UriTranslator uriTranslator, CompilerContext c) async {
+      UriTranslator uriTranslator, CompilerContext context) async {
     IncrementalCompilerData data = new IncrementalCompilerData();
     if (_dillLoadedData == null) {
-      int bytesLength = 0;
-      if (_componentToInitializeFrom != null) {
-        // If initializing from a component it has to include the sdk,
-        // so we explicitly don't load it here.
-        _initializeFromComponent(uriTranslator, c, data);
-        assert(_dillLoadedData != null);
-        _componentToInitializeFrom = null;
-      } else {
-        List<int>? summaryBytes = await c.options.loadSdkSummaryBytes();
-        bytesLength = _prepareSummary(summaryBytes, uriTranslator, c, data);
-        assert(_dillLoadedData != null);
-        if (_initializeFromDillUri != null) {
-          try {
-            bytesLength += await _initializeFromDill(uriTranslator, c, data);
-          } catch (e, st) {
-            // We might have loaded x out of y libraries into the component.
-            // To avoid any unforeseen problems start over.
-            bytesLength = _prepareSummary(summaryBytes, uriTranslator, c, data);
-
-            if (e is InvalidKernelVersionError ||
-                e is InvalidKernelSdkVersionError ||
-                e is PackageChangedError ||
-                e is CanonicalNameSdkError ||
-                e is CompilationModeError) {
-              // Don't report any warning.
-            } else {
-              Uri? gzInitializedFrom;
-              if (c.options.writeFileOnCrashReport) {
-                gzInitializedFrom = saveAsGzip(
-                    data.initializationBytes!, "initialize_from.dill");
-                recordTemporaryFileForTesting(gzInitializedFrom);
-              }
-              if (e is CanonicalNameError) {
-                Message message = gzInitializedFrom != null
-                    ? templateInitializeFromDillNotSelfContained.withArguments(
-                        _initializeFromDillUri.toString(), gzInitializedFrom)
-                    : templateInitializeFromDillNotSelfContainedNoDump
-                        .withArguments(_initializeFromDillUri.toString());
-                _dillLoadedData!.loader
-                    .addProblem(message, TreeNode.noOffset, 1, null);
-              } else {
-                // Unknown error: Report problem as such.
-                Message message = gzInitializedFrom != null
-                    ? templateInitializeFromDillUnknownProblem.withArguments(
-                        _initializeFromDillUri.toString(),
-                        "$e",
-                        "$st",
-                        gzInitializedFrom)
-                    : templateInitializeFromDillUnknownProblemNoDump
-                        .withArguments(
-                            _initializeFromDillUri.toString(), "$e", "$st");
-                _dillLoadedData!.loader
-                    .addProblem(message, TreeNode.noOffset, 1, null);
-              }
-            }
-          }
-        }
-      }
+      DillTarget dillLoadedData = _dillLoadedData =
+          new DillTarget(_ticker, uriTranslator, context.options.target);
+      int bytesLength = await _initializationStrategy.initialize(
+          dillLoadedData,
+          uriTranslator,
+          context,
+          data,
+          _componentProblems,
+          _incrementalSerializer,
+          recorderForTesting);
       _appendLibraries(data, bytesLength);
 
       // We suppress finalization errors because they will reported via
       // problemsAsJson fields (with better precision).
-      _dillLoadedData!.buildOutlines(suppressFinalizationErrors: true);
+      dillLoadedData.buildOutlines(suppressFinalizationErrors: true);
       _userBuilders = <Uri, LibraryBuilder>{};
       _platformBuilders = <LibraryBuilder>[];
       for (DillLibraryBuilder builder
-          in _dillLoadedData!.loader.libraryBuilders) {
+          in dillLoadedData.loader.libraryBuilders) {
         if (builder.importUri.scheme == "dart") {
           _platformBuilders!.add(builder);
         } else {
@@ -1482,75 +1441,12 @@
   }
 
   /// Internal method.
-  /// Re-issue problems on the component and return the filtered list.
-  List<String> _reissueComponentProblems(Component componentWithDill) {
-    // These problems have already been reported.
-    Set<String> issuedProblems = new Set<String>();
-    if (componentWithDill.problemsAsJson != null) {
-      issuedProblems.addAll(componentWithDill.problemsAsJson!);
-    }
-
-    // Report old problems that wasn't reported again.
-    Set<Uri>? strongModeNNBDPackageOptOutUris;
-    for (MapEntry<Uri, List<DiagnosticMessageFromJson>> entry
-        in _remainingComponentProblems.entries) {
-      List<DiagnosticMessageFromJson> messages = entry.value;
-      for (int i = 0; i < messages.length; i++) {
-        DiagnosticMessageFromJson message = messages[i];
-        if (message.codeName == "StrongModeNNBDPackageOptOut") {
-          // Special case this: Don't issue them here; instead collect them
-          // to get their uris and re-issue a new error.
-          strongModeNNBDPackageOptOutUris ??= {};
-          strongModeNNBDPackageOptOutUris.add(entry.key);
-          continue;
-        }
-        if (issuedProblems.add(message.toJsonString())) {
-          context.options.reportDiagnosticMessage(message);
-        }
-      }
-    }
-    if (strongModeNNBDPackageOptOutUris != null) {
-      // Get the builders for these uris; then call
-      // `SourceLoader.giveCombinedErrorForNonStrongLibraries` on them to issue
-      // a new error.
-      Set<LibraryBuilder> builders = {};
-      SourceLoader loader = _userCode!.loader;
-      for (LibraryBuilder builder in loader.libraryBuilders) {
-        if (strongModeNNBDPackageOptOutUris.contains(builder.fileUri)) {
-          builders.add(builder);
-        }
-      }
-      FormattedMessage message = loader.giveCombinedErrorForNonStrongLibraries(
-          builders,
-          emitNonPackageErrors: false)!;
-      issuedProblems.add(message.toJsonString());
-      // The problem was issued by the call so don't re-issue it here.
-    }
-
-    // Save any new component-problems.
-    _addProblemsAsJsonToRemainingProblems(componentWithDill.problemsAsJson);
-    return new List<String>.from(issuedProblems);
-  }
-
-  /// Internal method.
-  Uri? _getPartFileUri(
-      Uri parentFileUri, LibraryPart part, UriTranslator uriTranslator) {
-    Uri? fileUri = getPartUri(parentFileUri, part);
-    if (fileUri.scheme == "package") {
-      // Part was specified via package URI and the resolve above thus
-      // did not go as expected. Translate the package URI to get the
-      // actual file URI.
-      fileUri = uriTranslator.translate(fileUri, false);
-    }
-    return fileUri;
-  }
-
-  /// Internal method.
   /// Compute the transitive closure.
   ///
   /// As a side-effect, this also cleans-up now-unreferenced builders as well as
   /// any saved component problems for such builders.
   List<Library> _computeTransitiveClosure(
+      IncrementalKernelTarget userCode,
       List<Library> inputLibraries,
       List<Uri> entryPoints,
       List<LibraryBuilder> reusedLibraries,
@@ -1620,7 +1516,7 @@
           }
           for (LibraryPart part in library.parts) {
             Uri? partFileUri =
-                _getPartFileUri(library.fileUri, part, uriTranslator);
+                uriTranslator.getPartFileUri(library.fileUri, part);
             partsUsed.add(partFileUri);
           }
         }
@@ -1631,18 +1527,17 @@
     bool removedDillBuilders = false;
     for (Uri uri in potentiallyReferencedLibraries.keys) {
       if (uri.scheme == "package") continue;
-      LibraryBuilder? builder = _userCode!.loader.deregisterLibraryBuilder(uri);
+      LibraryBuilder? builder = userCode.loader.deregisterLibraryBuilder(uri);
       if (builder != null) {
         Library lib = builder.library;
         removedLibraries.add(lib);
         if (_dillLoadedData!.loader.deregisterLibraryBuilder(uri) != null) {
           removedDillBuilders = true;
         }
-        _cleanupSourcesForBuilder(null, builder, uriTranslator,
+        _cleanupSourcesForBuilder(userCode, null, builder, uriTranslator,
             CompilerContext.current.uriToSource, uriToSource, partsUsed);
         _userBuilders?.remove(uri);
-        _removeLibraryFromRemainingComponentProblems(
-            lib, uriTranslator, partsUsed);
+        _componentProblems.removeLibrary(lib, uriTranslator, partsUsed);
 
         // Technically this isn't necessary as the uri is not a package-uri.
         _incrementalSerializer?.invalidate(builder.fileUri);
@@ -1674,6 +1569,7 @@
   /// Those parts will not be cleaned up. This is useful when a part has been
   /// "moved" to be part of another library.
   void _cleanupSourcesForBuilder(
+      IncrementalKernelTarget? userCode,
       ReusageResult? reusedResult,
       LibraryBuilder builder,
       UriTranslator uriTranslator,
@@ -1684,15 +1580,15 @@
     uriToSourceExtra?.remove(builder.fileUri);
     Library lib = builder.library;
     for (LibraryPart part in lib.parts) {
-      Uri? partFileUri = _getPartFileUri(lib.fileUri, part, uriTranslator);
+      Uri? partFileUri = uriTranslator.getPartFileUri(lib.fileUri, part);
       if (partsUsed != null && partsUsed.contains(partFileUri)) continue;
 
       // If the builders map contain the "parts" import uri, it's a real library
       // (erroneously) used as a part so we don't want to remove that.
-      if (_userCode?.loader != null) {
+      if (userCode?.loader != null) {
         Uri? partImportUri = uriToSource[partFileUri]?.importUri;
         if (partImportUri != null &&
-            _userCode!.loader.containsLibraryBuilder(partImportUri)) {
+            userCode!.loader.containsLibraryBuilder(partImportUri)) {
           continue;
         }
       } else if (reusedResult != null) {
@@ -1716,189 +1612,6 @@
   }
 
   /// Internal method.
-  ///
-  /// [partsUsed] indicates part uris that are used by (other/alive) libraries.
-  /// Those parts will not be removed from the component problems.
-  /// This is useful when a part has been "moved" to be part of another library.
-  void _removeLibraryFromRemainingComponentProblems(
-      Library lib, UriTranslator uriTranslator,
-      [Set<Uri?>? partsUsed]) {
-    _remainingComponentProblems.remove(lib.fileUri);
-    // Remove parts too.
-    for (LibraryPart part in lib.parts) {
-      Uri? partFileUri = _getPartFileUri(lib.fileUri, part, uriTranslator);
-      _remainingComponentProblems.remove(partFileUri);
-    }
-  }
-
-  /// Internal method.
-  int _prepareSummary(List<int>? summaryBytes, UriTranslator uriTranslator,
-      CompilerContext c, IncrementalCompilerData data) {
-    _dillLoadedData = new DillTarget(_ticker, uriTranslator, c.options.target);
-    int bytesLength = 0;
-
-    data.component = c.options.target.configureComponent(new Component());
-    if (summaryBytes != null) {
-      _ticker.logMs("Read ${c.options.sdkSummary}");
-      new BinaryBuilderWithMetadata(summaryBytes,
-              disableLazyReading: false, disableLazyClassReading: true)
-          .readComponent(data.component!);
-      _ticker.logMs("Deserialized ${c.options.sdkSummary}");
-      bytesLength += summaryBytes.length;
-    }
-
-    return bytesLength;
-  }
-
-  /// Internal method.
-  // This procedure will try to load the dill file and will crash if it cannot.
-  Future<int> _initializeFromDill(UriTranslator uriTranslator,
-      CompilerContext c, IncrementalCompilerData data) async {
-    int bytesLength = 0;
-    FileSystemEntity entity =
-        c.options.fileSystem.entityForUri(_initializeFromDillUri!);
-    if (await entity.exists()) {
-      List<int> initializationBytes = await entity.readAsBytes();
-      // ignore: unnecessary_null_comparison
-      if (initializationBytes != null && initializationBytes.isNotEmpty) {
-        _ticker.logMs("Read $_initializeFromDillUri");
-        data.initializationBytes = initializationBytes;
-
-        // We're going to output all we read here so lazy loading it
-        // doesn't make sense.
-        List<SubComponentView> views = new BinaryBuilderWithMetadata(
-                initializationBytes,
-                disableLazyReading: true)
-            .readComponent(data.component!,
-                checkCanonicalNames: true, createView: true)!;
-
-        // Compute "output nnbd mode".
-        NonNullableByDefaultCompiledMode compiledMode;
-        if (c.options
-            .isExperimentEnabledGlobally(ExperimentalFlag.nonNullable)) {
-          switch (c.options.nnbdMode) {
-            case NnbdMode.Weak:
-              compiledMode = NonNullableByDefaultCompiledMode.Weak;
-              break;
-            case NnbdMode.Strong:
-              compiledMode = NonNullableByDefaultCompiledMode.Strong;
-              break;
-            case NnbdMode.Agnostic:
-              compiledMode = NonNullableByDefaultCompiledMode.Agnostic;
-              break;
-          }
-        } else {
-          compiledMode = NonNullableByDefaultCompiledMode.Weak;
-        }
-
-        // Check the any package-urls still point to the same file
-        // (e.g. the package still exists and hasn't been updated).
-        // Also verify NNBD settings.
-        for (Library lib in data.component!.libraries) {
-          if (lib.importUri.scheme == "package" &&
-              uriTranslator.translate(lib.importUri, false) != lib.fileUri) {
-            // Package has been removed or updated.
-            // This library should be thrown away.
-            // Everything that depends on it should be thrown away.
-            // TODO(jensj): Anything that doesn't depend on it can be kept.
-            // For now just don't initialize from this dill.
-            throw const PackageChangedError();
-          }
-          // Note: If a library has a NonNullableByDefaultCompiledMode.invalid
-          // we will throw and we won't initialize from it.
-          // That's wanted behavior.
-          if (compiledMode !=
-              mergeCompilationModeOrThrow(
-                  compiledMode, lib.nonNullableByDefaultCompiledMode)) {
-            throw new CompilationModeError(
-                "Can't compile to $compiledMode with library with mode "
-                "${lib.nonNullableByDefaultCompiledMode}.");
-          }
-        }
-
-        // Only initialize the incremental serializer when we know we'll
-        // actually use the data loaded from dill.
-        _initializedIncrementalSerializer =
-            _incrementalSerializer?.initialize(initializationBytes, views) ??
-                false;
-
-        _initializedFromDill = true;
-        bytesLength += initializationBytes.length;
-        _saveComponentProblems(data);
-      }
-    }
-    return bytesLength;
-  }
-
-  /// Internal method.
-  void _saveComponentProblems(IncrementalCompilerData data) {
-    List<String>? problemsAsJson = data.component!.problemsAsJson;
-    _addProblemsAsJsonToRemainingProblems(problemsAsJson);
-  }
-
-  void _addProblemsAsJsonToRemainingProblems(List<String>? problemsAsJson) {
-    if (problemsAsJson != null) {
-      for (String jsonString in problemsAsJson) {
-        DiagnosticMessageFromJson message =
-            new DiagnosticMessageFromJson.fromJson(jsonString);
-        assert(message.uri != null ||
-            (message.involvedFiles != null &&
-                message.involvedFiles!.isNotEmpty));
-        if (message.uri != null) {
-          List<DiagnosticMessageFromJson> messages =
-              _remainingComponentProblems[message.uri!] ??=
-                  <DiagnosticMessageFromJson>[];
-          messages.add(message);
-        }
-        if (message.involvedFiles != null) {
-          // This indexes the same message under several uris - this way it will
-          // be issued as long as it's a problem. It will because of
-          // deduplication when we re-issue these (in reissueComponentProblems)
-          // only be reported once.
-          for (Uri uri in message.involvedFiles!) {
-            List<DiagnosticMessageFromJson> messages =
-                _remainingComponentProblems[uri] ??=
-                    <DiagnosticMessageFromJson>[];
-            messages.add(message);
-          }
-        }
-      }
-    }
-  }
-
-  /// Internal method.
-  // This procedure will set up compiler from [componentToInitializeFrom].
-  void _initializeFromComponent(UriTranslator uriTranslator, CompilerContext c,
-      IncrementalCompilerData data) {
-    _ticker.logMs("About to initializeFromComponent");
-
-    _dillLoadedData = new DillTarget(_ticker, uriTranslator, c.options.target);
-    data.component = new Component(
-        libraries: _componentToInitializeFrom!.libraries,
-        uriToSource: _componentToInitializeFrom!.uriToSource)
-      ..setMainMethodAndMode(_componentToInitializeFrom!.mainMethod?.reference,
-          true, _componentToInitializeFrom!.mode);
-    _saveComponentProblems(data);
-
-    bool foundDartCore = false;
-    for (int i = 0; i < data.component!.libraries.length; i++) {
-      Library library = data.component!.libraries[i];
-      if (library.importUri.scheme == "dart" &&
-          library.importUri.path == "core") {
-        foundDartCore = true;
-        break;
-      }
-    }
-
-    if (!foundDartCore) {
-      throw const InitializeFromComponentError("Did not find dart:core when "
-          "tried to initialize from component.");
-    }
-
-    _ticker.logMs("Ran initializeFromComponent");
-  }
-
-  /// Internal method.
   void _appendLibraries(IncrementalCompilerData data, int bytesLength) {
     if (data.component != null) {
       _dillLoadedData!.loader
@@ -2177,8 +1890,8 @@
       } else if (libraryBuilder is DillLibraryBuilder) {
         for (LibraryPart part in libraryBuilder.library.parts) {
           Uri partUri = getPartUri(libraryBuilder.importUri, part);
-          Uri? fileUri = _getPartFileUri(
-              libraryBuilder.library.fileUri, part, uriTranslator);
+          Uri? fileUri = uriTranslator.getPartFileUri(
+              libraryBuilder.library.fileUri, part);
           partUriToParent[partUri] = libraryBuilder;
           partUriToParent[fileUri] = libraryBuilder;
 
@@ -2208,7 +1921,7 @@
       _userBuilders!.forEach(addBuilderAndInvalidateUris);
     }
 
-    recordInvalidatedImportUrisForTesting(invalidatedImportUris);
+    recorderForTesting?.recordInvalidatedImportUris(invalidatedImportUris);
     for (Uri uri in invalidatedImportUris) {
       directlyInvalidated.add(builders[uri]!);
     }
@@ -2305,18 +2018,6 @@
   void setModulesToLoadOnNextComputeDelta(List<Component> components) {
     _modulesToLoad = components.toList();
   }
-
-  /// Internal method.
-  void recordNonFullComponentForTesting(Component component) {}
-
-  /// Internal method.
-  void recordInvalidatedImportUrisForTesting(List<Uri> uris) {}
-
-  /// Internal method.
-  void recordRebuildBodiesCountForTesting(int count) {}
-
-  /// Internal method.
-  void recordTemporaryFileForTesting(Uri uri) {}
 }
 
 /// Translate a parts "partUri" to an actual uri with handling of invalid uris.
@@ -2480,3 +2181,410 @@
     librariesUsed.addAll(visitedLibraries);
   }
 }
+
+abstract class _InitializationStrategy {
+  const _InitializationStrategy();
+
+  factory _InitializationStrategy.fromComponent(Component? component) {
+    return component != null
+        ? new _InitializationFromComponent(component)
+        : const _InitializationFromSdkSummary();
+  }
+
+  factory _InitializationStrategy.fromUri(Uri? uri) {
+    return uri != null
+        ? new _InitializationFromUri(uri)
+        : const _InitializationFromSdkSummary();
+  }
+
+  bool get initializedFromDillForTesting => false;
+
+  bool get initializedIncrementalSerializerForTesting => false;
+
+  Future<int> initialize(
+      DillTarget dillLoadedData,
+      UriTranslator uriTranslator,
+      CompilerContext context,
+      IncrementalCompilerData data,
+      _ComponentProblems componentProblems,
+      IncrementalSerializer? incrementalSerializer,
+      RecorderForTesting? recorderForTesting);
+}
+
+class _InitializationFromSdkSummary extends _InitializationStrategy {
+  const _InitializationFromSdkSummary();
+
+  @override
+  Future<int> initialize(
+      DillTarget dillLoadedData,
+      UriTranslator uriTranslator,
+      CompilerContext context,
+      IncrementalCompilerData data,
+      _ComponentProblems componentProblems,
+      IncrementalSerializer? incrementalSerializer,
+      RecorderForTesting? recorderForTesting) async {
+    List<int>? summaryBytes = await context.options.loadSdkSummaryBytes();
+    return _prepareSummary(
+        dillLoadedData, summaryBytes, uriTranslator, context, data);
+  }
+
+  int _prepareSummary(
+      DillTarget dillLoadedTarget,
+      List<int>? summaryBytes,
+      UriTranslator uriTranslator,
+      CompilerContext context,
+      IncrementalCompilerData data) {
+    int bytesLength = 0;
+
+    data.component = context.options.target.configureComponent(new Component());
+    if (summaryBytes != null) {
+      dillLoadedTarget.ticker.logMs("Read ${context.options.sdkSummary}");
+      new BinaryBuilderWithMetadata(summaryBytes,
+              disableLazyReading: false, disableLazyClassReading: true)
+          .readComponent(data.component!);
+      dillLoadedTarget.ticker
+          .logMs("Deserialized ${context.options.sdkSummary}");
+      bytesLength += summaryBytes.length;
+    }
+
+    return bytesLength;
+  }
+}
+
+class _InitializationFromComponent extends _InitializationStrategy {
+  Component componentToInitializeFrom;
+
+  _InitializationFromComponent(this.componentToInitializeFrom);
+
+  @override
+  Future<int> initialize(
+      DillTarget dillLoadedData,
+      UriTranslator uriTranslator,
+      CompilerContext context,
+      IncrementalCompilerData data,
+      _ComponentProblems componentProblems,
+      IncrementalSerializer? incrementalSerializer,
+      RecorderForTesting? recorderForTesting) {
+    dillLoadedData.ticker.logMs("About to initializeFromComponent");
+
+    Component component = data.component = new Component(
+        libraries: componentToInitializeFrom.libraries,
+        uriToSource: componentToInitializeFrom.uriToSource)
+      ..setMainMethodAndMode(componentToInitializeFrom.mainMethod?.reference,
+          true, componentToInitializeFrom.mode);
+    componentProblems.saveComponentProblems(component);
+
+    bool foundDartCore = false;
+    for (int i = 0; i < component.libraries.length; i++) {
+      Library library = component.libraries[i];
+      if (library.importUri.scheme == "dart" &&
+          library.importUri.path == "core") {
+        foundDartCore = true;
+        break;
+      }
+    }
+
+    if (!foundDartCore) {
+      throw const InitializeFromComponentError("Did not find dart:core when "
+          "tried to initialize from component.");
+    }
+
+    dillLoadedData.ticker.logMs("Ran initializeFromComponent");
+    return new Future<int>.value(0);
+  }
+}
+
+class _InitializationFromUri extends _InitializationFromSdkSummary {
+  Uri initializeFromDillUri;
+
+  _InitializationFromUri(this.initializeFromDillUri);
+
+  @override
+  Future<int> initialize(
+      DillTarget dillLoadedData,
+      UriTranslator uriTranslator,
+      CompilerContext context,
+      IncrementalCompilerData data,
+      _ComponentProblems componentProblems,
+      IncrementalSerializer? incrementalSerializer,
+      RecorderForTesting? recorderForTesting) async {
+    List<int>? summaryBytes = await context.options.loadSdkSummaryBytes();
+    int bytesLength = _prepareSummary(
+        dillLoadedData, summaryBytes, uriTranslator, context, data);
+    try {
+      bytesLength += await _initializeFromDill(
+          dillLoadedData,
+          initializeFromDillUri,
+          uriTranslator,
+          context,
+          data,
+          componentProblems,
+          incrementalSerializer);
+    } catch (e, st) {
+      // We might have loaded x out of y libraries into the component.
+      // To avoid any unforeseen problems start over.
+      bytesLength = _prepareSummary(
+          dillLoadedData, summaryBytes, uriTranslator, context, data);
+
+      if (e is InvalidKernelVersionError ||
+          e is InvalidKernelSdkVersionError ||
+          e is PackageChangedError ||
+          e is CanonicalNameSdkError ||
+          e is CompilationModeError) {
+        // Don't report any warning.
+      } else {
+        Uri? gzInitializedFrom;
+        if (context.options.writeFileOnCrashReport) {
+          gzInitializedFrom =
+              saveAsGzip(data.initializationBytes!, "initialize_from.dill");
+          recorderForTesting?.recordTemporaryFile(gzInitializedFrom);
+        }
+        if (e is CanonicalNameError) {
+          Message message = gzInitializedFrom != null
+              ? templateInitializeFromDillNotSelfContained.withArguments(
+                  initializeFromDillUri.toString(), gzInitializedFrom)
+              : templateInitializeFromDillNotSelfContainedNoDump
+                  .withArguments(initializeFromDillUri.toString());
+          dillLoadedData.loader.addProblem(message, TreeNode.noOffset, 1, null);
+        } else {
+          // Unknown error: Report problem as such.
+          Message message = gzInitializedFrom != null
+              ? templateInitializeFromDillUnknownProblem.withArguments(
+                  initializeFromDillUri.toString(),
+                  "$e",
+                  "$st",
+                  gzInitializedFrom)
+              : templateInitializeFromDillUnknownProblemNoDump.withArguments(
+                  initializeFromDillUri.toString(), "$e", "$st");
+          dillLoadedData.loader.addProblem(message, TreeNode.noOffset, 1, null);
+        }
+      }
+    }
+    return bytesLength;
+  }
+
+  bool _initializedFromDill = false;
+  bool _initializedIncrementalSerializer = false;
+
+  @override
+  bool get initializedFromDillForTesting => _initializedFromDill;
+
+  @override
+  bool get initializedIncrementalSerializerForTesting =>
+      _initializedIncrementalSerializer;
+
+  // This procedure will try to load the dill file and will crash if it cannot.
+  Future<int> _initializeFromDill(
+      DillTarget dillLoadedData,
+      Uri initializeFromDillUri,
+      UriTranslator uriTranslator,
+      CompilerContext context,
+      IncrementalCompilerData data,
+      _ComponentProblems _componentProblems,
+      IncrementalSerializer? incrementalSerializer) async {
+    int bytesLength = 0;
+    FileSystemEntity entity =
+        context.options.fileSystem.entityForUri(initializeFromDillUri);
+    if (await entity.exists()) {
+      List<int> initializationBytes = await entity.readAsBytes();
+      // ignore: unnecessary_null_comparison
+      if (initializationBytes != null && initializationBytes.isNotEmpty) {
+        dillLoadedData.ticker.logMs("Read $initializeFromDillUri");
+        data.initializationBytes = initializationBytes;
+
+        // We're going to output all we read here so lazy loading it
+        // doesn't make sense.
+        List<SubComponentView> views = new BinaryBuilderWithMetadata(
+                initializationBytes,
+                disableLazyReading: true)
+            .readComponent(data.component!,
+                checkCanonicalNames: true, createView: true)!;
+
+        // Compute "output nnbd mode".
+        NonNullableByDefaultCompiledMode compiledMode;
+        if (context.options
+            .isExperimentEnabledGlobally(ExperimentalFlag.nonNullable)) {
+          switch (context.options.nnbdMode) {
+            case NnbdMode.Weak:
+              compiledMode = NonNullableByDefaultCompiledMode.Weak;
+              break;
+            case NnbdMode.Strong:
+              compiledMode = NonNullableByDefaultCompiledMode.Strong;
+              break;
+            case NnbdMode.Agnostic:
+              compiledMode = NonNullableByDefaultCompiledMode.Agnostic;
+              break;
+          }
+        } else {
+          compiledMode = NonNullableByDefaultCompiledMode.Weak;
+        }
+
+        // Check the any package-urls still point to the same file
+        // (e.g. the package still exists and hasn't been updated).
+        // Also verify NNBD settings.
+        for (Library lib in data.component!.libraries) {
+          if (lib.importUri.scheme == "package" &&
+              uriTranslator.translate(lib.importUri, false) != lib.fileUri) {
+            // Package has been removed or updated.
+            // This library should be thrown away.
+            // Everything that depends on it should be thrown away.
+            // TODO(jensj): Anything that doesn't depend on it can be kept.
+            // For now just don't initialize from this dill.
+            throw const PackageChangedError();
+          }
+          // Note: If a library has a NonNullableByDefaultCompiledMode.invalid
+          // we will throw and we won't initialize from it.
+          // That's wanted behavior.
+          if (compiledMode !=
+              mergeCompilationModeOrThrow(
+                  compiledMode, lib.nonNullableByDefaultCompiledMode)) {
+            throw new CompilationModeError(
+                "Can't compile to $compiledMode with library with mode "
+                "${lib.nonNullableByDefaultCompiledMode}.");
+          }
+        }
+
+        // Only initialize the incremental serializer when we know we'll
+        // actually use the data loaded from dill.
+        _initializedIncrementalSerializer =
+            incrementalSerializer?.initialize(initializationBytes, views) ??
+                false;
+
+        _initializedFromDill = true;
+        bytesLength += initializationBytes.length;
+        _componentProblems.saveComponentProblems(data.component!);
+      }
+    }
+    return bytesLength;
+  }
+}
+
+class _ComponentProblems {
+  Map<Uri, List<DiagnosticMessageFromJson>> _remainingComponentProblems =
+      new Map<Uri, List<DiagnosticMessageFromJson>>();
+
+  /// [partsUsed] indicates part uris that are used by (other/alive) libraries.
+  /// Those parts will not be removed from the component problems.
+  /// This is useful when a part has been "moved" to be part of another library.
+  void removeLibrary(Library lib, UriTranslator uriTranslator,
+      [Set<Uri?>? partsUsed]) {
+    if (_remainingComponentProblems.isNotEmpty) {
+      _remainingComponentProblems.remove(lib.fileUri);
+      // Remove parts too.
+      for (LibraryPart part in lib.parts) {
+        Uri? partFileUri = uriTranslator.getPartFileUri(lib.fileUri, part);
+        _remainingComponentProblems.remove(partFileUri);
+      }
+    }
+  }
+
+  /// Re-issue problems on the component and return the filtered list.
+  List<String> reissueProblems(CompilerContext context,
+      IncrementalKernelTarget userCode, Component componentWithDill) {
+    // These problems have already been reported.
+    Set<String> issuedProblems = new Set<String>();
+    if (componentWithDill.problemsAsJson != null) {
+      issuedProblems.addAll(componentWithDill.problemsAsJson!);
+    }
+
+    // Report old problems that wasn't reported again.
+    Set<Uri>? strongModeNNBDPackageOptOutUris;
+    for (MapEntry<Uri, List<DiagnosticMessageFromJson>> entry
+        in _remainingComponentProblems.entries) {
+      List<DiagnosticMessageFromJson> messages = entry.value;
+      for (int i = 0; i < messages.length; i++) {
+        DiagnosticMessageFromJson message = messages[i];
+        if (message.codeName == "StrongModeNNBDPackageOptOut") {
+          // Special case this: Don't issue them here; instead collect them
+          // to get their uris and re-issue a new error.
+          strongModeNNBDPackageOptOutUris ??= {};
+          strongModeNNBDPackageOptOutUris.add(entry.key);
+          continue;
+        }
+        if (issuedProblems.add(message.toJsonString())) {
+          context.options.reportDiagnosticMessage(message);
+        }
+      }
+    }
+    if (strongModeNNBDPackageOptOutUris != null) {
+      // Get the builders for these uris; then call
+      // `SourceLoader.giveCombinedErrorForNonStrongLibraries` on them to issue
+      // a new error.
+      Set<LibraryBuilder> builders = {};
+      SourceLoader loader = userCode.loader;
+      for (LibraryBuilder builder in loader.libraryBuilders) {
+        if (strongModeNNBDPackageOptOutUris.contains(builder.fileUri)) {
+          builders.add(builder);
+        }
+      }
+      FormattedMessage message = loader.giveCombinedErrorForNonStrongLibraries(
+          builders,
+          emitNonPackageErrors: false)!;
+      issuedProblems.add(message.toJsonString());
+      // The problem was issued by the call so don't re-issue it here.
+    }
+
+    // Save any new component-problems.
+    _addProblemsAsJson(componentWithDill.problemsAsJson);
+    return new List<String>.from(issuedProblems);
+  }
+
+  void saveComponentProblems(Component component) {
+    _addProblemsAsJson(component.problemsAsJson);
+  }
+
+  void _addProblemsAsJson(List<String>? problemsAsJson) {
+    if (problemsAsJson != null) {
+      for (String jsonString in problemsAsJson) {
+        DiagnosticMessageFromJson message =
+            new DiagnosticMessageFromJson.fromJson(jsonString);
+        assert(message.uri != null ||
+            (message.involvedFiles != null &&
+                message.involvedFiles!.isNotEmpty));
+        if (message.uri != null) {
+          List<DiagnosticMessageFromJson> messages =
+              _remainingComponentProblems[message.uri!] ??=
+                  <DiagnosticMessageFromJson>[];
+          messages.add(message);
+        }
+        if (message.involvedFiles != null) {
+          // This indexes the same message under several uris - this way it will
+          // be issued as long as it's a problem. It will because of
+          // deduplication when we re-issue these (in reissueComponentProblems)
+          // only be reported once.
+          for (Uri uri in message.involvedFiles!) {
+            List<DiagnosticMessageFromJson> messages =
+                _remainingComponentProblems[uri] ??=
+                    <DiagnosticMessageFromJson>[];
+            messages.add(message);
+          }
+        }
+      }
+    }
+  }
+}
+
+extension on UriTranslator {
+  Uri? getPartFileUri(Uri parentFileUri, LibraryPart part) {
+    Uri? fileUri = getPartUri(parentFileUri, part);
+    if (fileUri.scheme == "package") {
+      // Part was specified via package URI and the resolve above thus
+      // did not go as expected. Translate the package URI to get the
+      // actual file URI.
+      fileUri = translate(fileUri, false);
+    }
+    return fileUri;
+  }
+}
+
+class RecorderForTesting {
+  const RecorderForTesting();
+
+  void recordNonFullComponent(Component component) {}
+
+  void recordInvalidatedImportUris(List<Uri> uris) {}
+
+  void recordRebuildBodiesCount(int count) {}
+
+  void recordTemporaryFile(Uri uri) {}
+}
diff --git a/pkg/front_end/lib/src/fasta/source/source_loader.dart b/pkg/front_end/lib/src/fasta/source/source_loader.dart
index cff1197..4043a0a4 100644
--- a/pkg/front_end/lib/src/fasta/source/source_loader.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart
@@ -1258,8 +1258,8 @@
 
       Graph<List<Uri>> strongGraph =
           new StrongComponentGraph(libraryGraph, stronglyConnectedComponents);
-      List<List<List<Uri>>> componentLayers = [];
-      topologicalSort(strongGraph, layers: componentLayers);
+      List<List<List<Uri>>> componentLayers =
+          topologicalSort(strongGraph).layers;
       List<List<Uri>> layeredComponents = [];
       List<Uri> currentLayer = [];
       for (List<List<Uri>> layer in componentLayers) {
@@ -1493,20 +1493,6 @@
   /// pipeline (including backends) can assume that there are no hierarchy
   /// cycles.
   List<SourceClassBuilder> handleHierarchyCycles(ClassBuilder objectClass) {
-    // Compute the initial work list of all classes declared in this loader.
-    List<SourceClassBuilder> workList = <SourceClassBuilder>[];
-    for (LibraryBuilder library in libraryBuilders) {
-      if (library.loader == this) {
-        Iterator<Builder> members = library.iterator;
-        while (members.moveNext()) {
-          Builder member = members.current;
-          if (member is SourceClassBuilder) {
-            workList.add(member);
-          }
-        }
-      }
-    }
-
     Set<ClassBuilder> denyListedClasses = new Set<ClassBuilder>();
     for (int i = 0; i < denylistedCoreClasses.length; i++) {
       denyListedClasses.add(coreLibrary.lookupLocalMember(
@@ -1523,42 +1509,16 @@
     }
 
     // Sort the classes topologically.
-    Set<SourceClassBuilder> topologicallySortedClasses =
-        new Set<SourceClassBuilder>();
-    List<SourceClassBuilder> previousWorkList;
-    do {
-      previousWorkList = workList;
-      workList = <SourceClassBuilder>[];
-      for (int i = 0; i < previousWorkList.length; i++) {
-        SourceClassBuilder cls = previousWorkList[i];
-        Map<TypeDeclarationBuilder?, TypeAliasBuilder?> directSupertypeMap =
-            cls.computeDirectSupertypes(objectClass);
-        List<TypeDeclarationBuilder?> directSupertypes =
-            directSupertypeMap.keys.toList();
-        bool allSupertypesProcessed = true;
-        for (int i = 0; i < directSupertypes.length; i++) {
-          Builder? supertype = directSupertypes[i];
-          if (supertype is SourceClassBuilder &&
-              supertype.library.loader == this &&
-              !topologicallySortedClasses.contains(supertype)) {
-            allSupertypesProcessed = false;
-            break;
-          }
-        }
-        if (allSupertypesProcessed && cls.isPatch) {
-          allSupertypesProcessed =
-              topologicallySortedClasses.contains(cls.origin);
-        }
-        if (allSupertypesProcessed) {
-          topologicallySortedClasses.add(cls);
-          checkClassSupertypes(cls, directSupertypeMap, denyListedClasses);
-        } else {
-          workList.add(cls);
-        }
-      }
-    } while (previousWorkList.length != workList.length);
-    List<SourceClassBuilder> classes = topologicallySortedClasses.toList();
-    List<SourceClassBuilder> classesWithCycles = previousWorkList;
+    _SourceClassGraph classGraph = new _SourceClassGraph(this, objectClass);
+    TopologicalSortResult<SourceClassBuilder> result =
+        topologicalSort(classGraph);
+    List<SourceClassBuilder> classes = result.sortedVertices;
+    for (SourceClassBuilder cls in classes) {
+      checkClassSupertypes(
+          cls, classGraph.directSupertypeMap[cls]!, denyListedClasses);
+    }
+
+    List<SourceClassBuilder> classesWithCycles = result.cyclicVertices;
 
     // Once the work list doesn't change in size, it's either empty, or
     // contains all classes with cycles.
@@ -2478,3 +2438,44 @@
 
   final MacroApplicationData macroApplicationData = new MacroApplicationData();
 }
+
+class _SourceClassGraph implements Graph<SourceClassBuilder> {
+  @override
+  final List<SourceClassBuilder> vertices = [];
+  final ClassBuilder _objectClass;
+  final Map<SourceClassBuilder, Map<TypeDeclarationBuilder?, TypeAliasBuilder?>>
+      directSupertypeMap = {};
+  final Map<SourceClassBuilder, List<SourceClassBuilder>> _supertypeMap = {};
+
+  _SourceClassGraph(SourceLoader loader, this._objectClass) {
+    // Compute the vertices as all classes declared in this loader.
+    for (LibraryBuilder library in loader.libraryBuilders) {
+      if (library.loader == loader) {
+        Iterator<Builder> members = library.iterator;
+        while (members.moveNext()) {
+          Builder member = members.current;
+          if (member is SourceClassBuilder && !member.isPatch) {
+            vertices.add(member);
+          }
+        }
+      }
+    }
+  }
+
+  List<SourceClassBuilder> computeSuperClasses(SourceClassBuilder cls) {
+    Map<TypeDeclarationBuilder?, TypeAliasBuilder?> directSupertypes =
+        directSupertypeMap[cls] = cls.computeDirectSupertypes(_objectClass);
+    List<SourceClassBuilder> superClasses = [];
+    for (TypeDeclarationBuilder? directSupertype in directSupertypes.keys) {
+      if (directSupertype is SourceClassBuilder) {
+        superClasses.add(directSupertype);
+      }
+    }
+    return superClasses;
+  }
+
+  @override
+  Iterable<SourceClassBuilder> neighborsOf(SourceClassBuilder vertex) {
+    return _supertypeMap[vertex] ??= computeSuperClasses(vertex);
+  }
+}
diff --git a/pkg/front_end/test/incremental_dart2js_tester.dart b/pkg/front_end/test/incremental_dart2js_tester.dart
index 0e6717f..59add48 100644
--- a/pkg/front_end/test/incremental_dart2js_tester.dart
+++ b/pkg/front_end/test/incremental_dart2js_tester.dart
@@ -97,8 +97,9 @@
     Component c2 = compilerResult.component;
     print("Recompiled in ${localStopwatch.elapsedMilliseconds} ms");
     print("invalidatedImportUrisForTesting: "
-        "${compiler.invalidatedImportUrisForTesting}");
-    print("rebuildBodiesCount: ${compiler.rebuildBodiesCount}");
+        "${compiler.recorderForTesting.invalidatedImportUrisForTesting}");
+    print("rebuildBodiesCount: "
+        "${compiler.recorderForTesting.rebuildBodiesCount}");
     localStopwatch.reset();
     Set<Uri> thisUris = new Set<Uri>.from(c2.libraries.map((l) => l.importUri));
     if (componentUris.isNotEmpty) {
diff --git a/pkg/front_end/test/incremental_flutter_tester.dart b/pkg/front_end/test/incremental_flutter_tester.dart
index fc572aa3..4a745aa 100644
--- a/pkg/front_end/test/incremental_flutter_tester.dart
+++ b/pkg/front_end/test/incremental_flutter_tester.dart
@@ -140,8 +140,9 @@
     Component c2 = compilerResult.component;
     print("Recompiled in ${localStopwatch.elapsedMilliseconds} ms");
     print("invalidatedImportUrisForTesting: "
-        "${compiler.invalidatedImportUrisForTesting}");
-    print("rebuildBodiesCount: ${compiler.rebuildBodiesCount}");
+        "${compiler.recorderForTesting.invalidatedImportUrisForTesting}");
+    print("rebuildBodiesCount: "
+        "${compiler.recorderForTesting.rebuildBodiesCount}");
     localStopwatch.reset();
     Set<Uri> thisUris = new Set<Uri>.from(c2.libraries.map((l) => l.importUri));
     if (componentUris.isNotEmpty) {
diff --git a/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart b/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart
index 1de80e9..8280c8d 100644
--- a/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart
+++ b/pkg/front_end/test/incremental_load_from_invalid_dill_test.dart
@@ -42,7 +42,7 @@
         codeInitializeFromDillUnknownProblemNoDump;
 
 import 'package:front_end/src/fasta/incremental_compiler.dart'
-    show IncrementalCompiler;
+    show IncrementalCompiler, RecorderForTesting;
 
 import 'package:front_end/src/fasta/kernel/utils.dart' show serializeComponent;
 
@@ -84,7 +84,7 @@
             new ProcessedOptions(options: options, inputs: [entryPoint])),
         initializeFrom);
     await compiler.computeDelta();
-    if (compiler.initializedFromDill) {
+    if (compiler.initializedFromDillForTesting) {
       Expect.fail("Expected to not be able to initialized from dill, but did.");
     }
     if (errorMessages.isNotEmpty) {
@@ -112,9 +112,9 @@
     IncrementalCompilerResult compilerResult = await compiler.computeDelta();
     Component component = compilerResult.component;
 
-    if (compiler.initializedFromDill != initializedFromDill) {
+    if (compiler.initializedFromDillForTesting != initializedFromDill) {
       Expect.fail("Expected initializedFromDill to be $initializedFromDill "
-          "but was ${compiler.initializedFromDill}");
+          "but was ${compiler.initializedFromDillForTesting}");
     }
     if (errorMessages.isNotEmpty) {
       Expect.fail("Got unexpected errors: " + joinMessages(errorMessages));
@@ -316,7 +316,15 @@
       : super(context, initializeFromDillUri);
 
   @override
-  void recordTemporaryFileForTesting(Uri uri) {
+  final RecorderForTesting recorderForTesting =
+      const DeleteTempFilesRecorderForTesting();
+}
+
+class DeleteTempFilesRecorderForTesting extends RecorderForTesting {
+  const DeleteTempFilesRecorderForTesting();
+
+  @override
+  void recordTemporaryFile(Uri uri) {
     File f = new File.fromUri(uri);
     if (f.existsSync()) f.deleteSync();
   }
diff --git a/pkg/front_end/test/incremental_suite.dart b/pkg/front_end/test/incremental_suite.dart
index b854dfd..7c07a6a 100644
--- a/pkg/front_end/test/incremental_suite.dart
+++ b/pkg/front_end/test/incremental_suite.dart
@@ -43,7 +43,7 @@
     show DiagnosticMessageFromJson, FormattedMessage;
 
 import 'package:front_end/src/fasta/incremental_compiler.dart'
-    show IncrementalCompiler;
+    show IncrementalCompiler, RecorderForTesting;
 
 import 'package:front_end/src/fasta/incremental_serializer.dart'
     show IncrementalSerializer;
@@ -850,7 +850,8 @@
       }
 
       if (world["expectsRebuildBodiesOnly"] != null) {
-        bool didRebuildBodiesOnly = compiler.rebuildBodiesCount! > 0;
+        bool didRebuildBodiesOnly =
+            compiler.recorderForTesting.rebuildBodiesCount! > 0;
         if (world["expectsRebuildBodiesOnly"] != didRebuildBodiesOnly) {
           return new Result<TestData>(
               data,
@@ -878,19 +879,20 @@
           }
         }
       }
-      if (compiler.initializedFromDill != expectInitializeFromDill) {
+      if (compiler.initializedFromDillForTesting != expectInitializeFromDill) {
         return new Result<TestData>(
             data,
             InitializedFromDillMismatch,
             "Expected that initializedFromDill would be "
             "$expectInitializeFromDill but was "
-            "${compiler.initializedFromDill}");
+            "${compiler.initializedFromDillForTesting}");
       }
 
-      if (incrementalSerialization == true && compiler.initializedFromDill) {
-        Expect.isTrue(compiler.initializedIncrementalSerializer);
+      if (incrementalSerialization == true &&
+          compiler.initializedFromDillForTesting) {
+        Expect.isTrue(compiler.initializedIncrementalSerializerForTesting);
       } else {
-        Expect.isFalse(compiler.initializedIncrementalSerializer);
+        Expect.isFalse(compiler.initializedIncrementalSerializerForTesting);
       }
 
       if (world["checkInvalidatedFiles"] != false) {
@@ -1774,7 +1776,7 @@
   List<int> bytes =
       await normalCompileToBytes(input, options: options, compiler: compiler);
   new File.fromUri(output).writeAsBytesSync(bytes);
-  return compiler.initializedFromDill;
+  return compiler.initializedFromDillForTesting;
 }
 
 Future<List<int>> normalCompileToBytes(Uri input,
@@ -1814,7 +1816,7 @@
   Component initializedComponent = initializedCompilerResult.component;
   util.throwOnEmptyMixinBodies(initializedComponent);
   await util.throwOnInsufficientUriToSource(initializedComponent);
-  bool result = compiler.initializedFromDill;
+  bool result = compiler.initializedFromDillForTesting;
   new File.fromUri(output)
       .writeAsBytesSync(util.postProcess(initializedComponent));
   int actuallyInvalidatedCount = compiler
@@ -1875,8 +1877,9 @@
 }
 
 class TestIncrementalCompiler extends IncrementalCompiler {
-  Set<Uri>? invalidatedImportUrisForTesting;
-  int? rebuildBodiesCount;
+  @override
+  final TestRecorderForTesting recorderForTesting =
+      new TestRecorderForTesting();
   final Uri entryPoint;
 
   /// Filter out the automatically added entryPoint, unless it's explicitly
@@ -1886,12 +1889,12 @@
   /// This is not perfect, but works for what it's currently used for.
   Set<Uri>? getFilteredInvalidatedImportUrisForTesting(
       List<Uri> invalidatedUris) {
-    if (invalidatedImportUrisForTesting == null) return null;
+    if (recorderForTesting.invalidatedImportUrisForTesting == null) return null;
 
     Set<String> invalidatedFilenames =
         invalidatedUris.map((uri) => uri.pathSegments.last).toSet();
     Set<Uri> result = new Set<Uri>();
-    for (Uri uri in invalidatedImportUrisForTesting!) {
+    for (Uri uri in recorderForTesting.invalidatedImportUrisForTesting!) {
       if (uri.pathSegments.isNotEmpty &&
           uri.pathSegments.last == "nonexisting.dart") {
         continue;
@@ -1927,27 +1930,6 @@
             incrementalSerializer);
 
   @override
-  void recordInvalidatedImportUrisForTesting(List<Uri> uris) {
-    invalidatedImportUrisForTesting = uris.isEmpty ? null : uris.toSet();
-  }
-
-  @override
-  void recordNonFullComponentForTesting(Component component) {
-    // It should at least contain the sdk. Slight smoke test.
-    if (!component.libraries
-        .map((lib) => lib.importUri.toString())
-        .contains("dart:core")) {
-      throw "Loaders builder should contain the sdk, "
-          "but didn't even contain dart:core.";
-    }
-  }
-
-  @override
-  void recordRebuildBodiesCountForTesting(int count) {
-    rebuildBodiesCount = count;
-  }
-
-  @override
   Future<IncrementalCompilerResult> computeDelta(
       {List<Uri>? entryPoints,
       bool fullComponent = false,
@@ -1971,9 +1953,35 @@
     }
     return result;
   }
+}
+
+class TestRecorderForTesting extends RecorderForTesting {
+  Set<Uri>? invalidatedImportUrisForTesting;
+  int? rebuildBodiesCount;
 
   @override
-  void recordTemporaryFileForTesting(Uri uri) {
+  void recordInvalidatedImportUris(List<Uri> uris) {
+    invalidatedImportUrisForTesting = uris.isEmpty ? null : uris.toSet();
+  }
+
+  @override
+  void recordNonFullComponent(Component component) {
+    // It should at least contain the sdk. Slight smoke test.
+    if (!component.libraries
+        .map((lib) => lib.importUri.toString())
+        .contains("dart:core")) {
+      throw "Loaders builder should contain the sdk, "
+          "but didn't even contain dart:core.";
+    }
+  }
+
+  @override
+  void recordRebuildBodiesCount(int count) {
+    rebuildBodiesCount = count;
+  }
+
+  @override
+  void recordTemporaryFile(Uri uri) {
     File f = new File.fromUri(uri);
     if (f.existsSync()) f.deleteSync();
   }
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 9790926..3132c1c 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -1030,6 +1030,7 @@
 recompiling
 recompute
 recomputed
+recorder
 recoveries
 recreate
 recursion
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index 2320613..2a6a8f4 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -799,6 +799,7 @@
 reality
 recompile
 recompiles
+recorder
 redir
 redirections
 rediscover
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.expect b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.expect
index 1a2fe24..6095355 100644
--- a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.expect
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.expect
@@ -37,13 +37,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
-// class D3 = A with TAlias<B>;
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // class D4 = A with B implements TAlias<C>;
 //                                ^
@@ -58,13 +51,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
-// class D5 extends A with TAlias<B> {}
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // mixin N1 on TAlias<A> {}
 //             ^
@@ -100,6 +86,20 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
+// class D3 = A with TAlias<B>;
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
+// class D5 extends A with TAlias<B> {}
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
 //  - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
 // class C1 extends AAlias {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.transformed.expect b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.transformed.expect
index 5dac514..62c7762 100644
--- a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.strong.transformed.expect
@@ -37,13 +37,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
-// class D3 = A with TAlias<B>;
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // class D4 = A with B implements TAlias<C>;
 //                                ^
@@ -58,13 +51,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
-// class D5 extends A with TAlias<B> {}
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // mixin N1 on TAlias<A> {}
 //             ^
@@ -100,6 +86,20 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
+// class D3 = A with TAlias<B>;
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
+// class D5 extends A with TAlias<B> {}
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
 //  - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
 // class C1 extends AAlias {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.expect b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.expect
index 1a2fe24..6095355 100644
--- a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.expect
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.expect
@@ -37,13 +37,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
-// class D3 = A with TAlias<B>;
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // class D4 = A with B implements TAlias<C>;
 //                                ^
@@ -58,13 +51,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
-// class D5 extends A with TAlias<B> {}
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // mixin N1 on TAlias<A> {}
 //             ^
@@ -100,6 +86,20 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
+// class D3 = A with TAlias<B>;
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
+// class D5 extends A with TAlias<B> {}
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
 //  - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
 // class C1 extends AAlias {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.outline.expect b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.outline.expect
index a0cf738..22df973 100644
--- a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.outline.expect
@@ -37,13 +37,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
-// class D3 = A with TAlias<B>;
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // class D4 = A with B implements TAlias<C>;
 //                                ^
@@ -58,13 +51,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
-// class D5 extends A with TAlias<B> {}
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // mixin N1 on TAlias<A> {}
 //             ^
@@ -100,6 +86,20 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
+// class D3 = A with TAlias<B>;
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
+// class D5 extends A with TAlias<B> {}
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
 //  - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
 // class C1 extends AAlias {}
diff --git a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.transformed.expect b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.transformed.expect
index 5dac514..62c7762 100644
--- a/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart.weak.transformed.expect
@@ -37,13 +37,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
-// class D3 = A with TAlias<B>;
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // class D4 = A with B implements TAlias<C>;
 //                                ^
@@ -58,13 +51,6 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
-// class D5 extends A with TAlias<B> {}
-//       ^
-// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
-// typedef TAlias<T> = T?;
-//         ^
-//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
 // mixin N1 on TAlias<A> {}
 //             ^
@@ -100,6 +86,20 @@
 // typedef TAlias<T> = T?;
 //                ^
 //
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
+// class D3 = A with TAlias<B>;
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
+// class D5 extends A with TAlias<B> {}
+//       ^
+// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
+// typedef TAlias<T> = T?;
+//         ^
+//
 // pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
 //  - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
 // class C1 extends AAlias {}
diff --git a/pkg/kernel/lib/util/graph.dart b/pkg/kernel/lib/util/graph.dart
index aedbf14..105b170b 100644
--- a/pkg/kernel/lib/util/graph.dart
+++ b/pkg/kernel/lib/util/graph.dart
@@ -148,52 +148,63 @@
   Iterable<List<T>> get vertices => components;
 }
 
-/// Returns the non-cyclic vertices of [graph] sorted in topological order.
-///
-/// If [indexMap] is provided, it is filled with "index" of each vertex.
-/// If [layers] is provided, it is filled with a list of the vertices for each
-/// "index".
-///
-/// Here, the "index" of a vertex is the length of the longest path through
-/// neighbors. For vertices with no neighbors, the index is 0. For any other
-/// vertex, it is 1 plus max of the index of its neighbors.
-List<T> topologicalSort<T>(Graph<T> graph,
-    {Map<T, int>? indexMap, List<List<T>>? layers}) {
-  List<T> workList = graph.vertices.toList();
-  indexMap ??= {};
-  List<T> topologicallySortedVertices = [];
-  List<T> previousWorkList;
-  do {
-    previousWorkList = workList;
-    workList = [];
-    for (int i = 0; i < previousWorkList.length; i++) {
-      T vertex = previousWorkList[i];
-      int index = 0;
-      bool allSupertypesProcessed = true;
-      for (T neighbor in graph.neighborsOf(vertex)) {
-        int? neighborIndex = indexMap[neighbor];
-        if (neighborIndex == null) {
-          allSupertypesProcessed = false;
-          break;
-        } else {
-          index = max(index, neighborIndex + 1);
-        }
-      }
-      if (allSupertypesProcessed) {
-        indexMap[vertex] = index;
-        topologicallySortedVertices.add(vertex);
-        if (layers != null) {
-          if (index >= layers.length) {
-            assert(index == layers.length);
-            layers.add([vertex]);
-          } else {
-            layers[index].add(vertex);
-          }
-        }
+const int cyclicMarker = -1;
+
+int _topologicalSortInternal<T>(
+    Graph<T> graph, TopologicalSortResult<T> result, T vertex) {
+  int? index = result.indexMap[vertex];
+  if (index == null) {
+    result.indexMap[vertex] = cyclicMarker;
+    int index = 0;
+    for (T neighbor in graph.neighborsOf(vertex)) {
+      int neighborIndex = _topologicalSortInternal(graph, result, neighbor);
+      if (neighborIndex == cyclicMarker) {
+        result.cyclicVertices.add(vertex);
+        return cyclicMarker;
       } else {
-        workList.add(vertex);
+        index = max(index, neighborIndex + 1);
       }
     }
-  } while (previousWorkList.length != workList.length);
-  return topologicallySortedVertices;
+    result.sortedVertices.add(vertex);
+    if (index >= result.layers.length) {
+      assert(index == result.layers.length);
+      result.layers.add([vertex]);
+    } else {
+      result.layers[index].add(vertex);
+    }
+    return result.indexMap[vertex] = index;
+  }
+  return index;
+}
+
+/// Perform a topological sorting of the vertices in [graph], returning a
+/// [TopologicalSortResult] object with the result.
+TopologicalSortResult<T> topologicalSort<T>(Graph<T> graph) {
+  TopologicalSortResult<T> result = new TopologicalSortResult();
+
+  for (T vertex in graph.vertices) {
+    _topologicalSortInternal(graph, result, vertex);
+  }
+  return result;
+}
+
+/// The result of computing the [topologicalSort] on a [Graph].
+class TopologicalSortResult<T> {
+  /// The non-cyclic vertices of the graph sorted in topological order.
+  final List<T> sortedVertices = [];
+
+  /// The cyclic vertices of graph, including vertices that have a path to
+  /// a vertex.
+  final List<T> cyclicVertices = [];
+
+  /// The topological index of all non-cyclic vertices of the graph.
+  ///
+  /// The "topological index" of a vertex is the length of the longest path
+  /// through neighbors. For vertices with no neighbors, the index is 0.
+  /// For any other vertex, it is 1 plus max of the index of its neighbors.
+  final Map<T, int> indexMap = {};
+
+  /// The non-cyclic vertices in layers according to their topological index.
+  /// That is, `layers[i]` contain the list of vertices with index `i`.
+  final List<List<T>> layers = [];
 }
diff --git a/pkg/kernel/test/graph_test.dart b/pkg/kernel/test/graph_test.dart
index 143ab01..83827be 100644
--- a/pkg/kernel/test/graph_test.dart
+++ b/pkg/kernel/test/graph_test.dart
@@ -8,6 +8,13 @@
 
 import 'package:kernel/util/graph.dart';
 
+const String A = 'A';
+const String B = 'B';
+const String C = 'C';
+const String D = 'D';
+const String E = 'E';
+const String F = 'F';
+
 class TestGraph implements Graph<String> {
   final Map<String, List<String>> graph;
 
@@ -20,185 +27,533 @@
   Iterable<String> neighborsOf(String vertex) => graph[vertex]!;
 }
 
-void test(String expected, Map<String, List<String>> graph) {
-  List<List<String>> result = computeStrongComponents(new TestGraph(graph));
-  Expect.stringEquals(expected, "$result");
-}
+void test(
+    {required List<List<String>> expectedStrongComponents,
+    List<String> expectedSortedVertices = const [],
+    List<String> expectedCyclicVertices = const [],
+    List<List<String>> expectedLayers = const [],
+    List<List<List<String>>> expectedStrongLayers = const [],
+    required Map<String, List<String>> graphData}) {
+  Graph<String> graph = new TestGraph(graphData);
 
-void checkGraph(Map<String, List<String>> graph, String startingNodeName,
-    List<List<String>> expectedEvaluations, List<bool> expectedSccFlags) {
-  List<List<String>> result = computeStrongComponents(new TestGraph(graph));
-  List<List<String>> expectedReversed = <List<String>>[];
-  for (List<String> list in expectedEvaluations) {
-    expectedReversed.add(list.reversed.toList());
+  List<List<String>> strongComponentResult = computeStrongComponents(graph);
+  Expect.equals(
+      expectedStrongComponents.length,
+      strongComponentResult.length,
+      "Unexpected strongly connected components count. "
+      "Expected ${expectedStrongComponents}, "
+      "actual ${strongComponentResult}");
+  for (int index = 0; index < expectedStrongComponents.length; index++) {
+    Expect.listEquals(
+        expectedStrongComponents[index],
+        strongComponentResult[index],
+        "Unexpected strongly connected components. "
+        "Expected $expectedStrongComponents, actual $strongComponentResult.");
   }
-  Expect.stringEquals(expectedReversed.join(", "), result.join(", "));
+
+  TopologicalSortResult<String> topologicalResult = topologicalSort(graph);
+  Set<String> sortedAndCyclicVertices = topologicalResult.sortedVertices
+      .toSet()
+      .intersection(topologicalResult.cyclicVertices.toSet());
+  Expect.isTrue(sortedAndCyclicVertices.isEmpty,
+      "Found vertices both sorted and cyclic: $sortedAndCyclicVertices");
+  List<String> sortedOrCyclicVertices = [
+    ...topologicalResult.sortedVertices,
+    ...topologicalResult.cyclicVertices
+  ];
+  Expect.equals(
+      graphData.length,
+      sortedOrCyclicVertices.length,
+      "Unexpected vertex count. Expected ${graphData.length}, "
+      "found ${sortedOrCyclicVertices.length}.");
+  Expect.listEquals(
+      expectedSortedVertices,
+      topologicalResult.sortedVertices,
+      "Unexpected sorted vertices. "
+      "Expected $expectedSortedVertices, "
+      "actual ${topologicalResult.sortedVertices}.");
+  Expect.listEquals(
+      expectedCyclicVertices,
+      topologicalResult.cyclicVertices,
+      "Unexpected cyclic vertices. "
+      "Expected $expectedCyclicVertices, "
+      "actual ${topologicalResult.cyclicVertices}.");
+  Expect.equals(
+      expectedLayers.length,
+      topologicalResult.layers.length,
+      "Unexpected topological layer count. "
+      "Expected ${expectedLayers}, "
+      "actual ${topologicalResult.layers}");
+  for (int index = 0; index < expectedLayers.length; index++) {
+    Expect.listEquals(
+        expectedLayers[index],
+        topologicalResult.layers[index],
+        "Unexpected topological layers. "
+        "Expected $expectedLayers, "
+        "actual ${topologicalResult.layers}.");
+    for (String vertex in topologicalResult.layers[index]) {
+      int actualIndex = topologicalResult.indexMap[vertex]!;
+      Expect.equals(
+          index,
+          actualIndex,
+          "Unexpected topological index for $vertex. "
+          "Expected $index, found $actualIndex.");
+    }
+  }
+
+  StrongComponentGraph<String> strongComponentGraph =
+      new StrongComponentGraph(graph, strongComponentResult);
+  TopologicalSortResult<List<String>> strongTopologicalResult =
+      topologicalSort(strongComponentGraph);
+  Expect.equals(
+      expectedStrongLayers.length,
+      strongTopologicalResult.layers.length,
+      "Unexpected strong topological layer count. "
+      "Expected ${expectedStrongLayers}, "
+      "actual ${strongTopologicalResult.layers}");
+  for (int index = 0; index < expectedStrongLayers.length; index++) {
+    List<List<String>> expectedStrongLayer = expectedStrongLayers[index];
+    List<List<String>> strongLayer = strongTopologicalResult.layers[index];
+    Expect.equals(
+        expectedStrongLayer.length,
+        strongLayer.length,
+        "Unexpected strong topological layer $index count. "
+        "Expected ${expectedStrongLayers}, "
+        "actual ${strongTopologicalResult.layers}");
+
+    for (int subIndex = 0; subIndex < expectedStrongLayer.length; subIndex++) {
+      Expect.listEquals(
+          expectedStrongLayer[subIndex],
+          strongLayer[subIndex],
+          "Unexpected strong topological layer $index. "
+          "Expected $expectedStrongLayer, "
+          "actual $strongLayer.");
+    }
+    for (List<String> vertex in strongTopologicalResult.layers[index]) {
+      int actualIndex = strongTopologicalResult.indexMap[vertex]!;
+      Expect.equals(
+          index,
+          actualIndex,
+          "Unexpected strong topological index for $vertex. "
+          "Expected $index, found $actualIndex.");
+    }
+  }
 }
 
 void main() {
-  test("[[B, A], [C], [D]]", {
-    "A": ["B"],
-    "B": ["A"],
-    "C": ["A"],
-    "D": ["C"],
-  });
+  test(graphData: {
+    A: [B],
+    B: [A],
+    C: [A],
+    D: [C],
+  }, expectedStrongComponents: [
+    [B, A],
+    [C],
+    [D]
+  ], expectedCyclicVertices: [
+    B,
+    A,
+    C,
+    D
+  ], expectedStrongLayers: [
+    [
+      [B, A]
+    ],
+    [
+      [C]
+    ],
+    [
+      [D]
+    ]
+  ]);
 
-  test("[]", {});
+  test(graphData: {}, expectedStrongComponents: []);
 
-  test("[[A], [B], [C], [D]]", {
-    "A": [],
-    "B": [],
-    "C": [],
-    "D": [],
-  });
+  test(graphData: {
+    A: [],
+    B: [],
+    C: [],
+    D: [],
+  }, expectedStrongComponents: [
+    [A],
+    [B],
+    [C],
+    [D]
+  ], expectedSortedVertices: [
+    A,
+    B,
+    C,
+    D
+  ], expectedLayers: [
+    [A, B, C, D]
+  ], expectedStrongLayers: [
+    [
+      [A],
+      [B],
+      [C],
+      [D]
+    ]
+  ]);
 
-  test("[[B, A], [C], [D]]", {
-    "D": ["C"],
-    "C": ["A"],
-    "B": ["A"],
-    "A": ["B"],
-  });
+  test(graphData: {
+    D: [C],
+    C: [A],
+    B: [A],
+    A: [B],
+  }, expectedStrongComponents: [
+    [B, A],
+    [C],
+    [D]
+  ], expectedCyclicVertices: [
+    B,
+    A,
+    C,
+    D
+  ], expectedStrongLayers: [
+    [
+      [B, A]
+    ],
+    [
+      [C]
+    ],
+    [
+      [D]
+    ]
+  ]);
 
-  test("[[D], [C], [B], [A]]", {
-    "A": ["B"],
-    "B": ["C"],
-    "C": ["D"],
-    "D": [],
-  });
+  test(graphData: {
+    A: [B],
+    B: [C],
+    C: [D],
+    D: [],
+  }, expectedStrongComponents: [
+    [D],
+    [C],
+    [B],
+    [A]
+  ], expectedSortedVertices: [
+    D,
+    C,
+    B,
+    A
+  ], expectedLayers: [
+    [D],
+    [C],
+    [B],
+    [A]
+  ], expectedStrongLayers: [
+    [
+      [D]
+    ],
+    [
+      [C]
+    ],
+    [
+      [B]
+    ],
+    [
+      [A]
+    ]
+  ]);
 
-  test("[[D], [C], [B], [A]]", {
-    "D": [],
-    "C": ["D"],
-    "B": ["C"],
-    "A": ["B"],
-  });
+  test(graphData: {
+    D: [],
+    C: [D],
+    B: [C],
+    A: [B],
+  }, expectedStrongComponents: [
+    [D],
+    [C],
+    [B],
+    [A]
+  ], expectedSortedVertices: [
+    D,
+    C,
+    B,
+    A
+  ], expectedLayers: [
+    [D],
+    [C],
+    [B],
+    [A]
+  ], expectedStrongLayers: [
+    [
+      [D]
+    ],
+    [
+      [C]
+    ],
+    [
+      [B]
+    ],
+    [
+      [A]
+    ]
+  ]);
 
-  test("[[A], [B], [C], [D]]", {
-    "A": [],
-    "B": ["A"],
-    "C": ["A"],
-    "D": ["B", "C"],
-  });
+  test(graphData: {
+    A: [],
+    B: [A],
+    C: [A],
+    D: [B, C],
+  }, expectedStrongComponents: [
+    [A],
+    [B],
+    [C],
+    [D]
+  ], expectedSortedVertices: [
+    A,
+    B,
+    C,
+    D
+  ], expectedLayers: [
+    [A],
+    [B, C],
+    [D]
+  ], expectedStrongLayers: [
+    [
+      [A]
+    ],
+    [
+      [B],
+      [C]
+    ],
+    [
+      [D]
+    ]
+  ]);
 
   // Test a complex graph.
-  checkGraph(
-      {
-        'a': ['b', 'c'],
-        'b': ['c', 'd'],
-        'c': [],
-        'd': ['c', 'e'],
-        'e': ['b', 'f'],
-        'f': ['c', 'd']
-      },
-      'a',
-      [
-        ['c'],
-        ['b', 'd', 'e', 'f'],
-        ['a']
-      ],
-      [false, true, false]);
+  test(graphData: {
+    A: [B, C],
+    B: [C, D],
+    C: [],
+    D: [C, E],
+    E: [B, F],
+    F: [C, D]
+  }, expectedStrongComponents: [
+    [C],
+    [F, E, D, B],
+    [A],
+  ], expectedSortedVertices: [
+    C
+  ], expectedCyclicVertices: [
+    E,
+    D,
+    B,
+    A,
+    F
+  ], expectedLayers: [
+    [C]
+  ], expectedStrongLayers: [
+    [
+      [C]
+    ],
+    [
+      [F, E, D, B]
+    ],
+    [
+      [A]
+    ]
+  ]);
 
   // Test a diamond-shaped graph.
-  checkGraph(
-      {
-        'a': ['b', 'c'],
-        'b': ['d'],
-        'c': ['d'],
-        'd': []
-      },
-      'a',
-      [
-        ['d'],
-        ['b'],
-        ['c'],
-        ['a']
-      ],
-      [false, false, false, false]);
+  test(graphData: {
+    A: [B, C],
+    B: [D],
+    C: [D],
+    D: []
+  }, expectedStrongComponents: [
+    [D],
+    [B],
+    [C],
+    [A],
+  ], expectedSortedVertices: [
+    D,
+    B,
+    C,
+    A
+  ], expectedLayers: [
+    [D],
+    [B, C],
+    [A]
+  ], expectedStrongLayers: [
+    [
+      [D]
+    ],
+    [
+      [B],
+      [C]
+    ],
+    [
+      [A]
+    ]
+  ]);
 
   // Test a graph with a single node.
-  checkGraph(
-      {'a': []},
-      'a',
-      [
-        ['a']
-      ],
-      [false]);
+  test(graphData: {
+    A: []
+  }, expectedStrongComponents: [
+    [A]
+  ], expectedSortedVertices: [
+    A
+  ], expectedLayers: [
+    [A]
+  ], expectedStrongLayers: [
+    [
+      [A]
+    ]
+  ]);
 
   // Test a graph with a single node and a trivial cycle.
-  checkGraph(
-      {
-        'a': ['a']
-      },
-      'a',
-      [
-        ['a']
-      ],
-      [true]);
+  test(graphData: {
+    A: [A]
+  }, expectedStrongComponents: [
+    [A]
+  ], expectedCyclicVertices: [
+    A
+  ], expectedStrongLayers: [
+    [
+      [A]
+    ]
+  ]);
 
   // Test a graph with three nodes with circular dependencies.
-  checkGraph(
-      {
-        'a': ['b'],
-        'b': ['c'],
-        'c': ['a'],
-      },
-      'a',
-      [
-        ['a', 'b', 'c']
-      ],
-      [true]);
+  test(graphData: {
+    A: [B],
+    B: [C],
+    C: [A],
+  }, expectedStrongComponents: [
+    [C, B, A]
+  ], expectedCyclicVertices: [
+    C,
+    B,
+    A
+  ], expectedStrongLayers: [
+    [
+      [C, B, A]
+    ]
+  ]);
+
   // Test a graph A->B->C->D, where D points back to B and then C.
-  checkGraph(
-      {
-        'a': ['b'],
-        'b': ['c'],
-        'c': ['d'],
-        'd': ['b', 'c']
-      },
-      'a',
-      [
-        ['b', 'c', 'd'],
-        ['a']
-      ],
-      [true, false]);
+  test(graphData: {
+    A: [B],
+    B: [C],
+    C: [D],
+    D: [B, C]
+  }, expectedStrongComponents: [
+    [D, C, B],
+    [A]
+  ], expectedCyclicVertices: [
+    D,
+    C,
+    B,
+    A
+  ], expectedStrongLayers: [
+    [
+      [D, C, B]
+    ],
+    [
+      [A]
+    ]
+  ]);
 
   // Test a graph A->B->C->D, where D points back to C and then B.
-  checkGraph(
-      {
-        'a': ['b'],
-        'b': ['c'],
-        'c': ['d'],
-        'd': ['c', 'b']
-      },
-      'a',
-      [
-        ['b', 'c', 'd'],
-        ['a']
-      ],
-      [true, false]);
+  test(graphData: {
+    A: [B],
+    B: [C],
+    C: [D],
+    D: [C, B]
+  }, expectedStrongComponents: [
+    [D, C, B],
+    [A]
+  ], expectedCyclicVertices: [
+    D,
+    C,
+    B,
+    A
+  ], expectedStrongLayers: [
+    [
+      [D, C, B]
+    ],
+    [
+      [A]
+    ]
+  ]);
 
   // Test a graph with two nodes with circular dependencies.
-  checkGraph(
-      {
-        'a': ['b'],
-        'b': ['a']
-      },
-      'a',
-      [
-        ['a', 'b']
-      ],
-      [true]);
+  test(graphData: {
+    A: [B],
+    B: [A]
+  }, expectedStrongComponents: [
+    [B, A]
+  ], expectedCyclicVertices: [
+    B,
+    A
+  ], expectedStrongLayers: [
+    [
+      [B, A]
+    ]
+  ]);
 
   // Test a graph with two nodes and a single dependency.
-  checkGraph(
-      {
-        'a': ['b'],
-        'b': []
-      },
-      'a',
-      [
-        ['b'],
-        ['a']
-      ],
-      [false, false]);
+  test(graphData: {
+    A: [B],
+    B: []
+  }, expectedStrongComponents: [
+    [B],
+    [A]
+  ], expectedSortedVertices: [
+    B,
+    A
+  ], expectedLayers: [
+    [B],
+    [A]
+  ], expectedStrongLayers: [
+    [
+      [B]
+    ],
+    [
+      [A]
+    ]
+  ]);
+
+  test(graphData: {
+    A: [],
+    B: [A],
+    C: [B, D],
+    D: [C],
+    E: [A],
+    F: [B],
+  }, expectedStrongComponents: [
+    [A],
+    [B],
+    [D, C],
+    [E],
+    [F],
+  ], expectedSortedVertices: [
+    A,
+    B,
+    E,
+    F,
+  ], expectedCyclicVertices: [
+    D,
+    C,
+  ], expectedLayers: [
+    [A],
+    [B, E],
+    [F],
+  ], expectedStrongLayers: [
+    [
+      [A]
+    ],
+    [
+      [B],
+      [E]
+    ],
+    [
+      [D, C],
+      [F]
+    ]
+  ]);
 }
diff --git a/sdk/lib/collection/hash_map.dart b/sdk/lib/collection/hash_map.dart
index 9bc1661..e8a5a9b 100644
--- a/sdk/lib/collection/hash_map.dart
+++ b/sdk/lib/collection/hash_map.dart
@@ -43,7 +43,7 @@
 /// final Map<int, String> planets = HashMap(); // Is a HashMap
 /// ```
 /// To add data to a map, use [operator[]=], [addAll] or [addEntries].
-/// ```
+/// ```dart continued
 /// planets[3] = 'Earth';
 /// planets.addAll({4: 'Mars'});
 /// final gasGiants = {6: 'Jupiter', 5: 'Saturn'};
@@ -52,12 +52,12 @@
 /// ```
 /// To check if the map is empty, use [isEmpty] or [isNotEmpty].
 /// To find the number of map entries, use [length].
-/// ```
+/// ```dart continued
 /// final isEmpty = planets.isEmpty; // false
 /// final length = planets.length; // 4
 /// ```
 /// The [forEach] iterates through all entries of a map.
-/// ```
+/// ```dart continued
 /// planets.forEach((key, value) {
 ///   print('$key \t $value');
 ///   // 5        Saturn
@@ -67,32 +67,32 @@
 /// });
 /// ```
 /// To check whether the map has an entry with a specific key, use [containsKey].
-/// ```
+/// ```dart continued
 /// final keyOneExists = planets.containsKey(4); // true
 /// final keyFiveExists = planets.containsKey(1); // false
-/// ```
+/// ```dart continued
 /// To check whether the map has an entry with a specific value,
 /// use [containsValue].
-/// ```
+/// ```dart continued
 /// final marsExists = planets.containsValue('Mars'); // true
 /// final venusExists = planets.containsValue('Venus'); // false
 /// ```
 /// To remove an entry with a specific key, use [remove].
-/// ```
+/// ```dart continued
 /// final removeValue = planets.remove(5);
 /// print(removeValue); // Jupiter
 /// print(planets); // fx {4: Mars, 3: Earth, 5: Saturn}
 /// ```
 /// To remove multiple entries at the same time, based on their keys and values,
 /// use [removeWhere].
-/// ```
+/// ```dart continued
 /// planets.removeWhere((key, value) => key == 5);
 /// print(planets); // fx {3: Earth, 4: Mars}
 /// ```
 /// To conditionally add or modify a value for a specific key, depending on
 /// whether there already is an entry with that key,
 /// use [putIfAbsent] or [update].
-/// ```
+/// ```dart continued
 /// planets.update(4, (v) => 'Saturn');
 /// planets.update(8, (v) => '', ifAbsent: () => 'Neptune');
 /// planets.putIfAbsent(4, () => 'Another Saturn');
@@ -100,12 +100,12 @@
 /// ```
 /// To update the values of all keys, based on the existing key and value,
 /// use [updateAll].
-/// ```
+/// ```dart continued
 /// planets.updateAll((key, value) => 'X');
 /// print(planets); // fx {8: X, 3: X, 4: X}
 /// ```
 /// To remove all entries and empty the map, use [clear].
-/// ```
+/// ```dart continued
 /// planets.clear();
 /// print(planets); // {}
 /// print(planets.isEmpty); // true
@@ -152,7 +152,7 @@
   /// Example:
   /// ```dart template:expression
   /// HashMap<int,int>(equals: (int a, int b) => (b - a) % 5 == 0,
-  ///                  hashCode: (int e) => e % 5);
+  ///                  hashCode: (int e) => e % 5)
   /// ```
   /// This example map does not need an `isValidKey` function to be passed.
   /// The default function accepts precisely `int` values, which can safely be
diff --git a/sdk/lib/collection/hash_set.dart b/sdk/lib/collection/hash_set.dart
index 29ef55b..bd8860c 100644
--- a/sdk/lib/collection/hash_set.dart
+++ b/sdk/lib/collection/hash_set.dart
@@ -38,24 +38,24 @@
 /// final letters = HashSet<String>();
 /// ```
 /// To add data to a set, use  [add] or [addAll].
-/// ```
+/// ```dart continued
 /// letters.add('A');
 /// letters.addAll({'B', 'C', 'D'});
 /// ```
 /// To check if the set is empty, use [isEmpty] or [isNotEmpty].
 /// To find the number of elements in the set, use [length].
-/// ```
+/// ```dart continued
 /// print(letters.isEmpty); // false
 /// print(letters.length); // 4
 /// print(letters); // fx {A, D, C, B}
 /// ```
 /// To check whether the set has an element with a specific value,
 /// use [contains].
-/// ```
+/// ```dart continued
 /// final bExists = letters.contains('B'); // true
 /// ```
 /// The [forEach] method calls a function with each element of the set.
-/// ```
+/// ```dart continued
 /// letters.forEach(print);
 /// // A
 /// // D
@@ -63,29 +63,29 @@
 /// // B
 /// ```
 /// To make a copy of the set, use [toSet].
-/// ```
+/// ```dart continued
 /// final anotherSet = letters.toSet();
 /// print(anotherSet); // fx {A, C, D, B}
 /// ```
 /// To remove an element, use [remove].
-/// ```
+/// ```dart continued
 /// final removedValue = letters.remove('A'); // true
 /// print(letters); // fx {B, C, D}
 /// ```
 /// To remove multiple elements at the same time, use [removeWhere] or
 /// [removeAll].
-/// ```
+/// ```dart continued
 /// letters.removeWhere((element) => element.startsWith('B'));
 /// print(letters); // fx {D, C}
 /// ```
 /// To removes all elements in this set that do not meet a condition,
 /// use [retainWhere].
-/// ```
+/// ```dart continued
 /// letters.retainWhere((element) => element.contains('C'));
 /// print(letters); // {C}
 /// ```
 /// To remove all elements and empty the set, use [clear].
-/// ```
+/// ```dart continued
 /// letters.clear();
 /// print(letters.isEmpty); // true
 /// print(letters); // {}
@@ -118,7 +118,7 @@
   /// instance of [E], which means that:
   /// ```dart template:expression
   /// HashSet<int>(equals: (int e1, int e2) => (e1 - e2) % 5 == 0,
-  ///              hashCode: (int e) => e % 5);
+  ///              hashCode: (int e) => e % 5)
   /// ```
   /// does not need an `isValidKey` argument because it defaults to only
   /// accepting `int` values which are accepted by both `equals` and `hashCode`.
diff --git a/sdk/lib/collection/linked_hash_map.dart b/sdk/lib/collection/linked_hash_map.dart
index be71118..9ee9d30 100644
--- a/sdk/lib/collection/linked_hash_map.dart
+++ b/sdk/lib/collection/linked_hash_map.dart
@@ -41,20 +41,20 @@
 /// final planetsByDiameter = {0.949: 'Venus'}; // A new LinkedHashMap
 /// ```
 /// To add data to a map, use [operator[]=], [addAll] or [addEntries].
-/// ```
+/// ```dart continued
 /// planetsByDiameter[1] = 'Earth';
 /// planetsByDiameter.addAll({0.532: 'Mars', 11.209: 'Jupiter'});
 /// ```
 /// To check if the map is empty, use [isEmpty] or [isNotEmpty].
 /// To find the number of map entries, use [length].
-/// ```
+/// ```dart continued
 /// print(planetsByDiameter.isEmpty); // false
 /// print(planetsByDiameter.length); // 4
 /// print(planetsByDiameter);
 /// // {0.949: Venus, 1.0: Earth, 0.532: Mars, 11.209: Jupiter}
 /// ```
 /// The [forEach] method calls a function for each key/value entry of the map.
-/// ```
+/// ```dart continued
 /// planetsByDiameter.forEach((key, value) {
 ///   print('$key \t $value');
 ///   // 0.949    Venus
@@ -64,44 +64,44 @@
 /// });
 /// ```
 /// To check whether the map has an entry with a specific key, use [containsKey].
-/// ```
+/// ```dart continued
 /// final keyOneExists = planetsByDiameter.containsKey(1); // true
 /// final keyFiveExists = planetsByDiameter.containsKey(5); // false
 /// ```
 /// To check whether the map has an entry with a specific value,
 /// use [containsValue].
-/// ```
+/// ```dart continued
 /// final earthExists = planetsByDiameter.containsValue('Earth'); // true
 /// final saturnExists =  planetsByDiameter.containsValue('Saturn'); // false
 /// ```
 /// To remove an entry with a specific key, use [remove].
-/// ```
+/// ```dart continued
 /// final removedValue = planetsByDiameter.remove(1);
 /// print(removedValue); // Earth
 /// print(planetsByDiameter); // {0.949: Venus, 0.532: Mars, 11.209: Jupiter}
 /// ```
 /// To remove multiple entries at the same time, based on their keys and values,
 /// use [removeWhere].
-/// ```
+/// ```dart continued
 /// planetsByDiameter.removeWhere((key, value) => key == 0.949);
 /// print(planetsByDiameter); // {0.532: Mars, 11.209: Jupiter}
 /// ```
 /// To conditionally add or modify a value for a specific key, depending on
 /// whether there already is an entry with that key,
 /// use [putIfAbsent] or [update].
-/// ```
+/// ```dart continued
 /// planetsByDiameter.update(0.949, (v) => 'Venus', ifAbsent: () => 'Venus');
 /// planetsByDiameter.putIfAbsent(0.532, () => "Another Mars if needed");
 /// print(planetsByDiameter); // {0.532: Mars, 11.209: Jupiter, 0.949: Venus}
 /// ```
 /// To update the values of all keys, based on the existing key and value,
 /// use [updateAll].
-/// ```
+/// ```dart continued
 /// planetsByDiameter.updateAll((key, value) => 'X');
 /// print(planetsByDiameter); // {0.532: X, 11.209: X, 0.949: X}
 /// ```
 /// To remove all entries and empty the map, use [clear].
-/// ```
+/// ```dart continued
 /// planetsByDiameter.clear();
 /// print(planetsByDiameter); // {}
 /// print(planetsByDiameter.isEmpty); // true
diff --git a/sdk/lib/collection/linked_hash_set.dart b/sdk/lib/collection/linked_hash_set.dart
index 55349c9..fd21777 100644
--- a/sdk/lib/collection/linked_hash_set.dart
+++ b/sdk/lib/collection/linked_hash_set.dart
@@ -44,24 +44,24 @@
 /// final planets = <String>{}; // LinkedHashSet
 /// ```
 /// To add data to a set, use [add] or [addAll].
-/// ```
+/// ```dart continued
 /// final uranusAdded = planets.add('Uranus'); // true
 /// planets.addAll({'Venus', 'Mars', 'Earth', 'Jupiter'});
 /// print(planets); // {Uranus, Venus, Mars, Earth, Jupiter}
 /// ```
 /// To check if the set is empty, use [isEmpty] or [isNotEmpty].
 /// To find the number of elements in the set, use [length].
-/// ```
+/// ```dart continued
 /// print(planets.isEmpty); // false
 /// print(planets.length); // 5
 /// ```
 /// To check whether the set has an element with a specific value,
 /// use [contains].
-/// ```
+/// ```dart continued
 /// final marsExists = planets.contains('Mars'); // true
 /// ```
 /// The [forEach] method calls a function with each element of the set.
-/// ```
+/// ```dart continued
 /// planets.forEach(print);
 /// // Uranus
 /// // Venus
@@ -71,29 +71,29 @@
 /// ```
 ///
 /// To make a copy of the set, use [toSet].
-/// ```
+/// ```dart continued
 /// final copySet = planets.toSet();
 /// print(copySet); // {Uranus, Venus, Mars, Earth, Jupiter}
 /// ```
 /// To remove an element, use [remove].
-/// ```
+/// ```dart continued
 /// final removedValue = planets.remove('Mars'); // Mars
 /// print(planets); // {Uranus, Venus, Earth, Jupiter}
 /// ```
 /// To remove multiple elements at the same time, use [removeWhere] or
 /// [removeAll].
-/// ```
+/// ```dart continued
 /// planets.removeWhere((element) => element.startsWith('E'));
 /// print(planets); // {Uranus, Venus, Jupiter}
 /// ```
 /// To removes all elements in this set that do not meet a condition,
 /// use [retainWhere].
-/// ```
+/// ```dart continued
 /// planets.retainWhere((element) => element.contains('Jupiter'));
 /// print(planets); // {Jupiter}
-/// ```
+/// ```dart continued
 /// To remove all elements and empty the set, use [clear].
-/// ```
+/// ```dart continued
 /// planets.clear();
 /// print(planets.isEmpty); // true
 /// print(planets); // {}
diff --git a/sdk/lib/collection/queue.dart b/sdk/lib/collection/queue.dart
index 7f1a6ce..fa8b785 100644
--- a/sdk/lib/collection/queue.dart
+++ b/sdk/lib/collection/queue.dart
@@ -532,7 +532,7 @@
 /// final queue = ListQueue<int>();
 /// ```
 /// To add objects to a queue, use [add], [addAll], [addFirst] or[addLast].
-/// ```
+/// ```dart continued
 /// queue.add(5);
 /// queue.addFirst(0);
 /// queue.addLast(10);
@@ -541,44 +541,44 @@
 /// ```
 /// To check if the queue is empty, use [isEmpty] or [isNotEmpty].
 /// To find the number of queue entries, use [length].
-/// ```
+/// ```dart continued
 /// final isEmpty = queue.isEmpty; // false
 /// final queueSize = queue.length; // 6
 /// ```
 /// To get first or last item from queue, use [first] or [last].
-/// ```
+/// ```dart continued
 /// final first = queue.first; // 0
 /// final last = queue.last; // 3
 /// ```
 /// To get item value using index, use [elementAt].
-/// ```
+/// ```dart continued
 /// final itemAt = queue.elementAt(2); // 10
 /// ```
 /// To convert queue to list, call [toList].
-/// ```
+/// ```dart continued
 /// final numbers = queue.toList();
 /// print(numbers); // [0, 5, 10, 1, 2, 3]
 /// ```
 /// To remove item from queue, call [remove], [removeFirst] or [removeLast].
-/// ```
+/// ```dart continued
 /// queue.remove(10);
 /// queue.removeFirst();
 /// queue.removeLast();
 /// print(queue); // {5, 1, 2}
 /// ```
 /// To remove multiple elements at the same time, use [removeWhere].
-/// ```
+/// ```dart continued
 /// queue.removeWhere((element) => element == 1);
 /// print(queue); // {5, 2}
 /// ```
 /// To remove all elements in this queue that do not meet a condition,
 /// use [retainWhere].
-/// ```
+/// ```dart continued
 /// queue.retainWhere((element) => element < 4);
 /// print(queue); // {2}
 /// ```
 /// To remove all items and empty the set, use [clear].
-/// ```
+/// ```dart continued
 /// queue.clear();
 /// print(queue.isEmpty); // true
 /// print(queue); // {}
diff --git a/sdk/lib/core/annotations.dart b/sdk/lib/core/annotations.dart
index 4b7140b..1c2a416 100644
--- a/sdk/lib/core/annotations.dart
+++ b/sdk/lib/core/annotations.dart
@@ -159,7 +159,7 @@
 ///
 /// For example:
 ///
-/// ```dart template:none
+/// ```dart template:top
 /// @pragma('Tool:pragma-name', [param1, param2, ...])
 /// class Foo { }
 ///
diff --git a/sdk/lib/core/set.dart b/sdk/lib/core/set.dart
index f548888..9880ea6 100644
--- a/sdk/lib/core/set.dart
+++ b/sdk/lib/core/set.dart
@@ -57,7 +57,7 @@
   /// All the [elements] should be instances of [E].
   /// The `elements` iterable itself can have any type,
   /// so this constructor can be used to down-cast a `Set`, for example as:
-  /// ```dart
+  /// ```
   /// Set<SuperType> superSet = ...;
   /// Set<SubType> subSet =
   ///     Set<SubType>.from(superSet.where((e) => e is SubType));
@@ -68,6 +68,11 @@
   ///
   /// The set is equivalent to one created by
   /// `LinkedHashSet<E>.from(elements)`.
+  /// ```dart
+  /// final numbers = <num>{10, 20, 30};
+  /// final setFrom = Set<int>.from(numbers);
+  /// print(setFrom); // {10, 20, 30}
+  /// ```
   factory Set.from(Iterable elements) = LinkedHashSet<E>.from;
 
   /// Creates a [Set] from [elements].
@@ -78,12 +83,21 @@
   ///
   /// The set is equivalent to one created by
   /// `LinkedHashSet<E>.of(elements)`.
+  /// ```dart
+  /// final baseSet = <int>{1, 2, 3};
+  /// final setOf = Set<num>.of(baseSet);
+  /// print(setOf); // {1, 2, 3}
+  /// ```
   factory Set.of(Iterable<E> elements) = LinkedHashSet<E>.of;
 
   /// Creates an unmodifiable [Set] from [elements].
   ///
   /// The new set behaves like the result of [Set.of],
   /// except that the set returned by this constructor is not modifiable.
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// final unmodifiableSet = Set.unmodifiable(characters);
+  /// ```
   @Since("2.12")
   factory Set.unmodifiable(Iterable<E> elements) =>
       UnmodifiableSetView<E>(<E>{...elements});
@@ -103,7 +117,7 @@
   /// the store will throw unless the value is also an instance of [S].
   ///
   /// If all accessed elements of [source] are actually instances of [T],
-  /// and if all elements added to the returned set are actually instance
+  /// and if all elements added to the returned set are actually instances
   /// of [S],
   /// then the returned set can be used as a `Set<T>`.
   ///
@@ -121,7 +135,7 @@
   /// that is not an instance of [R], the access will throw instead.
   ///
   /// Elements added to the set (e.g., by using [add] or [addAll])
-  /// must be instance of [R] to be valid arguments to the adding function,
+  /// must be instances of [R] to be valid arguments to the adding function,
   /// and they must be instances of [E] as well to be accepted by
   /// this set as well.
   ///
@@ -140,6 +154,11 @@
   Iterator<E> get iterator;
 
   /// Whether [value] is in the set.
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// final containsB = characters.contains('B'); // true
+  /// final containsD = characters.contains('D'); // false
+  /// ```
   bool contains(Object? value);
 
   /// Adds [value] to the set.
@@ -149,18 +168,23 @@
   ///
   /// Example:
   /// ```dart
-  /// var set = Set();
-  /// var time1 = DateTime.fromMillisecondsSinceEpoch(0);
-  /// var time2 = DateTime.fromMillisecondsSinceEpoch(0);
+  /// final dateTimes = <DateTime>{};
+  /// final time1 = DateTime.fromMillisecondsSinceEpoch(0);
+  /// final time2 = DateTime.fromMillisecondsSinceEpoch(0);
   /// // time1 and time2 are equal, but not identical.
   /// assert(time1 == time2);
   /// assert(!identical(time1, time2));
-  /// set.add(time1);  // => true.
+  /// final time1Added = dateTimes.add(time1);
+  /// print(time1Added); // true
   /// // A value equal to time2 exists already in the set, and the call to
   /// // add doesn't change the set.
-  /// set.add(time2);  // => false.
-  /// assert(set.length == 1);
-  /// assert(identical(time1, set.first));
+  /// final time2Added = dateTimes.add(time2);
+  /// print(time2Added); // false
+  ///
+  /// print(dateTimes); // {1970-01-01 02:00:00.000}
+  /// assert(dateTimes.length == 1);
+  /// assert(identical(time1, dateTimes.first));
+  /// print(dateTimes.length);
   /// ```
   bool add(E value);
 
@@ -168,12 +192,23 @@
   ///
   /// Equivalent to adding each element in [elements] using [add],
   /// but some collections may be able to optimize it.
+  /// ```dart
+  /// final characters = <String>{'A', 'B'};
+  /// characters.addAll({'A', 'B', 'C'});
+  /// print(characters); // {A, B, C}
+  /// ```
   void addAll(Iterable<E> elements);
 
   /// Removes [value] from the set.
   ///
   /// Returns `true` if [value] was in the set, and `false` if not.
   /// The method has no effect if [value] was not in the set.
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// final didRemoveB = characters.remove('B'); // true
+  /// final didRemoveD = characters.remove('D'); // false
+  /// print(characters); // {A, C}
+  /// ```
   bool remove(Object? value);
 
   /// If an object equal to [object] is in the set, return it.
@@ -188,9 +223,21 @@
   /// rather than being based on an actual object instance,
   /// then there may not be a specific object instance representing the
   /// set element.
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// final containsB = characters.lookup('B');
+  /// print(containsB); // B
+  /// final containsD = characters.lookup('D');
+  /// print(containsD); // null
+  /// ```
   E? lookup(Object? object);
 
   /// Removes each element of [elements] from this set.
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// characters.removeAll({'A', 'B', 'X'});
+  /// print(characters); // {C}
+  /// ```
   void removeAll(Iterable<Object?> elements);
 
   /// Removes all elements of this set that are not elements in [elements].
@@ -199,36 +246,85 @@
   /// set that is equal to it (according to `this.contains`), and if so, the
   /// equal element in this set is retained, and elements that are not equal
   /// to any element in [elements] are removed.
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// characters.retainAll({'A', 'B', 'X'});
+  /// print(characters); // {A, B}
+  /// ```
   void retainAll(Iterable<Object?> elements);
 
   /// Removes all elements of this set that satisfy [test].
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// characters.removeWhere((element) => element.startsWith('B'));
+  /// print(characters); // {A, C}
+  /// ```
   void removeWhere(bool test(E element));
 
   /// Removes all elements of this set that fail to satisfy [test].
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// characters.retainWhere(
+  ///     (element) => element.startsWith('B') || element.startsWith('C'));
+  /// print(characters); // {B, C}
+  /// ```
   void retainWhere(bool test(E element));
 
   /// Whether this set contains all the elements of [other].
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// final containsAB = characters.containsAll({'A', 'B'});
+  /// print(containsAB); // true
+  /// final containsAD = characters.containsAll({'A', 'D'});
+  /// print(containsAD); // false
+  /// ```
   bool containsAll(Iterable<Object?> other);
 
   /// Creates a new set which is the intersection between this set and [other].
   ///
   /// That is, the returned set contains all the elements of this [Set] that
   /// are also elements of [other] according to `other.contains`.
+  /// ```dart
+  /// final characters1 = <String>{'A', 'B', 'C'};
+  /// final characters2 = <String>{'A', 'E', 'F'};
+  /// final unionSet = characters1.intersection(characters2);
+  /// print(unionSet); // {A}
+  /// ```
   Set<E> intersection(Set<Object?> other);
 
   /// Creates a new set which contains all the elements of this set and [other].
   ///
   /// That is, the returned set contains all the elements of this [Set] and
   /// all the elements of [other].
+  /// ```dart
+  /// final characters1 = <String>{'A', 'B', 'C'};
+  /// final characters2 = <String>{'A', 'E', 'F'};
+  /// final unionSet1 = characters1.union(characters2);
+  /// print(unionSet1); // {A, B, C, E, F}
+  /// final unionSet2 = characters2.union(characters1);
+  /// print(unionSet2); // {A, E, F, B, C}
+  /// ```
   Set<E> union(Set<E> other);
 
   /// Creates a new set with the elements of this that are not in [other].
   ///
   /// That is, the returned set contains all the elements of this [Set] that
   /// are not elements of [other] according to `other.contains`.
+  /// ```dart
+  /// final characters1 = <String>{'A', 'B', 'C'};
+  /// final characters2 = <String>{'A', 'E', 'F'};
+  /// final differenceSet1 = characters1.difference(characters2);
+  /// print(differenceSet1); // {B, C}
+  /// final differenceSet2 = characters2.difference(characters1);
+  /// print(differenceSet2); // {E, F}
+  /// ```
   Set<E> difference(Set<Object?> other);
 
   /// Removes all elements from the set.
+  /// ```dart
+  /// final characters = <String>{'A', 'B', 'C'};
+  /// characters.clear(); // {}
+  /// ```
   void clear();
 
   /// Creates a [Set] with the same elements and behavior as this `Set`.
diff --git a/sdk/lib/core/stopwatch.dart b/sdk/lib/core/stopwatch.dart
index 45da67e..959fcbe 100644
--- a/sdk/lib/core/stopwatch.dart
+++ b/sdk/lib/core/stopwatch.dart
@@ -4,7 +4,47 @@
 
 part of dart.core;
 
-/// A simple stopwatch interface to measure elapsed time.
+/// A stopwatch which measures time while it's running.
+///
+/// A stopwatch is either running or stopped.
+/// It measures the elapsed time that passes while the stopwatch is running.
+///
+/// When a stopwatch is initially created, it is stopped and has measured no
+/// elapsed time.
+///
+/// The elapsed time can be accessed in various formats using
+/// [elapsed], [elapsedMilliseconds], [elapsedMicroseconds] or [elapsedTicks].
+///
+/// The stopwatch is started by calling [start].
+///
+/// Example:
+/// ```dart
+/// final stopwatch = Stopwatch();
+/// print(stopwatch.elapsedMilliseconds); // 0
+/// print(stopwatch.isRunning); // false
+/// stopwatch.start();
+/// print(stopwatch.isRunning); // true
+/// ```
+/// To stop or pause the stopwatch, use [stop].
+/// Use [start] to continue again when only pausing temporarily.
+/// ```
+/// stopwatch.stop();
+/// print(stopwatch.isRunning); // false
+/// Duration elapsed = stopwatch.elapsed;
+/// await Future.delayed(const Duration(seconds: 1));
+/// assert(stopwatch.elapsed == elapsed); // No measured time elapsed.
+/// stopwatch.start(); // Continue measuring.
+/// ```
+/// The [reset] method sets the elapsed time back to zero.
+/// It can be called whether the stopwatch is running or not,
+/// and doesn't change whether it's running.
+/// ```
+/// // Do some work.
+/// stopwatch.stop();
+/// print(stopwatch.elapsedMilliseconds); // Likely > 0.
+/// stopwatch.reset();
+/// print(stopwatch.elapsedMilliseconds); // 0
+/// ```
 class Stopwatch {
   /// Cached frequency of the system in Hz (ticks per second).
   ///
@@ -22,7 +62,7 @@
   /// The following example shows how to start a [Stopwatch]
   /// immediately after allocation.
   /// ```dart
-  /// var stopwatch = Stopwatch()..start();
+  /// final stopwatch = Stopwatch()..start();
   /// ```
   Stopwatch() {
     _frequency; // Ensures initialization before using any method.
@@ -33,7 +73,7 @@
 
   /// Starts the [Stopwatch].
   ///
-  /// The [elapsed] count is increasing monotonically. If the [Stopwatch] has
+  /// The [elapsed] count increases monotonically. If the [Stopwatch] has
   /// been stopped, then calling start again restarts it without resetting the
   /// [elapsed] count.
   ///
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index d3d19c2..00eee44 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -827,7 +827,7 @@
 /// Annotation to be used for marking an external function as FFI native.
 ///
 /// Example:
-///```dart template:none
+///```dart template:top
 /// @FfiNative<Int64 Function(Int64, Int64)>('FfiNative_Sum', isLeaf:true)
 /// external int sum(int a, int b);
 ///```
diff --git a/sdk/lib/internal/internal.dart b/sdk/lib/internal/internal.dart
index fbe663b..5d38bd8 100644
--- a/sdk/lib/internal/internal.dart
+++ b/sdk/lib/internal/internal.dart
@@ -644,7 +644,7 @@
 ///
 /// Example:
 ///
-/// ```dart template:none
+/// ```dart template:top
 /// class Two<A, B> {}
 ///
 /// print(extractTypeArguments<List>(<int>[], <T>() => new Set<T>()));
@@ -658,7 +658,7 @@
 /// The type argument T is important to choose which specific type parameter
 /// list in [instance]'s type hierarchy is being extracted. Consider:
 ///
-/// ```dart template:none
+/// ```dart template:top
 /// class A<T> {}
 /// class B<T> {}
 ///
diff --git a/sdk/lib/js_util/js_util.dart b/sdk/lib/js_util/js_util.dart
index 7842a32..fcd668c 100644
--- a/sdk/lib/js_util/js_util.dart
+++ b/sdk/lib/js_util/js_util.dart
@@ -245,7 +245,7 @@
 
 /// Converts a JavaScript Promise to a Dart [Future].
 ///
-/// ```dart template:none
+/// ```dart template:top
 /// @JS()
 /// external Promise<num> get threePromise; // Resolves to 3
 ///
diff --git a/tools/VERSION b/tools/VERSION
index e7063a5..aaeea35 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 72
+PRERELEASE 73
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/get_builder_status.dart b/tools/bots/get_builder_status.dart
index d66167f..6abf4ef 100755
--- a/tools/bots/get_builder_status.dart
+++ b/tools/bots/get_builder_status.dart
@@ -8,8 +8,6 @@
 // These cloud functions write a success/failure result to the
 // builder table based on the approvals in Firestore.
 
-// @dart = 2.9
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -20,27 +18,26 @@
 const numAttempts = 20;
 const failuresPerConfiguration = 20;
 
-/*late*/ bool useStagingDatabase;
+late bool useStagingDatabase;
 
 Uri get _queryUrl {
-  var project = useStagingDatabase ? 'dart-ci-staging' : 'dart-ci';
+  final project = useStagingDatabase ? 'dart-ci-staging' : 'dart-ci';
   return Uri.https('firestore.googleapis.com',
       '/v1/projects/$project/databases/(default)/documents:runQuery');
 }
 
-/*late*/ String builder;
-/*late*/ String builderBase;
-/*late*/ int buildNumber;
-/*late*/ String token;
-/*late*/ http.Client client;
+late String builder;
+late String builderBase;
+late int buildNumber;
+late String token;
+late http.Client client;
 
 String get buildTable => builder.endsWith('-try') ? 'try_builds' : 'builds';
 String get resultsTable => builder.endsWith('-try') ? 'try_results' : 'results';
 
 bool booleanFieldOrFalse(Map<String, dynamic> document, String field) {
-  Map<String, dynamic> fieldObject = document['fields'][field];
-  if (fieldObject == null) return false;
-  return fieldObject['booleanValue'] ?? false;
+  final fieldObject = document['fields'][field];
+  return fieldObject?['booleanValue'] ?? false;
 }
 
 void usage(ArgParser parser) {
@@ -56,12 +53,12 @@
 }
 
 Future<String> readGcloudAuthToken(String path) async {
-  String token = await File(path).readAsString();
-  return token.split("\n").first;
+  final token = await File(path).readAsString();
+  return token.split('\n').first;
 }
 
-main(List<String> args) async {
-  final parser = new ArgParser();
+void main(List<String> args) async {
+  final parser = ArgParser();
   parser.addFlag('help', help: 'Show the program usage.', negatable: false);
   parser.addOption('auth_token',
       abbr: 'a', help: 'Authorization token with cloud-platform scope');
@@ -75,9 +72,9 @@
     usage(parser);
   }
 
-  useStagingDatabase = options['staging'] /*!*/;
-  builder = options['builder'] /*!*/;
-  buildNumber = int.parse(options['build_number'] /*!*/);
+  useStagingDatabase = options['staging'];
+  builder = options['builder'];
+  buildNumber = int.parse(options['build_number']);
   builderBase = builder.replaceFirst(RegExp('-try\$'), '');
   if (options['auth_token'] == null) {
     print('Option "--auth_token (-a)" is required\n');
@@ -85,83 +82,60 @@
   }
   token = await readGcloudAuthToken(options['auth_token']);
   client = http.Client();
-  for (int count = 0; count < numAttempts; ++count) {
-    if (count > 0) {
-      await Future.delayed(Duration(seconds: 10));
-    }
-    final response = await runFirestoreQuery(buildQuery());
-    if (response.statusCode == HttpStatus.ok) {
-      final documents = jsonDecode(response.body);
-      final document = documents.first['document'];
-      if (document != null) {
-        bool success = booleanFieldOrFalse(document, 'success');
-        bool completed = booleanFieldOrFalse(document, 'completed');
-        if (completed) {
-          print(success
-              ? 'No new unapproved failures'
-              : 'There are new unapproved failures on this build');
-          if (builder.endsWith('-try')) exit(success ? 0 : 1);
-          final configurations = await getConfigurations();
-          final failures = await fetchActiveFailures(configurations);
-          if (failures.isNotEmpty) {
-            print('There are unapproved failures');
-            printActiveFailures(failures);
-            exit(1);
-          } else {
-            print('There are no unapproved failures');
-            exit(0);
-          }
-        }
-        String chunks =
-            (document['fields']['num_chunks'] ?? const {})['integerValue'];
-        String processedChunks = (document['fields']['processed_chunks'] ??
-            const {})['integerValue'];
-        if (processedChunks != null) {
-          print([
-            'Received',
-            processedChunks,
-            if (chunks != null) ...['out of', chunks],
-            'chunks.'
-          ].join(' '));
-        }
-      } else {
-        print('No results received for build $buildNumber of $builder');
-      }
-    } else {
-      print('HTTP status ${response.statusCode} received '
-          'when fetching build data');
-    }
+  final response = await runFirestoreQuery(buildQuery());
+  if (response.statusCode != HttpStatus.ok) {
+    print('HTTP status ${response.statusCode} received '
+        'when fetching build data');
+    exit(2);
   }
-  print('No status received for build $buildNumber of $builder '
-      'after $numAttempts attempts, with 10 second waits.');
-  exit(2);
+  final documents = jsonDecode(response.body);
+  final document = documents.first['document'];
+  if (document == null) {
+    print('No results received for build $buildNumber of $builder');
+    exit(2);
+  }
+  final success = booleanFieldOrFalse(document, 'success');
+  print(success
+      ? 'No new unapproved failures'
+      : 'There are new unapproved failures on this build');
+  if (builder.endsWith('-try')) exit(success ? 0 : 1);
+  final configurations = await getConfigurations();
+  final failures = await fetchActiveFailures(configurations);
+  if (failures.isNotEmpty) {
+    print('There are unapproved failures');
+    printActiveFailures(failures);
+    exit(1);
+  } else {
+    print('There are no unapproved failures');
+    exit(0);
+  }
 }
 
 Future<List<String>> getConfigurations() async {
   final response = await runFirestoreQuery(configurationsQuery());
-  if (response.statusCode == HttpStatus.ok) {
-    final documents = jsonDecode(response.body);
-    final groups = <String /*!*/ >{
-      for (Map document in documents)
-        if (document.containsKey('document'))
-          document['document']['name'].split('/').last
-    };
-    return groups.toList();
+  if (response.statusCode != HttpStatus.ok) {
+    print('Could not fetch configurations for $builderBase');
+    return [];
   }
-  print('Could not fetch configurations for $builderBase');
-  return [];
+  final documents = jsonDecode(response.body);
+  final groups = <String>{
+    for (Map document in documents)
+      if (document.containsKey('document'))
+        document['document']['name'].split('/').last
+  };
+  return groups.toList();
 }
 
 Map<int, Future<String>> commitHashes = {};
 Future<String> commitHash(int index) =>
     commitHashes.putIfAbsent(index, () => fetchCommitHash(index));
 
-Future<String /*!*/ > fetchCommitHash(int index) async {
+Future<String> fetchCommitHash(int index) async {
   final response = await runFirestoreQuery(commitQuery(index));
   if (response.statusCode == HttpStatus.ok) {
     final document = jsonDecode(response.body).first['document'];
     if (document != null) {
-      return document['name'] /*!*/ .split('/').last;
+      return document['name'].split('/').last;
     }
   }
   print('Could not fetch commit with index $index');
@@ -169,7 +143,7 @@
 }
 
 Future<Map<String, List<Map<String, dynamic>>>> fetchActiveFailures(
-    List<String /*!*/ > configurations) async {
+    List<String> configurations) async {
   final failures = <String, List<Map<String, dynamic>>>{};
   for (final configuration in configurations) {
     final response =
@@ -197,9 +171,9 @@
 }
 
 void printActiveFailures(Map<String, List<Map<String, dynamic>>> failures) {
-  for (final configuration in failures.keys) {
+  failures.forEach((configuration, failureList) {
     print('($configuration):');
-    for (final failure in failures[configuration]) {
+    for (final failure in failureList) {
       print([
         '    ',
         failure['name'],
@@ -217,7 +191,7 @@
         ]
       ].join(''));
     }
-  }
+  });
 }
 
 Future<http.Response> runFirestoreQuery(String query) {
diff --git a/tools/bots/post_results_to_pubsub.dart b/tools/bots/post_results_to_pubsub.dart
deleted file mode 100644
index 774dec9..0000000
--- a/tools/bots/post_results_to_pubsub.dart
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
-// 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.
-
-// Post results from Dart tryjobs and CI builders to Cloud Pub/Sub.
-//
-// Reads a results.json input file, sends only the changed results from
-// that file to the Pub/Sub channel 'results' in the 'dart-ci' project.
-// Multiple messages are sent if there are more than 100 changed results,
-// so the cloud function only needs to process 100 records within its time
-// limit of 60s. Because of this, we never approach the limit of 10 MB
-// base64-encoded data bytes per message.
-
-import 'dart:convert';
-import 'dart:io';
-
-import 'package:args/args.dart';
-import 'package:http/http.dart' as http;
-
-void usage(ArgParser parser, {exitCode = 0}) {
-  print('''
-Usage: post_results_to_pubsub.dart [OPTIONS]
-Posts Dart CI results as messages to Google Cloud Pub/Sub
-
-The options are as follows:
-
-${parser.usage}''');
-  exit(exitCode);
-}
-
-const resultsPerMessage = 50;
-
-Uri _postUrl(String project) => Uri.https(
-    'pubsub.googleapis.com', 'v1/projects/$project/topics/results:publish');
-
-main(List<String> args) async {
-  final parser = new ArgParser();
-  parser.addFlag('help', help: 'Show the program usage.', negatable: false);
-  parser.addOption('auth_token',
-      abbr: 'a',
-      help: 'Authorization token with a scope including pubsub publish.');
-  parser.addOption('result_file',
-      abbr: 'f', help: 'File containing the results to send');
-  parser.addOption('id', abbr: 'i', help: 'Buildbucket ID of this build');
-  parser.addOption('base_revision', help: 'A try build\'s patch base');
-  parser.addFlag('staging',
-      abbr: 's', help: 'Publish to the staging system', defaultsTo: false);
-
-  final options = parser.parse(args);
-  if (options['help']) {
-    usage(parser);
-  }
-
-  if (options['result_file'] == null) {
-    print('Error: option "result_file" is required.\n');
-    usage(parser, exitCode: 1);
-  }
-
-  if (options['auth_token'] == null) {
-    print('Error: option "auth_token" is required.\n');
-    usage(parser, exitCode: 1);
-  }
-
-  final project = options['staging'] ? "dart-ci-staging" : "dart-ci";
-
-  final client = http.Client();
-
-  final lines = await File(options['result_file']).readAsLines();
-  final token = await File(options['auth_token']).readAsString();
-  final buildbucketID = options['id'];
-  final baseRevision = options['base_revision'];
-  if (lines.isEmpty) {
-    print('No results in input file');
-    return;
-  }
-
-  // TODO(karlklose): parse and validate data before sending it.
-
-  final changedPattern = '"changed":true';
-  List<String> changedResults =
-      lines.where((change) => change.contains(changedPattern)).toList();
-  // We need to send at least one result, to save build metadata to Firestore.
-  // Send an unchanged result - the cloud function filters these out.
-  if (changedResults.isEmpty) changedResults = lines.sublist(0, 1);
-
-  final chunks = <List<String>>[];
-  var position = 0;
-  final lastFullChunkStart = changedResults.length - resultsPerMessage;
-  while (position <= lastFullChunkStart) {
-    chunks.add(changedResults.sublist(position, position += resultsPerMessage));
-  }
-  if (position < changedResults.length)
-    chunks.add(changedResults.sublist(position));
-
-  // Send pubsub messages.
-  for (final chunk in chunks) {
-    // Space messages out to reduce scaling problems
-    const chunkDelay = Duration(seconds: 2);
-    if (chunk != chunks.first) {
-      await Future.delayed(chunkDelay);
-    }
-    final message = '[\n${chunk.join(",\n")}\n]';
-    final base64data = base64Encode(utf8.encode(message.toString()));
-    final attributes = {
-      if (chunk == chunks.last) 'num_chunks': chunks.length.toString(),
-      if (buildbucketID != null) 'buildbucket_id': buildbucketID,
-      if (baseRevision != null) 'base_revision': baseRevision,
-    };
-    final jsonMessage = jsonEncode({
-      'messages': [
-        {'attributes': attributes, 'data': base64data}
-      ]
-    });
-    final headers = {'Authorization': 'Bearer $token'};
-    final postUrl = _postUrl(project);
-    final response =
-        await client.post(postUrl, headers: headers, body: jsonMessage);
-
-    print('Sent pubsub message containing ${chunk.length} results');
-    print('Status ${response.statusCode}');
-    print('Response: ${response.body}');
-  }
-  print('Number of Pub/Sub messages sent: ${chunks.length}');
-  client.close();
-}
diff --git a/tools/verify_docs/README.md b/tools/verify_docs/README.md
index a79aa7c..41be863 100644
--- a/tools/verify_docs/README.md
+++ b/tools/verify_docs/README.md
@@ -1,19 +1,20 @@
-## Whats' this?
+## What’s this?
 
 A tool to validate the documentation comments for the `dart:` libraries.
 
 ## Running the tool
 
-To validate all the dart: libraries, run:
+To validate all the `dart:` libraries, run:
 
 ```
 dart tools/verify_docs/bin/verify_docs.dart
 ```
 
-Or to validate an individual library (async, collection, js_util, ...), run:
+Or to validate an individual library (async, collection, js_util, ...), run either of:
 
 ```
 dart tools/verify_docs/bin/verify_docs.dart sdk/lib/<lib-name>
+dart tools/verify_docs/bin/verify_docs.dart dart:<lib-name>
 ```
 
 The tool should be run from the root of the sdk repository.
@@ -22,68 +23,104 @@
 
 ### What gets analyzed
 
-This tool will walk all dartdoc api docs looking for code samples in doc comments.
+This tool will walk all DartDoc API docs looking for code samples in doc comments.
 It will analyze any code sample in a `dart` code fence. For example:
 
-> ```dart
-> print('hello world!');
-> ```
+> ````dart
+> /// ```dart
+> /// print('hello world!');
+> /// ```
+> ````
 
-By default, an import for that library is added to the sample being analyzed (i.e.,
-`import 'dart:async";`).
+By default, an import for that library is added to the sample being analyzed, e.g., `import 'dart:async";`.
 
 ### Excluding code samples from analysis
 
 In order to exclude a code sample from analysis, change it to a plain code fence style:
 
-> ```
-> print("I'm not analyzed :(");
-> ```
-
-### Specifying additional imports
-
-In order to reference code from other Dart core libraries, you can either explicitly add
-the import to the code sample - in-line in the sample - or use a directive on the same
-line as the code fence. The directive style looks like:
-
-> ```dart import:async
-> print('hello world ${Timer()}');
-> ```
-
-Multiple imports can be specified like this if desired (i.e., "```dart import:async import:convert").
+> ````dart
+> /// ```
+> /// print("I'm not analyzed :(");
+> /// ```
+> ````
 
 ### Specifying templates
 
 The analysis tool can inject the code sample into a template before analyzing the
-sample. This allows the author to focus on the import parts of the API being
+sample. This allows the author to focus on the important parts of the API being
 documented with less boilerplate in the generated docs.
 
+The template includes an automatic import of the library containing the example, so an example in, say, the documentation of `StreamController.add` would have `dart:async` imported automatically.
+
 The tool will try and automatically detect the right template to use based on
 code patterns within the sample itself. In order to explicitly indicate which template
 to use, you can specify it as part of the code fence line. For example:
 
-> ```dart template:main
-> print('hello world ${Timer()}');
+> ```dart
+> /// ```dart template:main
+> /// print('hello world ${Timer()}');
+> /// ```
 > ```
 
-The three current templates are:
-- `none`: do not wrap the code sample in any template
-- `main`: wrap the code sample in a simple main() method
-- `expression`: wrap the code sample in a statement within a main() method
+The current templates are:
 
-For most code sample, the auto-detection code will select `template:main` or
+- `none`: Do not wrap the code sample in any template, including no imports.
+- `top`: The code sample is top level code, preceded only by imports.
+- `main`: The code sample is one or more statements in a simple asynchronous  `main()` function.
+- `expression`: The code sample is an expression within a simple asynchronous `main()` method.
+
+For most code samples, the auto-detection code will select `template:main` or
 `template:expression`.
 
+If the example contains any `library` declarations, the template becomes `none`.
+
+### Specifying additional imports
+
+If your example contains any `library`, the default import of the current library is omitted. To avoid that, you can declare extra automatic imports in the code fence like:
+
+> ````dart
+> /// ```dart import:async
+> /// print('hello world ${Timer()}');
+> /// ```
+> ````
+
+Multiple imports can be specified like this if desired, e.g., "```` ```dart import:async import:convert````".
+
+Does not work if combined with `template:none`, whether the `none` template is specified explicitly or auto-detected.
+
+### Splitting examples
+
+Some examples may be split into separate code blocks, but should be seen
+as continuing the same running example.
+
+If the following code blocks are marked as `continued` as shown below, they
+are included into the previous code block instead of being treated as a new
+example.
+
+> ````dart
+> /// ```dart
+> /// var list = [1, 2, 3];
+> /// ```
+> /// And then you can also do the following:
+> /// ```dart continued
+> /// list.forEach(print);
+> /// ```
+> ````
+
+A `continued` code block cannot have any other flags in the fence.
+
 ### Including additional code for analysis
 
 You can declare code that should be included in the analysis but not shown in
 the API docs by adding a comment "// Examples can assume:" to the file (usually
 at the top of the file, after the imports), following by one or more
-commented-out lines of code. That code is included verbatim in the analysis. For
-example:
+commented-out lines of code. That code is included verbatim in the analysis, at top-level after the automatic imports. Does not work with `template:none`.
+
+For example:
 
 ```dart
 // Examples can assume:
 // final BuildContext context;
 // final String userAvatarUrl;
 ```
+
diff --git a/tools/verify_docs/bin/verify_docs.dart b/tools/verify_docs/bin/verify_docs.dart
old mode 100644
new mode 100755
index 085223f..8cba35f
--- a/tools/verify_docs/bin/verify_docs.dart
+++ b/tools/verify_docs/bin/verify_docs.dart
@@ -2,6 +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.
 
+// Read the ../README.md file for the recognized syntax.
+
 import 'dart:collection';
 import 'dart:io';
 
@@ -20,10 +22,10 @@
 import 'package:analyzer/src/util/comment.dart';
 import 'package:path/path.dart' as path;
 
+final libDir = Directory(path.join('sdk', 'lib'));
 void main(List<String> args) async {
-  final libDir = Directory('sdk/lib');
   if (!libDir.existsSync()) {
-    print('Please run this tool from the root of the sdk repo.');
+    print('Please run this tool from the root of the sdk repository.');
     exit(1);
   }
 
@@ -37,7 +39,7 @@
 
   final coreLibraries = args.isEmpty
       ? libDir.listSync().whereType<Directory>().toList()
-      : args.map((arg) => Directory(arg)).toList();
+      : args.map(parseArg).toList();
   coreLibraries.sort((a, b) => a.path.compareTo(b.path));
 
   // Skip some dart: libraries.
@@ -172,9 +174,9 @@
     var offset = text.indexOf(sampleStart);
     while (offset != -1) {
       // Collect template directives, like "```dart import:async".
-      final codeFenceSuffix = text.substring(
-          offset + sampleStart.length, text.indexOf('\n', offset));
-      final directives = Set.unmodifiable(codeFenceSuffix.trim().split(' '));
+      final codeFenceSuffix = text
+          .substring(offset + sampleStart.length, text.indexOf('\n', offset))
+          .trim();
 
       offset = text.indexOf('\n', offset) + 1;
       final end = text.indexOf(sampleEnd, offset);
@@ -183,73 +185,128 @@
       snippet = snippet.substring(0, snippet.lastIndexOf('\n'));
 
       List<String> lines = snippet.split('\n');
-
-      samples.add(
-        CodeSample(
-          lines.map((e) => '  ${cleanDocLine(e)}').join('\n'),
-          coreLibName: coreLibName,
-          directives: directives,
-          lineStartOffset: commentLineStart +
-              text.substring(0, offset - 1).split('\n').length -
-              1,
-        ),
-      );
+      var startLineNumber = commentLineStart +
+          text.substring(0, offset - 1).split('\n').length -
+          1;
+      if (codeFenceSuffix == "continued") {
+        if (samples.isEmpty) {
+          throw "Continued code block without previous code";
+        }
+        samples.last = samples.last.append(lines, startLineNumber);
+      } else {
+        final directives = Set.unmodifiable(codeFenceSuffix.split(' '));
+        samples.add(
+          CodeSample(
+            [for (var e in lines) '  ${cleanDocLine(e)}'],
+            coreLibName: coreLibName,
+            directives: directives,
+            lineStartOffset: startLineNumber,
+          ),
+        );
+      }
 
       offset = text.indexOf(sampleStart, offset);
     }
   }
 
-  Future validateCodeSample(CodeSample sample) async {
-    var text = sample.text;
-    final lines = sample.text.split('\n').map((l) => l.trim()).toList();
+  // RegExp detecting various top-level declarations or `main(`.
+  //
+  // If the top-level declaration is `library` or `import`,
+  // then match 1 (`libdecl`) will be non-null.
+  // This is a sign that no auto-imports should be added.
+  //
+  // If an import declaration is included in the sample, no
+  // assumed-declarations are added.
+  // Use the `import:foo` template to import other `dart:` libraries
+  // instead of writing them explicitly to.
+  //
+  // Captures:
+  // 1/libdecl: Non-null if mathcing a `library` declaration.
+  // 2: Internal use, quote around import URI.
+  // 3/importuri: Import URI.
+  final _toplevelDeclarationRE = RegExp(r'^\s*(?:'
+      r'library\b(?<libdecl>)|'
+      r'''import (['"])(?<importuri>.*?)\2|'''
+      r'class\b|mixin\b|enum\b|extension\b|typedef\b|.*\bmain\('
+      r')');
 
-    final hasImports = text.contains("import '") || text.contains('import "');
+  validateCodeSample(CodeSample sample) async {
+    final lines = sample.lines;
 
-    // One of 'none', 'main', or 'expression'.
-    String? template;
+    // The default imports includes the library itself
+    // and any import directives.
+    Set<String> autoImports = sample.imports;
 
-    if (sample.hasTemplateDirective) {
-      template = sample.templateDirective;
+    // One of 'none', 'top, 'main', or 'expression'.
+    String template;
+
+    bool hasImport = false;
+
+    final templateDirective = sample.templateDirective;
+    if (templateDirective != null) {
+      template = templateDirective;
     } else {
-      // If there's no explicit template, auto-detect one.
-      if (lines.any((line) =>
-          line.startsWith('class ') ||
-          line.startsWith('enum ') ||
-          line.startsWith('extension '))) {
+      // Scan lines for top-level declarations.
+      bool hasTopDeclaration = false;
+      bool hasLibraryDeclaration = false;
+      for (var line in lines) {
+        var topDeclaration = _toplevelDeclarationRE.firstMatch(line);
+        if (topDeclaration != null) {
+          hasTopDeclaration = true;
+          hasLibraryDeclaration |=
+              (topDeclaration.namedGroup("libdecl") != null);
+          var importDecl = topDeclaration.namedGroup("importuri");
+          if (importDecl != null) {
+            hasImport = true;
+            if (importDecl.startsWith('dart:')) {
+              // Remove explicit imports from automatic imports
+              // to avoid duplicate import warnings.
+              autoImports.remove(importDecl.substring('dart:'.length));
+            }
+          }
+        }
+      }
+      if (hasLibraryDeclaration) {
         template = 'none';
-      } else if (lines
-          .any((line) => line.startsWith('main(') || line.contains(' main('))) {
-        template = 'none';
+      } else if (hasTopDeclaration) {
+        template = 'top';
       } else if (lines.length == 1 && !lines.first.trim().endsWith(';')) {
+        // If single line with no trailing `;`, assume expression.
         template = 'expression';
       } else {
+        // Otherwise default to `main`.
         template = 'main';
       }
     }
 
-    final assumptions = sampleAssumptions ?? '';
+    var buffer = StringBuffer();
 
-    if (!hasImports) {
-      if (template == 'none') {
-        // just use the sample text as is
-      } else if (template == 'main') {
-        text = "${assumptions}main() async {\n${text.trimRight()}\n}\n";
-      } else if (template == 'expression') {
-        text = "${assumptions}main() async {\n${text.trimRight()}\n;\n}\n";
-      } else {
-        throw 'unexpected template directive: $template';
+    if (template != 'none') {
+      for (var library in autoImports) {
+        buffer.writeln("import 'dart:$library';");
       }
-
-      for (final directive
-          in sample.directives.where((str) => str.startsWith('import:'))) {
-        final libName = directive.substring('import:'.length);
-        text = "import 'dart:$libName';\n$text";
-      }
-
-      if (sample.coreLibName != 'internal') {
-        text = "import 'dart:${sample.coreLibName}';\n$text";
+      if (!hasImport) {
+        buffer.write(sampleAssumptions ?? '');
       }
     }
+    if (template == 'none' || template == 'top') {
+      buffer.writeAllLines(lines);
+    } else if (template == 'main') {
+      buffer
+        ..writeln('void main() async {')
+        ..writeAllLines(lines)
+        ..writeln('}');
+    } else if (template == 'expression') {
+      assert(lines.length >= 1);
+      buffer
+        ..writeln('void main() async =>')
+        ..writeAllLines(lines.take(lines.length - 1))
+        ..writeln("${lines.last.trimRight()};");
+    } else {
+      throw 'unexpected template directive: $template';
+    }
+
+    final text = buffer.toString();
 
     final result = await analysisHelper.resolveFile(text);
 
@@ -311,8 +368,7 @@
         print('');
 
         // Print out the code sample.
-        print(sample.text
-            .split('\n')
+        print(sample.lines
             .map((line) =>
                 '  >${line.length >= 5 ? line.substring(5) : line.trimLeft()}')
             .join('\n'));
@@ -337,27 +393,63 @@
 }
 
 class CodeSample {
+  /// Currently valid template names.
+  static const validTemplates = ['none', 'top', 'main', 'expression'];
+
   final String coreLibName;
   final Set<String> directives;
-  final String text;
+  final List<String> lines;
   final int lineStartOffset;
 
   CodeSample(
-    this.text, {
+    this.lines, {
     required this.coreLibName,
     this.directives = const {},
     required this.lineStartOffset,
   });
 
+  String get text => lines.join('\n');
+
   bool get hasTemplateDirective => templateDirective != null;
 
+  /// The specified template, or `null` if no template is specified.
+  ///
+  /// A specified template must be of [validTemplates].
   String? get templateDirective {
     const prefix = 'template:';
 
-    String? match = directives.cast<String?>().firstWhere(
-        (directive) => directive!.startsWith(prefix),
-        orElse: () => null);
-    return match == null ? match : match.substring(prefix.length);
+    for (var directive in directives) {
+      if (directive.startsWith(prefix)) {
+        var result = directive.substring(prefix.length);
+        if (!validTemplates.contains(result)) {
+          throw "Invalid template name: $result";
+        }
+        return result;
+      }
+    }
+    return null;
+  }
+
+  /// The implicit or explicitly requested imports.
+  Set<String> get imports => {
+        if (coreLibName != 'internal' && coreLibName != 'core') coreLibName,
+        for (var directive in directives)
+          if (directive.startsWith('import:'))
+            directive.substring('import:'.length)
+      };
+
+  /// Creates a new code sample by appending [lines] to this sample.
+  ///
+  /// The new sample only differs from this sample in that it has
+  /// more lines appended, first `this.lines`, then a gap of `  //` lines
+  /// and then [lines].
+  CodeSample append(List<String> lines, int lineStartOffset) {
+    var gapSize = lineStartOffset - (this.lineStartOffset + this.lines.length);
+    return CodeSample(
+        [...this.lines, for (var i = 0; i < gapSize; i++) "  //", ...lines],
+        coreLibName: coreLibName,
+        directives: directives,
+        lineStartOffset: this.lineStartOffset);
   }
 }
 
@@ -422,3 +514,35 @@
     return await analysisSession.getResolvedUnit(samplePath);
   }
 }
+
+// Helper function to make things easier to read.
+extension on StringBuffer {
+  /// Write every line, right-trimmed, of [lines] with a newline after.
+  void writeAllLines(Iterable<String> lines) {
+    for (var line in lines) {
+      this.writeln(line.trimRight());
+    }
+  }
+}
+
+/// Interprets [arg] as directory containing a platform library.
+///
+/// If [arg] is `dart:foo`, the directory is the default directory for
+/// the `dart:foo` library source.
+/// Otherwise, if [arg] is a directory (relative to the current directory)
+/// which exists, that is the result.
+/// Otherwise, if [arg] is the name of a platform library,
+/// like `foo` where `dart:foo` is a platform library,
+/// the result is the default directory for that library's source.
+/// Otherwise it's treated as a directory relative to the current directory,
+/// which doesn't exist (but that's what the error will refer to).
+Directory parseArg(String arg) {
+  if (arg.startsWith('dart:')) {
+    return Directory(path.join(libDir.path, arg.substring('dart:'.length)));
+  }
+  var dir = Directory(arg);
+  if (dir.existsSync()) return dir;
+  var relDir = Directory(path.join(libDir.path, arg));
+  if (relDir.existsSync()) return relDir;
+  return dir;
+}