Version 2.15.0-53.0.dev

Merge commit '2f66afbfb4244fd5cf690545bf46915103227c4f' into 'dev'
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 99c960c..d576487 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -2383,6 +2383,11 @@
   /// children of this element's parent.
   String get identifier => name!;
 
+  /// The informative data, or `null` if the element is synthetic, or if the
+  /// informative data is not available in this environment (e.g. semantics
+  /// only summaries in Bazel).
+  ElementInformativeDataSetImpl? get informative => null;
+
   bool get isNonFunctionTypeAliasesEnabled {
     return library!.featureSet.isEnabled(Feature.nonfunction_type_aliases);
   }
@@ -2599,6 +2604,51 @@
   FunctionType get typeInternal;
 }
 
+/// Informative data about an [ElementImpl].
+class ElementInformativeDataImpl {
+  /// The offset of the beginning of the element's code in the file.
+  final int codeOffset;
+
+  /// The length of the element's code in the file.
+  final int codeLength;
+
+  /// The documentation comment for this element.
+  final String? docComment;
+
+  /// The offset of the name of this element in the file that contains the
+  /// declaration of this element.
+  final int nameOffset;
+
+  ElementInformativeDataImpl({
+    required this.codeOffset,
+    required this.codeLength,
+    required this.docComment,
+    required this.nameOffset,
+  });
+}
+
+/// The set of informative data about an [ElementImpl].
+class ElementInformativeDataSetImpl {
+  /// Informative data in the user-written file.
+  ///
+  /// This property is `null` if the element was macro-generated.
+  final ElementInformativeDataImpl? written;
+
+  /// Informative data in the combined file, which is the user-written file
+  /// augmented with macro-generated declarations.
+  ///
+  /// This property cannot be `null`, because each element is either declared
+  /// by the user directly (so has [written] which is then transformed), or
+  /// is macro-generated, or is synthetic (so we don't have this object
+  /// at all).
+  final ElementInformativeDataImpl combined;
+
+  ElementInformativeDataSetImpl({
+    required this.written,
+    required this.combined,
+  });
+}
+
 /// A concrete implementation of an [ElementLocation].
 class ElementLocationImpl implements ElementLocation {
   /// The character used to separate components in the encoded form.
diff --git a/pkg/analyzer/lib/src/generated/error_verifier.dart b/pkg/analyzer/lib/src/generated/error_verifier.dart
index 61462f2..80e0fb6 100644
--- a/pkg/analyzer/lib/src/generated/error_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/error_verifier.dart
@@ -1949,6 +1949,9 @@
     if (!_enclosingExecutable.isConstConstructor) {
       return;
     }
+    if (!_enclosingExecutable.isGenerativeConstructor) {
+      return;
+    }
     // check if there is non-final field
     ClassElement classElement = constructorElement.enclosingElement;
     if (!classElement.hasNonFinalField) {
diff --git a/pkg/analyzer/test/src/diagnostics/const_constructor_with_non_final_field_test.dart b/pkg/analyzer/test/src/diagnostics/const_constructor_with_non_final_field_test.dart
index 0b5cf6e..36adecf 100644
--- a/pkg/analyzer/test/src/diagnostics/const_constructor_with_non_final_field_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/const_constructor_with_non_final_field_test.dart
@@ -15,7 +15,37 @@
 
 @reflectiveTest
 class ConstConstructorWithNonFinalFieldTest extends PubPackageResolutionTest {
-  test_this_named() async {
+  test_constFactoryNamed_hasNonFinal_redirect() async {
+    await assertNoErrorsInCode(r'''
+class A {
+  int x = 0;
+  const factory A.a() = B;
+}
+
+class B implements A {
+  const B();
+  int get x => 0;
+  void set x(_) {}
+}
+''');
+  }
+
+  test_constFactoryUnnamed_hasNonFinal_redirect() async {
+    await assertNoErrorsInCode(r'''
+class A {
+  int x = 0;
+  const factory A() = B;
+}
+
+class B implements A {
+  const B();
+  int get x => 0;
+  void set x(_) {}
+}
+''');
+  }
+
+  test_constGenerativeNamed_hasNonFinal() async {
     await assertErrorsInCode(r'''
 class A {
   int x = 0;
@@ -26,7 +56,7 @@
     ]);
   }
 
-  test_this_unnamed() async {
+  test_constGenerativeUnnamed_hasNonFinal() async {
     await assertErrorsInCode(r'''
 class A {
   int x = 0;
diff --git a/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart b/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
index 4db68d9..d855849 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/completion/optype.dart
@@ -485,7 +485,7 @@
       final entity = this.entity;
       if (entity != null) {
         if (entity.offset <= declarationStart()) {
-          optype.completionLocation = 'CompilationUnit_declaration';
+          optype.completionLocation = 'CompilationUnit_directive';
         } else {
           optype.completionLocation = 'CompilationUnit_declaration';
         }
diff --git a/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart b/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart
index 79c9e0c..ba7d92f 100644
--- a/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/completion/optype_test.dart
@@ -1418,21 +1418,21 @@
     // SimpleIdentifier  FunctionDeclaration  CompilationUnit
     addTestSource('const ^Fara();');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_functionDeclaration_name2() async {
     // SimpleIdentifier  FunctionDeclaration  CompilationUnit
     addTestSource('const F^ara();');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_functionDeclaration_returnType() async {
     // CompilationUnit
     addTestSource('^ zoo(z) { } String name;');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_functionDeclaration_returnType_afterLineComment() async {
@@ -1441,7 +1441,7 @@
       // normal comment
       ^ zoo(z) {} String name;''');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_functionDeclaration_returnType_afterLineComment2() async {
@@ -1451,7 +1451,7 @@
 // normal comment
 ^ zoo(z) {} String name;''');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_functionDeclaration_returnType_afterLineDocComment() async {
@@ -1479,14 +1479,14 @@
     // CompilationUnit
     addTestSource('/* */ ^ zoo(z) { } String name;');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_functionDeclaration_returnType_afterStarComment2() async {
     // CompilationUnit
     addTestSource('/* */^ zoo(z) { } String name;');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_functionDeclaration_returnType_afterStarDocComment() async {
@@ -2539,7 +2539,7 @@
     // TopLevelVariableDeclaration
     addTestSource('^ foo;');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_topLevelVariableDeclaration_type_no_semicolon() async {
@@ -2547,7 +2547,7 @@
     // TopLevelVariableDeclaration
     addTestSource('^ foo');
     await assertOpType(
-        completionLocation: 'CompilationUnit_declaration', typeNames: true);
+        completionLocation: 'CompilationUnit_directive', typeNames: true);
   }
 
   Future<void> test_topLevelVariableDeclaration_typed_name() async {
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 287b61d..7813107 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -2,6 +2,9 @@
 - Fix another possibility of `LateInitializationError` being thrown when trying to
   cleanup after an error during initialization.
 
+# 2.1.0
+- Added getAvailableCachedCpuSamples and getCachedCpuSamples.
+
 # 2.0.2
 - Fix possibility of `LateInitializationError` being thrown when trying to
   cleanup after an error during initialization.
diff --git a/pkg/dds/dds_protocol.md b/pkg/dds/dds_protocol.md
index b690c1a..9224302 100644
--- a/pkg/dds/dds_protocol.md
+++ b/pkg/dds/dds_protocol.md
@@ -1,6 +1,6 @@
-# Dart Development Service Protocol 1.2
+# Dart Development Service Protocol 1.3
 
-This document describes _version 1.2_ of the Dart Development Service Protocol.
+This document describes _version 1.3_ of the Dart Development Service Protocol.
 This protocol is an extension of the Dart VM Service Protocol and implements it
 in it's entirety. For details on the VM Service Protocol, see the [Dart VM Service Protocol Specification][service-protocol].
 
@@ -67,6 +67,29 @@
 
 The DDS Protocol supports all [public RPCs defined in the VM Service protocol][service-protocol-public-rpcs].
 
+### getAvailableCachedCpuSamples
+
+```
+AvailableCachedCpuSamples getAvailableCachedCpuSamples();
+```
+
+The _getAvailableCachedCpuSamples_ RPC is used to determine which caches of CPU samples
+are available. Caches are associated with individual _UserTag_ names and are specified
+when DDS is started via the _cachedUserTags_ parameter.
+
+See [AvailableCachedCpuSamples](#availablecachedcpusamples).
+
+### getCachedCpuSamples
+
+```
+CachedCpuSamples getCachedCpuSamples(string isolateId, string userTag);
+```
+
+The _getCachedCpuSamples_ RPC is used to retrieve a cache of CPU samples collected
+under a _UserTag_ with name _userTag_.
+
+See [CachedCpuSamples](#cachedcpusamples).
+
 ### getClientName
 
 ```
@@ -181,6 +204,37 @@
 
 The DDS Protocol supports all [public types defined in the VM Service protocol][service-protocol-public-types].
 
+### AvailableCachedCpuSamples
+
+```
+class AvailableCachedCpuSamples extends Response {
+  // A list of UserTag names associated with CPU sample caches.
+  string[] cacheNames;
+}
+```
+
+A collection of [UserTag] names associated with caches of CPU samples.
+
+See [getAvailableCachedCpuSamples](#getavailablecachedcpusamples).
+
+### CachedCpuSamples
+
+```
+class CachedCpuSamples extends CpuSamples {
+  // The name of the UserTag associated with this cache of samples.
+  string userTag;
+
+  // Provided if the CPU sample cache has filled and older samples have been
+  // dropped.
+  bool truncated [optional];
+}
+```
+
+An extension of [CpuSamples](#cpu-samples) which represents a set of cached
+samples, associated with a particular [UserTag] name.
+
+See [getCachedCpuSamples](#getcachedcpusamples).
+
 ### ClientName
 
 ```
@@ -220,10 +274,12 @@
 1.0 | Initial revision
 1.1 | Added `getDartDevelopmentServiceVersion` RPC.
 1.2 | Added `getStreamHistory` RPC.
+1.3 | Added `getAvailableCachedCpuSamples` and `getCachedCpuSamples` RPCs.
 
 [resume]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#resume
 [success]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#success
 [version]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#version
+[cpu-samples]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#cpusamples
 
 [service-protocol]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md
 [service-protocol-rpcs-requests-and-responses]: https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#rpcs-requests-and-responses
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index e8f05bb..f31e447 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -42,6 +42,7 @@
     Uri? serviceUri,
     bool enableAuthCodes = true,
     bool ipv6 = false,
+    List<String> cachedUserTags = const [],
     DevToolsConfiguration? devToolsConfiguration,
     bool logRequests = false,
   }) async {
@@ -79,6 +80,7 @@
       remoteVmServiceUri,
       serviceUri,
       enableAuthCodes,
+      cachedUserTags,
       ipv6,
       devToolsConfiguration,
       logRequests,
@@ -136,9 +138,13 @@
   /// requests.
   bool get isRunning;
 
+  /// The list of [UserTag]s used to determine which CPU samples are cached by
+  /// DDS.
+  List<String> get cachedUserTags;
+
   /// The version of the DDS protocol supported by this [DartDevelopmentService]
   /// instance.
-  static const String protocolVersion = '1.2';
+  static const String protocolVersion = '1.3';
 }
 
 class DartDevelopmentServiceException implements Exception {
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index 1df3a3a..771ccee 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.dart
@@ -206,6 +206,19 @@
       return supportedProtocols;
     });
 
+    _clientPeer.registerMethod(
+      'getAvailableCachedCpuSamples',
+      (_) => {
+        'type': 'AvailableCachedCpuSamples',
+        'cacheNames': dds.cachedUserTags,
+      },
+    );
+
+    _clientPeer.registerMethod(
+      'getCachedCpuSamples',
+      dds.isolateManager.getCachedCpuSamples,
+    );
+
     // `evaluate` and `evaluateInFrame` actually consist of multiple RPC
     // invocations, including a call to `compileExpression` which can be
     // overridden by clients which provide their own implementation (e.g.,
diff --git a/pkg/dds/lib/src/common/ring_buffer.dart b/pkg/dds/lib/src/common/ring_buffer.dart
new file mode 100644
index 0000000..a2efa53
--- /dev/null
+++ b/pkg/dds/lib/src/common/ring_buffer.dart
@@ -0,0 +1,68 @@
+// 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:math';
+
+class RingBuffer<T> {
+  RingBuffer(this._bufferSize) {
+    _buffer = List<T?>.filled(
+      _bufferSize,
+      null,
+    );
+  }
+
+  Iterable<T> call() sync* {
+    for (int i = _size - 1; i >= 0; --i) {
+      yield _buffer[(_count - i - 1) % _bufferSize]!;
+    }
+  }
+
+  /// Inserts a new element into the [RingBuffer].
+  ///
+  /// Returns the element evicted as a result of adding the new element if the
+  /// buffer is as max capacity, null otherwise.
+  T? add(T e) {
+    if (_buffer.isEmpty) {
+      return null;
+    }
+    T? evicted;
+    final index = _count % _bufferSize;
+    if (index < _count) {
+      evicted = _buffer[index];
+    }
+    _buffer[index] = e;
+    _count++;
+    return evicted;
+  }
+
+  void resize(int size) {
+    assert(size >= 0);
+    if (size == _bufferSize) {
+      return;
+    }
+    final resized = List<T?>.filled(
+      size,
+      null,
+    );
+    int count = 0;
+    if (size > 0) {
+      for (final e in this()) {
+        resized[count++ % size] = e;
+      }
+    }
+    _count = count;
+    _bufferSize = size;
+    _buffer = resized;
+  }
+
+  bool get isTruncated => _count % bufferSize < _count;
+
+  int get bufferSize => _bufferSize;
+
+  int get _size => min(_count, _bufferSize);
+
+  int _bufferSize;
+  int _count = 0;
+  late List<T?> _buffer;
+}
diff --git a/pkg/dds/lib/src/cpu_samples_manager.dart b/pkg/dds/lib/src/cpu_samples_manager.dart
new file mode 100644
index 0000000..a0c413a
--- /dev/null
+++ b/pkg/dds/lib/src/cpu_samples_manager.dart
@@ -0,0 +1,201 @@
+// 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 'package:dds/src/common/ring_buffer.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'dds_impl.dart';
+
+/// Manages CPU sample caches for an individual [Isolate].
+class CpuSamplesManager {
+  CpuSamplesManager(this.dds, this.isolateId) {
+    for (final userTag in dds.cachedUserTags) {
+      cpuSamplesCaches[userTag] = CpuSamplesRepository(userTag);
+    }
+  }
+
+  void handleUserTagEvent(Event event) {
+    assert(event.kind! == EventKind.kUserTagChanged);
+    _currentTag = event.updatedTag!;
+    final previousTag = event.previousTag!;
+    if (cpuSamplesCaches.containsKey(previousTag)) {
+      _lastCachedTag = previousTag;
+    }
+  }
+
+  void handleCpuSamplesEvent(Event event) {
+    assert(event.kind! == EventKind.kCpuSamples);
+    // There might be some samples left in the buffer for the previously set
+    // user tag. We'll check for them here and then close out the cache.
+    if (_lastCachedTag != null) {
+      cpuSamplesCaches[_lastCachedTag]!.cacheSamples(
+        event.cpuSamples!,
+      );
+      _lastCachedTag = null;
+    }
+    cpuSamplesCaches[_currentTag]?.cacheSamples(event.cpuSamples!);
+  }
+
+  final DartDevelopmentServiceImpl dds;
+  final String isolateId;
+  final cpuSamplesCaches = <String, CpuSamplesRepository>{};
+
+  String _currentTag = '';
+  String? _lastCachedTag;
+}
+
+class CpuSamplesRepository extends RingBuffer<CpuSample> {
+  // TODO(#46978): math to figure out proper buffer sizes.
+  CpuSamplesRepository(
+    this.tag, [
+    int bufferSize = 1000000,
+  ]) : super(bufferSize);
+
+  void cacheSamples(CpuSamples samples) {
+    String getFunctionId(ProfileFunction function) {
+      final functionObject = function.function;
+      if (functionObject is NativeFunction) {
+        return 'native/${functionObject.name}';
+      }
+      return functionObject.id!;
+    }
+
+    // Initialize upon seeing our first samples.
+    if (functions.isEmpty) {
+      samplePeriod = samples.samplePeriod!;
+      maxStackDepth = samples.maxStackDepth!;
+      pid = samples.pid!;
+      functions.addAll(samples.functions!);
+
+      // Build the initial id to function index mapping. This allows for us to
+      // lookup a ProfileFunction in the global function list stored in this
+      // cache. This works since most ProfileFunction objects will have an
+      // associated function with a *typically* stable service ID that we can
+      // use as a key.
+      //
+      // TODO(bkonyi): investigate creating some form of stable ID for
+      // Functions tied to closures.
+      for (int i = 0; i < functions.length; ++i) {
+        idToFunctionIndex[getFunctionId(functions[i])] = i;
+      }
+
+      // Clear tick information as we'll need to recalculate these values later
+      // when a request for samples from this repository is received.
+      for (final f in functions) {
+        f.inclusiveTicks = 0;
+        f.exclusiveTicks = 0;
+      }
+
+      _firstSampleTimestamp = samples.timeOriginMicros!;
+    } else {
+      final newFunctions = samples.functions!;
+      final indexMapping = <int, int>{};
+
+      // Check to see if we've got a function object we've never seen before.
+      for (int i = 0; i < newFunctions.length; ++i) {
+        final key = getFunctionId(newFunctions[i]);
+        if (!idToFunctionIndex.containsKey(key)) {
+          idToFunctionIndex[key] = functions.length;
+          // Keep track of the original index and the location of the function
+          // in the master function list so we can update the function indicies
+          // for each sample in this batch.
+          indexMapping[i] = functions.length;
+          functions.add(newFunctions[i]);
+
+          // Reset tick state as we'll recalculate later.
+          functions.last.inclusiveTicks = 0;
+          functions.last.exclusiveTicks = 0;
+        }
+      }
+
+      // Update the indicies into the function table for functions that were
+      // newly processed in the most recent event.
+      for (final sample in samples.samples!) {
+        final stack = sample.stack!;
+        for (int i = 0; i < stack.length; ++i) {
+          if (indexMapping.containsKey(stack[i])) {
+            stack[i] = indexMapping[stack[i]]!;
+          }
+        }
+      }
+    }
+
+    final relevantSamples = samples.samples!.where((s) => s.userTag == tag);
+    for (final sample in relevantSamples) {
+      add(sample);
+    }
+  }
+
+  @override
+  CpuSample? add(CpuSample sample) {
+    final evicted = super.add(sample);
+
+    void updateTicksForSample(CpuSample sample, int increment) {
+      final stack = sample.stack!;
+      for (int i = 0; i < stack.length; ++i) {
+        final function = functions[stack[i]];
+        function.inclusiveTicks = function.inclusiveTicks! + increment;
+        if (i + 1 == stack.length) {
+          function.exclusiveTicks = function.exclusiveTicks! + increment;
+        }
+      }
+    }
+
+    if (evicted != null) {
+      // If a sample is evicted from the cache, we need to decrement the tick
+      // counters for each function in the sample's stack.
+      updateTicksForSample(sample, -1);
+
+      // We also need to change the first timestamp to that of the next oldest
+      // sample.
+      _firstSampleTimestamp = call().first.timestamp!;
+    }
+    _lastSampleTimestamp = sample.timestamp!;
+
+    // Update function ticks to include the new sample.
+    updateTicksForSample(sample, 1);
+
+    return evicted;
+  }
+
+  Map<String, dynamic> toJson() {
+    return {
+      'type': 'CachedCpuSamples',
+      'userTag': tag,
+      'truncated': isTruncated,
+      if (functions.isNotEmpty) ...{
+        'samplePeriod': samplePeriod,
+        'maxStackDepth': maxStackDepth,
+      },
+      'timeOriginMicros': _firstSampleTimestamp,
+      'timeExtentMicros': _lastSampleTimestamp - _firstSampleTimestamp,
+      'functions': [
+        // TODO(bkonyi): remove functions with no ticks and update sample stacks.
+        for (final f in functions) f.toJson(),
+      ],
+      'sampleCount': call().length,
+      'samples': [
+        for (final s in call()) s.toJson(),
+      ]
+    };
+  }
+
+  /// The UserTag associated with all samples stored in this repository.
+  final String tag;
+
+  /// The list of function references with corresponding profiler tick data.
+  /// ** NOTE **: The tick values here need to be updated as new CpuSamples
+  /// events are delivered.
+  final functions = <ProfileFunction>[];
+  final idToFunctionIndex = <String, int>{};
+
+  /// Assume sample period and max stack depth won't change.
+  late final int samplePeriod;
+  late final int maxStackDepth;
+
+  late final int pid;
+
+  int _firstSampleTimestamp = 0;
+  int _lastSampleTimestamp = 0;
+}
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index bc70642..b2ace33 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -83,14 +83,22 @@
   /// The client should leave the data intact.
   final Object? restart;
 
-  final String vmServiceUri;
+  /// The VM Service URI to attach to.
+  ///
+  /// Either this or [vmServiceInfoFile] must be supplied.
+  final String? vmServiceUri;
+
+  /// The VM Service info file to extract the VM Service URI from to attach to.
+  ///
+  /// Either this or [vmServiceUri] must be supplied.
+  final String? vmServiceInfoFile;
 
   DartAttachRequestArguments({
     this.restart,
-    required this.vmServiceUri,
+    this.vmServiceUri,
+    this.vmServiceInfoFile,
     String? name,
     String? cwd,
-    String? vmServiceInfoFile,
     List<String>? additionalProjectPaths,
     bool? debugSdkLibraries,
     bool? debugExternalPackageLibraries,
@@ -100,7 +108,6 @@
   }) : super(
           name: name,
           cwd: cwd,
-          vmServiceInfoFile: vmServiceInfoFile,
           additionalProjectPaths: additionalProjectPaths,
           debugSdkLibraries: debugSdkLibraries,
           debugExternalPackageLibraries: debugExternalPackageLibraries,
@@ -111,14 +118,16 @@
 
   DartAttachRequestArguments.fromMap(Map<String, Object?> obj)
       : restart = obj['restart'],
-        vmServiceUri = obj['vmServiceUri'] as String,
+        vmServiceUri = obj['vmServiceUri'] as String?,
+        vmServiceInfoFile = obj['vmServiceInfoFile'] as String?,
         super.fromMap(obj);
 
   @override
   Map<String, Object?> toJson() => {
         ...super.toJson(),
         if (restart != null) 'restart': restart,
-        'vmServiceUri': vmServiceUri,
+        if (vmServiceUri != null) 'vmServiceUri': vmServiceUri,
+        if (vmServiceInfoFile != null) 'vmServiceInfoFile': vmServiceInfoFile,
       };
 
   static DartAttachRequestArguments fromJson(Map<String, Object?> obj) =>
@@ -130,7 +139,6 @@
 class DartCommonLaunchAttachRequestArguments extends RequestArguments {
   final String? name;
   final String? cwd;
-  final String? vmServiceInfoFile;
 
   /// Paths that should be considered the users local code.
   ///
@@ -181,7 +189,6 @@
   DartCommonLaunchAttachRequestArguments({
     required this.name,
     required this.cwd,
-    required this.vmServiceInfoFile,
     required this.additionalProjectPaths,
     required this.debugSdkLibraries,
     required this.debugExternalPackageLibraries,
@@ -193,7 +200,6 @@
   DartCommonLaunchAttachRequestArguments.fromMap(Map<String, Object?> obj)
       : name = obj['name'] as String?,
         cwd = obj['cwd'] as String?,
-        vmServiceInfoFile = obj['vmServiceInfoFile'] as String?,
         additionalProjectPaths =
             (obj['additionalProjectPaths'] as List?)?.cast<String>(),
         debugSdkLibraries = obj['debugSdkLibraries'] as bool?,
@@ -208,7 +214,6 @@
   Map<String, Object?> toJson() => {
         if (name != null) 'name': name,
         if (cwd != null) 'cwd': cwd,
-        if (vmServiceInfoFile != null) 'vmServiceInfoFile': vmServiceInfoFile,
         if (additionalProjectPaths != null)
           'additionalProjectPaths': additionalProjectPaths,
         if (debugSdkLibraries != null) 'debugSdkLibraries': debugSdkLibraries,
@@ -511,10 +516,10 @@
       // TODO(dantup): Implement these.
       // vmService.onExtensionEvent.listen(_handleExtensionEvent),
       // vmService.onServiceEvent.listen(_handleServiceEvent),
-      if (_subscribeToOutputStreams) ...[
+      if (_subscribeToOutputStreams)
         vmService.onStdoutEvent.listen(_handleStdoutEvent),
+      if (_subscribeToOutputStreams)
         vmService.onStderrEvent.listen(_handleStderrEvent),
-      ],
     ]);
     await Future.wait([
       vmService.streamListen(vm.EventStreams.kIsolate),
@@ -1663,7 +1668,6 @@
     this.enableAsserts,
     String? name,
     String? cwd,
-    String? vmServiceInfoFile,
     List<String>? additionalProjectPaths,
     bool? debugSdkLibraries,
     bool? debugExternalPackageLibraries,
@@ -1673,7 +1677,6 @@
   }) : super(
           name: name,
           cwd: cwd,
-          vmServiceInfoFile: vmServiceInfoFile,
           additionalProjectPaths: additionalProjectPaths,
           debugSdkLibraries: debugSdkLibraries,
           debugExternalPackageLibraries: debugExternalPackageLibraries,
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli.dart b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
index 837313a..7420692 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli.dart
@@ -10,6 +10,7 @@
 import 'package:pedantic/pedantic.dart';
 import 'package:vm_service/vm_service.dart' as vm;
 
+import '../exceptions.dart';
 import '../logging.dart';
 import '../protocol_generated.dart';
 import '../protocol_stream.dart';
@@ -20,17 +21,6 @@
     DartAttachRequestArguments> {
   Process? _process;
 
-  /// The location of the vm-service-info file (if debugging).
-  ///
-  /// This may be provided by the user (e.g. if attaching) or generated by the DA.
-  File? _vmServiceInfoFile;
-
-  /// A watcher for [_vmServiceInfoFile] to detect when the VM writes the service
-  /// info file.
-  ///
-  /// Should be cancelled once the file has been successfully read.
-  StreamSubscription<FileSystemEvent>? _vmServiceInfoFileWatcher;
-
   /// Process IDs to terminate during shutdown.
   ///
   /// This may be populated with pids from the VM Service to ensure we clean up
@@ -92,6 +82,40 @@
     );
   }
 
+  /// Waits for [vmServiceInfoFile] to exist and become valid before returning
+  /// the VM Service URI contained within.
+  Future<Uri> waitForVmServiceInfoFile(File vmServiceInfoFile) async {
+    final completer = Completer<Uri>();
+    late final StreamSubscription<FileSystemEvent> vmServiceInfoFileWatcher;
+
+    Uri? tryParseServiceInfoFile(FileSystemEvent event) {
+      final uri = _readVmServiceInfoFile(vmServiceInfoFile);
+      if (uri != null && !completer.isCompleted) {
+        vmServiceInfoFileWatcher.cancel();
+        completer.complete(uri);
+      }
+    }
+
+    vmServiceInfoFileWatcher = vmServiceInfoFile.parent
+        .watch(events: FileSystemEvent.all)
+        .where((event) => event.path == vmServiceInfoFile.path)
+        .listen(
+          tryParseServiceInfoFile,
+          onError: (e) => logger?.call('Ignoring exception from watcher: $e'),
+        );
+
+    // After setting up the watcher, also check if the file already exists to
+    // ensure we don't miss it if it was created right before we set the
+    // watched up.
+    final uri = _readVmServiceInfoFile(vmServiceInfoFile);
+    if (uri != null && !completer.isCompleted) {
+      unawaited(vmServiceInfoFileWatcher.cancel());
+      completer.complete(uri);
+    }
+
+    return completer.future;
+  }
+
   /// Called by [launchRequest] to request that we actually start the app to be
   /// run/debugged.
   ///
@@ -100,6 +124,7 @@
   Future<void> launchImpl() async {
     final args = this.args as DartLaunchRequestArguments;
     final vmPath = Platform.resolvedExecutable;
+    File? vmServiceInfoFile;
 
     final debug = !(args.noDebug ?? false);
     if (debug) {
@@ -112,17 +137,12 @@
         Directory.systemTemp.createTempSync('dart-vm-service').path,
         'vm.json',
       );
-      _vmServiceInfoFile = File(serviceInfoFilePath);
-      _vmServiceInfoFileWatcher = _vmServiceInfoFile?.parent
-          .watch(events: FileSystemEvent.all)
-          .where((event) => event.path == _vmServiceInfoFile?.path)
-          .listen(
-            _handleVmServiceInfoEvent,
-            onError: (e) => logger?.call('Ignoring exception from watcher: $e'),
-          );
+
+      vmServiceInfoFile = File(serviceInfoFilePath);
+      unawaited(waitForVmServiceInfoFile(vmServiceInfoFile)
+          .then((uri) => connectDebugger(uri)));
     }
 
-    final vmServiceInfoFile = _vmServiceInfoFile;
     final vmArgs = <String>[
       if (debug) ...[
         '--enable-vm-service=${args.vmServicePort ?? 0}${ipv6 ? '/::1' : ''}',
@@ -185,6 +205,14 @@
   /// to be debugged.
   Future<void> attachImpl() async {
     final args = this.args as DartAttachRequestArguments;
+    final vmServiceUri = args.vmServiceUri;
+    final vmServiceInfoFile = args.vmServiceInfoFile;
+
+    if ((vmServiceUri == null) == (vmServiceInfoFile == null)) {
+      throw DebugAdapterException(
+        'To attach, provide exactly one of vmServiceUri/vmServiceInfoFile',
+      );
+    }
 
     // Find the package_config file for this script.
     // TODO(dantup): Remove this once
@@ -198,7 +226,11 @@
       }
     }
 
-    unawaited(connectDebugger(Uri.parse(args.vmServiceUri)));
+    final uri = vmServiceUri != null
+        ? Uri.parse(vmServiceUri)
+        : await waitForVmServiceInfoFile(File(vmServiceInfoFile!));
+
+    unawaited(connectDebugger(uri));
   }
 
   /// Calls the client (via a `runInTerminal` request) to spawn the process so
@@ -318,19 +350,14 @@
     sendOutput('stdout', utf8.decode(data));
   }
 
-  /// Handles file watcher events for the vm-service-info file and connects the
-  /// debugger.
+  /// Attempts to read VM Service info from a watcher event.
   ///
-  /// The vm-service-info file is written by the VM when we start the app/script
-  /// to debug and contains the VM Service URI. This allows us to access the
-  /// auth token without needing to have the URI printed to/scraped from stdout.
-  void _handleVmServiceInfoEvent(FileSystemEvent event) {
+  /// If successful, returns the URI. Otherwise, returns null.
+  Uri? _readVmServiceInfoFile(File file) {
     try {
-      final content = _vmServiceInfoFile!.readAsStringSync();
+      final content = file.readAsStringSync();
       final json = jsonDecode(content);
-      final uri = Uri.parse(json['uri']);
-      unawaited(connectDebugger(uri));
-      _vmServiceInfoFileWatcher?.cancel();
+      return Uri.parse(json['uri']);
     } catch (e) {
       // It's possible we tried to read the file before it was completely
       // written so ignore and try again on the next event.
diff --git a/pkg/dds/lib/src/dds_impl.dart b/pkg/dds/lib/src/dds_impl.dart
index 9a574de..b0a2a50 100644
--- a/pkg/dds/lib/src/dds_impl.dart
+++ b/pkg/dds/lib/src/dds_impl.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:collection';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:math';
@@ -54,6 +55,7 @@
     this._remoteVmServiceUri,
     this._uri,
     this._authCodesEnabled,
+    this._cachedUserTags,
     this._ipv6,
     this._devToolsConfiguration,
     this.shouldLogRequests,
@@ -388,6 +390,9 @@
 
   final DevToolsConfiguration? _devToolsConfiguration;
 
+  List<String> get cachedUserTags => UnmodifiableListView(_cachedUserTags);
+  final List<String> _cachedUserTags;
+
   Future<void> get done => _done.future;
   Completer _done = Completer<void>();
   bool _initializationComplete = false;
diff --git a/pkg/dds/lib/src/isolate_manager.dart b/pkg/dds/lib/src/isolate_manager.dart
index 7c43bc6..7522183 100644
--- a/pkg/dds/lib/src/isolate_manager.dart
+++ b/pkg/dds/lib/src/isolate_manager.dart
@@ -2,12 +2,14 @@
 // 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:dds/src/utils/mutex.dart';
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:vm_service/vm_service.dart';
 
 import 'client.dart';
 import 'constants.dart';
+import 'cpu_samples_manager.dart';
 import 'dds_impl.dart';
+import 'utils/mutex.dart';
 
 /// This file contains functionality used to track the running state of
 /// all isolates in a given Dart process.
@@ -36,7 +38,11 @@
 }
 
 class _RunningIsolate {
-  _RunningIsolate(this.isolateManager, this.id, this.name);
+  _RunningIsolate(this.isolateManager, this.id, this.name)
+      : cpuSamplesManager = CpuSamplesManager(
+          isolateManager.dds,
+          id,
+        );
 
   // State setters.
   void pausedOnExit() => _state = _IsolateState.pauseExit;
@@ -104,6 +110,29 @@
   /// Should always be called after an isolate is resumed.
   void clearResumeApprovals() => _resumeApprovalsByName.clear();
 
+  Map<String, dynamic> getCachedCpuSamples(String userTag) {
+    final repo = cpuSamplesManager.cpuSamplesCaches[userTag];
+    if (repo == null) {
+      throw json_rpc.RpcException.invalidParams(
+        'CPU sample caching is not enabled for tag: "$userTag"',
+      );
+    }
+    return repo.toJson();
+  }
+
+  void handleEvent(Event event) {
+    switch (event.kind) {
+      case EventKind.kUserTagChanged:
+        cpuSamplesManager.handleUserTagEvent(event);
+        return;
+      case EventKind.kCpuSamples:
+        cpuSamplesManager.handleCpuSamplesEvent(event);
+        return;
+      default:
+        return;
+    }
+  }
+
   int get _isolateStateMask => isolateStateToMaskMapping[_state] ?? 0;
 
   static const isolateStateToMaskMapping = {
@@ -113,6 +142,7 @@
   };
 
   final IsolateManager isolateManager;
+  final CpuSamplesManager cpuSamplesManager;
   final String name;
   final String id;
   final Set<String?> _resumeApprovalsByName = {};
@@ -123,20 +153,25 @@
   IsolateManager(this.dds);
 
   /// Handles state changes for isolates.
-  void handleIsolateEvent(json_rpc.Parameters parameters) {
-    final event = parameters['event'];
-    final eventKind = event['kind'].asString;
-
+  void handleIsolateEvent(Event event) {
     // There's no interesting information about isolate state associated with
     // and IsolateSpawn event.
-    if (eventKind == ServiceEvents.isolateSpawn) {
+    // TODO(bkonyi): why isn't IsolateSpawn in package:vm_service
+    if (event.kind! == ServiceEvents.isolateSpawn) {
       return;
     }
 
-    final isolateData = event['isolate'];
-    final id = isolateData['id'].asString;
-    final name = isolateData['name'].asString;
-    _updateIsolateState(id, name, eventKind);
+    final isolateData = event.isolate!;
+    final id = isolateData.id!;
+    final name = isolateData.name!;
+    _updateIsolateState(id, name, event.kind!);
+  }
+
+  void routeEventToIsolate(Event event) {
+    final isolateId = event.isolate!.id!;
+    if (isolates.containsKey(isolateId)) {
+      isolates[isolateId]!.handleEvent(event);
+    }
   }
 
   void _updateIsolateState(String id, String name, String eventKind) {
@@ -248,6 +283,16 @@
     );
   }
 
+  Map<String, dynamic> getCachedCpuSamples(json_rpc.Parameters parameters) {
+    final isolateId = parameters['isolateId'].asString;
+    if (!isolates.containsKey(isolateId)) {
+      return RPCResponses.collectedSentinel;
+    }
+    final isolate = isolates[isolateId]!;
+    final userTag = parameters['userTag'].asString;
+    return isolate.getCachedCpuSamples(userTag);
+  }
+
   /// Forwards a `resume` request to the VM service.
   Future<Map<String, dynamic>> _sendResumeRequest(
     String isolateId,
diff --git a/pkg/dds/lib/src/logging_repository.dart b/pkg/dds/lib/src/logging_repository.dart
index 4537d7e..25d52cb 100644
--- a/pkg/dds/lib/src/logging_repository.dart
+++ b/pkg/dds/lib/src/logging_repository.dart
@@ -2,17 +2,16 @@
 // 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:math';
-
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
 
 import 'client.dart';
+import 'common/ring_buffer.dart';
 
 /// [LoggingRepository] is used to store historical log messages from the
 /// target VM service. Clients which connect to DDS and subscribe to the
 /// `Logging` stream will be sent all messages contained within this repository
 /// upon initial subscription.
-class LoggingRepository extends _RingBuffer<Map<String, dynamic>> {
+class LoggingRepository extends RingBuffer<Map<String, dynamic>> {
   LoggingRepository([int logHistoryLength = 10000]) : super(logHistoryLength) {
     // TODO(bkonyi): enforce log history limit when DartDevelopmentService
     // allows for this to be set via Dart code.
@@ -45,54 +44,3 @@
   final Set<DartDevelopmentServiceClient> _sentHistoricLogsClientSet = {};
   static const int _kMaxLogBufferSize = 100000;
 }
-
-// TODO(bkonyi): move to standalone file if we decide to use this elsewhere.
-class _RingBuffer<T> {
-  _RingBuffer(this._bufferSize) {
-    _buffer = List<T?>.filled(
-      _bufferSize,
-      null,
-    );
-  }
-
-  Iterable<T> call() sync* {
-    for (int i = _size - 1; i >= 0; --i) {
-      yield _buffer[(_count - i - 1) % _bufferSize]!;
-    }
-  }
-
-  void add(T e) {
-    if (_buffer.isEmpty) {
-      return;
-    }
-    _buffer[_count++ % _bufferSize] = e;
-  }
-
-  void resize(int size) {
-    assert(size >= 0);
-    if (size == _bufferSize) {
-      return;
-    }
-    final resized = List<T?>.filled(
-      size,
-      null,
-    );
-    int count = 0;
-    if (size > 0) {
-      for (final e in this()) {
-        resized[count++ % size] = e;
-      }
-    }
-    _count = count;
-    _bufferSize = size;
-    _buffer = resized;
-  }
-
-  int get bufferSize => _bufferSize;
-
-  int get _size => min(_count, _bufferSize);
-
-  int _bufferSize;
-  int _count = 0;
-  late List<T?> _buffer;
-}
diff --git a/pkg/dds/lib/src/stream_manager.dart b/pkg/dds/lib/src/stream_manager.dart
index fd1290c..46e22cf 100644
--- a/pkg/dds/lib/src/stream_manager.dart
+++ b/pkg/dds/lib/src/stream_manager.dart
@@ -5,6 +5,7 @@
 import 'dart:typed_data';
 
 import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc;
+import 'package:vm_service/vm_service.dart';
 
 import 'client.dart';
 import 'dds_impl.dart';
@@ -108,18 +109,31 @@
         // Stdout and Stderr streams may not exist.
       }
     }
+    if (dds.cachedUserTags.isNotEmpty) {
+      await streamListen(null, EventStreams.kProfiler);
+    }
     dds.vmServiceClient.registerMethod(
       'streamNotify',
-      (parameters) {
+      (json_rpc.Parameters parameters) {
         final streamId = parameters['streamId'].asString;
+        final event =
+            Event.parse(parameters['event'].asMap.cast<String, dynamic>())!;
+
         // Forward events from the streams IsolateManager subscribes to.
         if (isolateManagerStreams.contains(streamId)) {
-          dds.isolateManager.handleIsolateEvent(parameters);
+          dds.isolateManager.handleIsolateEvent(event);
         }
         // Keep a history of messages to send to clients when they first
         // subscribe to a stream with an event history.
         if (loggingRepositories.containsKey(streamId)) {
-          loggingRepositories[streamId]!.add(parameters.asMap);
+          loggingRepositories[streamId]!.add(
+            parameters.asMap.cast<String, dynamic>(),
+          );
+        }
+        // If the event contains an isolate, forward the event to the
+        // corresponding isolate to be handled.
+        if (event.isolate != null) {
+          dds.isolateManager.routeEventToIsolate(event);
         }
         streamNotify(streamId, parameters.value);
       },
@@ -262,6 +276,7 @@
   static const kExtensionStream = 'Extension';
   static const kIsolateStream = 'Isolate';
   static const kLoggingStream = 'Logging';
+  static const kProfilerStream = 'Profiler';
   static const kStderrStream = 'Stderr';
   static const kStdoutStream = 'Stdout';
 
@@ -283,6 +298,12 @@
     kStdoutStream,
   };
 
+  // Never cancel the profiler stream as `CpuSampleRepository` requires
+  // `UserTagChanged` events to enable/disable sample caching.
+  static const cpuSampleRepositoryStreams = <String>{
+    kProfilerStream,
+  };
+
   // The set of streams that DDS requires to function.
   static final ddsCoreStreams = <String>{
     ...isolateManagerStreams,
diff --git a/pkg/dds/lib/vm_service_extensions.dart b/pkg/dds/lib/vm_service_extensions.dart
index 903c14a..09bda25 100644
--- a/pkg/dds/lib/vm_service_extensions.dart
+++ b/pkg/dds/lib/vm_service_extensions.dart
@@ -13,18 +13,46 @@
   static bool _factoriesRegistered = false;
   static Version? _ddsVersion;
 
-  /// The _getDartDevelopmentServiceVersion_ RPC is used to determine what version of
+  /// The [getDartDevelopmentServiceVersion] RPC is used to determine what version of
   /// the Dart Development Service Protocol is served by a DDS instance.
   ///
   /// The result of this call is cached for subsequent invocations.
   Future<Version> getDartDevelopmentServiceVersion() async {
     if (_ddsVersion == null) {
-      _ddsVersion =
-          await _callHelper<Version>('getDartDevelopmentServiceVersion');
+      _ddsVersion = await _callHelper<Version>(
+        'getDartDevelopmentServiceVersion',
+      );
     }
     return _ddsVersion!;
   }
 
+  /// The [getCachedCpuSamples] RPC is used to retrieve a cache of CPU samples
+  /// collected under a [UserTag] with name `userTag`.
+  Future<CachedCpuSamples> getCachedCpuSamples(
+      String isolateId, String userTag) async {
+    if (!(await _versionCheck(1, 3))) {
+      throw UnimplementedError('getCachedCpuSamples requires DDS version 1.3');
+    }
+    return _callHelper<CachedCpuSamples>('getCachedCpuSamples', args: {
+      'isolateId': isolateId,
+      'userTag': userTag,
+    });
+  }
+
+  /// The [getAvailableCachedCpuSamples] RPC is used to determine which caches of CPU samples
+  /// are available. Caches are associated with individual [UserTag] names and are specified
+  /// when DDS is started via the `cachedUserTags` parameter.
+  Future<AvailableCachedCpuSamples> getAvailableCachedCpuSamples() async {
+    if (!(await _versionCheck(1, 3))) {
+      throw UnimplementedError(
+        'getAvailableCachedCpuSamples requires DDS version 1.3',
+      );
+    }
+    return _callHelper<AvailableCachedCpuSamples>(
+      'getAvailableCachedCpuSamples',
+    );
+  }
+
   /// Retrieve the event history for `stream`.
   ///
   /// If `stream` does not have event history collected, a parameter error is
@@ -126,6 +154,11 @@
 
   static void _registerFactories() {
     addTypeFactory('StreamHistory', StreamHistory.parse);
+    addTypeFactory(
+      'AvailableCachedCpuSamples',
+      AvailableCachedCpuSamples.parse,
+    );
+    addTypeFactory('CachedCpuSamples', CachedCpuSamples.parse);
     _factoriesRegistered = true;
   }
 }
@@ -154,3 +187,86 @@
   List<Event> get history => UnmodifiableListView(_history);
   final List<Event> _history;
 }
+
+/// An extension of [CpuSamples] which represents a set of cached samples,
+/// associated with a particular [UserTag] name.
+class CachedCpuSamples extends CpuSamples {
+  static CachedCpuSamples? parse(Map<String, dynamic>? json) =>
+      json == null ? null : CachedCpuSamples._fromJson(json);
+
+  CachedCpuSamples({
+    required this.userTag,
+    this.truncated,
+    required int? samplePeriod,
+    required int? maxStackDepth,
+    required int? sampleCount,
+    required int? timeSpan,
+    required int? timeOriginMicros,
+    required int? timeExtentMicros,
+    required int? pid,
+    required List<ProfileFunction>? functions,
+    required List<CpuSample>? samples,
+  }) : super(
+          samplePeriod: samplePeriod,
+          maxStackDepth: maxStackDepth,
+          sampleCount: sampleCount,
+          timeSpan: timeSpan,
+          timeOriginMicros: timeOriginMicros,
+          timeExtentMicros: timeExtentMicros,
+          pid: pid,
+          functions: functions,
+          samples: samples,
+        );
+
+  CachedCpuSamples._fromJson(Map<String, dynamic> json)
+      : userTag = json['userTag']!,
+        truncated = json['truncated'],
+        super(
+          samplePeriod: json['samplePeriod'] ?? -1,
+          maxStackDepth: json['maxStackDepth'] ?? -1,
+          sampleCount: json['sampleCount'] ?? -1,
+          timeSpan: json['timeSpan'] ?? -1,
+          timeOriginMicros: json['timeOriginMicros'] ?? -1,
+          timeExtentMicros: json['timeExtentMicros'] ?? -1,
+          pid: json['pid'] ?? -1,
+          functions: List<ProfileFunction>.from(
+            createServiceObject(json['functions'], const ['ProfileFunction'])
+                    as List? ??
+                [],
+          ),
+          samples: List<CpuSample>.from(
+            createServiceObject(json['samples'], const ['CpuSample'])
+                    as List? ??
+                [],
+          ),
+        );
+
+  @override
+  String get type => 'CachedCpuSamples';
+
+  /// The name of the [UserTag] associated with this cache of [CpuSamples].
+  final String userTag;
+
+  /// Provided if the CPU sample cache has filled and older samples have been
+  /// dropped.
+  final bool? truncated;
+}
+
+/// A collection of [UserTag] names associated with caches of CPU samples.
+class AvailableCachedCpuSamples extends Response {
+  static AvailableCachedCpuSamples? parse(Map<String, dynamic>? json) =>
+      json == null ? null : AvailableCachedCpuSamples._fromJson(json);
+
+  AvailableCachedCpuSamples({
+    required this.cacheNames,
+  });
+
+  AvailableCachedCpuSamples._fromJson(Map<String, dynamic> json)
+      : cacheNames = List<String>.from(json['cacheNames']);
+
+  @override
+  String get type => 'AvailableCachedUserTagCpuSamples';
+
+  /// A [List] of [UserTag] names associated with CPU sample caches.
+  final List<String> cacheNames;
+}
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index d9b8b92..c11e0f2 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -25,7 +25,7 @@
   shelf_web_socket: ^1.0.0
   sse: ^4.0.0
   stream_channel: ^2.0.0
-  vm_service: ^7.0.0
+  vm_service: ^7.2.0
   web_socket_channel: ^2.0.0
 
 dev_dependencies:
diff --git a/pkg/dds/test/dap/integration/debug_attach_test.dart b/pkg/dds/test/dap/integration/debug_attach_test.dart
index b986621..f0e3f14 100644
--- a/pkg/dds/test/dap/integration/debug_attach_test.dart
+++ b/pkg/dds/test/dap/integration/debug_attach_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 import 'test_client.dart';
@@ -61,6 +62,58 @@
         'Exited.',
       ]);
     });
+
+    test('can attach to a simple script using vmServiceInfoFile', () async {
+      final testFile = dap.createTestFile(simpleArgPrintingProgram);
+
+      // Spawn the program using --write-service-info which we'll pass the path
+      // of directly to the DAP to read.
+      final vmServiceInfoFilePath = path.join(
+        dap.testAppDir.path,
+        'vmServiceInfo.json',
+      );
+      await startDartProcessPaused(
+        testFile.path,
+        ['one', 'two'],
+        cwd: dap.testAppDir.path,
+        vmArgs: ['--write-service-info=${Uri.file(vmServiceInfoFilePath)}'],
+      );
+      final outputEvents = await dap.client.collectOutput(
+        launch: () => dap.client.attach(
+          vmServiceInfoFile: vmServiceInfoFilePath,
+          cwd: dap.testAppDir.path,
+        ),
+      );
+
+      // Expect a "console" output event that prints the URI of the VM Service
+      // the debugger connects to.
+      final vmConnection = outputEvents.first;
+      expect(
+        vmConnection.output,
+        startsWith('Connecting to VM Service at ws://127.0.0.1:'),
+      );
+      expect(vmConnection.category, equals('console'));
+
+      // Expect the normal applications output.
+      final output = outputEvents
+          .skip(1)
+          .map((e) => e.output)
+          // The stdout also contains the Observatory+DevTools banners.
+          .where(
+            (line) =>
+                !line.startsWith('Observatory listening on') &&
+                !line.startsWith(
+                    'The Dart DevTools debugger and profiler is available at'),
+          )
+          .join();
+      expectLines(output, [
+        'Hello!',
+        'World!',
+        'args: [one, two]',
+        '',
+        'Exited.',
+      ]);
+    });
     // These tests can be slow due to starting up the external server process.
   }, timeout: Timeout.none);
 }
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index e08e094..08d7e8f 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -62,7 +62,8 @@
   /// Send an attachRequest to the server, asking it to attach to an existing
   /// Dart program.
   Future<Response> attach({
-    required String vmServiceUri,
+    String? vmServiceUri,
+    String? vmServiceInfoFile,
     String? cwd,
     List<String>? additionalProjectPaths,
     bool? debugSdkLibraries,
@@ -70,9 +71,14 @@
     bool? evaluateGettersInDebugViews,
     bool? evaluateToStringInDebugViews,
   }) {
+    assert(
+      (vmServiceUri == null) != (vmServiceInfoFile == null),
+      'Provide exactly one of vmServiceUri/vmServiceInfoFile',
+    );
     return sendRequest(
       DartAttachRequestArguments(
         vmServiceUri: vmServiceUri,
+        vmServiceInfoFile: vmServiceInfoFile,
         cwd: cwd,
         additionalProjectPaths: additionalProjectPaths,
         debugSdkLibraries: debugSdkLibraries,
diff --git a/pkg/dds/test/dap/integration/test_support.dart b/pkg/dds/test/dap/integration/test_support.dart
index a8cbd0f..4c804c6 100644
--- a/pkg/dds/test/dap/integration/test_support.dart
+++ b/pkg/dds/test/dap/integration/test_support.dart
@@ -88,7 +88,7 @@
   vmArgs ??= [];
   vmArgs.addAll([
     '--enable-vm-service=0',
-    '--pause_isolates_on_start',
+    '--pause_isolates_on_start=true',
   ]);
   final processArgs = [
     ...vmArgs,
diff --git a/pkg/dds/test/get_cached_cpu_samples_script.dart b/pkg/dds/test/get_cached_cpu_samples_script.dart
new file mode 100644
index 0000000..5949574
--- /dev/null
+++ b/pkg/dds/test/get_cached_cpu_samples_script.dart
@@ -0,0 +1,21 @@
+// 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:developer';
+
+fib(int n) {
+  if (n <= 1) {
+    return n;
+  }
+  return fib(n - 1) + fib(n - 2);
+}
+
+void main() {
+  UserTag('Testing').makeCurrent();
+  int i = 5;
+  while (true) {
+    ++i;
+    fib(i);
+  }
+}
diff --git a/pkg/dds/test/get_cached_cpu_samples_test.dart b/pkg/dds/test/get_cached_cpu_samples_test.dart
new file mode 100644
index 0000000..77d2bdb
--- /dev/null
+++ b/pkg/dds/test/get_cached_cpu_samples_test.dart
@@ -0,0 +1,124 @@
+// 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:async';
+import 'dart:io';
+
+import 'package:dds/dds.dart';
+import 'package:dds/src/utils/mutex.dart';
+import 'package:dds/vm_service_extensions.dart';
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+import 'package:vm_service/vm_service_io.dart';
+import 'common/test_helper.dart';
+
+void main() {
+  late Process process;
+  late DartDevelopmentService dds;
+
+  setUp(() async {
+    process = await spawnDartProcess(
+      'get_cached_cpu_samples_script.dart',
+    );
+  });
+
+  tearDown(() async {
+    await dds.shutdown();
+    process.kill();
+  });
+
+  test(
+    'No UserTags to cache',
+    () async {
+      dds = await DartDevelopmentService.startDartDevelopmentService(
+        remoteVmServiceUri,
+      );
+      expect(dds.isRunning, true);
+      final service = await vmServiceConnectUri(dds.wsUri.toString());
+
+      // We didn't provide `cachedUserTags` when starting DDS, so we shouldn't
+      // be caching anything.
+      final availableCaches = await service.getAvailableCachedCpuSamples();
+      expect(availableCaches.cacheNames.length, 0);
+
+      final isolate = (await service.getVM()).isolates!.first;
+
+      try {
+        await service.getCachedCpuSamples(isolate.id!, 'Fake');
+        fail('Invalid userTag did not cause an exception');
+      } on RPCError catch (e) {
+        expect(
+          e.message,
+          'CPU sample caching is not enabled for tag: "Fake"',
+        );
+      }
+    },
+    timeout: Timeout.none,
+  );
+
+  test(
+    'Cache CPU samples for provided UserTag name',
+    () async {
+      const kUserTag = 'Testing';
+      dds = await DartDevelopmentService.startDartDevelopmentService(
+        remoteVmServiceUri,
+        cachedUserTags: [kUserTag],
+      );
+      expect(dds.isRunning, true);
+      final service = await vmServiceConnectUri(dds.wsUri.toString());
+
+      // Ensure we're caching results for samples under the 'Testing' UserTag.
+      final availableCaches = await service.getAvailableCachedCpuSamples();
+      expect(availableCaches.cacheNames.length, 1);
+      expect(availableCaches.cacheNames.first, kUserTag);
+
+      final isolate = (await service.getVM()).isolates!.first;
+
+      final completer = Completer<void>();
+      int i = 0;
+      int count = 0;
+      final mutex = Mutex();
+
+      late StreamSubscription sub;
+      sub = service.onProfilerEvent.listen(
+        (event) async {
+          // Process one event at a time to prevent racey updates to count.
+          await mutex.runGuarded(
+            () async {
+              if (event.kind == EventKind.kCpuSamples &&
+                  event.isolate!.id! == isolate.id!) {
+                ++i;
+                if (i > 3) {
+                  if (!completer.isCompleted) {
+                    await sub.cancel();
+                    completer.complete();
+                  }
+                  return;
+                }
+                // Ensure the number of CPU samples in the CpuSample event is
+                // is consistent with the number of samples in the cache.
+                expect(event.cpuSamples, isNotNull);
+                count += event.cpuSamples!.samples!
+                    .where((e) => e.userTag == kUserTag)
+                    .length;
+                final cache = await service.getCachedCpuSamples(
+                  isolate.id!,
+                  availableCaches.cacheNames.first,
+                );
+                // DDS may have processed more sample blocks than we've had a chance
+                // to, so just ensure we have at least as many samples in the cache
+                // as we've seen.
+                expect(cache.sampleCount! >= count, true);
+              }
+            },
+          );
+        },
+      );
+      await service.streamListen(EventStreams.kProfiler);
+      await service.resume(isolate.id!);
+      await completer.future;
+    },
+    timeout: Timeout.none,
+  );
+}
diff --git a/pkg/js_ast/lib/src/builder.dart b/pkg/js_ast/lib/src/builder.dart
index 30586c4..83bb11f 100644
--- a/pkg/js_ast/lib/src/builder.dart
+++ b/pkg/js_ast/lib/src/builder.dart
@@ -7,196 +7,190 @@
 
 part of js_ast;
 
-/**
- * Global template manager.  We should aim to have a fixed number of
- * templates. This implies that we do not use js('xxx') to parse text that is
- * constructed from values that depend on names in the Dart program.
- *
- * TODO(sra): Find the remaining places where js('xxx') used to parse an
- * unbounded number of expression, or institute a cache policy.
- */
-TemplateManager templateManager = new TemplateManager();
+/// Global template manager.  We should aim to have a fixed number of
+/// templates. This implies that we do not use js('xxx') to parse text that is
+/// constructed from values that depend on names in the Dart program.
+///
+/// TODO(sra): Find the remaining places where js('xxx') used to parse an
+/// unbounded number of expression, or institute a cache policy.
+TemplateManager templateManager = TemplateManager();
 
-/**
-
-[js] is a singleton instance of JsBuilder.  JsBuilder is a set of conveniences
-for constructing JavaScript ASTs.
-
-[string] and [number] are used to create leaf AST nodes:
-
-    var s = js.string('hello');    //  s = new LiteralString('"hello"')
-    var n = js.number(123);        //  n = new LiteralNumber(123)
-
-In the line above `a --> b` means Dart expression `a` evaluates to a JavaScript
-AST that would pretty-print as `b`.
-
-The [call] method constructs an Expression AST.
-
-No argument
-
-    js('window.alert("hello")')  -->  window.alert("hello")
-
-The input text can contain placeholders `#` that are replaced with provided
-arguments.  A single argument can be passed directly:
-
-    js('window.alert(#)', s)   -->  window.alert("hello")
-
-Multiple arguments are passed as a list:
-
-    js('# + #', [s, s])  -->  "hello" + "hello"
-
-The [statement] method constructs a Statement AST, but is otherwise like the
-[call] method.  This constructs a Return AST:
-
-    var ret = js.statement('return #;', n);  -->  return 123;
-
-A placeholder in a Statement context must be followed by a semicolon ';'.  You
-can think of a statement placeholder as being `#;` to explain why the output
-still has one semicolon:
-
-    js.statement('if (happy) #;', ret)
-    -->
-    if (happy)
-      return 123;
-
-If the placeholder is not followed by a semicolon, it is part of an expression.
-Here the placeholder is in the position of the function in a function call:
-
-    var vFoo = new VariableUse('foo');
-    js.statement('if (happy) #("Happy!")', vFoo)
-    -->
-    if (happy)
-      foo("Happy!");
-
-Generally, a placeholder in an expression position requires an Expression AST as
-an argument and a placeholder in a statement position requires a Statement AST.
-An expression will be converted to a Statement if needed by creating an
-ExpressionStatement.  A String argument will be converted into a VariableUse and
-requires that the string is a JavaScript identifier.
-
-    js('# + 1', vFoo)       -->  foo + 1
-    js('# + 1', 'foo')      -->  foo + 1
-    js('# + 1', 'foo.bar')  -->  assertion failure
-
-Some placeholder positions are _splicing contexts_.  A function argument list is
-a splicing expression context.  A placeholder in a splicing expression context
-can take a single Expression (or String, converted to VariableUse) or an
-Iterable of Expressions (and/or Strings).
-
-    // non-splicing argument:
-    js('#(#)', ['say', s])        -->  say("hello")
-    // splicing arguments:
-    js('#(#)', ['say', []])       -->  say()
-    js('#(#)', ['say', [s]])      -->  say("hello")
-    js('#(#)', ['say', [s, n]])   -->  say("hello", 123)
-
-A splicing context can be used to append 'lists' and add extra elements:
-
-    js('foo(#, #, 1)', [ ['a', n], s])       -->  foo(a, 123, "hello", 1)
-    js('foo(#, #, 1)', [ ['a', n], [s, n]])  -->  foo(a, 123, "hello", 123, 1)
-    js('foo(#, #, 1)', [ [], [s, n]])        -->  foo("hello", 123, 1)
-    js('foo(#, #, 1)', [ [], [] ])           -->  foo(1)
-
-The generation of a compile-time optional argument expression can be chosen by
-providing an empty or singleton list.
-
-In addition to Expressions and Statements, there are Parameters, which occur
-only in the parameter list of a function expression or declaration.
-Placeholders in parameter positions behave like placeholders in Expression
-positions, except only Parameter AST nodes are permitted.  String arguments for
-parameter placeholders are converted to Parameter AST nodes.
-
-    var pFoo = new Parameter('foo')
-    js('function(#) { return #; }', [pFoo, vFoo])
-    -->
-    function(foo) { return foo; }
-
-Expressions and Parameters are not compatible with each other's context:
-
-    js('function(#) { return #; }', [vFoo, vFoo]) --> error
-    js('function(#) { return #; }', [pFoo, pFoo]) --> error
-
-The parameter context is a splicing context.  When combined with the
-context-sensitive conversion of Strings, this simplifies the construction of
-trampoline-like functions:
-
-    var args = ['a', 'b'];
-    js('function(#) { return f(this, #); }', [args, args])
-    -->
-    function(a, b) { return f(this, a, b); }
-
-A statement placeholder in a Block is also in a splicing context.  In addition
-to splicing Iterables, statement placeholders in a Block will also splice a
-Block or an EmptyStatement.  This flattens nested blocks and allows blocks to be
-appended.
-
-    var b1 = js.statement('{ 1; 2; }');
-    var sEmpty = new Emptystatement();
-    js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty])
-    -->
-    { 1; 2; 1; 2; }
-
-A placeholder in the context of an if-statement condition also accepts a Dart
-bool argument, which selects the then-part or else-part of the if-statement:
-
-    js.statement('if (#) return;', vFoo)   -->  if (foo) return;
-    js.statement('if (#) return;', true)   -->  return;
-    js.statement('if (#) return;', false)  -->  ;   // empty statement
-    var eTrue = new LiteralBool(true);
-    js.statement('if (#) return;', eTrue)  -->  if (true) return;
-
-Combined with block splicing, if-statement condition context placeholders allows
-the creation of templates that select code depending on variables.
-
-    js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true)
-    --> { 1; 2; 5; }
-
-    js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false)
-    --> { 1; 3; 4; 5; }
-
-A placeholder following a period in a property access is in a property access
-context.  This is just like an expression context, except String arguments are
-converted to JavaScript property accesses.  In JavaScript, `a.b` is short-hand
-for `a["b"]`:
-
-    js('a[#]', vFoo)  -->  a[foo]
-    js('a[#]', s)     -->  a.hello    (i.e. a["hello"]).
-    js('a[#]', 'x')   -->  a[x]
-
-    js('a.#', vFoo)   -->  a[foo]
-    js('a.#', s)      -->  a.hello    (i.e. a["hello"])
-    js('a.#', 'x')    -->  a.x        (i.e. a["x"])
-
-(Question - should `.#` be restricted to permit only String arguments? The
-template should probably be written with `[]` if non-strings are accepted.)
-
-
-Object initializers allow placeholders in the key property name position:
-
-    js('{#:1, #:2}',  [s, 'bye'])    -->  {hello: 1, bye: 2}
-
-
-What is not implemented:
-
- -  Array initializers and object initializers could support splicing.  In the
-    array case, we would need some way to know if an ArrayInitializer argument
-    should be splice or is intended as a single value.
-
-*/
-const JsBuilder js = const JsBuilder();
+/// [js] is a singleton instance of JsBuilder.  JsBuilder is a set of
+/// conveniences for constructing JavaScript ASTs.
+///
+/// [string] and [number] are used to create leaf AST nodes:
+///
+///     var s = js.string('hello');    //  s = new LiteralString('"hello"')
+///     var n = js.number(123);        //  n = new LiteralNumber(123)
+///
+/// In the line above `a --> b` means Dart expression `a` evaluates to a
+/// JavaScript AST that would pretty-print as `b`.
+///
+/// The [call] method constructs an Expression AST.
+///
+/// No argument
+///
+///     js('window.alert("hello")')  -->  window.alert("hello")
+///
+/// The input text can contain placeholders `#` that are replaced with provided
+/// arguments.  A single argument can be passed directly:
+///
+///     js('window.alert(#)', s)   -->  window.alert("hello")
+///
+/// Multiple arguments are passed as a list:
+///
+///     js('# + #', [s, s])  -->  "hello" + "hello"
+///
+/// The [statement] method constructs a Statement AST, but is otherwise like the
+/// [call] method.  This constructs a Return AST:
+///
+///     var ret = js.statement('return #;', n);  -->  return 123;
+///
+/// A placeholder in a Statement context must be followed by a semicolon ';'.
+/// One can think of a statement placeholder as being `#;` to explain why the
+/// output still has one semicolon:
+///
+///     js.statement('if (happy) #;', ret)
+///     -->
+///     if (happy)
+///       return 123;
+///
+/// If the placeholder is not followed by a semicolon, it is part of an
+/// expression.  Here the placeholder is in the position of the function in a
+/// function call:
+///
+///     var vFoo = new VariableUse('foo');
+///     js.statement('if (happy) #("Happy!")', vFoo)
+///     -->
+///     if (happy)
+///       foo("Happy!");
+///
+/// Generally, a placeholder in an expression position requires an Expression
+/// AST as an argument and a placeholder in a statement position requires a
+/// Statement AST.  An expression will be converted to a Statement if needed by
+/// creating an ExpressionStatement.  A String argument will be converted into a
+/// VariableUse and requires that the string is a JavaScript identifier.
+///
+///     js('# + 1', vFoo)       -->  foo + 1
+///     js('# + 1', 'foo')      -->  foo + 1
+///     js('# + 1', 'foo.bar')  -->  assertion failure
+///
+/// Some placeholder positions are _splicing contexts_.  A function argument list is
+/// a splicing expression context.  A placeholder in a splicing expression context
+/// can take a single Expression (or String, converted to VariableUse) or an
+/// Iterable of Expressions (and/or Strings).
+///
+///     // non-splicing argument:
+///     js('#(#)', ['say', s])        -->  say("hello")
+///     // splicing arguments:
+///     js('#(#)', ['say', []])       -->  say()
+///     js('#(#)', ['say', [s]])      -->  say("hello")
+///     js('#(#)', ['say', [s, n]])   -->  say("hello", 123)
+///
+/// A splicing context can be used to append 'lists' and add extra elements:
+///
+///     js('foo(#, #, 1)', [ ['a', n], s])       -->  foo(a, 123, "hello", 1)
+///     js('foo(#, #, 1)', [ ['a', n], [s, n]])  -->  foo(a, 123, "hello", 123, 1)
+///     js('foo(#, #, 1)', [ [], [s, n]])        -->  foo("hello", 123, 1)
+///     js('foo(#, #, 1)', [ [], [] ])           -->  foo(1)
+///
+/// The generation of a compile-time optional argument expression can be chosen by
+/// providing an empty or singleton list.
+///
+/// In addition to Expressions and Statements, there are Parameters, which occur
+/// only in the parameter list of a function expression or declaration.
+/// Placeholders in parameter positions behave like placeholders in Expression
+/// positions, except only Parameter AST nodes are permitted.  String arguments for
+/// parameter placeholders are converted to Parameter AST nodes.
+///
+///     var pFoo = new Parameter('foo')
+///     js('function(#) { return #; }', [pFoo, vFoo])
+///     -->
+///     function(foo) { return foo; }
+///
+/// Expressions and Parameters are not compatible with each other's context:
+///
+///     js('function(#) { return #; }', [vFoo, vFoo]) --> error
+///     js('function(#) { return #; }', [pFoo, pFoo]) --> error
+///
+/// The parameter context is a splicing context.  When combined with the
+/// context-sensitive conversion of Strings, this simplifies the construction of
+/// trampoline-like functions:
+///
+///     var args = ['a', 'b'];
+///     js('function(#) { return f(this, #); }', [args, args])
+///     -->
+///     function(a, b) { return f(this, a, b); }
+///
+/// A statement placeholder in a Block is also in a splicing context.  In addition
+/// to splicing Iterables, statement placeholders in a Block will also splice a
+/// Block or an EmptyStatement.  This flattens nested blocks and allows blocks to be
+/// appended.
+///
+///     var b1 = js.statement('{ 1; 2; }');
+///     var sEmpty = new Emptystatement();
+///     js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty])
+///     -->
+///     { 1; 2; 1; 2; }
+///
+/// A placeholder in the context of an if-statement condition also accepts a Dart
+/// bool argument, which selects the then-part or else-part of the if-statement:
+///
+///     js.statement('if (#) return;', vFoo)   -->  if (foo) return;
+///     js.statement('if (#) return;', true)   -->  return;
+///     js.statement('if (#) return;', false)  -->  ;   // empty statement
+///     var eTrue = new LiteralBool(true);
+///     js.statement('if (#) return;', eTrue)  -->  if (true) return;
+///
+/// Combined with block splicing, if-statement condition context placeholders allows
+/// the creation of templates that select code depending on variables.
+///
+///     js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true)
+///     --> { 1; 2; 5; }
+///
+///     js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false)
+///     --> { 1; 3; 4; 5; }
+///
+/// A placeholder following a period in a property access is in a property access
+/// context.  This is just like an expression context, except String arguments are
+/// converted to JavaScript property accesses.  In JavaScript, `a.b` is short-hand
+/// for `a["b"]`:
+///
+///     js('a[#]', vFoo)  -->  a[foo]
+///     js('a[#]', s)     -->  a.hello    (i.e. a["hello"]).
+///     js('a[#]', 'x')   -->  a[x]
+///
+///     js('a.#', vFoo)   -->  a[foo]
+///     js('a.#', s)      -->  a.hello    (i.e. a["hello"])
+///     js('a.#', 'x')    -->  a.x        (i.e. a["x"])
+///
+/// (Question - should `.#` be restricted to permit only String arguments? The
+/// template should probably be written with `[]` if non-strings are accepted.)
+///
+///
+/// Object initializers allow placeholders in the key property name position:
+///
+///     js('{#:1, #:2}',  [s, 'bye'])    -->  {hello: 1, bye: 2}
+///
+///
+/// What is not implemented:
+///
+///  -  Array initializers and object initializers could support splicing.  In the
+///     array case, we would need some way to know if an ArrayInitializer argument
+///     should be splice or is intended as a single value.
+///
+const JsBuilder js = JsBuilder();
 
 class JsBuilder {
   const JsBuilder();
 
-  /**
-   * Parses a bit of JavaScript, and returns an expression.
-   *
-   * See the MiniJsParser class.
-   *
-   * [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or
-   * a list of [Node]s, which will be interpolated into the source at the '#'
-   * signs.
-   */
+  /// Parses a bit of JavaScript, and returns an expression.
+  ///
+  /// See the MiniJsParser class.
+  ///
+  /// [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or
+  /// a list of [Node]s, which will be interpolated into the source at the '#'
+  /// signs.
   Expression call(String source, [var arguments]) {
     Template template = _findExpressionTemplate(source);
     if (arguments == null) return template.instantiate([]);
@@ -205,9 +199,7 @@
     return template.instantiate(arguments);
   }
 
-  /**
-   * Parses a JavaScript Statement, otherwise just like [call].
-   */
+  /// Parses a JavaScript Statement, otherwise just like [call].
   Statement statement(String source, [var arguments]) {
     Template template = _findStatementTemplate(source);
     if (arguments == null) return template.instantiate([]);
@@ -216,12 +208,10 @@
     return template.instantiate(arguments);
   }
 
-  /**
-   * Parses JavaScript written in the `JS` foreign instruction.
-   *
-   * The [source] must be a JavaScript expression or a JavaScript throw
-   * statement.
-   */
+  /// Parses JavaScript written in the `JS` foreign instruction.
+  ///
+  /// The [source] must be a JavaScript expression or a JavaScript throw
+  /// statement.
   Template parseForeignJS(String source) {
     // TODO(sra): Parse with extra validation to forbid `#` interpolation in
     // functions, as this leads to unanticipated capture of temporaries that are
@@ -236,7 +226,7 @@
   Template _findExpressionTemplate(String source) {
     Template template = templateManager.lookupExpressionTemplate(source);
     if (template == null) {
-      MiniJsParser parser = new MiniJsParser(source);
+      MiniJsParser parser = MiniJsParser(source);
       Expression expression = parser.expression();
       template = templateManager.defineExpressionTemplate(source, expression);
     }
@@ -246,53 +236,43 @@
   Template _findStatementTemplate(String source) {
     Template template = templateManager.lookupStatementTemplate(source);
     if (template == null) {
-      MiniJsParser parser = new MiniJsParser(source);
+      MiniJsParser parser = MiniJsParser(source);
       Statement statement = parser.statement();
       template = templateManager.defineStatementTemplate(source, statement);
     }
     return template;
   }
 
-  /**
-   * Creates an Expression template for the given [source].
-   *
-   * The returned template is cached.
-   */
+  /// Creates an Expression template for the given [source].
+  ///
+  /// The returned template is cached.
   Template expressionTemplateFor(String source) {
     return _findExpressionTemplate(source);
   }
 
-  /**
-   * Creates an Expression template without caching the result.
-   */
+  /// Creates an Expression template without caching the result.
   Template uncachedExpressionTemplate(String source) {
-    MiniJsParser parser = new MiniJsParser(source);
+    MiniJsParser parser = MiniJsParser(source);
     Expression expression = parser.expression();
-    return new Template(source, expression,
-        isExpression: true, forceCopy: false);
+    return Template(source, expression, isExpression: true, forceCopy: false);
   }
 
-  /**
-   * Creates a Statement template without caching the result.
-   */
+  /// Creates a Statement template without caching the result.
   Template uncachedStatementTemplate(String source) {
-    MiniJsParser parser = new MiniJsParser(source);
+    MiniJsParser parser = MiniJsParser(source);
     Statement statement = parser.statement();
-    return new Template(source, statement,
-        isExpression: false, forceCopy: false);
+    return Template(source, statement, isExpression: false, forceCopy: false);
   }
 
-  /**
-   * Create an Expression template which has [ast] as the result.  This is used
-   * to wrap a generated AST in a zero-argument Template so it can be passed to
-   * context that expects a template.
-   */
+  /// Create an Expression template which has [ast] as the result.  This is used
+  /// to wrap a generated AST in a zero-argument Template so it can be passed to
+  /// context that expects a template.
   Template expressionTemplateYielding(Node ast) {
-    return new Template.withExpressionResult(ast);
+    return Template.withExpressionResult(ast);
   }
 
   Template statementTemplateYielding(Node ast) {
-    return new Template.withStatementResult(ast);
+    return Template.withStatementResult(ast);
   }
 
   /// Creates a literal js string from [value].
@@ -316,29 +296,29 @@
     return LiteralStringFromName(name);
   }
 
-  LiteralNumber number(num value) => new LiteralNumber('$value');
+  LiteralNumber number(num value) => LiteralNumber('$value');
 
-  LiteralBool boolean(bool value) => new LiteralBool(value);
+  LiteralBool boolean(bool value) => LiteralBool(value);
 
   ArrayInitializer numArray(Iterable<int> list) =>
-      new ArrayInitializer(list.map(number).toList());
+      ArrayInitializer(list.map(number).toList());
 
   ArrayInitializer stringArray(Iterable<String> list) =>
-      new ArrayInitializer(list.map(string).toList());
+      ArrayInitializer(list.map(string).toList());
 
-  Comment comment(String text) => new Comment(text);
+  Comment comment(String text) => Comment(text);
 
   Call propertyCall(
       Expression receiver, Expression fieldName, List<Expression> arguments) {
-    return new Call(new PropertyAccess(receiver, fieldName), arguments);
+    return Call(PropertyAccess(receiver, fieldName), arguments);
   }
 
   ObjectInitializer objectLiteral(Map<String, Expression> map) {
-    List<Property> properties = <Property>[];
+    List<Property> properties = [];
     map.forEach((name, value) {
-      properties.add(new Property(string(name), value));
+      properties.add(Property(string(name), value));
     });
-    return new ObjectInitializer(properties);
+    return ObjectInitializer(properties);
   }
 }
 
@@ -392,7 +372,7 @@
 
     // Replace non-tabs with spaces, giving a print indent that matches the text
     // for tabbing.
-    String spaces = prefix.replaceAll(new RegExp(r'[^\t]'), ' ');
+    String spaces = prefix.replaceAll(RegExp(r'[^\t]'), ' ');
     return 'Error in MiniJsParser:\n${src}\n$spaces^\n$spaces$message\n';
   }
 }
@@ -436,7 +416,7 @@
   bool skippedNewline = false; // skipped newline in last getToken?
   final String src;
 
-  final List<InterpolatedNode> interpolatedValues = <InterpolatedNode>[];
+  final List<InterpolatedNode> interpolatedValues = [];
   bool get hasNamedHoles =>
       interpolatedValues.isNotEmpty && interpolatedValues.first.isNamed;
   bool get hasPositionalHoles =>
@@ -515,7 +495,7 @@
     return "Unknown: $cat";
   }
 
-  static const CATEGORIES = const <int>[
+  static const CATEGORIES = <int>[
     OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7
     OTHER, WHITESPACE, WHITESPACE, OTHER, OTHER, WHITESPACE, // 8-13
     OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 14-21
@@ -810,7 +790,7 @@
   }
 
   void error(message) {
-    throw new MiniJsParserError(this, message);
+    throw MiniJsParserError(this, message);
   }
 
   /// Returns either the name for the hole, or its integer position.
@@ -835,31 +815,31 @@
     String last = lastToken;
     if (acceptCategory(ALPHA)) {
       if (last == "true") {
-        return new LiteralBool(true);
+        return LiteralBool(true);
       } else if (last == "false") {
-        return new LiteralBool(false);
+        return LiteralBool(false);
       } else if (last == "null") {
-        return new LiteralNull();
+        return LiteralNull();
       } else if (last == "function") {
         return parseFunctionExpression();
       } else if (last == "this") {
-        return new This();
+        return This();
       } else {
-        return new VariableUse(last);
+        return VariableUse(last);
       }
     } else if (acceptCategory(LPAREN)) {
       return parseExpressionOrArrowFunction();
     } else if (acceptCategory(STRING)) {
-      return new LiteralString(last);
+      return LiteralString(last);
     } else if (acceptCategory(NUMERIC)) {
-      return new LiteralNumber(last);
+      return LiteralNumber(last);
     } else if (acceptCategory(LBRACE)) {
       return parseObjectInitializer();
     } else if (acceptCategory(LSQUARE)) {
       var values = <Expression>[];
       while (true) {
         if (acceptCategory(COMMA)) {
-          values.add(new ArrayHole());
+          values.add(ArrayHole());
           continue;
         }
         if (acceptCategory(RSQUARE)) break;
@@ -867,18 +847,18 @@
         if (acceptCategory(RSQUARE)) break;
         expectCategory(COMMA);
       }
-      return new ArrayInitializer(values);
+      return ArrayInitializer(values);
     } else if (last != null && last.startsWith("/")) {
       String regexp = getRegExp(lastPosition);
       getToken();
       String flags = lastToken;
       if (!acceptCategory(ALPHA)) flags = "";
-      Expression expression = new RegExpLiteral(regexp + flags);
+      Expression expression = RegExpLiteral(regexp + flags);
       return expression;
     } else if (acceptCategory(HASH)) {
       var nameOrPosition = parseHash();
       InterpolatedExpression expression =
-          new InterpolatedExpression(nameOrPosition);
+          InterpolatedExpression(nameOrPosition);
       interpolatedValues.add(expression);
       return expression;
     } else {
@@ -890,26 +870,26 @@
   Expression parseFunctionExpression() {
     if (lastCategory == ALPHA || lastCategory == HASH) {
       Declaration name = parseVariableDeclaration();
-      return new NamedFunction(name, parseFun());
+      return NamedFunction(name, parseFun());
     }
     return parseFun();
   }
 
   Expression parseFun() {
-    List<Parameter> params = <Parameter>[];
+    List<Parameter> params = [];
     expectCategory(LPAREN);
     if (!acceptCategory(RPAREN)) {
       for (;;) {
         if (acceptCategory(HASH)) {
           var nameOrPosition = parseHash();
           InterpolatedParameter parameter =
-              new InterpolatedParameter(nameOrPosition);
+              InterpolatedParameter(nameOrPosition);
           interpolatedValues.add(parameter);
           params.add(parameter);
         } else {
           String argumentName = lastToken;
           expectCategory(ALPHA);
-          params.add(new Parameter(argumentName));
+          params.add(Parameter(argumentName));
         }
         if (acceptCategory(COMMA)) continue;
         expectCategory(RPAREN);
@@ -931,18 +911,18 @@
     }
     expectCategory(LBRACE);
     Block block = parseBlock();
-    return new Fun(params, block, asyncModifier: asyncModifier);
+    return Fun(params, block, asyncModifier: asyncModifier);
   }
 
   Expression parseObjectInitializer() {
-    List<Property> properties = <Property>[];
+    List<Property> properties = [];
     for (;;) {
       if (acceptCategory(RBRACE)) break;
       properties.add(parseMethodDefinitionOrProperty());
       if (acceptCategory(RBRACE)) break;
       expectCategory(COMMA);
     }
-    return new ObjectInitializer(properties);
+    return ObjectInitializer(properties);
   }
 
   Property parseMethodDefinitionOrProperty() {
@@ -959,7 +939,7 @@
     } else if (acceptCategory(HASH)) {
       var nameOrPosition = parseHash();
       InterpolatedLiteral interpolatedLiteral =
-          new InterpolatedLiteral(nameOrPosition);
+          InterpolatedLiteral(nameOrPosition);
       interpolatedValues.add(interpolatedLiteral);
       propertyName = interpolatedLiteral;
     } else {
@@ -967,10 +947,10 @@
     }
     if (acceptCategory(COLON)) {
       Expression value = parseAssignment();
-      return new Property(propertyName, value);
+      return Property(propertyName, value);
     } else {
       Expression fun = parseFun();
-      return new MethodDefinition(propertyName, fun);
+      return MethodDefinition(propertyName, fun);
     }
   }
 
@@ -982,7 +962,7 @@
       } else if (acceptCategory(LSQUARE)) {
         Expression inBraces = parseExpression();
         expectCategory(RSQUARE);
-        receiver = new PropertyAccess(receiver, inBraces);
+        receiver = PropertyAccess(receiver, inBraces);
       } else {
         break;
       }
@@ -1004,14 +984,13 @@
             expectCategory(COMMA);
           }
         }
-        receiver = constructor
-            ? new New(receiver, arguments)
-            : new Call(receiver, arguments);
+        receiver =
+            constructor ? New(receiver, arguments) : Call(receiver, arguments);
         constructor = false;
       } else if (!constructor && acceptCategory(LSQUARE)) {
         Expression inBraces = parseExpression();
         expectCategory(RSQUARE);
-        receiver = new PropertyAccess(receiver, inBraces);
+        receiver = PropertyAccess(receiver, inBraces);
       } else if (!constructor && acceptCategory(DOT)) {
         receiver = getDotRhs(receiver);
       } else {
@@ -1026,9 +1005,9 @@
   Expression getDotRhs(Expression receiver) {
     if (acceptCategory(HASH)) {
       var nameOrPosition = parseHash();
-      InterpolatedSelector property = new InterpolatedSelector(nameOrPosition);
+      InterpolatedSelector property = InterpolatedSelector(nameOrPosition);
       interpolatedValues.add(property);
-      return new PropertyAccess(receiver, property);
+      return PropertyAccess(receiver, property);
     }
     String identifier = lastToken;
     // In ES5 keywords like delete and continue are allowed as property
@@ -1040,7 +1019,7 @@
     } else {
       expectCategory(ALPHA);
     }
-    return new PropertyAccess.field(receiver, identifier);
+    return PropertyAccess.field(receiver, identifier);
   }
 
   Expression parsePostfix() {
@@ -1051,7 +1030,7 @@
     if (lastCategory == SYMBOL &&
         !skippedNewline &&
         (acceptString("++") || acceptString("--"))) {
-      return new Postfix(operator, expression);
+      return Postfix(operator, expression);
     }
     // If we don't accept '++' or '--' due to skippedNewline a newline, no other
     // part of the parser will accept the token and we will get an error at the
@@ -1064,8 +1043,8 @@
     if (lastCategory == SYMBOL &&
         UNARY_OPERATORS.contains(operator) &&
         (acceptString("++") || acceptString("--") || acceptString('await'))) {
-      if (operator == "await") return new Await(parsePostfix());
-      return new Prefix(operator, parsePostfix());
+      if (operator == "await") return Await(parsePostfix());
+      return Prefix(operator, parsePostfix());
     }
     return parsePostfix();
   }
@@ -1077,8 +1056,8 @@
         operator != "++" &&
         operator != "--") {
       expectCategory(SYMBOL);
-      if (operator == "await") return new Await(parsePostfix());
-      return new Prefix(operator, parseUnaryLow());
+      if (operator == "await") return Await(parsePostfix());
+      return Prefix(operator, parseUnaryLow());
     }
     return parseUnaryHigh();
   }
@@ -1097,17 +1076,17 @@
       }
       expectCategory(SYMBOL);
       if (rhs == null || BINARY_PRECEDENCE[symbol] >= minPrecedence) {
-        if (rhs != null) lhs = new Binary(lastSymbol, lhs, rhs);
+        if (rhs != null) lhs = Binary(lastSymbol, lhs, rhs);
         minPrecedence = BINARY_PRECEDENCE[symbol];
         rhs = parseUnaryLow();
         lastSymbol = symbol;
       } else {
         Expression higher = parseBinary(BINARY_PRECEDENCE[symbol]);
-        rhs = new Binary(symbol, rhs, higher);
+        rhs = Binary(symbol, rhs, higher);
       }
     }
     if (rhs == null) return lhs;
-    return new Binary(lastSymbol, lhs, rhs);
+    return Binary(lastSymbol, lhs, rhs);
   }
 
   Expression parseConditional() {
@@ -1116,7 +1095,7 @@
     Expression ifTrue = parseAssignment();
     expectCategory(COLON);
     Expression ifFalse = parseAssignment();
-    return new Conditional(lhs, ifTrue, ifFalse);
+    return Conditional(lhs, ifTrue, ifFalse);
   }
 
   Expression parseAssignment() {
@@ -1125,12 +1104,12 @@
     if (acceptCategory(ASSIGNMENT)) {
       Expression rhs = parseAssignment();
       if (assignmentOperator == "=") {
-        return new Assignment(lhs, rhs);
+        return Assignment(lhs, rhs);
       } else {
         // Handle +=, -=, etc.
         String operator =
             assignmentOperator.substring(0, assignmentOperator.length - 1);
-        return new Assignment.compound(lhs, operator, rhs);
+        return Assignment.compound(lhs, operator, rhs);
       }
     }
     return lhs;
@@ -1140,7 +1119,7 @@
     Expression expression = parseAssignment();
     while (acceptCategory(COMMA)) {
       Expression right = parseAssignment();
-      expression = new Binary(',', expression, right);
+      expression = Binary(',', expression, right);
     }
     return expression;
   }
@@ -1148,7 +1127,7 @@
   Expression parseExpressionOrArrowFunction() {
     if (acceptCategory(RPAREN)) {
       expectCategory(ARROW);
-      return parseArrowFunctionBody(<Parameter>[]);
+      return parseArrowFunctionBody([]);
     }
     List<Expression> expressions = [parseAssignment()];
     while (acceptCategory(COMMA)) {
@@ -1159,7 +1138,7 @@
       var params = <Parameter>[];
       for (Expression e in expressions) {
         if (e is VariableUse) {
-          params.add(new Parameter(e.name));
+          params.add(Parameter(e.name));
         } else if (e is InterpolatedExpression) {
           params.add(InterpolatedParameter(e.nameOrPosition));
         } else {
@@ -1168,8 +1147,8 @@
       }
       return parseArrowFunctionBody(params);
     }
-    return expressions.reduce((Expression value, Expression element) =>
-        new Binary(',', value, element));
+    return expressions.reduce(
+        (Expression value, Expression element) => Binary(',', value, element));
   }
 
   Expression parseArrowFunctionBody(List<Parameter> params) {
@@ -1179,7 +1158,7 @@
     } else {
       body = parseAssignment();
     }
-    return new ArrowFunction(params, body);
+    return ArrowFunction(params, body);
   }
 
   VariableDeclarationList parseVariableDeclarationList() {
@@ -1196,7 +1175,7 @@
       if (acceptString("=")) {
         initializer = parseAssignment();
       }
-      initialization.add(new VariableInitialization(declaration, initializer));
+      initialization.add(VariableInitialization(declaration, initializer));
     }
 
     declare(firstVariable);
@@ -1204,7 +1183,7 @@
       Declaration variable = parseVariableDeclaration();
       declare(variable);
     }
-    return new VariableDeclarationList(initialization);
+    return VariableDeclarationList(initialization);
   }
 
   Expression parseVarDeclarationOrExpression() {
@@ -1233,19 +1212,19 @@
   }
 
   Block parseBlock() {
-    List<Statement> statements = <Statement>[];
+    List<Statement> statements = [];
 
     while (!acceptCategory(RBRACE)) {
       Statement statement = parseStatement();
       statements.add(statement);
     }
-    return new Block(statements);
+    return Block(statements);
   }
 
   Statement parseStatement() {
     if (acceptCategory(LBRACE)) return parseBlock();
 
-    if (acceptCategory(SEMICOLON)) return new EmptyStatement();
+    if (acceptCategory(SEMICOLON)) return EmptyStatement();
 
     if (lastCategory == ALPHA) {
       if (acceptString('return')) return parseReturn();
@@ -1253,11 +1232,11 @@
       if (acceptString('throw')) return parseThrow();
 
       if (acceptString('break')) {
-        return parseBreakOrContinue((label) => new Break(label));
+        return parseBreakOrContinue((label) => Break(label));
       }
 
       if (acceptString('continue')) {
-        return parseBreakOrContinue((label) => new Continue(label));
+        return parseBreakOrContinue((label) => Continue(label));
       }
 
       if (acceptString('if')) return parseIfThenElse();
@@ -1271,7 +1250,7 @@
       if (acceptString('var')) {
         Expression declarations = parseVariableDeclarationList();
         expectSemicolon();
-        return new ExpressionStatement(declarations);
+        return ExpressionStatement(declarations);
       }
 
       if (acceptString('while')) return parseWhile();
@@ -1296,7 +1275,7 @@
     Expression expression = parseExpression();
 
     if (expression is VariableUse && acceptCategory(COLON)) {
-      return new LabeledStatement(expression.name, parseStatement());
+      return LabeledStatement(expression.name, parseStatement());
     }
 
     expectSemicolon();
@@ -1307,34 +1286,34 @@
       if (expression is InterpolatedExpression) {
         assert(identical(interpolatedValues.last, expression));
         InterpolatedStatement statement =
-            new InterpolatedStatement(expression.nameOrPosition);
+            InterpolatedStatement(expression.nameOrPosition);
         interpolatedValues[interpolatedValues.length - 1] = statement;
         return statement;
       }
     }
 
-    return new ExpressionStatement(expression);
+    return ExpressionStatement(expression);
   }
 
   Statement parseReturn() {
-    if (acceptSemicolon()) return new Return();
+    if (acceptSemicolon()) return Return();
     Expression expression = parseExpression();
     expectSemicolon();
-    return new Return(expression);
+    return Return(expression);
   }
 
   Statement parseYield() {
     bool hasStar = acceptString('*');
     Expression expression = parseExpression();
     expectSemicolon();
-    return new DartYield(expression, hasStar);
+    return DartYield(expression, hasStar);
   }
 
   Statement parseThrow() {
     if (skippedNewline) error('throw expression must be on same line');
     Expression expression = parseExpression();
     expectSemicolon();
-    return new Throw(expression);
+    return Throw(expression);
   }
 
   Statement parseBreakOrContinue(constructor) {
@@ -1355,9 +1334,9 @@
     if (acceptString('else')) {
       // Resolves dangling else by binding 'else' to closest 'if'.
       Statement elseStatement = parseStatement();
-      return new If(condition, thenStatement, elseStatement);
+      return If(condition, thenStatement, elseStatement);
     } else {
-      return new If.noElse(condition, thenStatement);
+      return If.noElse(condition, thenStatement);
     }
   }
 
@@ -1380,7 +1359,7 @@
         expectCategory(RPAREN);
       }
       Statement body = parseStatement();
-      return new For(init, condition, update, body);
+      return For(init, condition, update, body);
     }
 
     expectCategory(LPAREN);
@@ -1394,9 +1373,9 @@
         Expression objectExpression = parseExpression();
         expectCategory(RPAREN);
         Statement body = parseStatement();
-        return new ForIn(
-            new VariableDeclarationList(
-                [new VariableInitialization(declaration, null)]),
+        return ForIn(
+            VariableDeclarationList(
+                [VariableInitialization(declaration, null)]),
             objectExpression,
             body);
       }
@@ -1414,20 +1393,20 @@
     if (acceptCategory(HASH)) {
       var nameOrPosition = parseHash();
       InterpolatedDeclaration declaration =
-          new InterpolatedDeclaration(nameOrPosition);
+          InterpolatedDeclaration(nameOrPosition);
       interpolatedValues.add(declaration);
       return declaration;
     } else {
       String token = lastToken;
       expectCategory(ALPHA);
-      return new VariableDeclaration(token);
+      return VariableDeclaration(token);
     }
   }
 
   Statement parseFunctionDeclaration() {
     Declaration name = parseVariableDeclaration();
     Expression fun = parseFun();
-    return new FunctionDeclaration(name, fun);
+    return FunctionDeclaration(name, fun);
   }
 
   Statement parseTry() {
@@ -1442,7 +1421,7 @@
     } else {
       if (catchPart == null) error("expected 'finally'");
     }
-    return new Try(body, catchPart, finallyPart);
+    return Try(body, catchPart, finallyPart);
   }
 
   SwitchClause parseSwitchClause() {
@@ -1463,8 +1442,8 @@
       statements.add(parseStatement());
     }
     return expression == null
-        ? new Default(new Block(statements))
-        : new Case(expression, new Block(statements));
+        ? Default(Block(statements))
+        : Case(expression, Block(statements));
   }
 
   Statement parseWhile() {
@@ -1472,7 +1451,7 @@
     Expression condition = parseExpression();
     expectCategory(RPAREN);
     Statement body = parseStatement();
-    return new While(condition, body);
+    return While(condition, body);
   }
 
   Statement parseDo() {
@@ -1483,7 +1462,7 @@
     Expression condition = parseExpression();
     expectCategory(RPAREN);
     expectSemicolon();
-    return new Do(body, condition);
+    return Do(body, condition);
   }
 
   Statement parseSwitch() {
@@ -1491,12 +1470,12 @@
     Expression key = parseExpression();
     expectCategory(RPAREN);
     expectCategory(LBRACE);
-    List<SwitchClause> clauses = <SwitchClause>[];
+    List<SwitchClause> clauses = [];
     while (lastCategory != RBRACE) {
       clauses.add(parseSwitchClause());
     }
     expectCategory(RBRACE);
-    return new Switch(key, clauses);
+    return Switch(key, clauses);
   }
 
   Catch parseCatch() {
@@ -1505,6 +1484,6 @@
     expectCategory(RPAREN);
     expectCategory(LBRACE);
     Block body = parseBlock();
-    return new Catch(errorName, body);
+    return Catch(errorName, body);
   }
 }
