Version 2.19.0-19.0.dev

Merge commit '2c891cda56a285ce93b8f198b6f26326c5a8c946' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart
index 4376b67..ab45689 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_text_document_changes.dart
@@ -103,10 +103,6 @@
       );
       server.onOverlayCreated(path, doc.text);
 
-      // If the file did not exist, and is "overlay only", it still should be
-      // analyzed. Add it to driver to which it should have been added.
-      server.contextManager.getDriverFor(path)?.addFile(path);
-
       await server.addPriorityFile(path);
 
       return success(null);
diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
index f28d646..4c7ca36 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
@@ -497,10 +497,33 @@
   }
 
   void onOverlayCreated(String path, String content) {
+    final currentFile = resourceProvider.getFile(path);
+    String? currentContent;
+
+    try {
+      currentContent = currentFile.readAsStringSync();
+    } on FileSystemException {
+      // It's possible we're creating an overlay for a file that doesn't yet
+      // exist on disk so must handle missing file exceptions. Checking for
+      // exists first would introduce a race.
+    }
+
     resourceProvider.setOverlay(path,
         content: content, modificationStamp: overlayModificationStamp++);
 
-    _afterOverlayChanged(path, plugin.AddContentOverlay(content));
+    // If the overlay is exactly the same as the previous content we can skip
+    // notifying drivers which avoids re-analyzing the same content.
+    if (content != currentContent) {
+      _afterOverlayChanged(path, plugin.AddContentOverlay(content));
+
+      // If the file did not exist, and is "overlay only", it still should be
+      // analyzed. Add it to driver to which it should have been added.
+      contextManager.getDriverFor(path)?.addFile(path);
+    } else {
+      // If we skip the work above, we still need to ensure plugins are notified
+      // of the new overlay (which usually happens in `_afterOverlayChanged`).
+      _notifyPluginsOverlayChanged(path, plugin.AddContentOverlay(content));
+    }
   }
 
   void onOverlayDestroyed(String path) {
@@ -772,9 +795,7 @@
     for (var driver in driverMap.values) {
       driver.changeFile(path);
     }
-    pluginManager.setAnalysisUpdateContentParams(
-      plugin.AnalysisUpdateContentParams({path: changeForPlugins}),
-    );
+    _notifyPluginsOverlayChanged(path, changeForPlugins);
 
     notifyDeclarationsTracker(path);
     notifyFlutterWidgetDescriptions(path);
@@ -834,6 +855,13 @@
     }
   }
 
+  void _notifyPluginsOverlayChanged(
+      String path, plugin.HasToJson changeForPlugins) {
+    pluginManager.setAnalysisUpdateContentParams(
+      plugin.AnalysisUpdateContentParams({path: changeForPlugins}),
+    );
+  }
+
   void _onPluginsChanged() {
     capabilitiesComputer.performDynamicRegistration();
   }
diff --git a/pkg/analysis_server/test/lsp/document_changes_test.dart b/pkg/analysis_server/test/lsp/document_changes_test.dart
index db65c63..cf146e8 100644
--- a/pkg/analysis_server/test/lsp/document_changes_test.dart
+++ b/pkg/analysis_server/test/lsp/document_changes_test.dart
@@ -114,6 +114,49 @@
     expect(driverForInside.addedFiles, isNot(contains(fileOutsideRootPath)));
   }
 
