Version 2.19.0-64.0.dev

Merge commit 'dbd65b829fbb34f3c5999eac1c253e15e8adda15' into 'dev'
diff --git a/benchmarks/Example/dart2/Example.dart b/benchmarks/Example/dart2/Example.dart
index cd5219e..da484b1 100644
--- a/benchmarks/Example/dart2/Example.dart
+++ b/benchmarks/Example/dart2/Example.dart
@@ -17,7 +17,7 @@
   @override
   void setup() {}
 
-  // Not measures teardown code executed after the benchark runs.
+  // Not measured teardown code executed after the benchark runs.
   @override
   void teardown() {}
 }
diff --git a/pkg/frontend_server/test/frontend_server_test.dart b/pkg/frontend_server/test/frontend_server_test.dart
index 5bac7ef..41bc4db 100644
--- a/pkg/frontend_server/test/frontend_server_test.dart
+++ b/pkg/frontend_server/test/frontend_server_test.dart
@@ -2351,6 +2351,252 @@
       expect(count, 1);
     });
 
+    // This test exercises what happens when a change occurs with a single
+    // module of a multi-module compilation.
+    test('recompile to JavaScript with in-body change', () async {
+      // Five libraries, a to e, in two modules, {a, b} and {c, d, e}:
+      //    (a <-> b) -> (c <-> d <-> e)
+      // In body changes are performed on d and e. With advanced invalidation,
+      // not currently enabled, only the module {c, d, e} will be recompiled.
+      File('${tempDir.path}/a.dart')
+        ..createSync()
+        ..writeAsStringSync("""
+import 'b.dart';
+main() {
+  b();
+}
+a() => "<<a>>";
+""");
+      File('${tempDir.path}/b.dart')
+        ..createSync()
+        ..writeAsStringSync("""
+import 'a.dart';
+import 'c.dart';
+b() {
+  a();
+  "<<b>>";
+  c();
+}
+""");
+      File('${tempDir.path}/c.dart')
+        ..createSync()
+        ..writeAsStringSync("""
+import 'd.dart';
+c() {
+  "<<c>>";
+  d();
+}
+""");
+      var fileD = File('${tempDir.path}/d.dart')
+        ..createSync()
+        ..writeAsStringSync("""
+import 'e.dart';
+d() {
+  "<<d>>";
+  e();
+}
+""");
+      var fileE = File('${tempDir.path}/e.dart')
+        ..createSync()
+        ..writeAsStringSync("""
+import 'c.dart';
+e() {
+  c();
+  "<<e>>";
+}
+""");
+      var packageConfig = File('${tempDir.path}/.dart_tool/package_config.json')
+        ..createSync(recursive: true)
+        ..writeAsStringSync('''
+  {
+    "configVersion": 2,
+    "packages": [
+      {
+        "name": "a",
+        "rootUri": "../",
+        "packageUri": "./"
+      }
+    ]
+  }
+  ''');
+
+      var entryPoint = 'package:a/a.dart';
+
+      var dillFile = File('${tempDir.path}/app.dill');
+      var sourceFile = File('${dillFile.path}.sources');
+      var manifestFile = File('${dillFile.path}.json');
+      var sourceMapsFile = File('${dillFile.path}.map');
+      var metadataFile = File('${dillFile.path}.metadata');
+      var symbolsFile = File('${dillFile.path}.symbols');
+
+      expect(dillFile.existsSync(), false);
+      expect(sourceFile.existsSync(), false);
+      expect(manifestFile.existsSync(), false);
+      expect(sourceMapsFile.existsSync(), false);
+      expect(metadataFile.existsSync(), false);
+      expect(symbolsFile.existsSync(), false);
+
+      final List<String> args = <String>[
+        '--sdk-root=${sdkRoot.toFilePath()}',
+        '--incremental',
+        '--platform=${ddcPlatformKernel.path}',
+        '--output-dill=${dillFile.path}',
+        '--target=dartdevc',
+        '--packages=${packageConfig.path}',
+        // TODO(johnniwinther): Enable this.
+        //'--enable-experiment=alternative-invalidation-strategy',
+        '--emit-debug-symbols',
+      ];
+
+      final StreamController<List<int>> streamController =
+          StreamController<List<int>>();
+      final StreamController<List<int>> stdoutStreamController =
+          StreamController<List<int>>();
+      final IOSink ioSink = IOSink(stdoutStreamController.sink);
+      StreamController<Result> receivedResults = StreamController<Result>();
+      final outputParser = OutputParser(receivedResults);
+      stdoutStreamController.stream
+          .transform(utf8.decoder)
+          .transform(const LineSplitter())
+          .listen(outputParser.listener);
+
+      Future<int> result =
+          starter(args, input: streamController.stream, output: ioSink);
+      streamController.add('compile $entryPoint\n'.codeUnits);
+      int count = 0;
+      receivedResults.stream.listen((Result compiledResult) {
+        switch (count) {
+          case 0:
+            CompilationResult result =
+                CompilationResult.parse(compiledResult.status);
+            expect(result.errorsCount, equals(0));
+            expect(result.filename, dillFile.path);
+            expect(sourceFile.existsSync(), equals(true));
+
+            var source = sourceFile.readAsStringSync();
+            // Split on the comment at the end of each module.
+            var jsModules =
+                source.split(RegExp("\/\/# sourceMappingURL=.*\.map"));
+
+            expect(jsModules[0], contains('<<a>>'));
+            expect(jsModules[0], contains('<<b>>'));
+            expect(jsModules[0], not(contains('<<c>>')));
+            expect(jsModules[0], not(contains('<<d>>')));
+            expect(jsModules[0], not(contains('<<e>>')));
+
+            expect(jsModules[1], not(contains('<<a>>')));
+            expect(jsModules[1], not(contains('<<b>>')));
+            expect(jsModules[1], contains('<<c>>'));
+            expect(jsModules[1], contains('<<d>>'));
+            expect(jsModules[1], contains('<<e>>'));
+
+            streamController.add('accept\n'.codeUnits);
+
+            fileD.writeAsStringSync("""
+import 'e.dart';
+d() {
+  "<<d1>>";
+  "<<d2>>";
+  e();
+}
+""");
+            // Trigger a recompile that invalidates 'd.dart'. The input uri
+            // (a.dart) is passed explicitly.
+            streamController.add('recompile ${entryPoint} abc\n'
+                    '${fileD.uri}\n'
+                    'abc\n'
+                .codeUnits);
+            break;
+          case 1:
+            CompilationResult result =
+                CompilationResult.parse(compiledResult.status);
+            expect(result.errorsCount, equals(0));
+            expect(result.filename, '${dillFile.path}.incremental.dill');
+            File incrementalSourceFile =
+                File('${dillFile.path}.incremental.dill.sources');
+            expect(incrementalSourceFile.existsSync(), equals(true));
+
+            var source = incrementalSourceFile.readAsStringSync();
+            // Split on the comment at the end of each module.
+            var jsModules =
+                source.split(RegExp("\/\/# sourceMappingURL=.*\.map"));
+
+            expect(jsModules[0], contains('<<a>>'));
+            expect(jsModules[0], contains('<<b>>'));
+            expect(jsModules[0], not(contains('<<c>>')));
+            expect(jsModules[0], not(contains('<<d>>')));
+            expect(jsModules[0], not(contains('<<e>>')));
+
+            expect(jsModules[1], not(contains('<<a>>')));
+            expect(jsModules[1], not(contains('<<b>>')));
+            expect(jsModules[1], contains('<<c>>'));
+            expect(jsModules[1], not(contains('<<d>>')));
+            expect(jsModules[1], contains('<<d1>>'));
+            expect(jsModules[1], contains('<<d2>>'));
+            expect(jsModules[1], contains('<<e>>'));
+
+            streamController.add('accept\n'.codeUnits);
+
+            fileE.writeAsStringSync("""
+import 'c.dart';
+e() {
+  c();
+  "<<e1>>";
+  "<<e2>>";
+}
+""");
+            // Trigger a recompile that invalidates 'd.dart'. The input uri
+            // (a.dart) is omitted.
+            streamController.add('recompile abc\n'
+                    '${fileE.uri}\n'
+                    'abc\n'
+                .codeUnits);
+            break;
+            break;
+          case 2:
+            CompilationResult result =
+                CompilationResult.parse(compiledResult.status);
+            expect(result.errorsCount, equals(0));
+            expect(result.filename, '${dillFile.path}.incremental.dill');
+            File incrementalSourceFile =
+                File('${dillFile.path}.incremental.dill.sources');
+            expect(incrementalSourceFile.existsSync(), equals(true));
+
+            var source = incrementalSourceFile.readAsStringSync();
+            // Split on the comment at the end of each module.
+            var jsModules =
+                source.split(RegExp("\/\/# sourceMappingURL=.*\.map"));
+
+            expect(jsModules[0], contains('<<a>>'));
+            expect(jsModules[0], contains('<<b>>'));
+            expect(jsModules[0], not(contains('<<c>>')));
+            expect(jsModules[0], not(contains('<<d>>')));
+            expect(jsModules[0], not(contains('<<e>>')));
+
+            expect(jsModules[1], not(contains('<<a>>')));
+            expect(jsModules[1], not(contains('<<b>>')));
+            expect(jsModules[1], contains('<<c>>'));
+            expect(jsModules[1], not(contains('<<d>>')));
+            expect(jsModules[1], contains('<<d1>>'));
+            expect(jsModules[1], contains('<<d2>>'));
+            expect(jsModules[1], not(contains('<<e>>')));
+            expect(jsModules[1], contains('<<e1>>'));
+            expect(jsModules[1], contains('<<e2>>'));
+
+            streamController.add('accept\n'.codeUnits);
+            outputParser.expectSources = false;
+            streamController.add('quit\n'.codeUnits);
+            break;
+          default:
+            break;
+        }
+        count++;
+      });
+
+      expect(await result, 0);
+      expect(count, 3);
+    });
+
     test('compile to JavaScript all modules with unsound null safety',
         () async {
       var file = File('${tempDir.path}/foo.dart')..createSync();
@@ -3275,3 +3521,21 @@
 class _MockedCompiler extends Mock implements CompilerInterface {}
 
 class _MockedIncrementalCompiler extends Mock implements IncrementalCompiler {}
+
+/// Creates a matcher for the negation of [matcher].
+Matcher not(Matcher matcher) => NotMatcher(matcher);
+
+class NotMatcher extends Matcher {
+  final Matcher matcher;
+
+  const NotMatcher(this.matcher);
+
+  @override
+  Description describe(Description description) =>
+      matcher.describe(description.add('not '));
+
+  @override
+  bool matches(item, Map matchState) {
+    return !matcher.matches(item, matchState);
+  }
+}
diff --git a/tools/VERSION b/tools/VERSION
index a42fce5..191e9a0 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 19
 PATCH 0
-PRERELEASE 63
+PRERELEASE 64
 PRERELEASE_PATCH 0
\ No newline at end of file