diff --git a/pkg/js_ast/lib/src/nodes.dart b/pkg/js_ast/lib/src/nodes.dart
index a69342b..9ded615 100644
--- a/pkg/js_ast/lib/src/nodes.dart
+++ b/pkg/js_ast/lib/src/nodes.dart
@@ -459,7 +459,7 @@
   bool get isFinalized => true;
 
   Statement toStatement() {
-    throw new UnsupportedError('toStatement');
+    throw UnsupportedError('toStatement');
   }
 
   String debugPrint() => DebugPrint(this);
@@ -482,7 +482,7 @@
     for (Statement statement in body) statement.accept1(visitor, arg);
   }
 
-  Program _clone() => new Program(body);
+  Program _clone() => Program(body);
 }
 
 abstract class Statement extends Node {
@@ -513,7 +513,7 @@
 
   Block(this.statements);
 
-  Block.empty() : this.statements = <Statement>[];
+  Block.empty() : this.statements = [];
 
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitBlock(this);
 
@@ -528,7 +528,7 @@
     for (Statement statement in statements) statement.accept1(visitor, arg);
   }
 
-  Block _clone() => new Block(statements);
+  Block _clone() => Block(statements);
 }
 
 class ExpressionStatement extends Statement {
@@ -551,7 +551,7 @@
     expression.accept1(visitor, arg);
   }
 