+  Future<void> test_documentOpen_contentChanged_analysis() async {
+    const content = '// original content';
+    const newContent = '// new content';
+    newFile(mainFilePath, content);
+
+    // Wait for initial analysis to provide diagnostics for the file.
+    await Future.wait([
+      waitForDiagnostics(mainFileUri),
+      initialize(),
+    ]);
+
+    // Capture any further diagnostics sent after we open the file.
+    List<Diagnostic>? diagnostics;
+    unawaited(waitForDiagnostics(mainFileUri).then((d) => diagnostics = d));
+    await openFile(mainFileUri, newContent);
+    await pumpEventQueue(times: 5000);
+
+    // Expect diagnostics, because changing the content will have triggered
+    // analysis.
+    expect(diagnostics, isNotNull);
+  }
+
+  Future<void> test_documentOpen_contentUnchanged_noAnalysis() async {
+    const content = '// original content';
+    newFile(mainFilePath, content);
+
+    // Wait for initial analysis to provide diagnostics for the file.
+    await Future.wait([
+      waitForDiagnostics(mainFileUri),
+      initialize(),
+    ]);
+
+    // Capture any further diagnostics sent after we open the file.
+    List<Diagnostic>? diagnostics;
+    unawaited(waitForDiagnostics(mainFileUri).then((d) => diagnostics = d));
+    await openFile(mainFileUri, content);
+    await pumpEventQueue(times: 5000);
+
+    // Expect no diagnostics because the file didn't actually change content
+    // when the overlay was created, so it should not have triggered analysis.
+    expect(diagnostics, isNull);
+  }
+
   Future<void> test_documentOpen_createsOverlay() async {
     await _initializeAndOpen();
 
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
index 15ea65d..f1c3150 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_analyzer.dart
@@ -684,7 +684,6 @@
           errorReporter: containerErrorReporter,
         );
       } else if (directive is LibraryAugmentationDirectiveImpl) {
-        // TODO(scheglov) test
         directive.element = containerElement;
       } else if (directive is LibraryDirectiveImpl) {
         directive.element = containerElement;
diff --git a/pkg/analyzer/test/src/dart/resolution/library_augmentation_test.dart b/pkg/analyzer/test/src/dart/resolution/library_augmentation_test.dart
new file mode 100644
index 0000000..d115d4b
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/resolution/library_augmentation_test.dart
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(LibraryAugmentationDirectiveResolutionTest);
+  });
+}
+
+@reflectiveTest
+class LibraryAugmentationDirectiveResolutionTest
+    extends PubPackageResolutionTest {
+  test_directive() async {
+    newFile('$testPackageLibPath/a.dart', r'''
+import augment 'b.dart';
+''');
+
+    final b = newFile('$testPackageLibPath/b.dart', r'''
+library augment 'a.dart';
+''');
+
+    newFile('$testPackageLibPath/c.dart', '');
+
+    await resolveFile2(b.path);
+    assertNoErrorsInResult();
+
+    final node = findNode.libraryAugmentation('a.dart');
+    assertResolvedNodeText(node, r'''
+LibraryAugmentationDirective
+  libraryKeyword: library
+  augmentKeyword: augment
+  uri: SimpleStringLiteral
+    literal: 'a.dart'
+  semicolon: ;
+  element: package:test/a.dart::@augmentation::package:test/b.dart
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/test_all.dart b/pkg/analyzer/test/src/dart/resolution/test_all.dart
index 82538d6..f070c0e 100644
--- a/pkg/analyzer/test/src/dart/resolution/test_all.dart
+++ b/pkg/analyzer/test/src/dart/resolution/test_all.dart
@@ -39,6 +39,7 @@
     as instance_member_inference_mixin;
 import 'interpolation_string_test.dart' as interpolation_string;
 import 'language_version_test.dart' as language_version;
+import 'library_augmentation_test.dart' as library_element2;
 import 'library_element_test.dart' as library_element;
 import 'library_export_test.dart' as library_export;
 import 'library_import_prefix_test.dart' as library_import_prefix;
@@ -106,6 +107,7 @@
     instance_member_inference_mixin.main();
     interpolation_string.main();
     language_version.main();
+    library_element2.main();
     library_element.main();
     library_export.main();
     library_import_prefix.main();
diff --git a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
index c41a403..ac8fd7c 100644
--- a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
+++ b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
@@ -774,11 +774,7 @@
     _writeln('LibraryAugmentationDirective');
     _withIndent(() {
       _writeNamedChildEntities(node);
-      // TODO(scheglov) Implement.
-      // _writeElement('element', node.element);
-      // _writeRaw('uriContent', node.uriContent);
-      // _writeElement('uriElement', node.uriElement);
-      // _writeSource('uriSource', node.uriSource);
+      _writeElement('element', node.element);
     });
   }
 
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index c24f897..e10b3e2 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -814,6 +814,10 @@
     w.Label loop = b.loop();
     _branchIf(node.condition, block, negated: true);
     node.body.accept(this);
+
+    // If the loop variable is captured then we have to create a new context for
+    // each iteration of the loop. We must also ensure the local pointing to the
+    // [oldContext] now points to [newContext].
     if (node.variables.any((v) => closures.captures.containsKey(v))) {
       w.Local oldContext = context!.currentLocal;
       allocateContext(node);
@@ -821,12 +825,14 @@
       for (VariableDeclaration variable in node.variables) {
         Capture? capture = closures.captures[variable];
         if (capture != null) {
+          b.local_get(newContext);
           b.local_get(oldContext);
           b.struct_get(context.struct, capture.fieldIndex);
-          b.local_get(newContext);
           b.struct_set(context.struct, capture.fieldIndex);
         }
       }
+      b.local_get(newContext);
+      b.local_set(oldContext);
     } else {
       allocateContext(node);
     }
diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart
index 3351fa7..fe9d23e 100644
--- a/pkg/dart2wasm/lib/transformers.dart
+++ b/pkg/dart2wasm/lib/transformers.dart
@@ -51,10 +51,25 @@
     return result;
   }
 
-  /// We can reuse a superclass' `_typeArguments` method if the subclass and the
-  /// superclass have the exact same type parameters in the exact same order.
+  /// Checks to see if it is safe to reuse `super._typeArguments`.
   bool canReuseSuperMethod(Class cls) {
-    Supertype supertype = cls.supertype!;
+    // We search for the first non-abstract super in [cls]'s inheritance chain
+    // to see if we can reuse its `_typeArguments` method.
+    Class classIter = cls;
+    late Supertype supertype;
+    while (classIter.supertype != null) {
+      Supertype supertypeIter = classIter.supertype!;
+      Class superclass = supertypeIter.classNode;
+      if (!superclass.isAbstract) {
+        supertype = supertypeIter;
+        break;
+      }
+      classIter = classIter.supertype!.classNode;
+    }
+
+    // We can reuse a superclass' `_typeArguments` method if the subclass and
+    // the superclass have the exact same type parameters in the exact same
+    // order.
     if (cls.typeParameters.length != supertype.typeArguments.length) {
       return false;
     }
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_shared.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_shared.dart
index ded97c0..ac03b93 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_shared.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_shared.dart
@@ -936,22 +936,15 @@
     try {
       var entity = fileSystem.entityForUri(uri);
       if (await entity.existsAsyncIfPossible()) {
-        if (request.method == 'HEAD') {
-          var headers = {
-            'content-length': '-1',
-            ...request.headers,
-          };
-          return Response.ok(null, headers: headers);
-        }
-
-        if (request.method == 'GET') {
+        if (request.method == 'HEAD' || request.method == 'GET') {
           // 'readAsBytes'
           var contents = await entity.readAsBytesAsyncIfPossible();
           var headers = {
             'content-length': '${contents.length}',
             ...request.headers,
           };
-          return Response.ok(contents, headers: headers);
+          return Response.ok(request.method == 'GET' ? contents : null,
+              headers: headers);
         }
       }
       return Response.notFound(path);
diff --git a/tools/VERSION b/tools/VERSION
index c27657f..cf62a55 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 19
 PATCH 0
-PRERELEASE 18
+PRERELEASE 19
 PRERELEASE_PATCH 0
\ No newline at end of file