Version 2.15.0-244.0.dev

Merge commit 'd9edb9bc297469bb87d4dcb8196c3a1c49aeb824' into 'dev'
diff --git a/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart b/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
new file mode 100644
index 0000000..9becaf1
--- /dev/null
+++ b/pkg/analyzer/lib/src/clients/build_resolvers/build_resolvers.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2021, 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 'dart:typed_data';
+
+import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/session.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/packages.dart';
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/dart/analysis/performance_logger.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/summary/package_bundle_reader.dart';
+import 'package:analyzer/src/summary/summary_sdk.dart';
+import 'package:analyzer/src/summary2/package_bundle_format.dart';
+
+export 'package:analyzer/src/context/packages.dart' show Packages, Package;
+export 'package:analyzer/src/dart/analysis/experiments.dart'
+    show ExperimentStatus;
+export 'package:analyzer/src/generated/engine.dart'
+    show AnalysisOptions, AnalysisOptionsImpl;
+export 'package:analyzer/src/generated/source.dart' show Source, UriResolver;
+
+/// A somewhat low level API to create [AnalysisSession].
+///
+/// Ideally we want clients to use [AnalysisContextCollection], which
+/// encapsulates any internals and is driven by `package_config.json` and
+/// `analysis_options.yaml` files. But so far it looks that `build_resolvers`
+/// wants to provide [UriResolver], and push [Packages] created by other means
+/// than parsing `package_config.json`.
+AnalysisDriverForPackageBuild createAnalysisDriver({
+  required ResourceProvider resourceProvider,
+  required Uint8List sdkSummaryBytes,
+  required AnalysisOptions analysisOptions,
+  required List<UriResolver> uriResolvers,
+  required Packages packages,
+}) {
+  var sdkBundle = PackageBundleReader(sdkSummaryBytes);
+  var sdk = SummaryBasedDartSdk.forBundle(sdkBundle);
+
+  var sourceFactory = SourceFactory([
+    DartUriResolver(sdk),
+    ...uriResolvers,
+  ]);
+
+  var dataStore = SummaryDataStore([]);
+  dataStore.addBundle('', sdkBundle);
+
+  var logger = PerformanceLog(null);
+  var scheduler = AnalysisDriverScheduler(logger);
+  var driver = AnalysisDriver.tmp1(
+    scheduler: scheduler,
+    logger: logger,
+    resourceProvider: resourceProvider,
+    byteStore: MemoryByteStore(),
+    sourceFactory: sourceFactory,
+    analysisOptions: analysisOptions as AnalysisOptionsImpl,
+    externalSummaries: dataStore,
+    packages: packages,
+  );
+
+  scheduler.start();
+
+  return AnalysisDriverForPackageBuild._(driver);
+}
+
+/// [AnalysisSession] plus a tiny bit more.
+class AnalysisDriverForPackageBuild {
+  final AnalysisDriver _driver;
+
+  AnalysisDriverForPackageBuild._(this._driver);
+
+  AnalysisSession get currentSession {
+    return _driver.currentSession;
+  }
+
+  /// The file with the given [path] might have changed - updated, added or
+  /// removed. Or not, we don't know. Or it might have, but then changed back.
+  ///
+  /// The [path] must be absolute and normalized.
+  ///
+  /// The [currentSession] most probably will be invalidated.
+  /// Note, is does NOT at the time of writing this comment.
+  /// But we are going to fix this.
+  void changeFile(String path) {
+    _driver.changeFile(path);
+  }
+
+  /// Return `true` if the [uri] can be resolved to an existing file.
+  bool isUriOfExistingFile(Uri uri) {
+    var source = _driver.sourceFactory.forUri2(uri);
+    return source != null && source.exists();
+  }
+}
diff --git a/pkg/analyzer/lib/src/dart/analysis/context_builder.dart b/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
index 3e2f98b..d8131da 100644
--- a/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/context_builder.dart
@@ -25,6 +25,7 @@
 import 'package:analyzer/src/hint/sdk_constraint_extractor.dart';
 import 'package:analyzer/src/summary/package_bundle_reader.dart';
 import 'package:analyzer/src/summary/summary_sdk.dart';