-  ExpressionStatement _clone() => new ExpressionStatement(expression);
+  ExpressionStatement _clone() => ExpressionStatement(expression);
 }
 
 class EmptyStatement extends Statement {
@@ -566,7 +566,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  EmptyStatement _clone() => new EmptyStatement();
+  EmptyStatement _clone() => EmptyStatement();
 }
 
 class If extends Statement {
@@ -576,7 +576,7 @@
 
   If(this.condition, this.then, this.otherwise);
 
-  If.noElse(this.condition, this.then) : this.otherwise = new EmptyStatement();
+  If.noElse(this.condition, this.then) : this.otherwise = EmptyStatement();
 
   bool get hasElse => otherwise is! EmptyStatement;
 
@@ -597,7 +597,7 @@
     otherwise.accept1(visitor, arg);
   }
 
-  If _clone() => new If(condition, then, otherwise);
+  If _clone() => If(condition, then, otherwise);
 }
 
 abstract class Loop extends Statement {
@@ -632,7 +632,7 @@
     body.accept1(visitor, arg);
   }
 
-  For _clone() => new For(init, condition, update, body);
+  For _clone() => For(init, condition, update, body);
 }
 
 class ForIn extends Loop {
@@ -660,7 +660,7 @@
     body.accept1(visitor, arg);
   }
 