+import 'package:analyzer/src/summary2/package_bundle_format.dart';
 import 'package:analyzer/src/task/options.dart';
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:cli_util/cli_util.dart';
@@ -150,8 +151,11 @@
     String? sdkSummaryPath,
   }) {
     if (sdkSummaryPath != null) {
-      return SummaryBasedDartSdk(sdkSummaryPath, true,
-          resourceProvider: resourceProvider);
+      var file = resourceProvider.getFile(sdkSummaryPath);
+      var bytes = file.readAsBytesSync();
+      return SummaryBasedDartSdk.forBundle(
+        PackageBundleReader(bytes),
+      );
     }
 
     var folderSdk = FolderBasedDartSdk(
diff --git a/pkg/analyzer/lib/src/summary/package_bundle_reader.dart b/pkg/analyzer/lib/src/summary/package_bundle_reader.dart
index 6b789a8..03cce04 100644
--- a/pkg/analyzer/lib/src/summary/package_bundle_reader.dart
+++ b/pkg/analyzer/lib/src/summary/package_bundle_reader.dart
@@ -88,6 +88,7 @@
 /// The [UriResolver] that knows about sources that are served from their
 /// summaries.
 class InSummaryUriResolver extends UriResolver {
+  /// TODO(scheglov) Remove it, we don't need it.
   ResourceProvider? resourceProvider;
   final SummaryDataStore _dataStore;
 
diff --git a/pkg/analyzer/lib/src/summary/summary_sdk.dart b/pkg/analyzer/lib/src/summary/summary_sdk.dart
index 5ad073a..1ad7efe 100644
--- a/pkg/analyzer/lib/src/summary/summary_sdk.dart
+++ b/pkg/analyzer/lib/src/summary/summary_sdk.dart
@@ -14,11 +14,14 @@
 /// suitable only for command-line tools, but not for IDEs - it does not
 /// implement [sdkLibraries], [uris] and [fromFileUri].
 class SummaryBasedDartSdk implements DartSdk {
+  late final PackageBundleReader _bundle;
   late final SummaryDataStore _dataStore;
   late final InSummaryUriResolver _uriResolver;
-  late final PackageBundleReader _bundle;
+
+  /// TODO(scheglov) Remove it when the default constructor.
   ResourceProvider? resourceProvider;
 
+  @Deprecated('Use SummaryBasedDartSdk.forBundle() instead')
   SummaryBasedDartSdk(String summaryPath, bool _, {this.resourceProvider}) {
     _dataStore = SummaryDataStore(<String>[summaryPath],
         resourceProvider: resourceProvider);
@@ -26,6 +29,16 @@
     _bundle = _dataStore.bundles.single;
   }
 
+  SummaryBasedDartSdk.forBundle(PackageBundleReader bundle) {
+    _bundle = bundle;
+
+    _dataStore = SummaryDataStore([]);
+    // TODO(scheglov) We need a solution to avoid these paths at all.
+    _dataStore.addBundle('', bundle);
+
+    _uriResolver = InSummaryUriResolver(resourceProvider, _dataStore);
+  }
+
   @override
   String get allowedExperimentsJson {
     return _bundle.sdk!.allowedExperimentsJson;
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index 374580cfb..994bd26 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -318,12 +318,19 @@
 }
 
 abstract class double extends num {
+  // TODO: remove these old constants when no tests rely on them.
   static const double NAN = 0.0 / 0.0;
   static const double INFINITY = 1.0 / 0.0;
   static const double NEGATIVE_INFINITY = -INFINITY;
   static const double MIN_POSITIVE = 5e-324;
   static const double MAX_FINITE = 1.7976931348623157e+308;
 
+  static const double nan = 0.0 / 0.0;
+  static const double infinity = 1.0 / 0.0;
+  static const double negativeInfinity = -infinity;
+  static const double minPositive = 5e-324;
+  static const double maxFinite = 1.7976931348623157e+308;
+
   double get sign;
   double operator %(num other);
   double operator *(num other);
diff --git a/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart b/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
index 459974c..777ccda 100644
--- a/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
+++ b/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
@@ -4,7 +4,6 @@
 
 import 'dart:collection';
 import 'dart:convert' hide JsonDecoder;
-import 'dart:math' as math;
 
 import 'package:analyzer_plugin/protocol/protocol.dart';
 import 'package:analyzer_plugin/protocol/protocol_common.dart';
@@ -29,17 +28,6 @@
 /// If the invariants can't be preserved, then a [ConflictingEditException] is
 /// thrown.
 void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) {
-  /// If the [leftEdit] and the [rightEdit] can be merged, then merge them.
-  SourceEdit? _merge(SourceEdit leftEdit, SourceEdit rightEdit) {
-    assert(leftEdit.offset <= rightEdit.offset);
-    if (leftEdit.isDeletion && rightEdit.isDeletion) {
-      var offset = leftEdit.offset;
-      var end = math.max(leftEdit.end, rightEdit.end);
-      return SourceEdit(offset, end - offset, '');
-    }
-    return null;
-  }
-
   var edits = sourceFileEdit.edits;
   var length = edits.length;
   var index = 0;
@@ -51,12 +39,7 @@
     // The [previousEdit] has an offset that is strictly greater than the offset
     // of the [sourceEdit] so we only need to look at the end of the
     // [sourceEdit] to know whether they overlap.
-    if (sourceEdit.end > previousEdit.offset) {
-      var mergedEdit = _merge(sourceEdit, previousEdit);
-      if (mergedEdit != null) {
-        edits[index - 1] = mergedEdit;
-        return;
-      }
+    if (sourceEdit.offset + sourceEdit.length > previousEdit.offset) {
       throw ConflictingEditException(
           newEdit: sourceEdit, existingEdit: previousEdit);
     }
@@ -71,12 +54,7 @@
     if ((sourceEdit.offset == nextEdit.offset &&
             sourceEdit.length > 0 &&
             nextEdit.length > 0) ||
-        nextEdit.end > sourceEdit.offset) {
-      var mergedEdit = _merge(nextEdit, sourceEdit);
-      if (mergedEdit != null) {
-        edits[index] = mergedEdit;
-        return;
-      }
+        nextEdit.offset + nextEdit.length > sourceEdit.offset) {
       throw ConflictingEditException(
           newEdit: sourceEdit, existingEdit: nextEdit);
     }
@@ -490,11 +468,3 @@
   /// the given [id], where the request was received at the given [requestTime].
   Response toResponse(String id, int requestTime);
 }
-
-extension SourceEditExtensions on SourceEdit {
-  /// Return `true` if this source edit represents a deletion.
-  bool get isDeletion => replacement.isEmpty;
-
-  /// Return `true` if this source edit represents an insertion.
-  bool get isInsertion => length == 0;
-}
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
index 6ed22f9..a571f04 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
@@ -17,7 +17,6 @@
   defineReflectiveSuite(() {
     defineReflectiveTests(ChangeBuilderImplTest);
     defineReflectiveTests(EditBuilderImplTest);
-    defineReflectiveTests(FileEditBuilderImpl_ConflictingTest);
     defineReflectiveTests(FileEditBuilderImplTest);
     defineReflectiveTests(LinkedEditBuilderImplTest);
   });
@@ -298,33 +297,27 @@
   }
 }
 
-/// Tests that are specifically targeted at the handling of conflicting edits.
 @reflectiveTest
-class FileEditBuilderImpl_ConflictingTest extends AbstractChangeBuilderTest {
+class FileEditBuilderImplTest extends AbstractChangeBuilderTest {
   String path = '/test.dart';
 
-  Matcher get hasConflict => throwsA(isA<ConflictingEditException>());
-
-  Future<void> test_deletion_deletion_adjacent_left() async {
-    var firstOffset = 30;
-    var firstLength = 5;
-    var secondOffset = 23;
-    var secondLength = 7;
+  Future<void> test_addDeletion() async {
+    var offset = 23;
+    var length = 7;
     await builder.addGenericFileEdit(path, (builder) {
-      builder.addDeletion(SourceRange(firstOffset, firstLength));
-      builder.addDeletion(SourceRange(secondOffset, secondLength));
+      builder.addDeletion(SourceRange(offset, length));
     });
     var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(2));
-    expect(edits[0].offset, firstOffset);
-    expect(edits[0].length, firstLength);
+    expect(edits, hasLength(1));
+    expect(edits[0].offset, offset);
+    expect(edits[0].length, length);
     expect(edits[0].replacement, isEmpty);
-    expect(edits[1].offset, secondOffset);
-    expect(edits[1].length, secondLength);
-    expect(edits[1].replacement, isEmpty);
   }
 
-  Future<void> test_deletion_deletion_adjacent_right() async {
+  Future<void> test_addDeletion_adjacent_lowerOffsetFirst() async {
+    // TODO(brianwilkerson) This should also merge the deletions, but is written
+    //  to ensure that existing uses of FileEditBuilder continue to work even
+    //  without that change.
     var firstOffset = 23;
     var firstLength = 7;
     var secondOffset = 30;
@@ -343,23 +336,31 @@
     expect(edits[1].replacement, isEmpty);
   }
 
-  Future<void> test_deletion_deletion_overlap_left() async {
-    var firstOffset = 27;
-    var firstLength = 8;
-    var secondOffset = 23;
-    var secondLength = 7;
+  Future<void> test_addDeletion_adjacent_lowerOffsetSecond() async {
+    // TODO(brianwilkerson) This should also merge the deletions, but is written
+    //  to ensure that existing uses of FileEditBuilder continue to work even
+    //  without that change.
+    var firstOffset = 23;
+    var firstLength = 7;
+    var secondOffset = 30;
+    var secondLength = 5;
     await builder.addGenericFileEdit(path, (builder) {
-      builder.addDeletion(SourceRange(firstOffset, firstLength));
       builder.addDeletion(SourceRange(secondOffset, secondLength));
+      builder.addDeletion(SourceRange(firstOffset, firstLength));
     });
     var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(1));
+    expect(edits, hasLength(2));
     expect(edits[0].offset, secondOffset);
-    expect(edits[0].length, firstOffset + firstLength - secondOffset);
+    expect(edits[0].length, secondLength);
     expect(edits[0].replacement, isEmpty);
+    expect(edits[1].offset, firstOffset);
+    expect(edits[1].length, firstLength);
+    expect(edits[1].replacement, isEmpty);
   }
 
-  Future<void> test_deletion_deletion_overlap_right() async {
+  @failingTest
+  Future<void> test_addDeletion_overlapping() async {
+    // This support is not yet implemented.
     var firstOffset = 23;
     var firstLength = 7;
     var secondOffset = 27;
@@ -375,169 +376,6 @@
     expect(edits[0].replacement, isEmpty);
   }
 
-  Future<void> test_deletion_insertion_adjacent_left() async {
-    var deletionOffset = 23;
-    var deletionLength = 7;
-    var insertionOffset = 23;
-    var insertionText = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addDeletion(SourceRange(deletionOffset, deletionLength));
-      expect(() {
-        builder.addSimpleInsertion(insertionOffset, insertionText);
-      }, hasConflict);
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(1));
-    expect(edits[0].offset, deletionOffset);
-    expect(edits[0].length, deletionLength);
-    expect(edits[0].replacement, '');
-  }
-
-  Future<void> test_deletion_insertion_adjacent_right() async {
-    var deletionOffset = 23;
-    var deletionLength = 7;
-    var insertionOffset = 30;
-    var insertionText = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addDeletion(SourceRange(deletionOffset, deletionLength));
-      builder.addSimpleInsertion(insertionOffset, insertionText);
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(2));
-    expect(edits[0].offset, insertionOffset);
-    expect(edits[0].length, 0);
-    expect(edits[0].replacement, insertionText);
-    expect(edits[1].offset, deletionOffset);
-    expect(edits[1].length, deletionLength);
-    expect(edits[1].replacement, isEmpty);
-  }
-
-  Future<void> test_deletion_insertion_overlap() async {
-    var deletionOffset = 23;
-    var deletionLength = 7;
-    var insertionOffset = 26;
-    var insertionText = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addDeletion(SourceRange(deletionOffset, deletionLength));
-      expect(() {
-        builder.addSimpleInsertion(insertionOffset, insertionText);
-      }, hasConflict);
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(1));
-    expect(edits[0].offset, deletionOffset);
-    expect(edits[0].length, deletionLength);
-    expect(edits[0].replacement, '');
-  }
-
-  Future<void> test_insertion_deletion_adjacent_left() async {
-    var deletionOffset = 23;
-    var deletionLength = 7;
-    var insertionOffset = 23;
-    var insertionText = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addSimpleInsertion(insertionOffset, insertionText);
-      builder.addDeletion(SourceRange(deletionOffset, deletionLength));
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(2));
-    expect(edits[0].offset, deletionOffset);
-    expect(edits[0].length, deletionLength);
-    expect(edits[0].replacement, isEmpty);
-    expect(edits[1].offset, insertionOffset);
-    expect(edits[1].length, 0);
-    expect(edits[1].replacement, insertionText);
-  }
-
-  Future<void> test_insertion_deletion_adjacent_right() async {
-    var deletionOffset = 23;
-    var deletionLength = 7;
-    var insertionOffset = 30;
-    var insertionText = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addSimpleInsertion(insertionOffset, insertionText);
-      builder.addDeletion(SourceRange(deletionOffset, deletionLength));
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(2));
-    expect(edits[0].offset, insertionOffset);
-    expect(edits[0].length, 0);
-    expect(edits[0].replacement, insertionText);
-    expect(edits[1].offset, deletionOffset);
-    expect(edits[1].length, deletionLength);
-    expect(edits[1].replacement, isEmpty);
-  }
-
-  Future<void> test_insertion_deletion_overlap() async {
-    var deletionOffset = 23;
-    var deletionLength = 7;
-    var insertionOffset = 26;
-    var insertionText = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addSimpleInsertion(insertionOffset, insertionText);
-      expect(() {
-        builder.addDeletion(SourceRange(deletionOffset, deletionLength));
-      }, hasConflict);
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(1));
-    expect(edits[0].offset, insertionOffset);
-    expect(edits[0].length, 0);
-    expect(edits[0].replacement, insertionText);
-  }
-
-  Future<void> test_replacement_replacement_overlap_left() async {
-    var offset = 23;
-    var length = 7;
-    var text = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addSimpleReplacement(SourceRange(offset, length), text);
-      expect(() {
-        builder.addSimpleReplacement(SourceRange(offset - 2, length), text);
-      }, hasConflict);
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(1));
-    expect(edits[0].offset, offset);
-    expect(edits[0].length, length);
-    expect(edits[0].replacement, text);
-  }
-
-  Future<void> test_replacement_replacement_overlap_right() async {
-    var offset = 23;
-    var length = 7;
-    var text = 'x';
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addSimpleReplacement(SourceRange(offset, length), text);
-      expect(() {
-        builder.addSimpleReplacement(SourceRange(offset + 2, length), text);
-      }, hasConflict);
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(1));
-    expect(edits[0].offset, offset);
-    expect(edits[0].length, length);
-    expect(edits[0].replacement, text);
-  }
-}
-
-@reflectiveTest
-class FileEditBuilderImplTest extends AbstractChangeBuilderTest {
-  String path = '/test.dart';
-
-  Future<void> test_addDeletion() async {
-    var offset = 23;
-    var length = 7;
-    await builder.addGenericFileEdit(path, (builder) {
-      builder.addDeletion(SourceRange(offset, length));
-    });
-    var edits = builder.sourceChange.edits[0].edits;
-    expect(edits, hasLength(1));
-    expect(edits[0].offset, offset);
-    expect(edits[0].length, length);
-    expect(edits[0].replacement, isEmpty);
-  }
-
   Future<void> test_addInsertion() async {
     await builder.addGenericFileEdit(path, (builder) {
       builder.addInsertion(10, (builder) {
@@ -634,6 +472,40 @@
     expect(edits[1].replacement, text);
   }
 
+  Future<void> test_addSimpleReplacement_overlapsHead() async {
+    var offset = 23;
+    var length = 7;
+    var text = 'xyz';
+    await builder.addGenericFileEdit(path, (builder) {
+      builder.addSimpleReplacement(SourceRange(offset, length), text);
+      expect(() {
+        builder.addSimpleReplacement(SourceRange(offset - 2, length), text);
+      }, throwsA(isA<ConflictingEditException>()));
+    });
+    var edits = builder.sourceChange.edits[0].edits;
+    expect(edits, hasLength(1));
+    expect(edits[0].offset, offset);
+    expect(edits[0].length, length);
+    expect(edits[0].replacement, text);
+  }
+
+  Future<void> test_addSimpleReplacement_overlapsTail() async {
+    var offset = 23;
+    var length = 7;
+    var text = 'xyz';
+    await builder.addGenericFileEdit(path, (builder) {
+      builder.addSimpleReplacement(SourceRange(offset, length), text);
+      expect(() {
+        builder.addSimpleReplacement(SourceRange(offset + 2, length), text);
+      }, throwsA(isA<ConflictingEditException>()));
+    });
+    var edits = builder.sourceChange.edits[0].edits;
+    expect(edits, hasLength(1));
+    expect(edits[0].offset, offset);
+    expect(edits[0].length, length);
+    expect(edits[0].replacement, text);
+  }
+
   Future<void> test_createEditBuilder() async {
     await builder.addGenericFileEdit(path, (builder) {
       var offset = 4;
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index 73cf9c2..c0082b3 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -116,9 +116,6 @@
     addCommand(CompileCommand(verbose: verbose));
     addCommand(DevToolsCommand(
       verbose: verbose,
-      // TODO(devoncarew): Un-hide this command after a stabilization period
-      // likely before the next stable release (before Dart 2.15).
-      hidden: !verbose,
       customDevToolsPath: sdk.devToolsBinaries,
     ));
     addCommand(FixCommand(verbose: verbose));
diff --git a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
index 9b74eca..526c449 100644
--- a/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
+++ b/runtime/tests/vm/dart/v8_snapshot_profile_writer_test.dart
@@ -59,8 +59,9 @@
   return profile;
 }
 
-Future<void> testJIT(String dillPath) async {
-  final description = 'jit';
+Future<void> testJIT(String dillPath, String snapshotKind) async {
+  final includesCode = snapshotKind == 'core-jit';
+  final description = snapshotKind;
   Expect.isTrue(_seenDescriptions.add(description),
       "test configuration $description would be run multiple times");
 
@@ -73,9 +74,11 @@
     final isolateDataPath = path.join(tempDir, 'isolate_data.bin');
 
     await run(genSnapshot, <String>[
-      '--snapshot-kind=core-jit',
-      '--vm_snapshot_instructions=$vmTextPath',
-      '--isolate_snapshot_instructions=$isolateTextPath',
+      '--snapshot-kind=$snapshotKind',
+      if (includesCode) ...<String>[
+        '--vm_snapshot_instructions=$vmTextPath',
+        '--isolate_snapshot_instructions=$isolateTextPath',
+      ],
       '--vm_snapshot_data=$vmDataPath',
       '--isolate_snapshot_data=$isolateDataPath',
       "--write-v8-snapshot-profile-to=$profilePath",
@@ -89,10 +92,12 @@
     // Verify that the total size of the snapshot text and data sections is
     // the same as the sum of the shallow sizes of all objects in the profile.
     // This ensures that all bytes are accounted for in some way.
-    final actualSize = await File(vmTextPath).length() +
-        await File(isolateTextPath).length() +
-        await File(vmDataPath).length() +
-        await File(isolateDataPath).length();
+    int actualSize =
+        await File(vmDataPath).length() + await File(isolateDataPath).length();
+    if (includesCode) {
+      actualSize += await File(vmTextPath).length() +
+          await File(isolateTextPath).length();
+    }
     final expectedSize =
         profile.nodes.fold<int>(0, (size, n) => size + n.selfSize);
 
@@ -426,8 +431,10 @@
     //   extra information that needs stripping), so no need to specify
     //   stripUtil for useAsm tests.
 
-    // Test profile generation with a core JIT snapshot.
-    await testJIT(jitDillPath);
+    // Test profile generation with a core snapshot (no code).
+    await testJIT(jitDillPath, 'core');
+    // Test profile generation with a core JIT snapshot (with code).
+    await testJIT(jitDillPath, 'core-jit');
 
     // Test unstripped ELF generation directly.
     await testAOT(aotDillPath);
diff --git a/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart b/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart
index 37057d8..a4d23af 100644
--- a/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart
+++ b/runtime/tests/vm/dart_2/v8_snapshot_profile_writer_test.dart
@@ -61,8 +61,9 @@
   return profile;
 }
 
-Future<void> testJIT(String dillPath) async {
-  final description = 'jit';
+Future<void> testJIT(String dillPath, String snapshotKind) async {
+  final includesCode = snapshotKind == 'core-jit';
+  final description = snapshotKind;
   Expect.isTrue(_seenDescriptions.add(description),
       "test configuration $description would be run multiple times");
 
@@ -75,9 +76,11 @@
     final isolateDataPath = path.join(tempDir, 'isolate_data.bin');
 
     await run(genSnapshot, <String>[
-      '--snapshot-kind=core-jit',
-      '--vm_snapshot_instructions=$vmTextPath',
-      '--isolate_snapshot_instructions=$isolateTextPath',
+      '--snapshot-kind=$snapshotKind',
+      if (includesCode) ...<String>[
+        '--vm_snapshot_instructions=$vmTextPath',
+        '--isolate_snapshot_instructions=$isolateTextPath',
+      ],
       '--vm_snapshot_data=$vmDataPath',
       '--isolate_snapshot_data=$isolateDataPath',
       "--write-v8-snapshot-profile-to=$profilePath",
@@ -91,10 +94,12 @@
     // Verify that the total size of the snapshot text and data sections is
     // the same as the sum of the shallow sizes of all objects in the profile.
     // This ensures that all bytes are accounted for in some way.
-    final actualSize = await File(vmTextPath).length() +
-        await File(isolateTextPath).length() +
-        await File(vmDataPath).length() +
-        await File(isolateDataPath).length();
+    int actualSize =
+        await File(vmDataPath).length() + await File(isolateDataPath).length();
+    if (includesCode) {
+      actualSize += await File(vmTextPath).length() +
+          await File(isolateTextPath).length();
+    }
     final expectedSize =
         profile.nodes.fold<int>(0, (size, n) => size + n.selfSize);
 
@@ -420,8 +425,10 @@
     //   extra information that needs stripping), so no need to specify
     //   stripUtil for useAsm tests.
 
-    // Test profile generation with a core JIT snapshot.
-    await testJIT(jitDillPath);
+    // Test profile generation with a core snapshot (no code).
+    await testJIT(jitDillPath, 'core');
+    // Test profile generation with a core JIT snapshot (with code).
+    await testJIT(jitDillPath, 'core-jit');
 
     // Test unstripped ELF generation directly.
     await testAOT(aotDillPath);
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index e25f0bc..8f2e5ab 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -5527,7 +5527,7 @@
 
     if (!Snapshot::IncludesCode(s->kind())) {
       for (intptr_t i = 0; i < StubCode::NumEntries(); i++) {
-        s->AddBaseObject(StubCode::EntryAt(i).ptr(), "Code", "<stub code>");
+        s->AddBaseObject(StubCode::EntryAt(i).ptr());
       }
     }
   }
diff --git a/runtime/vm/v8_snapshot_writer.cc b/runtime/vm/v8_snapshot_writer.cc
index d886122..1a224df 100644
--- a/runtime/vm/v8_snapshot_writer.cc
+++ b/runtime/vm/v8_snapshot_writer.cc
@@ -42,7 +42,7 @@
   const intptr_t type_index = node_types_.Add(type);
   if (info->type != kInvalidString && info->type != type_index) {
     FATAL("Attempting to assign mismatching type %s to node %s", type,
-          info->ToCString(nullptr, zone_));
+          info->ToCString(this, zone_));
   }
   info->type = type_index;
   // Don't overwrite any existing name.
diff --git a/tools/VERSION b/tools/VERSION
index f7fff19..f2b85ea 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 243
+PRERELEASE 244
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/utils/dartdevc/BUILD.gn b/utils/dartdevc/BUILD.gn
index 67e0411..da80743 100644
--- a/utils/dartdevc/BUILD.gn
+++ b/utils/dartdevc/BUILD.gn
@@ -302,7 +302,10 @@
   }
 
   prebuilt_dart_action(target_name) {
-    deps = [ platform_dep ]
+    deps = [
+      ":dartdevc_files_stamp",
+      platform_dep,
+    ]
 
     inputs = [ platform_input ]