-  ForIn _clone() => new ForIn(leftHandSide, object, body);
+  ForIn _clone() => ForIn(leftHandSide, object, body);
 }
 
 class While extends Loop {
@@ -683,7 +683,7 @@
     body.accept1(visitor, arg);
   }
 
-  While _clone() => new While(condition, body);
+  While _clone() => While(condition, body);
 }
 
 class Do extends Loop {
@@ -706,7 +706,7 @@
     condition.accept1(visitor, arg);
   }
 
-  Do _clone() => new Do(body, condition);
+  Do _clone() => Do(body, condition);
 }
 
 class Continue extends Statement {
@@ -723,7 +723,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  Continue _clone() => new Continue(targetLabel);
+  Continue _clone() => Continue(targetLabel);
 }
 
 class Break extends Statement {
@@ -740,7 +740,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  Break _clone() => new Break(targetLabel);
+  Break _clone() => Break(targetLabel);
 }
 
 class Return extends Statement {
@@ -761,7 +761,7 @@
     if (value != null) value.accept1(visitor, arg);
   }
 
-  Return _clone() => new Return(value);
+  Return _clone() => Return(value);
 }
 
 class Throw extends Statement {
@@ -782,7 +782,7 @@
     expression.accept1(visitor, arg);
   }
 
-  Throw _clone() => new Throw(expression);
+  Throw _clone() => Throw(expression);
 }
 
 class Try extends Statement {
@@ -811,7 +811,7 @@
     if (finallyPart != null) finallyPart.accept1(visitor, arg);
   }
 
-  Try _clone() => new Try(body, catchPart, finallyPart);
+  Try _clone() => Try(body, catchPart, finallyPart);
 }
 
 class Catch extends Node {
@@ -835,7 +835,7 @@
     body.accept1(visitor, arg);
   }
 
-  Catch _clone() => new Catch(declaration, body);
+  Catch _clone() => Catch(declaration, body);
 }
 
 class Switch extends Statement {
@@ -859,7 +859,7 @@
     for (SwitchClause clause in cases) clause.accept1(visitor, arg);
   }
 
-  Switch _clone() => new Switch(key, cases);
+  Switch _clone() => Switch(key, cases);
 }
 
 abstract class SwitchClause extends Node {
@@ -888,7 +888,7 @@
     body.accept1(visitor, arg);
   }
 
-  Case _clone() => new Case(expression, body);
+  Case _clone() => Case(expression, body);
 }
 
 class Default extends SwitchClause {
@@ -907,7 +907,7 @@
     body.accept1(visitor, arg);
   }
 
-  Default _clone() => new Default(body);
+  Default _clone() => Default(body);
 }
 
 class FunctionDeclaration extends Statement {
@@ -931,7 +931,7 @@
     function.accept1(visitor, arg);
   }
 
-  FunctionDeclaration _clone() => new FunctionDeclaration(name, function);
+  FunctionDeclaration _clone() => FunctionDeclaration(name, function);
 }
 
 class LabeledStatement extends Statement {
@@ -953,7 +953,7 @@
     body.accept1(visitor, arg);
   }
 
-  LabeledStatement _clone() => new LabeledStatement(label, body);
+  LabeledStatement _clone() => LabeledStatement(label, body);
 }
 
 class LiteralStatement extends Statement {
@@ -970,7 +970,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  LiteralStatement _clone() => new LiteralStatement(code);
+  LiteralStatement _clone() => LiteralStatement(code);
 }
 
 // Not a real JavaScript node, but represents the yield statement from a dart
@@ -995,7 +995,7 @@
     expression.accept1(visitor, arg);
   }
 
-  DartYield _clone() => new DartYield(expression, hasStar);
+  DartYield _clone() => DartYield(expression, hasStar);
 }
 
 abstract class Expression extends Node {
@@ -1003,7 +1003,7 @@
   // have precedence depending on how the deferred node is resolved.
   int get precedenceLevel;
 
-  Statement toStatement() => new ExpressionStatement(this);
+  Statement toStatement() => ExpressionStatement(this);
 }
 
 abstract class Declaration implements VariableReference {}
@@ -1081,10 +1081,8 @@
   int get precedenceLevel => PRIMARY;
 }
 
-/**
- * [VariableDeclarationList] is a subclass of [Expression] to simplify the
- * AST.
- */
+/// [VariableDeclarationList] is a subclass of [Expression] to simplify the
+/// AST.
 class VariableDeclarationList extends Expression {
   final List<VariableInitialization> declarations;
 
@@ -1113,7 +1111,7 @@
     }
   }
 
-  VariableDeclarationList _clone() => new VariableDeclarationList(declarations);
+  VariableDeclarationList _clone() => VariableDeclarationList(declarations);
 
   int get precedenceLevel => EXPRESSION;
 }
@@ -1138,7 +1136,7 @@
     enclosed.accept1(visitor, arg);
   }
 
-  Parentheses _clone() => new Parentheses(enclosed);
+  Parentheses _clone() => Parentheses(enclosed);
 
   int get precedenceLevel => PRIMARY;
 }
@@ -1172,11 +1170,11 @@
     if (value != null) value.accept1(visitor, arg);
   }
 
-  Assignment _clone() => new Assignment.compound(leftHandSide, op, value);
+  Assignment _clone() => Assignment.compound(leftHandSide, op, value);
 }
 
 class VariableInitialization extends Assignment {
-  /** [value] may be null. */
+  /// [value] may be null.
   VariableInitialization(Declaration declaration, Expression value)
       : super(declaration, value);
 
@@ -1188,8 +1186,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitVariableInitialization(this, arg);
 
-  VariableInitialization _clone() =>
-      new VariableInitialization(declaration, value);
+  VariableInitialization _clone() => VariableInitialization(declaration, value);
 }
 
 class Conditional extends Expression {
@@ -1216,7 +1213,7 @@
     otherwise.accept1(visitor, arg);
   }
 
-  Conditional _clone() => new Conditional(condition, then, otherwise);
+  Conditional _clone() => Conditional(condition, then, otherwise);
 
   int get precedenceLevel => ASSIGNMENT;
 }
@@ -1249,7 +1246,7 @@
     }
   }
 
-  Call _clone() => new Call(target, arguments);
+  Call _clone() => Call(target, arguments);
 
   int get precedenceLevel => CALL;
 }
@@ -1262,7 +1259,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitNew(this, arg);
 
-  New _clone() => new New(target, arguments);
+  New _clone() => New(target, arguments);
 }
 
 class Binary extends Expression {
@@ -1277,7 +1274,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitBinary(this, arg);
 
-  Binary _clone() => new Binary(op, left, right);
+  Binary _clone() => Binary(op, left, right);
 
   void visitChildren<T>(NodeVisitor<T> visitor) {
     left.accept(visitor);
@@ -1346,7 +1343,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitPrefix(this, arg);
 
-  Prefix _clone() => new Prefix(op, argument);
+  Prefix _clone() => Prefix(op, argument);
 
   void visitChildren<T>(NodeVisitor<T> visitor) {
     argument.accept(visitor);
@@ -1370,7 +1367,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitPostfix(this, arg);
 
-  Postfix _clone() => new Postfix(op, argument);
+  Postfix _clone() => Postfix(op, argument);
 
   void visitChildren<T>(NodeVisitor<T> visitor) {
     argument.accept(visitor);
@@ -1383,7 +1380,7 @@
   int get precedenceLevel => UNARY;
 }
 
-RegExp _identifierRE = new RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
+RegExp _identifierRE = RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
 
 abstract class VariableReference extends Expression {
   final String name;
@@ -1409,7 +1406,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitVariableUse(this, arg);
 
-  VariableUse _clone() => new VariableUse(name);
+  VariableUse _clone() => VariableUse(name);
 
   String toString() => 'VariableUse($name)';
 }
@@ -1417,14 +1414,14 @@
 class VariableDeclaration extends VariableReference implements Declaration {
   final bool allowRename;
 
-  VariableDeclaration(String name, {this.allowRename: true}) : super(name);
+  VariableDeclaration(String name, {this.allowRename = true}) : super(name);
 
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitVariableDeclaration(this);
 
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitVariableDeclaration(this, arg);
 
-  VariableDeclaration _clone() => new VariableDeclaration(name);
+  VariableDeclaration _clone() => VariableDeclaration(name);
 }
 
 class Parameter extends VariableDeclaration {
@@ -1435,7 +1432,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitParameter(this, arg);
 
-  Parameter _clone() => new Parameter(name);
+  Parameter _clone() => Parameter(name);
 }
 
 class This extends Parameter {
@@ -1446,7 +1443,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitThis(this, arg);
 
-  This _clone() => new This();
+  This _clone() => This();
 }
 
 class NamedFunction extends Expression {
@@ -1470,7 +1467,7 @@
     function.accept1(visitor, arg);
   }
 
-  NamedFunction _clone() => new NamedFunction(name, function);
+  NamedFunction _clone() => NamedFunction(name, function);
 
   int get precedenceLevel => LEFT_HAND_SIDE;
 }
@@ -1489,7 +1486,7 @@
   @override
   final AsyncModifier asyncModifier;
 
-  Fun(this.params, this.body, {this.asyncModifier: AsyncModifier.sync});
+  Fun(this.params, this.body, {this.asyncModifier = AsyncModifier.sync});
 
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitFun(this);
 
@@ -1506,7 +1503,7 @@
     body.accept1(visitor, arg);
   }
 
-  Fun _clone() => new Fun(params, body, asyncModifier: asyncModifier);
+  Fun _clone() => Fun(params, body, asyncModifier: asyncModifier);
 
   int get precedenceLevel => LEFT_HAND_SIDE;
 }
@@ -1520,7 +1517,7 @@
   final AsyncModifier asyncModifier;
 
   ArrowFunction(this.params, this.body,
-      {this.asyncModifier: AsyncModifier.sync});
+      {this.asyncModifier = AsyncModifier.sync});
 
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitArrowFunction(this);
 
@@ -1538,7 +1535,7 @@
   }
 
   ArrowFunction _clone() =>
-      new ArrowFunction(params, body, asyncModifier: asyncModifier);
+      ArrowFunction(params, body, asyncModifier: asyncModifier);
 
   int get precedenceLevel => ASSIGNMENT;
 }
@@ -1553,13 +1550,13 @@
       {this.isAsync, this.isYielding});
 
   static const AsyncModifier sync =
-      const AsyncModifier(0, "sync", isAsync: false, isYielding: false);
+      AsyncModifier(0, "sync", isAsync: false, isYielding: false);
   static const AsyncModifier async =
-      const AsyncModifier(1, "async", isAsync: true, isYielding: false);
+      AsyncModifier(1, "async", isAsync: true, isYielding: false);
   static const AsyncModifier asyncStar =
-      const AsyncModifier(2, "async*", isAsync: true, isYielding: true);
+      AsyncModifier(2, "async*", isAsync: true, isYielding: true);
   static const AsyncModifier syncStar =
-      const AsyncModifier(3, "sync*", isAsync: false, isYielding: true);
+      AsyncModifier(3, "sync*", isAsync: false, isYielding: true);
 
   static const List<AsyncModifier> values = [sync, async, asyncStar, syncStar];
 
@@ -1593,7 +1590,7 @@
     selector.accept1(visitor, arg);
   }
 
-  PropertyAccess _clone() => new PropertyAccess(receiver, selector);
+  PropertyAccess _clone() => PropertyAccess(receiver, selector);
 
   int get precedenceLevel => LEFT_HAND_SIDE;
 }
@@ -1669,7 +1666,7 @@
 
   // [visitChildren] inherited from [Literal].
 
-  LiteralBool _clone() => new LiteralBool(value);
+  LiteralBool _clone() => LiteralBool(value);
 }
 
 class LiteralNull extends Literal {
@@ -1680,7 +1677,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitLiteralNull(this, arg);
 
-  LiteralNull _clone() => new LiteralNull();
+  LiteralNull _clone() => LiteralNull();
 }
 
 class LiteralString extends Literal {
@@ -1746,7 +1743,7 @@
     for (Literal part in parts) part.accept1(visitor, arg);
   }
 
-  StringConcatenation _clone() => new StringConcatenation(this.parts);
+  StringConcatenation _clone() => StringConcatenation(this.parts);
 }
 
 class LiteralNumber extends Literal {
@@ -1761,7 +1758,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitLiteralNumber(this, arg);
 
-  LiteralNumber _clone() => new LiteralNumber(value);
+  LiteralNumber _clone() => LiteralNumber(value);
 }
 
 class ArrayInitializer extends Expression {
@@ -1782,15 +1779,13 @@
     for (Expression element in elements) element.accept1(visitor, arg);
   }
 
-  ArrayInitializer _clone() => new ArrayInitializer(elements);
+  ArrayInitializer _clone() => ArrayInitializer(elements);
 
   int get precedenceLevel => PRIMARY;
 }
 
-/**
- * An empty place in an [ArrayInitializer].
- * For example the list [1, , , 2] would contain two holes.
- */
+/// An empty place in an [ArrayInitializer].
+/// For example the list [1, , , 2] would contain two holes.
 class ArrayHole extends Expression {
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitArrayHole(this);
 
@@ -1801,7 +1796,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  ArrayHole _clone() => new ArrayHole();
+  ArrayHole _clone() => ArrayHole();
 
   int get precedenceLevel => PRIMARY;
 }
@@ -1810,14 +1805,12 @@
   final List<Property> properties;
   final bool isOneLiner;
 
-  /**
-   * Constructs a new object-initializer containing the given [properties].
-   *
-   * [isOneLiner] describes the behaviour when pretty-printing (non-minified).
-   * If true print all properties on the same line.
-   * If false print each property on a seperate line.
-   */
-  ObjectInitializer(this.properties, {this.isOneLiner: true});
+  /// Constructs a new object-initializer containing the given [properties].
+  ///
+  /// [isOneLiner] describes the behaviour when pretty-printing (non-minified).
+  /// If true print all properties on the same line.
+  /// If false print each property on a seperate line.
+  ObjectInitializer(this.properties, {this.isOneLiner = true});
 
   T accept<T>(NodeVisitor<T> visitor) => visitor.visitObjectInitializer(this);
 
@@ -1833,7 +1826,7 @@
   }
 
   ObjectInitializer _clone() =>
-      new ObjectInitializer(properties, isOneLiner: isOneLiner);
+      ObjectInitializer(properties, isOneLiner: isOneLiner);
 
   int get precedenceLevel => PRIMARY;
 }
@@ -1860,7 +1853,7 @@
     value.accept1(visitor, arg);
   }
 
-  Property _clone() => new Property(name, value);
+  Property _clone() => Property(name, value);
 }
 
 class MethodDefinition extends Node implements Property {
@@ -1919,7 +1912,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  InterpolatedExpression _clone() => new InterpolatedExpression(nameOrPosition);
+  InterpolatedExpression _clone() => InterpolatedExpression(nameOrPosition);
 
   int get precedenceLevel => PRIMARY;
 }
@@ -1938,7 +1931,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  InterpolatedLiteral _clone() => new InterpolatedLiteral(nameOrPosition);
+  InterpolatedLiteral _clone() => InterpolatedLiteral(nameOrPosition);
 }
 
 class InterpolatedParameter extends Expression
@@ -1964,7 +1957,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  InterpolatedParameter _clone() => new InterpolatedParameter(nameOrPosition);
+  InterpolatedParameter _clone() => InterpolatedParameter(nameOrPosition);
 
   int get precedenceLevel => PRIMARY;
 }
@@ -1984,7 +1977,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  InterpolatedSelector _clone() => new InterpolatedSelector(nameOrPosition);
+  InterpolatedSelector _clone() => InterpolatedSelector(nameOrPosition);
 
   int get precedenceLevel => PRIMARY;
 }
@@ -2004,7 +1997,7 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  InterpolatedStatement _clone() => new InterpolatedStatement(nameOrPosition);
+  InterpolatedStatement _clone() => InterpolatedStatement(nameOrPosition);
 }
 
 class InterpolatedDeclaration extends Expression
@@ -2025,7 +2018,7 @@
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
   InterpolatedDeclaration _clone() {
-    return new InterpolatedDeclaration(nameOrPosition);
+    return InterpolatedDeclaration(nameOrPosition);
   }
 
   @override
@@ -2035,13 +2028,11 @@
   int get precedenceLevel => PRIMARY;
 }
 
-/**
- * [RegExpLiteral]s, despite being called "Literal", do not inherit from
- * [Literal]. Indeed, regular expressions in JavaScript have a side-effect and
- * are thus not in the same category as numbers or strings.
- */
+/// [RegExpLiteral]s, despite being called "Literal", do not inherit from
+/// [Literal]. Indeed, regular expressions in JavaScript have a side-effect and
+/// are thus not in the same category as numbers or strings.
 class RegExpLiteral extends Expression {
-  /** Contains the pattern and the flags.*/
+  /// Contains the pattern and the flags.
   final String pattern;
 
   RegExpLiteral(this.pattern);
@@ -2055,19 +2046,17 @@
 
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) {}
 
-  RegExpLiteral _clone() => new RegExpLiteral(pattern);
+  RegExpLiteral _clone() => RegExpLiteral(pattern);
 
   int get precedenceLevel => PRIMARY;
 }
 
-/**
- * An asynchronous await.
- *
- * Not part of JavaScript. We desugar this expression before outputting.
- * Should only occur in a [Fun] with `asyncModifier` async or asyncStar.
- */
+/// An asynchronous await.
+///
+/// Not part of JavaScript. We desugar this expression before outputting.
+/// Should only occur in a [Fun] with `asyncModifier` async or asyncStar.
 class Await extends Expression {
-  /** The awaited expression. */
+  /// The awaited expression.
   final Expression expression;
 
   Await(this.expression);
@@ -2084,15 +2073,13 @@
   void visitChildren1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       expression.accept1(visitor, arg);
 
-  Await _clone() => new Await(expression);
+  Await _clone() => Await(expression);
 }
 
-/**
- * A comment.
- *
- * Extends [Statement] so we can add comments before statements in
- * [Block] and [Program].
- */
+/// A comment.
+///
+/// Extends [Statement] so we can add comments before statements in
+/// [Block] and [Program].
 class Comment extends Statement {
   final String comment;
 
@@ -2103,7 +2090,7 @@
   R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
       visitor.visitComment(this, arg);
 
-  Comment _clone() => new Comment(comment);
+  Comment _clone() => Comment(comment);
 
   void visitChildren<T>(NodeVisitor<T> visitor) {}
 
diff --git a/pkg/js_ast/lib/src/printer.dart b/pkg/js_ast/lib/src/printer.dart
index 8dcc958..af0c66a 100644
--- a/pkg/js_ast/lib/src/printer.dart
+++ b/pkg/js_ast/lib/src/printer.dart
@@ -49,7 +49,7 @@
 
 /// A simple implementation of [JavaScriptPrintingContext] suitable for tests.
 class SimpleJavaScriptPrintingContext extends JavaScriptPrintingContext {
-  final StringBuffer buffer = new StringBuffer();
+  final StringBuffer buffer = StringBuffer();
 
   void emit(String string) {
     buffer.write(string);
@@ -82,7 +82,7 @@
   // The current indentation level.
   int _indentLevel = 0;
   // A cache of all indentation strings used so far.
-  List<String> _indentList = <String>[""];
+  final List<String> _indentList = [""];
 
   static final identifierCharacterRegExp = RegExp(r'^[a-zA-Z_0-9$]');
   static final expressionContinuationRegExp = RegExp(r'^[-+([]');
@@ -91,15 +91,15 @@
       : options = options,
         context = context,
         shouldCompressOutput = options.shouldCompressOutput,
-        danglingElseVisitor = new DanglingElseVisitor(context),
+        danglingElseVisitor = DanglingElseVisitor(context),
         localNamer = determineRenamer(
             options.shouldCompressOutput, options.minifyLocalVariables);
 
   static LocalNamer determineRenamer(
       bool shouldCompressOutput, bool allowVariableMinification) {
     return (shouldCompressOutput && allowVariableMinification)
-        ? new MinifyRenamer()
-        : new IdentityNamer();
+        ? MinifyRenamer()
+        : IdentityNamer();
   }
 
   // The current indentation string.
@@ -141,7 +141,7 @@
     return lastAddedString.codeUnitAt(lastAddedString.length - 1);
   }
 
-  void out(String str, {bool isWhitespace: false}) {
+  void out(String str, {bool isWhitespace = false}) {
     if (str != "") {
       if (pendingSemicolon) {
         if (!shouldCompressOutput) {
@@ -218,7 +218,7 @@
   }
 
   void startNode(Node node) {
-    currentNode = new EnterExitNode(currentNode, node);
+    currentNode = EnterExitNode(currentNode, node);
     if (node is DeferredExpression) {
       startNode(node.value);
     }
@@ -356,7 +356,7 @@
     if (hasElse) {
       bool needsBraces = then.accept(danglingElseVisitor) || then is Do;
       if (needsBraces) {
-        then = new Block(<Statement>[then]);
+        then = Block(<Statement>[then]);
       }
     }
     if (shouldIndent) indent();
@@ -598,7 +598,7 @@
     // See:
     // https://connect.microsoft.com/IE/feedback/details/891889/parser-bugs
     if (body is Break && body.targetLabel == node.label) {
-      visit(new EmptyStatement());
+      visit(EmptyStatement());
       return;
     }
     outIndent("${node.label}:");
@@ -645,7 +645,7 @@
 
   @override
   visitFunctionDeclaration(FunctionDeclaration declaration) {
-    VarCollector vars = new VarCollector();
+    VarCollector vars = VarCollector();
     vars.visitFunctionDeclaration(declaration);
     indent();
     startNode(declaration.function);
@@ -1083,7 +1083,7 @@
 
   @override
   void visitNamedFunction(NamedFunction namedFunction) {
-    VarCollector vars = new VarCollector();
+    VarCollector vars = VarCollector();
     vars.visitNamedFunction(namedFunction);
     startNode(namedFunction.function);
     int closingPosition = currentNode.closingPosition =
@@ -1096,14 +1096,14 @@
 
   @override
   void visitFun(Fun fun) {
-    VarCollector vars = new VarCollector();
+    VarCollector vars = VarCollector();
     vars.visitFun(fun);
     currentNode.closingPosition = functionOut(fun, null, vars);
   }
 
   @override
   void visitArrowFunction(ArrowFunction fun) {
-    VarCollector vars = new VarCollector();
+    VarCollector vars = VarCollector();
     vars.visitArrowFunction(fun);
     currentNode.closingPosition = arrowFunctionOut(fun, vars);
   }
@@ -1307,7 +1307,7 @@
   @override
   visitMethodDefinition(MethodDefinition node) {
     propertyNameOut(node);
-    VarCollector vars = new VarCollector();
+    VarCollector vars = VarCollector();
     vars.visitMethodDefinition(node);
     startNode(node.function);
     currentNode.closingPosition = methodOut(node, vars);
@@ -1465,7 +1465,7 @@
   final List<T> list;
 
   OrderedSet()
-      : set = new Set<T>(),
+      : set = Set<T>(),
         list = <T>[];
 
   void add(T x) {
@@ -1493,8 +1493,8 @@
 
   VarCollector()
       : nested = false,
-        vars = new OrderedSet<String>(),
-        params = new OrderedSet<String>();
+        vars = OrderedSet<String>(),
+        params = OrderedSet<String>();
 
   void forEachVar(void fn(String v)) => vars.forEach(fn);
   void forEachParam(void fn(String p)) => params.forEach(fn);
@@ -1550,10 +1550,8 @@
   }
 }
 
-/**
- * Returns true, if the given node must be wrapped into braces when used
- * as then-statement in an [If] that has an else branch.
- */
+/// Returns true, if the given node must be wrapped into braces when used
+/// as then-statement in an [If] that has an else branch.
 class DanglingElseVisitor extends BaseVisitor<bool> {
   JavaScriptPrintingContext context;
 
@@ -1631,7 +1629,7 @@
   MinifyRenamer();
 
   void enterScope(VarCollector vars) {
-    maps.add(new Map<String, String>());
+    maps.add({});
     variableNumberStack.add(variableNumber);
     parameterNumberStack.add(parameterNumber);
     vars.forEachVar(declareVariable);
@@ -1711,7 +1709,7 @@
     String newName;
     if (n < LETTERS) {
       // Start naming variables a, b, c, ..., z, A, B, C, ..., Z.
-      newName = new String.fromCharCodes([nthLetter(n)]);
+      newName = String.fromCharCodes([nthLetter(n)]);
     } else {
       // Then name variables a0, a1, a2, ..., a9, b0, b1, ..., Z9, aa0, aa1, ...
       // For all functions with fewer than 500 locals this is just as compact
@@ -1734,9 +1732,9 @@
         codes.add(nthLetter((n ~/ nameSpaceSize) % LETTERS));
       }
       codes.add(charCodes.$0 + digit);
-      newName = new String.fromCharCodes(codes);
+      newName = String.fromCharCodes(codes);
     }
-    assert(new RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName));
+    assert(RegExp(r'[a-zA-Z][a-zA-Z0-9]*').hasMatch(newName));
     maps.last[oldName] = newName;
     return newName;
   }
diff --git a/pkg/js_ast/lib/src/template.dart b/pkg/js_ast/lib/src/template.dart
index 1fee00a..239a717 100644
--- a/pkg/js_ast/lib/src/template.dart
+++ b/pkg/js_ast/lib/src/template.dart
@@ -5,8 +5,8 @@
 part of js_ast;
 
 class TemplateManager {
-  Map<String, Template> expressionTemplates = new Map<String, Template>();
-  Map<String, Template> statementTemplates = new Map<String, Template>();
+  Map<String, Template> expressionTemplates = {};
+  Map<String, Template> statementTemplates = {};
 
   TemplateManager();
 
@@ -16,7 +16,7 @@
 
   Template defineExpressionTemplate(String source, Node ast) {
     Template template =
-        new Template(source, ast, isExpression: true, forceCopy: false);
+        Template(source, ast, isExpression: true, forceCopy: false);
     expressionTemplates[source] = template;
     return template;
   }
@@ -27,18 +27,16 @@
 
   Template defineStatementTemplate(String source, Node ast) {
     Template template =
-        new Template(source, ast, isExpression: false, forceCopy: false);
+        Template(source, ast, isExpression: false, forceCopy: false);
     statementTemplates[source] = template;
     return template;
   }
 }
 
-/**
- * A Template is created with JavaScript AST containing placeholders (interface
- * InterpolatedNode).  The [instantiate] method creates an AST that looks like
- * the original with the placeholders replaced by the arguments to
- * [instantiate].
- */
+/// A Template is created with JavaScript AST containing placeholders (interface
+/// InterpolatedNode).  The [instantiate] method creates an AST that looks like
+/// the original with the placeholders replaced by the arguments to
+/// [instantiate].
 class Template {
   final String source;
   final bool isExpression;
@@ -54,7 +52,7 @@
   bool get isPositional => holeNames == null;
 
   Template(this.source, this.ast,
-      {this.isExpression: true, this.forceCopy: false}) {
+      {this.isExpression = true, this.forceCopy = false}) {
     assert(this.isExpression ? ast is Expression : ast is Statement);
     _compile();
   }
@@ -81,14 +79,14 @@
 
   bool _checkNoPlaceholders() {
     InstantiatorGeneratorVisitor generator =
-        new InstantiatorGeneratorVisitor(false);
+        InstantiatorGeneratorVisitor(false);
     generator.compile(ast);
     return generator.analysis.count == 0;
   }
 
   void _compile() {
     InstantiatorGeneratorVisitor generator =
-        new InstantiatorGeneratorVisitor(forceCopy);
+        InstantiatorGeneratorVisitor(forceCopy);
     instantiator = generator.compile(ast);
     positionalArgumentCount = generator.analysis.count;
     Set<String> names = generator.analysis.holeNames;
@@ -125,26 +123,20 @@
   }
 }
 
-/**
- * An Instantiator is a Function that generates a JS AST tree or List of
- * trees. [arguments] is a List for positional templates, or Map for
- * named templates.
- */
-typedef /*Node|Iterable<Node>*/ Instantiator(var arguments);
+/// An Instantiator is a Function that generates a JS AST tree or List of
+/// trees. [arguments] is a List for positional templates, or Map for named
+/// templates.
+typedef Instantiator = /*Node|Iterable<Node>*/ Function(dynamic arguments);
 
-/**
- * InstantiatorGeneratorVisitor compiles a template.  This class compiles a tree
- * containing [InterpolatedNode]s into a function that will create a copy of the
- * tree with the interpolated nodes substituted with provided values.
- */
+/// InstantiatorGeneratorVisitor compiles a template.  This class compiles a tree
+/// containing [InterpolatedNode]s into a function that will create a copy of the
+/// tree with the interpolated nodes substituted with provided values.
 class InstantiatorGeneratorVisitor implements NodeVisitor<Instantiator> {
   final bool forceCopy;
 
-  InterpolatedNodeAnalysis analysis = new InterpolatedNodeAnalysis();
+  InterpolatedNodeAnalysis analysis = InterpolatedNodeAnalysis();
 
-  /**
-   * The entire tree is cloned if [forceCopy] is true.
-   */
+  /// The entire tree is cloned if [forceCopy] is true.
   InstantiatorGeneratorVisitor(this.forceCopy);
 
   Instantiator compile(Node node) {
@@ -181,16 +173,16 @@
     throw 'Unimplemented InstantiatorGeneratorVisitor for $node';
   }
 
-  static RegExp identifierRE = new RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
+  static RegExp identifierRE = RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
 
   static Expression convertStringToVariableUse(String value) {
     assert(identifierRE.hasMatch(value));
-    return new VariableUse(value);
+    return VariableUse(value);
   }
 
   static Expression convertStringToVariableDeclaration(String value) {
     assert(identifierRE.hasMatch(value));
-    return new VariableDeclaration(value);
+    return VariableDeclaration(value);
   }
 
   Instantiator visitInterpolatedExpression(InterpolatedExpression node) {
@@ -250,7 +242,7 @@
 
       Parameter toParameter(item) {
         if (item is Parameter) return item;
-        if (item is String) return new Parameter(item);
+        if (item is String) return Parameter(item);
         throw error('Interpolated value #$nameOrPosition is not a Parameter or'
             ' List of Parameters: $value');
       }
@@ -307,7 +299,7 @@
     List<Instantiator> instantiators =
         node.body.map(visitSplayableStatement).toList();
     return (arguments) {
-      List<Statement> statements = <Statement>[];
+      List<Statement> statements = [];
       void add(node) {
         if (node is EmptyStatement) return;
         if (node is Iterable) {
@@ -320,7 +312,7 @@
       for (Instantiator instantiator in instantiators) {
         add(instantiator(arguments));
       }
-      return new Program(statements);
+      return Program(statements);
     };
   }
 
@@ -328,7 +320,7 @@
     List<Instantiator> instantiators =
         node.statements.map(visitSplayableStatement).toList();
     return (arguments) {
-      List<Statement> statements = <Statement>[];
+      List<Statement> statements = [];
       void add(node) {
         if (node is EmptyStatement) return;
         if (node is Iterable) {
@@ -343,7 +335,7 @@
       for (Instantiator instantiator in instantiators) {
         add(instantiator(arguments));
       }
-      return new Block(statements);
+      return Block(statements);
     };
   }
 
@@ -355,7 +347,7 @@
   }
 
   Instantiator visitEmptyStatement(EmptyStatement node) =>
-      (arguments) => new EmptyStatement();
+      (arguments) => EmptyStatement();
 
   Instantiator visitIf(If node) {
     if (node.condition is InterpolatedExpression) {
@@ -391,7 +383,7 @@
           return makeOtherwise(arguments);
         }
       }
-      return new If(condition, makeThen(arguments), makeOtherwise(arguments));
+      return If(condition, makeThen(arguments), makeOtherwise(arguments));
     };
   }
 
@@ -400,7 +392,7 @@
     Instantiator makeThen = visit(node.then);
     Instantiator makeOtherwise = visit(node.otherwise);
     return (arguments) {
-      return new If(makeCondition(arguments), makeThen(arguments),
+      return If(makeCondition(arguments), makeThen(arguments),
           makeOtherwise(arguments));
     };
   }
@@ -411,7 +403,7 @@
     Instantiator makeUpdate = visitNullable(node.update);
     Instantiator makeBody = visit(node.body);
     return (arguments) {
-      return new For(makeInit(arguments), makeCondition(arguments),
+      return For(makeInit(arguments), makeCondition(arguments),
           makeUpdate(arguments), makeBody(arguments));
     };
   }
@@ -421,20 +413,20 @@
     Instantiator makeObject = visit(node.object);
     Instantiator makeBody = visit(node.body);
     return (arguments) {
-      return new ForIn(makeLeftHandSide(arguments), makeObject(arguments),
+      return ForIn(makeLeftHandSide(arguments), makeObject(arguments),
           makeBody(arguments));
     };
   }
 
   TODO(String name) {
-    throw new UnimplementedError('$this.$name');
+    throw UnimplementedError('$this.$name');
   }
 
   Instantiator visitWhile(While node) {
     Instantiator makeCondition = visit(node.condition);
     Instantiator makeBody = visit(node.body);
     return (arguments) {
-      return new While(makeCondition(arguments), makeBody(arguments));
+      return While(makeCondition(arguments), makeBody(arguments));
     };
   }
 
@@ -442,52 +434,50 @@
     Instantiator makeBody = visit(node.body);
     Instantiator makeCondition = visit(node.condition);
     return (arguments) {
-      return new Do(makeBody(arguments), makeCondition(arguments));
+      return Do(makeBody(arguments), makeCondition(arguments));
     };
   }
 
   Instantiator visitContinue(Continue node) =>
-      (arguments) => new Continue(node.targetLabel);
+      (arguments) => Continue(node.targetLabel);
 
-  Instantiator visitBreak(Break node) =>
-      (arguments) => new Break(node.targetLabel);
+  Instantiator visitBreak(Break node) => (arguments) => Break(node.targetLabel);
 
   Instantiator visitReturn(Return node) {
     Instantiator makeExpression = visitNullable(node.value);
-    return (arguments) => new Return(makeExpression(arguments));
+    return (arguments) => Return(makeExpression(arguments));
   }
 
   Instantiator visitDartYield(DartYield node) {
     Instantiator makeExpression = visit(node.expression);
-    return (arguments) =>
-        new DartYield(makeExpression(arguments), node.hasStar);
+    return (arguments) => DartYield(makeExpression(arguments), node.hasStar);
   }
 
   Instantiator visitThrow(Throw node) {
     Instantiator makeExpression = visit(node.expression);
-    return (arguments) => new Throw(makeExpression(arguments));
+    return (arguments) => Throw(makeExpression(arguments));
   }
 
   Instantiator visitTry(Try node) {
     Instantiator makeBody = visit(node.body);
     Instantiator makeCatch = visitNullable(node.catchPart);
     Instantiator makeFinally = visitNullable(node.finallyPart);
-    return (arguments) => new Try(
-        makeBody(arguments), makeCatch(arguments), makeFinally(arguments));
+    return (arguments) =>
+        Try(makeBody(arguments), makeCatch(arguments), makeFinally(arguments));
   }
 
   Instantiator visitCatch(Catch node) {
     Instantiator makeDeclaration = visit(node.declaration);
     Instantiator makeBody = visit(node.body);
     return (arguments) =>
-        new Catch(makeDeclaration(arguments), makeBody(arguments));
+        Catch(makeDeclaration(arguments), makeBody(arguments));
   }
 
   Instantiator visitSwitch(Switch node) {
     Instantiator makeKey = visit(node.key);
     Iterable<Instantiator> makeCases = node.cases.map(visit);
     return (arguments) {
-      return new Switch(
+      return Switch(
           makeKey(arguments),
           makeCases
               .map<SwitchClause>((Instantiator makeCase) => makeCase(arguments))
@@ -499,14 +489,14 @@
     Instantiator makeExpression = visit(node.expression);
     Instantiator makeBody = visit(node.body);
     return (arguments) {
-      return new Case(makeExpression(arguments), makeBody(arguments));
+      return Case(makeExpression(arguments), makeBody(arguments));
     };
   }
 
   Instantiator visitDefault(Default node) {
     Instantiator makeBody = visit(node.body);
     return (arguments) {
-      return new Default(makeBody(arguments));
+      return Default(makeBody(arguments));
     };
   }
 
@@ -514,12 +504,12 @@
     Instantiator makeName = visit(node.name);
     Instantiator makeFunction = visit(node.function);
     return (arguments) =>
-        new FunctionDeclaration(makeName(arguments), makeFunction(arguments));
+        FunctionDeclaration(makeName(arguments), makeFunction(arguments));
   }
 
   Instantiator visitLabeledStatement(LabeledStatement node) {
     Instantiator makeBody = visit(node.body);
-    return (arguments) => new LabeledStatement(node.label, makeBody(arguments));
+    return (arguments) => LabeledStatement(node.label, makeBody(arguments));
   }
 
   Instantiator visitLiteralStatement(LiteralStatement node) =>
@@ -531,12 +521,12 @@
     List<Instantiator> declarationMakers =
         node.declarations.map(visit).toList();
     return (arguments) {
-      List<VariableInitialization> declarations = <VariableInitialization>[];
+      List<VariableInitialization> declarations = [];
       for (Instantiator instantiator in declarationMakers) {
         var result = instantiator(arguments);
         declarations.add(result);
       }
-      return new VariableDeclarationList(declarations);
+      return VariableDeclarationList(declarations);
     };
   }
 
@@ -545,7 +535,7 @@
     String op = node.op;
     Instantiator makeValue = visitNullable(node.value);
     return (arguments) {
-      return new Assignment.compound(
+      return Assignment.compound(
           makeLeftHandSide(arguments), op, makeValue(arguments));
     };
   }
@@ -554,7 +544,7 @@
     Instantiator makeDeclaration = visit(node.declaration);
     Instantiator makeValue = visitNullable(node.value);
     return (arguments) {
-      return new VariableInitialization(
+      return VariableInitialization(
           makeDeclaration(arguments), makeValue(arguments));
     };
   }
@@ -563,15 +553,15 @@
     Instantiator makeCondition = visit(cond.condition);
     Instantiator makeThen = visit(cond.then);
     Instantiator makeOtherwise = visit(cond.otherwise);
-    return (arguments) => new Conditional(makeCondition(arguments),
+    return (arguments) => Conditional(makeCondition(arguments),
         makeThen(arguments), makeOtherwise(arguments));
   }
 
   Instantiator visitNew(New node) =>
-      handleCallOrNew(node, (target, arguments) => new New(target, arguments));
+      handleCallOrNew(node, (target, arguments) => New(target, arguments));
 
   Instantiator visitCall(Call node) =>
-      handleCallOrNew(node, (target, arguments) => new Call(target, arguments));
+      handleCallOrNew(node, (target, arguments) => Call(target, arguments));
 
   Instantiator handleCallOrNew(Call node, finish(target, arguments)) {
     Instantiator makeTarget = visit(node.target);
@@ -582,7 +572,7 @@
     // copying.
     return (arguments) {
       Node target = makeTarget(arguments);
-      List<Expression> callArguments = <Expression>[];
+      List<Expression> callArguments = [];
       for (Instantiator instantiator in argumentMakers) {
         var result = instantiator(arguments);
         if (result is Iterable) {
@@ -599,45 +589,44 @@
     Instantiator makeLeft = visit(node.left);
     Instantiator makeRight = visit(node.right);
     String op = node.op;
-    return (arguments) =>
-        new Binary(op, makeLeft(arguments), makeRight(arguments));
+    return (arguments) => Binary(op, makeLeft(arguments), makeRight(arguments));
   }
 
   Instantiator visitPrefix(Prefix node) {
     Instantiator makeOperand = visit(node.argument);
     String op = node.op;
-    return (arguments) => new Prefix(op, makeOperand(arguments));
+    return (arguments) => Prefix(op, makeOperand(arguments));
   }
 
   Instantiator visitPostfix(Postfix node) {
     Instantiator makeOperand = visit(node.argument);
     String op = node.op;
-    return (arguments) => new Postfix(op, makeOperand(arguments));
+    return (arguments) => Postfix(op, makeOperand(arguments));
   }
 
   Instantiator visitVariableUse(VariableUse node) =>
-      (arguments) => new VariableUse(node.name);
+      (arguments) => VariableUse(node.name);
 
-  Instantiator visitThis(This node) => (arguments) => new This();
+  Instantiator visitThis(This node) => (arguments) => This();
 
   Instantiator visitVariableDeclaration(VariableDeclaration node) =>
-      (arguments) => new VariableDeclaration(node.name);
+      (arguments) => VariableDeclaration(node.name);
 
   Instantiator visitParameter(Parameter node) =>
-      (arguments) => new Parameter(node.name);
+      (arguments) => Parameter(node.name);
 
   Instantiator visitAccess(PropertyAccess node) {
     Instantiator makeReceiver = visit(node.receiver);
     Instantiator makeSelector = visit(node.selector);
     return (arguments) =>
-        new PropertyAccess(makeReceiver(arguments), makeSelector(arguments));
+        PropertyAccess(makeReceiver(arguments), makeSelector(arguments));
   }
 
   Instantiator visitNamedFunction(NamedFunction node) {
     Instantiator makeDeclaration = visit(node.name);
     Instantiator makeFunction = visit(node.function);
     return (arguments) =>
-        new NamedFunction(makeDeclaration(arguments), makeFunction(arguments));
+        NamedFunction(makeDeclaration(arguments), makeFunction(arguments));
   }
 
   Instantiator visitFun(Fun node) {
@@ -645,7 +634,7 @@
     Instantiator makeBody = visit(node.body);
     // TODO(sra): Avoid copying params if no interpolation or forced copying.
     return (arguments) {
-      List<Parameter> params = <Parameter>[];
+      List<Parameter> params = [];
       for (Instantiator instantiator in paramMakers) {
         var result = instantiator(arguments);
         if (result is Iterable) {
@@ -655,7 +644,7 @@
         }
       }
       Statement body = makeBody(arguments);
-      return new Fun(params, body);
+      return Fun(params, body);
     };
   }
 
@@ -664,7 +653,7 @@
     Instantiator makeBody = visit(node.body);
     // TODO(sra): Avoid copying params if no interpolation or forced copying.
     return (arguments) {
-      List<Parameter> params = <Parameter>[];
+      List<Parameter> params = [];
       for (Instantiator instantiator in paramMakers) {
         var result = instantiator(arguments);
         if (result is Iterable) {
@@ -675,7 +664,7 @@
       }
       // Either a Block or Expression.
       Node body = makeBody(arguments);
-      return new ArrowFunction(params, body);
+      return ArrowFunction(params, body);
     };
   }
 
@@ -688,16 +677,16 @@
   Instantiator visitDeferredString(DeferredString node) => (arguments) => node;
 
   Instantiator visitLiteralBool(LiteralBool node) =>
-      (arguments) => new LiteralBool(node.value);
+      (arguments) => LiteralBool(node.value);
 
   Instantiator visitLiteralString(LiteralString node) =>
-      (arguments) => new LiteralString(node.value);
+      (arguments) => LiteralString(node.value);
 
   Instantiator visitLiteralNumber(LiteralNumber node) =>
-      (arguments) => new LiteralNumber(node.value);
+      (arguments) => LiteralNumber(node.value);
 
   Instantiator visitLiteralNull(LiteralNull node) =>
-      (arguments) => new LiteralNull();
+      (arguments) => LiteralNull();
 
   Instantiator visitStringConcatenation(StringConcatenation node) {
     List<Instantiator> partMakers =
@@ -706,7 +695,7 @@
       List<Literal> parts = partMakers
           .map((Instantiator instantiator) => instantiator(arguments))
           .toList(growable: false);
-      return new StringConcatenation(parts);
+      return StringConcatenation(parts);
     };
   }
 
@@ -729,12 +718,12 @@
           .map<Expression>(
               (Instantiator instantiator) => instantiator(arguments))
           .toList(growable: false);
-      return new ArrayInitializer(elements);
+      return ArrayInitializer(elements);
     };
   }
 
   Instantiator visitArrayHole(ArrayHole node) {
-    return (arguments) => new ArrayHole();
+    return (arguments) => ArrayHole();
   }
 
   Instantiator visitObjectInitializer(ObjectInitializer node) {
@@ -742,7 +731,7 @@
         node.properties.map(visitSplayable).toList();
     bool isOneLiner = node.isOneLiner;
     return (arguments) {
-      List<Property> properties = <Property>[];
+      List<Property> properties = [];
       for (Instantiator instantiator in propertyMakers) {
         var result = instantiator(arguments);
         if (result is Iterable) {
@@ -751,7 +740,7 @@
           properties.add(result);
         }
       }
-      return new ObjectInitializer(properties, isOneLiner: isOneLiner);
+      return ObjectInitializer(properties, isOneLiner: isOneLiner);
     };
   }
 
@@ -759,7 +748,7 @@
     Instantiator makeName = visit(node.name);
     Instantiator makeValue = visit(node.value);
     return (arguments) {
-      return new Property(makeName(arguments), makeValue(arguments));
+      return Property(makeName(arguments), makeValue(arguments));
     };
   }
 
@@ -767,30 +756,28 @@
     Instantiator makeName = visit(node.name);
     Instantiator makeFunction = visit(node.function);
     return (arguments) {
-      return new MethodDefinition(makeName(arguments), makeFunction(arguments));
+      return MethodDefinition(makeName(arguments), makeFunction(arguments));
     };
   }
 
   Instantiator visitRegExpLiteral(RegExpLiteral node) =>
-      (arguments) => new RegExpLiteral(node.pattern);
+      (arguments) => RegExpLiteral(node.pattern);
 
   Instantiator visitComment(Comment node) => TODO('visitComment');
 
   Instantiator visitAwait(Await node) {
     Instantiator makeExpression = visit(node.expression);
     return (arguments) {
-      return new Await(makeExpression(arguments));
+      return Await(makeExpression(arguments));
     };
   }
 }
 
-/**
- * InterpolatedNodeAnalysis determines which AST trees contain
- * [InterpolatedNode]s, and the names of the named interpolated nodes.
- */
+/// InterpolatedNodeAnalysis determines which AST trees contain
+/// [InterpolatedNode]s, and the names of the named interpolated nodes.
 class InterpolatedNodeAnalysis extends BaseVisitor {
-  final Set<Node> containsInterpolatedNode = new Set<Node>();
-  final Set<String> holeNames = new Set<String>();
+  final Set<Node> containsInterpolatedNode = {};
+  final Set<String> holeNames = {};
   int count = 0;
 
   InterpolatedNodeAnalysis();
diff --git a/pkg/js_ast/test/printer_callback_test.dart b/pkg/js_ast/test/printer_callback_test.dart
index 153c41d..a54ddbc 100644
--- a/pkg/js_ast/test/printer_callback_test.dart
+++ b/pkg/js_ast/test/printer_callback_test.dart
@@ -28,8 +28,8 @@
   const TestCase(this.data, [this.environment = const {}]);
 }
 
-const List<TestCase> DATA = const <TestCase>[
-  const TestCase(const {
+const List<TestCase> DATA = <TestCase>[
+  TestCase({
     TestMode.NONE: """
 function(a, b) {
   return null;
@@ -47,7 +47,7 @@
   return null@5;
 @4}@3@0"""
   }),
-  const TestCase(const {
+  TestCase({
     TestMode.NONE: """
 function() {
   if (true) {
@@ -105,7 +105,7 @@
 @26  }@22
 @20}@1@0""",
   }),
-  const TestCase(const {
+  TestCase({
     TestMode.NONE: """
 function() {
   function foo() {
@@ -127,7 +127,7 @@
   }@5@3
 @2}@1@0"""
   }),
-  const TestCase(const {
+  TestCase({
     TestMode.INPUT: """
 function() {
   a['b'];
@@ -154,13 +154,13 @@
 @2  [1@8,,@9 2@10]@7;
 @6}@1@0""",
   }),
-  const TestCase(const {
+  TestCase({
     TestMode.INPUT: "a.#nameTemplate = #nameTemplate",
     TestMode.NONE: "a.nameValue = nameValue",
     TestMode.ENTER: "@0@1@2a.@3nameValue = @3nameValue",
     TestMode.DELIMITER: "a.nameValue = nameValue",
     TestMode.EXIT: "a@2.nameValue@3@1 = nameValue@3@0",
-  }, const {
+  }, {
     'nameTemplate': 'nameValue'
   }),
 ];
@@ -179,16 +179,16 @@
     // Input is the same as output.
     code = map[TestMode.NONE];
   }
-  JavaScriptPrintingOptions options = new JavaScriptPrintingOptions();
+  JavaScriptPrintingOptions options = JavaScriptPrintingOptions();
   Map arguments = {};
   testCase.environment.forEach((String name, String value) {
-    arguments[name] = new FixedName(value);
+    arguments[name] = FixedName(value);
   });
   Node node = js.parseForeignJS(code).instantiate(arguments);
   map.forEach((TestMode mode, String expectedOutput) {
     if (mode == TestMode.INPUT) return;
-    Context context = new Context(mode);
-    new Printer(options, context).visit(node);
+    Context context = Context(mode);
+    Printer(options, context).visit(node);
     // TODO(johnniwinther): Remove `replaceAll(...)` when dart2js behaves as the
     // VM on newline in multiline strings.
     expect(context.getText(), equals(expectedOutput.replaceAll('\r\n', '\n')),
@@ -227,7 +227,7 @@
   String getText() {
     String text = super.getText();
     int offset = 0;
-    StringBuffer sb = new StringBuffer();
+    StringBuffer sb = StringBuffer();
     for (int position in tagMap.keys.toList()..sort()) {
       if (offset < position) {
         sb.write(text.substring(offset, position));
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index 2059774..db0c8fc 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -27,7 +27,6 @@
 coverage_optimized_function_test: Pass, Slow
 evaluate_activation_test/instance: RuntimeError # http://dartbug.com/20047
 evaluate_activation_test/scope: RuntimeError # http://dartbug.com/20047
-get_source_report_test: RuntimeError # Should pass again when constant evaluation is relanded, see http://dartbug.com/36600
 pause_on_exception_from_slow_path_test: Pass, Slow
 pause_on_unhandled_async_exceptions2_test: Pass, Slow
 
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index f926c9a..d0bfe512 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -27,7 +27,6 @@
 coverage_optimized_function_test: Pass, Slow
 evaluate_activation_test/instance: RuntimeError # http://dartbug.com/20047
 evaluate_activation_test/scope: RuntimeError # http://dartbug.com/20047
-get_source_report_test: RuntimeError # Should pass again when constant evaluation is relanded, see http://dartbug.com/36600
 pause_on_exception_from_slow_path_test: Pass, Slow
 pause_on_unhandled_async_exceptions2_test: Pass, Slow
 
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index 99f76c0..97c5270 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -184,17 +184,17 @@
 static constexpr dart::compiler::target::word ICData_owner_offset = 20;
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
 static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
-static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
-static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
-static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
+static constexpr dart::compiler::target::word Isolate_current_tag_offset = 24;
+static constexpr dart::compiler::target::word Isolate_default_tag_offset = 28;
+static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 32;
 static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
     20;
 static constexpr dart::compiler::target::word
     IsolateGroup_shared_class_table_offset = 8;
 static constexpr dart::compiler::target::word
     IsolateGroup_cached_class_table_table_offset = 16;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 36;
-static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 40;
+static constexpr dart::compiler::target::word Isolate_user_tag_offset = 20;
 static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 12;
 static constexpr dart::compiler::target::word
     LinkedHashBase_deleted_keys_offset = 20;
@@ -727,17 +727,17 @@
 static constexpr dart::compiler::target::word ICData_owner_offset = 40;
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
-static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
-static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
-static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
+static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
+static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
+static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
 static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
     40;
 static constexpr dart::compiler::target::word
     IsolateGroup_shared_class_table_offset = 16;
 static constexpr dart::compiler::target::word
     IsolateGroup_cached_class_table_table_offset = 32;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
-static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
+static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 24;
 static constexpr dart::compiler::target::word
     LinkedHashBase_deleted_keys_offset = 40;
@@ -1275,17 +1275,17 @@
 static constexpr dart::compiler::target::word ICData_owner_offset = 20;
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 28;
 static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
-static constexpr dart::compiler::target::word Isolate_current_tag_offset = 20;
-static constexpr dart::compiler::target::word Isolate_default_tag_offset = 24;
-static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 28;
+static constexpr dart::compiler::target::word Isolate_current_tag_offset = 24;
+static constexpr dart::compiler::target::word Isolate_default_tag_offset = 28;
+static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 32;
 static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
     20;
 static constexpr dart::compiler::target::word
     IsolateGroup_shared_class_table_offset = 8;
 static constexpr dart::compiler::target::word
     IsolateGroup_cached_class_table_table_offset = 16;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 36;
-static constexpr dart::compiler::target::word Isolate_user_tag_offset = 16;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 40;
+static constexpr dart::compiler::target::word Isolate_user_tag_offset = 20;
 static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 12;
 static constexpr dart::compiler::target::word
     LinkedHashBase_deleted_keys_offset = 20;
@@ -1815,17 +1815,17 @@
 static constexpr dart::compiler::target::word ICData_owner_offset = 40;
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
-static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
-static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
-static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
+static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
+static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
+static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
 static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
     40;
 static constexpr dart::compiler::target::word
     IsolateGroup_shared_class_table_offset = 16;
 static constexpr dart::compiler::target::word
     IsolateGroup_cached_class_table_table_offset = 32;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
-static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
+static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 24;
 static constexpr dart::compiler::target::word
     LinkedHashBase_deleted_keys_offset = 40;
@@ -2364,17 +2364,17 @@
 static constexpr dart::compiler::target::word ICData_owner_offset = 40;
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
-static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
-static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
-static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
+static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
+static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
+static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
 static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
     40;
 static constexpr dart::compiler::target::word
     IsolateGroup_shared_class_table_offset = 16;
 static constexpr dart::compiler::target::word
     IsolateGroup_cached_class_table_table_offset = 32;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
-static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
+static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 16;
 static constexpr dart::compiler::target::word
     LinkedHashBase_deleted_keys_offset = 24;
@@ -2912,17 +2912,17 @@
 static constexpr dart::compiler::target::word ICData_owner_offset = 40;
 static constexpr dart::compiler::target::word ICData_state_bits_offset = 52;
 static constexpr dart::compiler::target::word Int32x4_value_offset = 8;
-static constexpr dart::compiler::target::word Isolate_current_tag_offset = 40;
-static constexpr dart::compiler::target::word Isolate_default_tag_offset = 48;
-static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 56;
+static constexpr dart::compiler::target::word Isolate_current_tag_offset = 48;
+static constexpr dart::compiler::target::word Isolate_default_tag_offset = 56;
+static constexpr dart::compiler::target::word Isolate_ic_miss_code_offset = 64;
 static constexpr dart::compiler::target::word IsolateGroup_object_store_offset =
     40;
 static constexpr dart::compiler::target::word
     IsolateGroup_shared_class_table_offset = 16;
 static constexpr dart::compiler::target::word
     IsolateGroup_cached_class_table_table_offset = 32;
-static constexpr dart::compiler::target::word Isolate_single_step_offset = 72;
-static constexpr dart::compiler::target::word Isolate_user_tag_offset = 32;
+static constexpr dart::compiler::target::word Isolate_single_step_offset = 80;
+static constexpr dart::compiler::target::word Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word LinkedHashBase_data_offset = 16;
 static constexpr dart::compiler::target::word
     LinkedHashBase_deleted_keys_offset = 24;
@@ -6720,11 +6720,11 @@
 static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 20;
 static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
-    20;
-static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     24;
-static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     28;
+static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+    32;
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_object_store_offset = 20;
 static constexpr dart::compiler::target::word
@@ -6732,8 +6732,8 @@
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_cached_class_table_table_offset = 16;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    36;
-static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 16;
+    40;
+static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 20;
 static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
     12;
 static constexpr dart::compiler::target::word
@@ -7327,11 +7327,11 @@
 static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
 static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
-    40;
-static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
-static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     56;
+static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+    64;
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_object_store_offset = 40;
 static constexpr dart::compiler::target::word
@@ -7339,8 +7339,8 @@
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_cached_class_table_table_offset = 32;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    72;
-static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
+    80;
+static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
     24;
 static constexpr dart::compiler::target::word
@@ -7940,11 +7940,11 @@
 static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
 static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
-    40;
-static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
-static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     56;
+static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+    64;
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_object_store_offset = 40;
 static constexpr dart::compiler::target::word
@@ -7952,8 +7952,8 @@
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_cached_class_table_table_offset = 32;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    72;
-static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
+    80;
+static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
     24;
 static constexpr dart::compiler::target::word
@@ -8550,11 +8550,11 @@
 static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
 static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
-    40;
-static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
-static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     56;
+static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+    64;
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_object_store_offset = 40;
 static constexpr dart::compiler::target::word
@@ -8562,8 +8562,8 @@
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_cached_class_table_table_offset = 32;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    72;
-static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
+    80;
+static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
     16;
 static constexpr dart::compiler::target::word
@@ -9159,11 +9159,11 @@
 static constexpr dart::compiler::target::word AOT_ICData_state_bits_offset = 40;
 static constexpr dart::compiler::target::word AOT_Int32x4_value_offset = 8;
 static constexpr dart::compiler::target::word AOT_Isolate_current_tag_offset =
-    40;
-static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     48;
-static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+static constexpr dart::compiler::target::word AOT_Isolate_default_tag_offset =
     56;
+static constexpr dart::compiler::target::word AOT_Isolate_ic_miss_code_offset =
+    64;
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_object_store_offset = 40;
 static constexpr dart::compiler::target::word
@@ -9171,8 +9171,8 @@
 static constexpr dart::compiler::target::word
     AOT_IsolateGroup_cached_class_table_table_offset = 32;
 static constexpr dart::compiler::target::word AOT_Isolate_single_step_offset =
-    72;
-static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 32;
+    80;
+static constexpr dart::compiler::target::word AOT_Isolate_user_tag_offset = 40;
 static constexpr dart::compiler::target::word AOT_LinkedHashBase_data_offset =
     16;
 static constexpr dart::compiler::target::word
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 31b26f8..aadc7a4 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2364,6 +2364,55 @@
   }
   current_allocation_sample_block_ = current;
 }
+
+void Isolate::FreeSampleBlock(SampleBlock* block) {
+  if (block == nullptr) {
+    return;
+  }
+  SampleBlock* head;
+  // We're pushing the freed sample block to the front of the free_block_list_,
+  // which means the last element of the list will be the oldest freed sample.
+  do {
+    head = free_block_list_.load(std::memory_order_acquire);
+    block->next_free_ = head;
+  } while (!free_block_list_.compare_exchange_weak(head, block,
+                                                   std::memory_order_release));
+}
+
+void Isolate::ProcessFreeSampleBlocks(Thread* thread) {
+  SampleBlock* head = free_block_list_.exchange(nullptr);
+  // Reverse the list before processing so older blocks are streamed and reused
+  // first.
+  SampleBlock* reversed_head = nullptr;
+  while (head != nullptr) {
+    SampleBlock* next = head->next_free_;
+    if (reversed_head == nullptr) {
+      reversed_head = head;
+      reversed_head->next_free_ = nullptr;
+    } else {
+      head->next_free_ = reversed_head;
+      reversed_head = head;
+    }
+    head = next;
+  }
+  head = reversed_head;
+  while (head != nullptr) {
+    if (Service::profiler_stream.enabled() && !IsSystemIsolate(this)) {
+      StackZone zone(thread);
+      HandleScope handle_scope(thread);
+      Profile profile;
+      profile.Build(thread, nullptr, head);
+      ServiceEvent event(this, ServiceEvent::kCpuSamples);
+      event.set_cpu_profile(&profile);
+      Service::HandleEvent(&event);
+    }
+    SampleBlock* next = head->next_free_;
+    head->next_free_ = nullptr;
+    head->evictable_ = true;
+    Profiler::sample_block_buffer()->FreeBlock(head);
+    head = next;
+  }
+}
 #endif  // !defined(PRODUCT)
 
 // static
@@ -2464,6 +2513,25 @@
     ServiceIsolate::SendIsolateShutdownMessage();
 #if !defined(PRODUCT)
     debugger()->Shutdown();
+    // Cleanup profiler state.
+    SampleBlock* cpu_block = current_sample_block();
+    if (cpu_block != nullptr) {
+      cpu_block->release_block();
+    }
+    SampleBlock* allocation_block = current_allocation_sample_block();
+    if (allocation_block != nullptr) {
+      allocation_block->release_block();
+    }
+
+    // Process the previously assigned sample blocks if we're using the
+    // profiler's sample buffer. Some tests create their own SampleBlockBuffer
+    // and handle block processing themselves.
+    if ((cpu_block != nullptr || allocation_block != nullptr) &&
+        Profiler::sample_block_buffer() != nullptr) {
+      StackZone zone(thread);
+      HandleScope handle_scope(thread);
+      Profiler::sample_block_buffer()->ProcessCompletedBlocks();
+    }
 #endif
   }
 
@@ -2522,26 +2590,6 @@
   // requests anymore.
   Thread::ExitIsolate();
 
-#if !defined(PRODUCT)
-  // Cleanup profiler state.
-  SampleBlock* cpu_block = isolate->current_sample_block();
-  if (cpu_block != nullptr) {
-    cpu_block->release_block();
-  }
-  SampleBlock* allocation_block = isolate->current_allocation_sample_block();
-  if (allocation_block != nullptr) {
-    allocation_block->release_block();
-  }
-
-  // Process the previously assigned sample blocks if we're using the
-  // profiler's sample buffer. Some tests create their own SampleBlockBuffer
-  // and handle block processing themselves.
-  if ((cpu_block != nullptr || allocation_block != nullptr) &&
-      Profiler::sample_block_buffer() != nullptr) {
-    Profiler::sample_block_buffer()->ProcessCompletedBlocks();
-  }
-#endif  // !defined(PRODUCT)
-
   // Now it's safe to delete the isolate.
   delete isolate;
 
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 8b4f88b..8dc8f37 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -1099,6 +1099,13 @@
   SampleBlock* current_sample_block() const { return current_sample_block_; }
   void set_current_sample_block(SampleBlock* current);
 
+  void FreeSampleBlock(SampleBlock* block);
+  void ProcessFreeSampleBlocks(Thread* thread);
+  bool should_process_blocks() const {
+    return free_block_list_.load(std::memory_order_relaxed) != nullptr;
+  }
+  std::atomic<SampleBlock*> free_block_list_ = nullptr;
+
   // Returns the current SampleBlock used to track Dart allocation samples.
   //
   // Allocations should only occur on the mutator thread for an isolate, so we
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 857404b..da7fc64 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -12788,7 +12788,6 @@
 }
 
 ArrayPtr Library::LoadedScripts() const {
-  ASSERT(Thread::Current()->IsMutatorThread());
   // We compute the list of loaded scripts lazily. The result is
   // cached in loaded_scripts_.
   if (loaded_scripts() == Array::null()) {
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc
index fae638f..2d8b2a5 100644
--- a/runtime/vm/profiler.cc
+++ b/runtime/vm/profiler.cc
@@ -66,6 +66,13 @@
 AllocationSampleBuffer* Profiler::allocation_sample_buffer_ = nullptr;
 ProfilerCounters Profiler::counters_ = {};
 
+bool SampleBlockProcessor::initialized_ = false;
+bool SampleBlockProcessor::shutdown_ = false;
+bool SampleBlockProcessor::thread_running_ = false;
+ThreadJoinId SampleBlockProcessor::processor_thread_id_ =
+    OSThread::kInvalidThreadJoinId;
+Monitor* SampleBlockProcessor::monitor_ = nullptr;
+
 void Profiler::Init() {
   // Place some sane restrictions on user controlled flags.
   SetSampleDepth(FLAG_max_profile_depth);
@@ -83,6 +90,8 @@
   }
   ThreadInterrupter::Init();
   ThreadInterrupter::Startup();
+  SampleBlockProcessor::Init();
+  SampleBlockProcessor::Startup();
   initialized_ = true;
 }
 
@@ -113,6 +122,7 @@
   }
   ASSERT(initialized_);
   ThreadInterrupter::Cleanup();
+  SampleBlockProcessor::Cleanup();
   SampleBlockCleanupVisitor visitor;
   Isolate::VisitIsolates(&visitor);
   initialized_ = false;
@@ -223,21 +233,9 @@
 
 void SampleBlockBuffer::ProcessCompletedBlocks() {
   Thread* thread = Thread::Current();
+  DisableThreadInterruptsScope dtis(thread);
   int64_t start = Dart_TimelineGetMicros();
-  for (intptr_t i = 0; i < capacity_; ++i) {
-    SampleBlock* block = &blocks_[i];
-    if (block->is_full() && !block->evictable()) {
-      if (Service::profiler_stream.enabled()) {
-        Profile profile(block->owner());
-        profile.Build(thread, nullptr, block);
-        ServiceEvent event(block->owner(), ServiceEvent::kCpuSamples);
-        event.set_cpu_profile(&profile);
-        Service::HandleEvent(&event);
-      }
-      block->evictable_ = true;
-      FreeBlock(block);
-    }
-  }
+  thread->isolate()->ProcessFreeSampleBlocks(thread);
   int64_t end = Dart_TimelineGetMicros();
   Dart_TimelineEvent("SampleBlockBuffer::ProcessCompletedBlocks", start, end,
                      Dart_Timeline_Event_Duration, 0, nullptr, nullptr);
@@ -306,12 +304,6 @@
   Sample* sample = nullptr;
   if (block != nullptr) {
     sample = block->ReserveSample();
-    if (sample != nullptr && block->is_full()) {
-      // TODO(bkonyi): remove once streaming is re-enabled.
-      // https://github.com/dart-lang/sdk/issues/46825
-      block->evictable_ = true;
-      FreeBlock(block);
-    }
   }
   if (sample != nullptr) {
     return sample;
@@ -326,6 +318,7 @@
       return nullptr;
     }
     isolate->set_current_allocation_sample_block(next);
+    isolate->FreeSampleBlock(block);
   } else {
     MutexLocker locker(isolate->current_sample_block_lock());
     next = ReserveSampleBlock();
@@ -334,13 +327,14 @@
       return nullptr;
     }
     isolate->set_current_sample_block(next);
+    isolate->FreeSampleBlock(block);
   }
   next->set_is_allocation_block(allocation_sample);
 
-  can_process_block_.store(true);
-  // TODO(bkonyi): re-enable after block streaming is fixed.
-  // See https://github.com/dart-lang/sdk/issues/46825
-  // isolate->mutator_thread()->ScheduleInterrupts(Thread::kVMInterrupt);
+  bool scheduled = can_process_block_.exchange(true);
+  if (!scheduled) {
+    isolate->mutator_thread()->ScheduleInterrupts(Thread::kVMInterrupt);
+  }
   return ReserveSampleImpl(isolate, allocation_sample);
 }
 
@@ -1825,6 +1819,94 @@
   ASSERT(code_lookup_table_ != NULL);
 }
 
+void SampleBlockProcessor::Init() {
+  ASSERT(!initialized_);
+  if (monitor_ == nullptr) {
+    monitor_ = new Monitor();
+  }
+  ASSERT(monitor_ != nullptr);
+  initialized_ = true;
+  shutdown_ = false;
+}
+
+void SampleBlockProcessor::Startup() {
+  ASSERT(initialized_);
+  ASSERT(processor_thread_id_ == OSThread::kInvalidThreadJoinId);
+  MonitorLocker startup_ml(monitor_);
+  OSThread::Start("Dart Profiler SampleBlockProcessor", ThreadMain, 0);
+  while (!thread_running_) {
+    startup_ml.Wait();
+  }
+  ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId);
+}
+
+void SampleBlockProcessor::Cleanup() {
+  {
+    MonitorLocker shutdown_ml(monitor_);
+    if (shutdown_) {
+      // Already shutdown.
+      return;
+    }
+    shutdown_ = true;
+    // Notify.
+    shutdown_ml.Notify();
+    ASSERT(initialized_);
+  }
+
+  // Join the thread.
+  ASSERT(processor_thread_id_ != OSThread::kInvalidThreadJoinId);
+  OSThread::Join(processor_thread_id_);
+  processor_thread_id_ = OSThread::kInvalidThreadJoinId;
+  initialized_ = false;
+  ASSERT(!thread_running_);
+}
+
+class SampleBlockProcessorVisitor : public IsolateVisitor {
+ public:
+  SampleBlockProcessorVisitor() = default;
+  virtual ~SampleBlockProcessorVisitor() = default;
+
+  void VisitIsolate(Isolate* isolate) {
+    if (!isolate->should_process_blocks()) {
+      return;
+    }
+    Thread::EnterIsolateAsHelper(isolate, Thread::kSampleBlockTask);
+    Thread* thread = Thread::Current();
+    {
+      DisableThreadInterruptsScope dtis(thread);
+      isolate->ProcessFreeSampleBlocks(thread);
+    }
+    Thread::ExitIsolateAsHelper();
+  }
+};
+
+void SampleBlockProcessor::ThreadMain(uword parameters) {
+  ASSERT(initialized_);
+  {
+    // Signal to main thread we are ready.
+    MonitorLocker startup_ml(monitor_);
+    OSThread* os_thread = OSThread::Current();
+    ASSERT(os_thread != NULL);
+    processor_thread_id_ = OSThread::GetCurrentThreadJoinId(os_thread);
+    thread_running_ = true;
+    startup_ml.Notify();
+  }
+
+  SampleBlockProcessorVisitor visitor;
+  MonitorLocker wait_ml(monitor_);
+  // Wakeup every 100ms.
+  const int64_t wakeup_interval = 1000 * 100;
+  while (true) {
+    wait_ml.WaitMicros(wakeup_interval);
+    if (shutdown_) {
+      break;
+    }
+    Isolate::VisitIsolates(&visitor);
+  }
+  // Signal to main thread we are exiting.
+  thread_running_ = false;
+}
+
 #endif  // !PRODUCT
 
 }  // namespace dart
diff --git a/runtime/vm/profiler.h b/runtime/vm/profiler.h
index dd30bcc..5455e7b 100644
--- a/runtime/vm/profiler.h
+++ b/runtime/vm/profiler.h
@@ -73,6 +73,9 @@
   static SampleBlockBuffer* sample_block_buffer() {
     return sample_block_buffer_;
   }
+  static void set_sample_block_buffer(SampleBlockBuffer* buffer) {
+    sample_block_buffer_ = buffer;
+  }
   static AllocationSampleBuffer* allocation_sample_buffer() {
     return allocation_sample_buffer_;
   }
@@ -408,9 +411,9 @@
     kTruncatedTraceBit = 5,
     kClassAllocationSampleBit = 6,
     kContinuationSampleBit = 7,
-    kThreadTaskBit = 8,  // 6 bits.
-    kMetadataBit = 14,   // 16 bits.
-    kNextFreeBit = 30,
+    kThreadTaskBit = 8,  // 7 bits.
+    kMetadataBit = 15,   // 16 bits.
+    kNextFreeBit = 31,
   };
   class HeadSampleBit : public BitField<uint32_t, bool, kHeadSampleBit, 1> {};
   class LeafFrameIsDart
@@ -426,7 +429,7 @@
   class ContinuationSampleBit
       : public BitField<uint32_t, bool, kContinuationSampleBit, 1> {};
   class ThreadTaskBit
-      : public BitField<uint32_t, Thread::TaskKind, kThreadTaskBit, 6> {};
+      : public BitField<uint32_t, Thread::TaskKind, kThreadTaskBit, 7> {};
   class MetadataBits : public BitField<uint32_t, intptr_t, kMetadataBit, 16> {};
 
   int64_t timestamp_;
@@ -710,7 +713,7 @@
 class SampleBlock : public SampleBuffer {
  public:
   // The default number of samples per block. Overridden by some tests.
-  static const intptr_t kSamplesPerBlock = 1000;
+  static const intptr_t kSamplesPerBlock = 100;
 
   SampleBlock() = default;
   virtual ~SampleBlock() = default;
@@ -765,13 +768,14 @@
 
  private:
   friend class SampleBlockBuffer;
+  friend class Isolate;
 
   DISALLOW_COPY_AND_ASSIGN(SampleBlock);
 };
 
 class SampleBlockBuffer : public ProcessedSampleBufferBuilder {
  public:
-  static const intptr_t kDefaultBlockCount = 60;
+  static const intptr_t kDefaultBlockCount = 600;
 
   // Creates a SampleBlockBuffer with a predetermined number of blocks.
   //
@@ -864,6 +868,8 @@
   // Sample buffer management.
   VirtualMemory* memory_;
   Sample* sample_buffer_;
+
+  friend class Isolate;
   DISALLOW_COPY_AND_ASSIGN(SampleBlockBuffer);
 };
 
@@ -1037,6 +1043,24 @@
   DISALLOW_COPY_AND_ASSIGN(ProcessedSampleBuffer);
 };
 
+class SampleBlockProcessor : public AllStatic {
+ public:
+  static void Init();
+
+  static void Startup();
+  static void Cleanup();
+
+ private:
+  static const intptr_t kMaxThreads = 4096;
+  static bool initialized_;
+  static bool shutdown_;
+  static bool thread_running_;
+  static ThreadJoinId processor_thread_id_;
+  static Monitor* monitor_;
+
+  static void ThreadMain(uword parameters);
+};
+
 }  // namespace dart
 
 #endif  // RUNTIME_VM_PROFILER_H_
diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc
index e9f333e..6836e9d 100644
--- a/runtime/vm/profiler_service.cc
+++ b/runtime/vm/profiler_service.cc
@@ -1450,9 +1450,8 @@
   ProfileInfoKind info_kind_;
 };  // ProfileBuilder.
 
-Profile::Profile(Isolate* isolate)
-    : isolate_(isolate),
-      zone_(Thread::Current()->zone()),
+Profile::Profile()
+    : zone_(Thread::Current()->zone()),
       samples_(NULL),
       live_code_(NULL),
       dead_code_(NULL),
@@ -1461,9 +1460,7 @@
       dead_code_index_offset_(-1),
       tag_code_index_offset_(-1),
       min_time_(kMaxInt64),
-      max_time_(0) {
-  ASSERT(isolate_ != NULL);
-}
+      max_time_(0) {}
 
 void Profile::Build(Thread* thread,
                     SampleFilter* filter,
@@ -1765,14 +1762,12 @@
                                     SampleFilter* filter,
                                     ProcessedSampleBufferBuilder* buffer,
                                     bool include_code_samples) {
-  Isolate* isolate = thread->isolate();
-
   // We should bail out in service.cc if the profiler is disabled.
   ASSERT(buffer != nullptr);
 
   StackZone zone(thread);
   HANDLESCOPE(thread);
-  Profile profile(isolate);
+  Profile profile;
   profile.Build(thread, filter, buffer);
   profile.PrintProfileJSON(stream, include_code_samples);
 }
diff --git a/runtime/vm/profiler_service.h b/runtime/vm/profiler_service.h
index 1c02db3..a9df89b 100644
--- a/runtime/vm/profiler_service.h
+++ b/runtime/vm/profiler_service.h
@@ -364,7 +364,7 @@
 // a zone must be created that lives longer than this object.
 class Profile : public ValueObject {
  public:
-  explicit Profile(Isolate* isolate);
+  Profile();
 
   // Build a filtered model using |filter|.
   void Build(Thread* thread,
@@ -403,7 +403,6 @@
                                intptr_t frame_index);
   void PrintSamplesJSON(JSONObject* obj, bool code_samples);
 
-  Isolate* isolate_;
   Zone* zone_;
   ProcessedSampleBuffer* samples_;
   ProfileCodeTable* live_code_;
diff --git a/runtime/vm/profiler_test.cc b/runtime/vm/profiler_test.cc
index 1171458..987787c 100644
--- a/runtime/vm/profiler_test.cc
+++ b/runtime/vm/profiler_test.cc
@@ -102,9 +102,30 @@
   buffer->VisitSamples(visitor);
 }
 
+class SampleBlockBufferOverrideScope {
+ public:
+  explicit SampleBlockBufferOverrideScope(SampleBlockBuffer* buffer)
+      : override_(buffer) {
+    orig_ = Profiler::sample_block_buffer();
+    Profiler::set_sample_block_buffer(override_);
+  }
+
+  ~SampleBlockBufferOverrideScope() {
+    Profiler::set_sample_block_buffer(orig_);
+    delete override_;
+  }
+
+ private:
+  SampleBlockBuffer* orig_;
+  SampleBlockBuffer* override_;
+};
+
 TEST_CASE(Profiler_SampleBufferWrapTest) {
   Isolate* isolate = Isolate::Current();
-  SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(3, 1);
+
+  SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1));
+  SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
+
   Dart_Port i = 123;
   ProfileSampleBufferTestHelper visitor(i);
 
@@ -142,12 +163,14 @@
     MutexLocker ml(isolate->current_sample_block_lock());
     isolate->set_current_sample_block(nullptr);
   }
-  delete sample_buffer;
 }
 
 TEST_CASE(Profiler_SampleBufferIterateTest) {
   Isolate* isolate = Isolate::Current();
-  SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(3, 1);
+
+  SampleBlockBufferOverrideScope sbbos(new SampleBlockBuffer(3, 1));
+  SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
+
   Dart_Port i = 123;
   ProfileSampleBufferTestHelper visitor(i);
 
@@ -182,7 +205,6 @@
     MutexLocker ml(isolate->current_sample_block_lock());
     isolate->set_current_sample_block(nullptr);
   }
-  delete sample_buffer;
 }
 
 TEST_CASE(Profiler_AllocationSampleTest) {
@@ -480,7 +502,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     // Filter for the class in the time range.
     AllocationFilter filter(isolate->main_port(), class_a.id(),
                             before_allocations_micros,
@@ -510,7 +532,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id(),
                             Dart_TimelineGetMicros(), 16000);
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
@@ -555,10 +577,9 @@
   // with each node.
   {
     Thread* thread = Thread::Current();
-    Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
 
     // Filter for the class in the time range.
     NativeAllocationSampleFilter filter(before_allocations_micros,
@@ -595,10 +616,9 @@
   // freed above is marked as free and is no longer reported.
   {
     Thread* thread = Thread::Current();
-    Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
 
     // Filter for the class in the time range.
     NativeAllocationSampleFilter filter(before_allocations_micros,
@@ -611,10 +631,9 @@
   // Query with a time filter where no allocations occurred.
   {
     Thread* thread = Thread::Current();
-    Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     NativeAllocationSampleFilter filter(Dart_TimelineGetMicros(), 16000);
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples because none occured within
@@ -660,7 +679,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -677,7 +696,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation sample.
@@ -707,7 +726,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -745,7 +764,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -765,7 +784,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have three allocation samples.
@@ -820,7 +839,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -840,7 +859,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have three allocation samples.
@@ -890,7 +909,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), double_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -903,7 +922,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), double_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation sample.
@@ -925,7 +944,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), double_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -952,7 +971,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), array_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -965,7 +984,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), array_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation sample.
@@ -987,7 +1006,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), array_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -1009,7 +1028,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), array_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples, since empty
@@ -1039,7 +1058,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), context_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -1052,7 +1071,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), context_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation sample.
@@ -1072,7 +1091,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), context_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -1112,7 +1131,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), closure_class.id());
     filter.set_enable_vm_ticks(true);
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
@@ -1136,7 +1155,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), closure_class.id());
     filter.set_enable_vm_ticks(true);
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
@@ -1167,7 +1186,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), float32_list_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -1180,7 +1199,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), float32_list_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation sample.
@@ -1202,7 +1221,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), float32_list_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -1215,7 +1234,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), float32_list_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should now have two allocation samples.
@@ -1247,7 +1266,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -1260,7 +1279,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -1280,7 +1299,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -1293,7 +1312,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should now have two allocation samples.
@@ -1325,7 +1344,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -1338,7 +1357,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -1364,7 +1383,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should still only have one allocation sample.
@@ -1377,7 +1396,7 @@
   {
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should now have two allocation samples.
@@ -1433,7 +1452,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -1451,7 +1470,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have 50,000 allocation samples.
@@ -1580,7 +1599,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have no allocation samples.
@@ -1597,7 +1616,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     EXPECT_EQ(1, profile.sample_count());
@@ -1673,7 +1692,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have 1 allocation sample.
@@ -1768,7 +1787,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation samples.
@@ -1850,7 +1869,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation samples.
@@ -1928,7 +1947,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation samples.
@@ -2038,7 +2057,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation samples.
@@ -2133,7 +2152,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation samples.
@@ -2251,7 +2270,7 @@
     Isolate* isolate = thread->isolate();
     StackZone zone(thread);
     HANDLESCOPE(thread);
-    Profile profile(isolate);
+    Profile profile;
     AllocationFilter filter(isolate->main_port(), class_a.id());
     profile.Build(thread, &filter, Profiler::sample_block_buffer());
     // We should have one allocation samples.
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc
index f999c81..1bd8a93 100644
--- a/runtime/vm/source_report.cc
+++ b/runtime/vm/source_report.cc
@@ -29,7 +29,6 @@
       script_(NULL),
       start_pos_(TokenPosition::kMinSource),
       end_pos_(TokenPosition::kMaxSource),
-      profile_(Isolate::Current()),
       next_script_index_(0) {}
 
 SourceReport::~SourceReport() {
diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc
index a9e319e..f026c1d 100644
--- a/runtime/vm/thread.cc
+++ b/runtime/vm/thread.cc
@@ -450,11 +450,15 @@
     }
 
 #if !defined(PRODUCT)
-    // Processes completed SampleBlocks and sends CPU sample events over the
-    // service protocol when applicable.
-    SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
-    if (sample_buffer != nullptr && sample_buffer->process_blocks()) {
-      sample_buffer->ProcessCompletedBlocks();
+    // Don't block system isolates to process CPU samples to avoid blocking
+    // them during critical tasks (e.g., initial compilation).
+    if (!Isolate::IsSystemIsolate(isolate())) {
+      // Processes completed SampleBlocks and sends CPU sample events over the
+      // service protocol when applicable.
+      SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
+      if (sample_buffer != nullptr && sample_buffer->process_blocks()) {
+        sample_buffer->ProcessCompletedBlocks();
+      }
     }
 #endif  // !defined(PRODUCT)
   }
diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h
index 6e3ff67..7cf251e 100644
--- a/runtime/vm/thread.h
+++ b/runtime/vm/thread.h
@@ -273,6 +273,7 @@
     kSweeperTask = 0x8,
     kCompactorTask = 0x10,
     kScavengerTask = 0x20,
+    kSampleBlockTask = 0x40,
   };
   // Converts a TaskKind to its corresponding C-String name.
   static const char* TaskKindToCString(TaskKind kind);
diff --git a/tools/VERSION b/tools/VERSION
index e582617..752efff 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 52
+PRERELEASE 53
 PRERELEASE_PATCH 0
\ No newline at end of file