Version 2.12.0-110.0.dev

Merge commit '923facf2262114994992f01d82fb8287372fb7f5' into 'dev'
diff --git a/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart b/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
index e3e581b..5a1107c 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/change_to_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -126,7 +127,9 @@
   c.Future v = null;
   print(v);
 }
-''');
+''', errorFilter: (error) {
+      return error.errorCode == CompileTimeErrorCode.UNDEFINED_CLASS;
+    });
   }
 
   Future<void> test_class_with() async {
@@ -177,7 +180,9 @@
   c.main();
 }
 ''');
-    await assertNoFix();
+    await assertNoFix(errorFilter: (error) {
+      return error.errorCode == CompileTimeErrorCode.UNDEFINED_FUNCTION;
+    });
   }
 
   Future<void> test_function_thisLibrary() async {
diff --git a/pkg/analyzer/lib/src/error/imports_verifier.dart b/pkg/analyzer/lib/src/error/imports_verifier.dart
index 674e4f5..8a9b8c3 100644
--- a/pkg/analyzer/lib/src/error/imports_verifier.dart
+++ b/pkg/analyzer/lib/src/error/imports_verifier.dart
@@ -217,11 +217,6 @@
   /// element, the import directive can be marked as used (removed from the
   /// unusedImports) by looking at the resolved `lib` in `lib.X`, instead of
   /// looking at which library the `lib.X` resolves.
-  ///
-  /// TODO (jwren) Since multiple [ImportDirective]s can share the same
-  /// [PrefixElement], it is possible to have an unreported unused import in
-  /// situations where two imports use the same prefix and at least one import
-  /// directive is used.
   final HashMap<PrefixElement, List<ImportDirective>> _prefixElementMap =
       HashMap<PrefixElement, List<ImportDirective>>();
 
@@ -410,22 +405,21 @@
 
   /// Remove elements from [_unusedImports] using the given [usedElements].
   void removeUsedElements(UsedImportedElements usedElements) {
-    // Stop if all the imports and shown names are known to be used.
-    if (_unusedImports.isEmpty && _unusedShownNamesMap.isEmpty) {
-      return;
-    }
+    bool everythingIsKnownToBeUsed() =>
+        _unusedImports.isEmpty && _unusedShownNamesMap.isEmpty;
+
     // Process import prefixes.
     usedElements.prefixMap
         .forEach((PrefixElement prefix, List<Element> elements) {
-      List<ImportDirective> importDirectives = _prefixElementMap[prefix];
-      if (importDirectives != null) {
-        int importLength = importDirectives.length;
-        for (int i = 0; i < importLength; i++) {
-          ImportDirective importDirective = importDirectives[i];
-          _unusedImports.remove(importDirective);
-          int elementLength = elements.length;
-          for (int j = 0; j < elementLength; j++) {
-            Element element = elements[j];
+      if (everythingIsKnownToBeUsed()) {
+        return;
+      }
+      // Find import directives using namespaces.
+      for (var importDirective in _prefixElementMap[prefix] ?? []) {
+        Namespace namespace = _computeNamespace(importDirective);
+        for (var element in elements) {
+          if (namespace?.getPrefixed(prefix.name, element.name) != null) {
+            _unusedImports.remove(importDirective);
             _removeFromUnusedShownNamesMap(element, importDirective);
           }
         }
@@ -433,15 +427,13 @@
     });
     // Process top-level elements.
     for (Element element in usedElements.elements) {
-      // Stop if all the imports and shown names are known to be used.
-      if (_unusedImports.isEmpty && _unusedShownNamesMap.isEmpty) {
+      if (everythingIsKnownToBeUsed()) {
         return;
       }
       // Find import directives using namespaces.
-      String name = element.name;
       for (ImportDirective importDirective in _allImports) {
         Namespace namespace = _computeNamespace(importDirective);
-        if (namespace?.get(name) != null) {
+        if (namespace?.get(element.name) != null) {
           _unusedImports.remove(importDirective);
           _removeFromUnusedShownNamesMap(element, importDirective);
         }
@@ -449,15 +441,13 @@
     }
     // Process extension elements.
     for (ExtensionElement extensionElement in usedElements.usedExtensions) {
-      // Stop if all the imports and shown names are known to be used.
-      if (_unusedImports.isEmpty && _unusedShownNamesMap.isEmpty) {
+      if (everythingIsKnownToBeUsed()) {
         return;
       }
       // Find import directives using namespaces.
-      String name = extensionElement.name;
       for (ImportDirective importDirective in _allImports) {
         Namespace namespace = _computeNamespace(importDirective);
-        if (namespace?.get(name) == extensionElement) {
+        if (namespace?.get(extensionElement.name) == extensionElement) {
           _unusedImports.remove(importDirective);
           _removeFromUnusedShownNamesMap(extensionElement, importDirective);
         }
diff --git a/pkg/analyzer/test/generated/error_suppression_test.dart b/pkg/analyzer/test/generated/error_suppression_test.dart
index 07a3c23..87797c5 100644
--- a/pkg/analyzer/test/generated/error_suppression_test.dart
+++ b/pkg/analyzer/test/generated/error_suppression_test.dart
@@ -271,7 +271,9 @@
 // ignore: undefined_prefixed_name
 f() => c.g;
 ''',
-      [],
+      [
+        error(HintCode.UNUSED_IMPORT, 7, 17),
+      ],
     );
   }
 }
diff --git a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
index d07c1ac..5a97135 100644
--- a/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/assignment_test.dart
@@ -1533,6 +1533,7 @@
   x = 2;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 37, 1),
     ]);
 
diff --git a/pkg/analyzer/test/src/dart/resolution/import_prefix_test.dart b/pkg/analyzer/test/src/dart/resolution/import_prefix_test.dart
index 66c665f..362da76 100644
--- a/pkg/analyzer/test/src/dart/resolution/import_prefix_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/import_prefix_test.dart
@@ -24,6 +24,7 @@
   p; // use
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 12),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 38, 1),
     ]);
 
@@ -40,6 +41,7 @@
   for (var x in p) {}
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 12),
       error(HintCode.UNUSED_LOCAL_VARIABLE, 47, 1),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 52, 1),
     ]);
@@ -64,6 +66,7 @@
   var x = new C(p);
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 12),
       error(HintCode.UNUSED_LOCAL_VARIABLE, 66, 1),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 76, 1),
     ]);
diff --git a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
index 28af7ee..c1ff99b 100644
--- a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
@@ -755,6 +755,7 @@
   math?.loadLibrary();
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 49, 4),
     ]);
 
@@ -778,6 +779,7 @@
   foo();
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 39, 3),
     ]);
     _assertInvalidInvocation(
@@ -806,6 +808,7 @@
   math.foo(0);
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.UNDEFINED_FUNCTION, 45, 3),
     ]);
     _assertUnresolvedMethodInvocation('foo(0);');
@@ -1309,13 +1312,15 @@
   }
 
   test_hasReceiver_deferredImportPrefix_loadLibrary() async {
-    await assertNoErrorsInCode(r'''
+    await assertErrorsInCode(r'''
 import 'dart:math' deferred as math;
 
 main() {
   math.loadLibrary();
 }
-''');
+''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
+    ]);
 
     var import = findElement.importFind('dart:math');
 
@@ -1337,6 +1342,7 @@
   math.loadLibrary(1 + 2);
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.EXTRA_POSITIONAL_ARGUMENTS, 65, 7),
     ]);
 
@@ -1769,6 +1775,7 @@
   math();
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 40, 4),
     ]);
     assertElement(findNode.simple('math()'), findElement.prefix('math'));
@@ -2332,14 +2339,16 @@
 class A {}
 ''');
 
-    await assertNoErrorsInCode(r'''
+    await assertErrorsInCode(r'''
 // @dart = 2.7
 import 'a.dart' deferred as a;
 
 main() {
   a.loadLibrary();
 }
-''');
+''', [
+      error(HintCode.UNUSED_IMPORT, 22, 8),
+    ]);
 
     var import = findElement.importFind('package:test/a.dart');
 
diff --git a/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart b/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart
index ed75ddb..50f5527 100644
--- a/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/prefixed_identifier_test.dart
@@ -278,14 +278,16 @@
 class A {}
 ''');
 
-    await assertNoErrorsInCode(r'''
+    await assertErrorsInCode(r'''
 // @dart = 2.7
 import 'a.dart' deferred as a;
 
 main() {
   a.loadLibrary;
 }
-''');
+''', [
+      error(HintCode.UNUSED_IMPORT, 22, 8),
+    ]);
 
     var import = findElement.importFind('package:test/a.dart');
 
diff --git a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
index 562aebb..6036780 100644
--- a/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/type_name_test.dart
@@ -197,6 +197,7 @@
   new math.A();
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.NEW_WITH_NON_TYPE, 49, 1),
     ]);
 
diff --git a/pkg/analyzer/test/src/diagnostics/const_with_non_type_test.dart b/pkg/analyzer/test/src/diagnostics/const_with_non_type_test.dart
index 67edb47..1f14038 100644
--- a/pkg/analyzer/test/src/diagnostics/const_with_non_type_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/const_with_non_type_test.dart
@@ -23,6 +23,7 @@
   const lib.A();
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.CONST_WITH_NON_TYPE, 50, 1),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/extends_non_class_test.dart b/pkg/analyzer/test/src/diagnostics/extends_non_class_test.dart
index 5313441..6f6cb91 100644
--- a/pkg/analyzer/test/src/diagnostics/extends_non_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/extends_non_class_test.dart
@@ -118,6 +118,7 @@
 
 class C extends p.A {}
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.EXTENDS_NON_CLASS, 42, 3),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart b/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart
index 8f903e5..2c89f6d 100644
--- a/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/invalid_null_aware_operator_test.dart
@@ -260,6 +260,7 @@
   p?.x;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 8),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 31, 1),
     ]);
   }
@@ -471,6 +472,7 @@
   p?.x = 0;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 8),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 31, 1),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart b/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart
index 948a8dd..dbc72fa 100644
--- a/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/mixin_of_non_class_test.dart
@@ -158,6 +158,7 @@
 
 class C with p.M {}
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.MIXIN_OF_NON_CLASS, 39, 3),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart b/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart
index 4f01073..686b88b 100644
--- a/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/prefix_identifier_not_followed_by_dot_test.dart
@@ -27,6 +27,7 @@
   }
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 46, 1),
     ]);
   }
@@ -41,6 +42,7 @@
   p += 1;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 32, 1),
     ]);
   }
@@ -57,6 +59,7 @@
   }
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 46, 1),
     ]);
   }
@@ -90,6 +93,7 @@
   p = 1;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 32, 1),
     ]);
   }
@@ -104,6 +108,7 @@
   p += 1;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 32, 1),
     ]);
   }
@@ -133,6 +138,7 @@
   p?.loadLibrary();
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 41, 1),
     ]);
   }
@@ -148,6 +154,7 @@
   return p?.x;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 39, 1),
     ]);
   }
@@ -162,6 +169,7 @@
   return p?.loadLibrary;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 48, 1),
     ]);
   }
@@ -177,6 +185,7 @@
   p?.x = null;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 32, 1),
     ]);
   }
@@ -191,6 +200,7 @@
   p?.loadLibrary = null;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 41, 1),
     ]);
   }
@@ -205,6 +215,7 @@
   return p;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT, 39, 1),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/undefined_annotation_test.dart b/pkg/analyzer/test/src/diagnostics/undefined_annotation_test.dart
index 711ac51..4012199 100644
--- a/pkg/analyzer/test/src/diagnostics/undefined_annotation_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/undefined_annotation_test.dart
@@ -52,6 +52,7 @@
 main() {
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.UNDEFINED_ANNOTATION, 25, 13),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/undefined_class_test.dart b/pkg/analyzer/test/src/diagnostics/undefined_class_test.dart
index 025eb5f..7c35f6a 100644
--- a/pkg/analyzer/test/src/diagnostics/undefined_class_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/undefined_class_test.dart
@@ -140,6 +140,7 @@
 
 p.A a;
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 11),
       error(CompileTimeErrorCode.UNDEFINED_CLASS, 26, 3),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/undefined_prefixed_name_test.dart b/pkg/analyzer/test/src/diagnostics/undefined_prefixed_name_test.dart
index 87cafbf..611ed57 100644
--- a/pkg/analyzer/test/src/diagnostics/undefined_prefixed_name_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/undefined_prefixed_name_test.dart
@@ -21,6 +21,7 @@
 import 'lib.dart' as p;
 f() => p.c;
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME, 33, 1),
     ]);
   }
@@ -33,6 +34,7 @@
   p.c = 0;
 }
 ''', [
+      error(HintCode.UNUSED_IMPORT, 7, 10),
       error(CompileTimeErrorCode.UNDEFINED_PREFIXED_NAME, 34, 1),
     ]);
   }
diff --git a/pkg/analyzer/test/src/diagnostics/unused_import_test.dart b/pkg/analyzer/test/src/diagnostics/unused_import_test.dart
index f574819..de780d7 100644
--- a/pkg/analyzer/test/src/diagnostics/unused_import_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/unused_import_test.dart
@@ -41,7 +41,6 @@
   }
 
   test_as_equalPrefixes_referenced() async {
-    // 18818
     newFile('$testPackageLibPath/lib1.dart', content: r'''
 class A {}
 ''');
@@ -56,9 +55,25 @@
 ''');
   }
 
-  @failingTest
+  test_as_equalPrefixes_referenced_via_export() async {
+    newFile('$testPackageLibPath/lib1.dart', content: r'''
+class A {}
+''');
+    newFile('$testPackageLibPath/lib2.dart', content: r'''
+class B {}
+''');
+    newFile('$testPackageLibPath/lib3.dart', content: r'''
+export 'lib2.dart';
+''');
+    await assertNoErrorsInCode(r'''
+import 'lib1.dart' as one;
+import 'lib3.dart' as one;
+one.A a;
+one.B b;
+''');
+  }
+
   test_as_equalPrefixes_unreferenced() async {
-    // See todo at ImportsVerifier.prefixElementMap.
     newFile('$testPackageLibPath/lib1.dart', content: r'''
 class A {}
 ''');
@@ -70,7 +85,59 @@
 import 'lib2.dart' as one;
 one.A a;
 ''', [
-      error(HintCode.UNUSED_IMPORT, 32, 11),
+      error(HintCode.UNUSED_IMPORT, 34, 11),
+    ]);
+  }
+
+  test_as_show_multipleElements() async {
+    newFile('$testPackageLibPath/lib1.dart', content: r'''
+class A {}
+class B {}
+''');
+    await assertNoErrorsInCode(r'''
+import 'lib1.dart' as one show A, B;
+one.A a = one.A();
+one.B b = one.B();
+''');
+  }
+
+  test_as_showTopLevelFunction() async {
+    newFile('$testPackageLibPath/lib1.dart', content: r'''
+class One {}
+topLevelFunction() {}
+''');
+    await assertErrorsInCode(r'''
+import 'lib1.dart' hide topLevelFunction;
+import 'lib1.dart' as one show topLevelFunction;
+class A {
+  static void x() {
+    One o;
+    one.topLevelFunction();
+  }
+}
+''', [
+      error(HintCode.UNUSED_LOCAL_VARIABLE, 129, 1),
+    ]);
+  }
+
+  test_as_showTopLevelFunction_multipleDirectives() async {
+    newFile('$testPackageLibPath/lib1.dart', content: r'''
+class One {}
+topLevelFunction() {}
+''');
+    await assertErrorsInCode(r'''
+import 'lib1.dart' hide topLevelFunction;
+import 'lib1.dart' as one show topLevelFunction;
+import 'lib1.dart' as two show topLevelFunction;
+class A {
+  static void x() {
+    One o;
+    one.topLevelFunction();
+    two.topLevelFunction();
+  }
+}
+''', [
+      error(HintCode.UNUSED_LOCAL_VARIABLE, 178, 1),
     ]);
   }
 
@@ -323,46 +390,6 @@
     ]);
   }
 
-  test_prefix_topLevelFunction() async {
-    newFile('$testPackageLibPath/lib1.dart', content: r'''
-class One {}
-topLevelFunction() {}
-''');
-    await assertErrorsInCode(r'''
-import 'lib1.dart' hide topLevelFunction;
-import 'lib1.dart' as one show topLevelFunction;
-class A {
-  static void x() {
-    One o;
-    one.topLevelFunction();
-  }
-}
-''', [
-      error(HintCode.UNUSED_LOCAL_VARIABLE, 129, 1),
-    ]);
-  }
-
-  test_prefix_topLevelFunction2() async {
-    newFile('$testPackageLibPath/lib1.dart', content: r'''
-class One {}
-topLevelFunction() {}
-''');
-    await assertErrorsInCode(r'''
-import 'lib1.dart' hide topLevelFunction;
-import 'lib1.dart' as one show topLevelFunction;
-import 'lib1.dart' as two show topLevelFunction;
-class A {
-  static void x() {
-    One o;
-    one.topLevelFunction();
-    two.topLevelFunction();
-  }
-}
-''', [
-      error(HintCode.UNUSED_LOCAL_VARIABLE, 178, 1),
-    ]);
-  }
-
   test_show() async {
     newFile('$testPackageLibPath/lib1.dart', content: r'''
 class A {}
diff --git a/pkg/analyzer/test/verify_diagnostics_test.dart b/pkg/analyzer/test/verify_diagnostics_test.dart
index af40e72..b057f27 100644
--- a/pkg/analyzer/test/verify_diagnostics_test.dart
+++ b/pkg/analyzer/test/verify_diagnostics_test.dart
@@ -49,6 +49,9 @@
     // Need a way to make auxiliary files that (a) are not included in the
     // generated docs or (b) can be made persistent for fixes.
     'CompileTimeErrorCode.PART_OF_NON_PART',
+    // Need to avoid reporting an unused import with a prefix, when the prefix
+    // is only referenced in an invalid way.
+    'CompileTimeErrorCode.PREFIX_IDENTIFIER_NOT_FOLLOWED_BY_DOT',
     // Produces the diagnostic HintCode.UNUSED_LOCAL_VARIABLE when it shouldn't.
     'CompileTimeErrorCode.UNDEFINED_IDENTIFIER_AWAIT',
     // The code has been replaced but is not yet removed.
diff --git a/pkg/analyzer_cli/lib/src/build_mode.dart b/pkg/analyzer_cli/lib/src/build_mode.dart
index f022eeb..76679f5 100644
--- a/pkg/analyzer_cli/lib/src/build_mode.dart
+++ b/pkg/analyzer_cli/lib/src/build_mode.dart
@@ -30,7 +30,6 @@
 import 'package:analyzer/src/summary2/bundle_reader.dart';
 import 'package:analyzer/src/summary2/link.dart' as summary2;
 import 'package:analyzer/src/summary2/linked_element_factory.dart' as summary2;
-import 'package:analyzer/src/summary2/linked_library_context.dart' as summary2;
 import 'package:analyzer/src/summary2/package_bundle_format.dart';
 import 'package:analyzer/src/summary2/reference.dart' as summary2;
 import 'package:analyzer_cli/src/context_cache.dart';
diff --git a/pkg/analyzer_plugin/test/src/utilities/completion/completion_target_test.dart b/pkg/analyzer_plugin/test/src/utilities/completion/completion_target_test.dart
index 04ab568..f95ad22 100644
--- a/pkg/analyzer_plugin/test/src/utilities/completion/completion_target_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/completion/completion_target_test.dart
@@ -5,7 +5,6 @@
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/generated/engine.dart' as analyzer;
-import 'package:analyzer/src/generated/parser.dart' as analyzer;
 import 'package:analyzer/src/test_utilities/find_element.dart';
 import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
 import 'package:test/test.dart';
diff --git a/pkg/front_end/test/crashing_test_case_minimizer.dart b/pkg/front_end/test/crashing_test_case_minimizer.dart
index 4f043ed..3248a9d 100644
--- a/pkg/front_end/test/crashing_test_case_minimizer.dart
+++ b/pkg/front_end/test/crashing_test_case_minimizer.dart
@@ -2,16 +2,19 @@
 // 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:convert' show utf8;
+import 'dart:async' show Future, StreamSubscription;
 
-import 'dart:io' show BytesBuilder, File, stdin;
+import 'dart:convert' show JsonEncoder, jsonDecode, utf8;
+
+import 'dart:io' show BytesBuilder, File, stdin, stdout;
+import 'dart:math' show max;
 
 import 'dart:typed_data' show Uint8List;
 
 import 'package:_fe_analyzer_shared/src/parser/parser.dart' show Parser;
 
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
-    show ScannerConfiguration, Token;
+    show ErrorToken, ScannerConfiguration, Token;
 
 import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
 
@@ -34,9 +37,19 @@
 import 'package:front_end/src/fasta/incremental_compiler.dart'
     show IncrementalCompiler;
 
+import 'package:front_end/src/fasta/kernel/utils.dart' show ByteSink;
+import 'package:front_end/src/fasta/util/direct_parser_ast.dart';
+import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
+
+import 'package:front_end/src/fasta/util/textual_outline.dart'
+    show textualOutline;
+
 import 'package:kernel/ast.dart' show Component;
 
+import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter;
+
 import 'package:kernel/target/targets.dart' show Target, TargetFlags;
+import 'package:package_config/package_config.dart';
 
 import "package:vm/target/flutter.dart" show FlutterTarget;
 
@@ -53,21 +66,47 @@
 Uri platformUri;
 bool noPlatform = false;
 bool nnbd = false;
+bool experimentalInvalidation = false;
+bool serialize = false;
 bool widgetTransformation = false;
 List<Uri> invalidate = [];
 String targetString = "VM";
 String expectedCrashLine;
+bool oldBlockDelete = false;
+bool lineDelete = false;
 bool byteDelete = false;
 bool askAboutRedirectCrashTarget = false;
 int stackTraceMatches = 1;
 Set<String> askedAboutRedirect = {};
+bool _quit = false;
+bool skip = false;
+
+Future<bool> shouldQuit() async {
+  // allow some time for stdin.listen to process data.
+  await new Future.delayed(new Duration(milliseconds: 5));
+  return _quit;
+}
+
+// TODO(jensj): Option to automatically find and search for _all_ crashes that
+// it uncovers --- i.e. it currently has an option to ask if we want to search
+// for the other crash instead --- add an option so it does that automatically
+// for everything it sees. One can possibly just make a copy of the state of
+// the file system and save that for later...
 
 main(List<String> arguments) async {
   String filename;
+  Uri loadFsJson;
   for (String arg in arguments) {
     if (arg.startsWith("--")) {
       if (arg == "--nnbd") {
         nnbd = true;
+      } else if (arg == "--experimental-invalidation") {
+        experimentalInvalidation = true;
+      } else if (arg == "--serialize") {
+        serialize = true;
+      } else if (arg.startsWith("--fsJson=")) {
+        String jsJson = arg.substring("--fsJson=".length);
+        loadFsJson = Uri.base.resolve(jsJson);
       } else if (arg.startsWith("--platform=")) {
         String platform = arg.substring("--platform=".length);
         platformUri = Uri.base.resolve(platform);
@@ -85,6 +124,10 @@
         targetString = "flutter";
       } else if (arg.startsWith("--target=ddc")) {
         targetString = "ddc";
+      } else if (arg == "--oldBlockDelete") {
+        oldBlockDelete = true;
+      } else if (arg == "--lineDelete") {
+        lineDelete = true;
       } else if (arg == "--byteDelete") {
         byteDelete = true;
       } else if (arg == "--ask-redirect-target") {
@@ -123,10 +166,17 @@
   if (!file.existsSync()) throw "File $filename doesn't exist.";
   mainUri = file.absolute.uri;
 
-  await tryToMinimize();
+  try {
+    await tryToMinimize(loadFsJson);
+  } catch (e) {
+    print("\n\n\nABOUT TO CRASH. DUMPING FS.");
+    dumpFsToJson();
+    print("\n\n\nABOUT TO CRASH. FS DUMPED.");
+    rethrow;
+  }
 }
 
-Future tryToMinimize() async {
+Future tryToMinimize(Uri loadFsJson) async {
   // Set main to be basically empty up front.
   fs.data[mainUri] = utf8.encode("main() {}");
   Component initialComponent = await getInitialComponent();
@@ -134,6 +184,11 @@
   // Remove fake cache.
   fs.data.remove(mainUri);
 
+  if (loadFsJson != null) {
+    File f = new File.fromUri(loadFsJson);
+    fs.initializeFromJson((jsonDecode(f.readAsStringSync())));
+  }
+
   // First assure it actually crash on the input.
   if (!await crashesOnCompile(initialComponent)) {
     throw "Input doesn't crash the compiler.";
@@ -143,13 +198,49 @@
   // All file should now be cached.
   fs._redirectAndRecord = false;
 
+  try {
+    stdin.echoMode = false;
+    stdin.lineMode = false;
+  } catch (e) {
+    print("error setting settings on stdin");
+  }
+  StreamSubscription<List<int>> stdinSubscription =
+      stdin.listen((List<int> event) {
+    if (event.length == 1 && event.single == "q".codeUnits.single) {
+      print("\n\nGot told to quit!\n\n");
+      _quit = true;
+    } else if (event.length == 1 && event.single == "s".codeUnits.single) {
+      print("\n\nGot told to skip!\n\n");
+      skip = true;
+    } else if (event.length == 1 && event.single == "i".codeUnits.single) {
+      print("\n\n--- STATUS INFORMATION START ---\n\n");
+      int totalFiles = 0;
+      int emptyFiles = 0;
+      int combinedSize = 0;
+      for (Uri uri in fs.data.keys) {
+        final Uint8List originalBytes = fs.data[uri];
+        if (originalBytes == null) continue;
+        totalFiles++;
+        if (originalBytes.isEmpty) emptyFiles++;
+        combinedSize += originalBytes.length;
+      }
+      print("Total files left: $totalFiles.");
+      print("Of which empty: $emptyFiles.");
+      print("Combined size left: $combinedSize bytes.");
+      print("\n\n--- STATUS INFORMATION END ---\n\n");
+      skip = true;
+    } else {
+      print("\n\nGot stdin input: $event\n\n");
+    }
+  });
+
   // For all dart files: Parse them as set their source as the parsed source
   // to "get around" any encoding issues when printing later.
   Map<Uri, Uint8List> copy = new Map.from(fs.data);
   for (Uri uri in fs.data.keys) {
+    if (await shouldQuit()) break;
     String uriString = uri.toString();
     if (uriString.endsWith(".json") ||
-        uriString.endsWith(".json") ||
         uriString.endsWith(".packages") ||
         uriString.endsWith(".dill") ||
         fs.data[uri] == null ||
@@ -174,18 +265,41 @@
   // Operate on one file at a time: Try to delete all content in file.
   List<Uri> uris = new List<Uri>.from(fs.data.keys);
 
-  bool removedSome = true;
-  while (removedSome) {
-    while (removedSome) {
-      removedSome = false;
+  // TODO(jensj): Can we "thread" this?
+  bool changedSome = true;
+  while (changedSome) {
+    if (await shouldQuit()) break;
+    while (changedSome) {
+      if (await shouldQuit()) break;
+      changedSome = false;
       for (int i = 0; i < uris.length; i++) {
+        if (await shouldQuit()) break;
         Uri uri = uris[i];
         if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
         print("About to work on file $i of ${uris.length}");
         await deleteContent(uris, i, false, initialComponent);
-        if (fs.data[uri] == null || fs.data[uri].isEmpty) removedSome = true;
+        if (fs.data[uri] == null || fs.data[uri].isEmpty) changedSome = true;
       }
     }
+
+    // Try to delete empty files.
+    bool changedSome2 = true;
+    while (changedSome2) {
+      if (await shouldQuit()) break;
+      changedSome2 = false;
+      for (int i = 0; i < uris.length; i++) {
+        if (await shouldQuit()) break;
+        Uri uri = uris[i];
+        if (fs.data[uri] == null || fs.data[uri].isNotEmpty) continue;
+        print("About to work on file $i of ${uris.length}");
+        await deleteContent(uris, i, false, initialComponent, deleteFile: true);
+        if (fs.data[uri] == null) {
+          changedSome = true;
+          changedSome2 = true;
+        }
+      }
+    }
+
     int left = 0;
     for (Uri uri in uris) {
       if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
@@ -195,15 +309,29 @@
 
     // Operate on one file at a time.
     for (Uri uri in fs.data.keys) {
+      if (await shouldQuit()) break;
       if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
 
       print("Now working on $uri");
 
-      // Try to delete lines.
       int prevLength = fs.data[uri].length;
-      await deleteLines(uri, initialComponent);
-      print("We're now at ${fs.data[uri].length} bytes for $uri.");
-      if (prevLength != fs.data[uri].length) removedSome = true;
+
+      await deleteBlocks(uri, initialComponent);
+      await deleteEmptyLines(uri, initialComponent);
+
+      if (oldBlockDelete) {
+        // Try to delete blocks.
+        await deleteBlocksOld(uri, initialComponent);
+      }
+
+      if (lineDelete) {
+        // Try to delete lines.
+        await deleteLines(uri, initialComponent);
+      }
+
+      print("We're now at ${fs.data[uri].length} bytes for $uri "
+          "(was $prevLength).");
+      if (prevLength != fs.data[uri].length) changedSome = true;
       if (fs.data[uri].isEmpty) continue;
 
       if (byteDelete) {
@@ -211,6 +339,7 @@
         // exponential binary search).
         int prevLength = fs.data[uri].length;
         while (true) {
+          if (await shouldQuit()) break;
           await binarySearchDeleteData(uri, initialComponent);
 
           if (fs.data[uri].length == prevLength) {
@@ -219,28 +348,349 @@
           } else {
             print("We're now at ${fs.data[uri].length} bytes");
             prevLength = fs.data[uri].length;
-            removedSome = true;
+            changedSome = true;
           }
         }
       }
     }
-  }
-
-  print("\n\nDONE\n\n");
-
-  for (Uri uri in uris) {
-    if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
-    print("Uri $uri has this content:");
-
-    try {
-      String utfDecoded = utf8.decode(fs.data[uri], allowMalformed: true);
-      print(utfDecoded);
-    } catch (e) {
-      print(fs.data[uri]);
-      print("(which crashes when trying to decode as utf8)");
+    for (Uri uri in fs.data.keys) {
+      if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
+      if (await shouldQuit()) break;
+      if (await attemptInline(uri, initialComponent)) {
+        changedSome = true;
+      }
     }
-    print("\n\n====================\n\n");
   }
+
+  if (await shouldQuit()) {
+    print("\n\nASKED TO QUIT\n\n");
+  } else {
+    print("\n\nDONE\n\n");
+  }
+
+  Uri jsonFsOut = dumpFsToJson();
+
+  await stdinSubscription.cancel();
+
+  if (!await shouldQuit()) {
+    // Test converting to incremental compiler yaml test.
+    outputIncrementalCompilerYamlTest();
+    print("\n\n\n");
+
+    for (Uri uri in uris) {
+      if (fs.data[uri] == null || fs.data[uri].isEmpty) continue;
+      print("Uri $uri has this content:");
+
+      try {
+        String utfDecoded = utf8.decode(fs.data[uri], allowMalformed: true);
+        print(utfDecoded);
+      } catch (e) {
+        print(fs.data[uri]);
+        print("(which crashes when trying to decode as utf8)");
+      }
+      print("\n\n====================\n\n");
+    }
+
+    print("Wrote json file system to $jsonFsOut");
+  }
+}
+
+Uri dumpFsToJson() {
+  JsonEncoder jsonEncoder = new JsonEncoder.withIndent("  ");
+  String jsonFs = jsonEncoder.convert(fs);
+  int i = 0;
+  Uri jsonFsOut;
+  while (jsonFsOut == null || new File.fromUri(jsonFsOut).existsSync()) {
+    jsonFsOut = Uri.base.resolve("crash_minimizer_result_$i");
+    i++;
+  }
+  new File.fromUri(jsonFsOut).writeAsStringSync(jsonFs);
+  print("Wrote json file system to $jsonFsOut");
+  return jsonFsOut;
+}
+
+/// Attempts to inline small files in other files.
+/// Returns true if anything was changed, i.e. if at least one inlining was a
+/// success.
+Future<bool> attemptInline(Uri uri, Component initialComponent) async {
+  // Don't attempt to inline the main uri --- that's our entry!
+  if (uri == mainUri) return false;
+
+  Uint8List inlineData = fs.data[uri];
+  bool hasMultipleLines = false;
+  for (int i = 0; i < inlineData.length; i++) {
+    if (inlineData[i] == $LF) {
+      hasMultipleLines = true;
+      break;
+    }
+  }
+  // TODO(jensj): Maybe inline slightly bigger files too?
+  if (hasMultipleLines) {
+    return false;
+  }
+
+  Uri inlinableUri = uri;
+
+  int compileTry = 0;
+  bool changed = false;
+
+  for (Uri uri in fs.data.keys) {
+    final Uint8List originalBytes = fs.data[uri];
+    if (originalBytes == null || originalBytes.isEmpty) continue;
+    DirectParserASTContentCompilationUnitEnd ast = getAST(originalBytes,
+        includeBody: false,
+        includeComments: false,
+        enableExtensionMethods: true,
+        enableNonNullable: nnbd);
+    // Find all imports/exports of this file (if any).
+    // If finding any:
+    // * remove all of them, then
+    // * find the end of the last import/export, and
+    // * insert the content of the file there.
+    // * if that *doesn't* work and we've inserted an export,
+    //   try converting that to an import instead.
+    List<Replacement> replacements = [];
+    for (DirectParserASTContentImportEnd import in ast.getImports()) {
+      Token importUriToken = import.importKeyword.next;
+      Uri importUri = _getUri(importUriToken, uri);
+      if (inlinableUri == importUri) {
+        replacements.add(new Replacement(
+            import.importKeyword.offset - 1, import.semicolon.offset + 1));
+      }
+    }
+    for (DirectParserASTContentExportEnd export in ast.getExports()) {
+      Token exportUriToken = export.exportKeyword.next;
+      Uri exportUri = _getUri(exportUriToken, uri);
+      if (inlinableUri == exportUri) {
+        replacements.add(new Replacement(
+            export.exportKeyword.offset - 1, export.semicolon.offset + 1));
+      }
+    }
+    if (replacements.isEmpty) continue;
+
+    // Step 1: Remove all imports/exports of this file.
+    Uint8List candidate = _replaceRange(replacements, originalBytes);
+
+    // Step 2: Find the last import/export.
+    int offsetOfLast = 0;
+    ast = getAST(candidate,
+        includeBody: false,
+        includeComments: false,
+        enableExtensionMethods: true,
+        enableNonNullable: nnbd);
+    for (DirectParserASTContentImportEnd import in ast.getImports()) {
+      offsetOfLast = max(offsetOfLast, import.semicolon.offset + 1);
+    }
+    for (DirectParserASTContentExportEnd export in ast.getExports()) {
+      offsetOfLast = max(offsetOfLast, export.semicolon.offset + 1);
+    }
+
+    // Step 3: Insert the content of the file there. Note, though,
+    // that any imports/exports in _that_ file should be changed to be valid
+    // in regards to the new placement.
+    BytesBuilder builder = new BytesBuilder();
+    for (int i = 0; i < offsetOfLast; i++) {
+      builder.addByte(candidate[i]);
+    }
+    builder.addByte($LF);
+    builder.add(_rewriteImportsExportsToUri(inlineData, uri, inlinableUri));
+    builder.addByte($LF);
+    for (int i = offsetOfLast; i < candidate.length; i++) {
+      builder.addByte(candidate[i]);
+    }
+    candidate = builder.takeBytes();
+
+    // Step 4: Try it out.
+    if (await shouldQuit()) break;
+    if (skip) {
+      skip = false;
+      break;
+    }
+    stdout.write(".");
+    compileTry++;
+    if (compileTry % 50 == 0) {
+      stdout.write("(at $compileTry)\n");
+    }
+    fs.data[uri] = candidate;
+    if (await crashesOnCompile(initialComponent)) {
+      print("Could inline $inlinableUri into $uri.");
+      changed = true;
+      // File was already updated.
+    } else {
+      // Couldn't replace that.
+      // Insert the original again.
+      fs.data[uri] = originalBytes;
+
+      // If we've inlined an export, try changing that to an import.
+      builder = new BytesBuilder();
+      for (int i = 0; i < offsetOfLast; i++) {
+        builder.addByte(candidate[i]);
+      }
+      // TODO(jensj): Only try compile again, if export was actually converted
+      // to import.
+      builder.addByte($LF);
+      builder.add(_rewriteImportsExportsToUri(inlineData, uri, inlinableUri,
+          convertExportToImport: true));
+      builder.addByte($LF);
+      for (int i = offsetOfLast; i < candidate.length; i++) {
+        builder.addByte(candidate[i]);
+      }
+      candidate = builder.takeBytes();
+
+      // Step 4: Try it out.
+      if (await shouldQuit()) break;
+      if (skip) {
+        skip = false;
+        break;
+      }
+      stdout.write(".");
+      compileTry++;
+      if (compileTry % 50 == 0) {
+        stdout.write("(at $compileTry)\n");
+      }
+      fs.data[uri] = candidate;
+      if (await crashesOnCompile(initialComponent)) {
+        print("Could inline $inlinableUri into $uri "
+            "(by converting export to import).");
+        changed = true;
+        // File was already updated.
+      } else {
+        // Couldn't replace that.
+        // Insert the original again.
+        fs.data[uri] = originalBytes;
+      }
+    }
+  }
+
+  return changed;
+}
+
+Uint8List _rewriteImportsExportsToUri(Uint8List oldData, Uri newUri, Uri oldUri,
+    {bool convertExportToImport: false}) {
+  DirectParserASTContentCompilationUnitEnd ast = getAST(oldData,
+      includeBody: false,
+      includeComments: false,
+      enableExtensionMethods: true,
+      enableNonNullable: nnbd);
+  List<Replacement> replacements = [];
+  for (DirectParserASTContentImportEnd import in ast.getImports()) {
+    _rewriteImportsExportsToUriInternal(
+        import.importKeyword.next, oldUri, replacements, newUri);
+  }
+  for (DirectParserASTContentExportEnd export in ast.getExports()) {
+    if (convertExportToImport) {
+      replacements.add(new Replacement(
+        export.exportKeyword.offset - 1,
+        export.exportKeyword.offset + export.exportKeyword.length,
+        nullOrReplacement: utf8.encode('import'),
+      ));
+    }
+    _rewriteImportsExportsToUriInternal(
+        export.exportKeyword.next, oldUri, replacements, newUri);
+  }
+  if (replacements.isNotEmpty) {
+    Uint8List candidate = _replaceRange(replacements, oldData);
+    return candidate;
+  }
+  return oldData;
+}
+
+void _rewriteImportsExportsToUriInternal(
+    Token uriToken, Uri oldUri, List<Replacement> replacements, Uri newUri) {
+  Uri tokenUri = _getUri(uriToken, oldUri, resolvePackage: false);
+  if (tokenUri.scheme == "package" || tokenUri.scheme == "dart") return;
+  Uri asPackageUri = _getImportUri(tokenUri);
+  if (asPackageUri.scheme == "package") {
+    // Just replace with this package uri.
+    replacements.add(new Replacement(
+      uriToken.offset - 1,
+      uriToken.offset + uriToken.length,
+      nullOrReplacement: utf8.encode('"${asPackageUri.toString()}"'),
+    ));
+  } else {
+    // TODO(jensj): Rewrite relative path to be correct.
+    throw "Rewrite $oldUri importing/exporting $tokenUri as $uriToken "
+        "for $newUri (notice $asPackageUri)";
+  }
+}
+
+Uri _getUri(Token uriToken, Uri uri, {bool resolvePackage: true}) {
+  String uriString = uriToken.lexeme;
+  uriString = uriString.substring(1, uriString.length - 1);
+  Uri uriTokenUri = uri.resolve(uriString);
+  if (resolvePackage && uriTokenUri.scheme == "package") {
+    Package package = _latestIncrementalCompiler
+        .currentPackagesMap[uriTokenUri.pathSegments.first];
+    uriTokenUri = package.packageUriRoot
+        .resolve(uriTokenUri.pathSegments.skip(1).join("/"));
+  }
+  return uriTokenUri;
+}
+
+Uri _getImportUri(Uri uri) {
+  return _latestIncrementalCompiler.userCode
+      .getEntryPointUri(uri, issueProblem: false);
+}
+
+void outputIncrementalCompilerYamlTest() {
+  int dartFiles = 0;
+  for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
+    if (entry.key.pathSegments.last.endsWith(".dart")) {
+      if (entry.value != null) dartFiles++;
+    }
+  }
+
+  print("------ Reproduction as semi-done incremental yaml test file ------");
+
+  // TODO(jensj): don't use full uris.
+  print("""
+# Copyright (c) 2020, 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.md file.
+
+# Reproduce a crash.
+
+type: newworld""");
+  if (widgetTransformation) {
+    print("trackWidgetCreation: true");
+    print("target: DDC # basically needed for widget creation to be run");
+  }
+  print("""
+worlds:
+  - entry: $mainUri""");
+  if (experimentalInvalidation) {
+    print("    experiments: alternative-invalidation-strategy");
+  }
+  print("    sources:");
+  for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
+    if (entry.value == null) continue;
+    print("      ${entry.key}: |");
+    String string = utf8.decode(entry.value);
+    List<String> lines = string.split("\n");
+    for (String line in lines) {
+      print("        $line");
+    }
+  }
+  print("    expectedLibraryCount: $dartFiles "
+      "# with parts this is not right");
+  print("");
+
+  for (Uri uri in invalidate) {
+    print("  - entry: $mainUri");
+    if (experimentalInvalidation) {
+      print("    experiments: alternative-invalidation-strategy");
+    }
+    print("    worldType: updated");
+    print("    expectInitializeFromDill: false # or true?");
+    print("    invalidate:");
+    print("      - $uri");
+    print("    expectedLibraryCount: $dartFiles "
+        "# with parts this is not right");
+    print("    expectsRebuildBodiesOnly: true # or false?");
+    print("");
+  }
+
+  print("------------------------------------------------------------------");
 }
 
 Uint8List sublist(Uint8List data, int start, int end) {
@@ -344,17 +794,23 @@
   fs.data[uri] = latestCrashData;
 }
 
-void _tryToRemoveUnreferencedFileContent(Component initialComponent) async {
+void _tryToRemoveUnreferencedFileContent(Component initialComponent,
+    {bool deleteFile: false}) async {
   // Check if there now are any unused files.
   if (_latestComponent == null) return;
   Set<Uri> neededUris = _latestComponent.uriToSource.keys.toSet();
   Map<Uri, Uint8List> copy = new Map.from(fs.data);
   bool removedSome = false;
+  if (await shouldQuit()) return;
   for (MapEntry<Uri, Uint8List> entry in fs.data.entries) {
     if (entry.value == null || entry.value.isEmpty) continue;
     if (!entry.key.toString().endsWith(".dart")) continue;
     if (!neededUris.contains(entry.key) && fs.data[entry.key].length != 0) {
-      fs.data[entry.key] = new Uint8List(0);
+      if (deleteFile) {
+        fs.data[entry.key] = null;
+      } else {
+        fs.data[entry.key] = new Uint8List(0);
+      }
       print(" => Can probably also delete ${entry.key}");
       removedSome = true;
     }
@@ -370,14 +826,23 @@
   }
 }
 
-void deleteContent(List<Uri> uris, int uriIndex, bool limitTo1,
-    Component initialComponent) async {
+void deleteContent(
+    List<Uri> uris, int uriIndex, bool limitTo1, Component initialComponent,
+    {bool deleteFile: false}) async {
+  String extraMessageText = "all content of ";
+  if (deleteFile) extraMessageText = "";
+
   if (!limitTo1) {
+    if (await shouldQuit()) return;
     Map<Uri, Uint8List> copy = new Map.from(fs.data);
     // Try to remove content of i and the next 9 (10 files in total).
     for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
       Uri uri = uris[j];
-      fs.data[uri] = new Uint8List(0);
+      if (deleteFile) {
+        fs.data[uri] = null;
+      } else {
+        fs.data[uri] = new Uint8List(0);
+      }
     }
     if (!await crashesOnCompile(initialComponent)) {
       // Couldn't delete all 10 files. Restore and try the single one.
@@ -386,29 +851,646 @@
     } else {
       for (int j = uriIndex; j < uriIndex + 10 && j < uris.length; j++) {
         Uri uri = uris[j];
-        print("Can delete all content of file $uri");
+        print("Can delete ${extraMessageText}file $uri");
       }
-      await _tryToRemoveUnreferencedFileContent(initialComponent);
+      await _tryToRemoveUnreferencedFileContent(initialComponent,
+          deleteFile: deleteFile);
       return;
     }
   }
 
+  if (await shouldQuit()) return;
   Uri uri = uris[uriIndex];
   Uint8List data = fs.data[uri];
-  fs.data[uri] = new Uint8List(0);
+  if (deleteFile) {
+    fs.data[uri] = null;
+  } else {
+    fs.data[uri] = new Uint8List(0);
+  }
   if (!await crashesOnCompile(initialComponent)) {
-    print("Can't delete all content of file $uri -- keeping it (for now)");
+    print("Can't delete ${extraMessageText}file $uri -- keeping it (for now)");
+    fs.data[uri] = data;
+
+    // For dart files we can't truncate completely try to "outline" them
+    // instead.
+    if (uri.toString().endsWith(".dart")) {
+      String textualOutlined =
+          textualOutline(data)?.replaceAll(RegExp(r'\n+'), "\n");
+
+      bool outlined = false;
+      if (textualOutlined != null) {
+        Uint8List candidate = utf8.encode(textualOutlined);
+        if (candidate.length != fs.data[uri].length) {
+          if (await shouldQuit()) return;
+          fs.data[uri] = candidate;
+          if (!await crashesOnCompile(initialComponent)) {
+            print("Can't outline the file $uri -- keeping it (for now)");
+            fs.data[uri] = data;
+          } else {
+            outlined = true;
+            print(
+                "Can outline the file $uri (now ${fs.data[uri].length} bytes)");
+          }
+        }
+      }
+      if (!outlined) {
+        // We can probably at least remove all comments then...
+        try {
+          List<String> strings = utf8.decode(fs.data[uri]).split("\n");
+          List<String> stringsLeft = [];
+          for (String string in strings) {
+            if (!string.trim().startsWith("//")) stringsLeft.add(string);
+          }
+
+          Uint8List candidate = utf8.encode(stringsLeft.join("\n"));
+          if (candidate.length != fs.data[uri].length) {
+            if (await shouldQuit()) return;
+            fs.data[uri] = candidate;
+            if (!await crashesOnCompile(initialComponent)) {
+              print("Can't remove comments for file $uri -- "
+                  "keeping it (for now)");
+              fs.data[uri] = data;
+            } else {
+              print("Removed comments for the file $uri");
+            }
+          }
+        } catch (e) {
+          // crash in scanner/parser --- keep original file. This crash might
+          // be what we're looking for!
+        }
+      }
+    }
+  } else {
+    print("Can delete ${extraMessageText}file $uri");
+    await _tryToRemoveUnreferencedFileContent(initialComponent);
+  }
+}
+
+void deleteBlocksOld(Uri uri, Component initialComponent) async {
+  if (uri.toString().endsWith(".json")) {
+    // Try to find annoying
+    //
+    //    },
+    //    {
+    //    }
+    //
+    // part of json and remove it.
+    Uint8List data = fs.data[uri];
+    String string = utf8.decode(data);
+    List<String> lines = string.split("\n");
+    for (int i = 0; i < lines.length - 2; i++) {
+      if (lines[i].trim() == "}," &&
+          lines[i + 1].trim() == "{" &&
+          lines[i + 2].trim() == "}") {
+        // This is the pattern we wanted to find. Remove it.
+        lines.removeRange(i, i + 2);
+        i--;
+      }
+    }
+    string = lines.join("\n");
+    fs.data[uri] = utf8.encode(string);
+    if (!await crashesOnCompile(initialComponent)) {
+      // For some reason that didn't work.
+      fs.data[uri] = data;
+    }
+  }
+  if (!uri.toString().endsWith(".dart")) return;
+
+  Uint8List data = fs.data[uri];
+  Uint8List latestCrashData = data;
+
+  List<int> lineStarts = new List<int>();
+
+  Token firstToken = parser_suite.scanRawBytes(data,
+      nnbd ? scannerConfiguration : scannerConfigurationNonNNBD, lineStarts);
+
+  if (firstToken == null) {
+    print("Got null token from scanner for $uri");
+    return;
+  }
+
+  int compileTry = 0;
+  Token token = firstToken;
+  while (token is ErrorToken) {
+    token = token.next;
+  }
+  List<Replacement> replacements = [];
+  while (token != null && !token.isEof) {
+    bool tryCompile = false;
+    Token skipToToken = token;
+    // Skip very small blocks (e.g. "{}" or "{\n}");
+    if (token.endGroup != null && token.offset + 3 < token.endGroup.offset) {
+      replacements.add(new Replacement(token.offset, token.endGroup.offset));
+      tryCompile = true;
+      skipToToken = token.endGroup;
+    } else if (token.lexeme == "@") {
+      if (token.next.next.endGroup != null) {
+        int end = token.next.next.endGroup.offset;
+        skipToToken = token.next.next.endGroup;
+        replacements.add(new Replacement(token.offset - 1, end + 1));
+        tryCompile = true;
+      }
+    } else if (token.lexeme == "assert") {
+      if (token.next.endGroup != null) {
+        int end = token.next.endGroup.offset;
+        skipToToken = token.next.endGroup;
+        if (token.next.endGroup.next.lexeme == ",") {
+          end = token.next.endGroup.next.offset;
+          skipToToken = token.next.endGroup.next;
+        }
+        // +/- 1 to not include the start and the end character.
+        replacements.add(new Replacement(token.offset - 1, end + 1));
+        tryCompile = true;
+      }
+    } else if ((token.lexeme == "abstract" && token.next.lexeme == "class") ||
+        token.lexeme == "class" ||
+        token.lexeme == "enum" ||
+        token.lexeme == "mixin" ||
+        token.lexeme == "static" ||
+        token.next.lexeme == "get" ||
+        token.next.lexeme == "set" ||
+        token.next.next.lexeme == "(" ||
+        (token.next.lexeme == "<" &&
+            token.next.endGroup != null &&
+            token.next.endGroup.next.next.lexeme == "(")) {
+      // Try to find and remove the entire class/enum/mixin/
+      // static procedure/getter/setter/simple procedure.
+      Token bracket = token;
+      for (int i = 0; i < 20; i++) {
+        // Find "{", but only go a maximum of 20 tokens to do that.
+        bracket = bracket.next;
+        if (bracket.lexeme == "{" && bracket.endGroup != null) {
+          break;
+        } else if ((bracket.lexeme == "(" || bracket.lexeme == "<") &&
+            bracket.endGroup != null) {
+          bracket = bracket.endGroup;
+        }
+      }
+      if (bracket.lexeme == "{" && bracket.endGroup != null) {
+        int end = bracket.endGroup.offset;
+        skipToToken = bracket.endGroup;
+        // +/- 1 to not include the start and the end character.
+        replacements.add(new Replacement(token.offset - 1, end + 1));
+        tryCompile = true;
+      }
+    }
+
+    if (tryCompile) {
+      if (await shouldQuit()) break;
+      if (skip) {
+        skip = false;
+        break;
+      }
+      stdout.write(".");
+      compileTry++;
+      if (compileTry % 50 == 0) {
+        stdout.write("(at $compileTry)\n");
+      }
+      Uint8List candidate = _replaceRange(replacements, data);
+      fs.data[uri] = candidate;
+      if (await crashesOnCompile(initialComponent)) {
+        print("Found block from "
+            "${replacements.last.from} to "
+            "${replacements.last.to} "
+            "that can be removed.");
+        latestCrashData = candidate;
+        token = skipToToken;
+      } else {
+        // Couldn't delete that.
+        replacements.removeLast();
+      }
+    }
+    token = token.next;
+  }
+  fs.data[uri] = latestCrashData;
+}
+
+void deleteBlocks(final Uri uri, Component initialComponent) async {
+  if (uri.toString().endsWith(".json")) {
+    // Try to find annoying
+    //
+    //    },
+    //    {
+    //    }
+    //
+    // part of json and remove it.
+    Uint8List data = fs.data[uri];
+    String string = utf8.decode(data);
+    List<String> lines = string.split("\n");
+    for (int i = 0; i < lines.length - 2; i++) {
+      if (lines[i].trim() == "}," &&
+          lines[i + 1].trim() == "{" &&
+          lines[i + 2].trim() == "}") {
+        // This is the pattern we wanted to find. Remove it.
+        lines.removeRange(i, i + 2);
+        i--;
+      }
+    }
+    string = lines.join("\n");
+    Uint8List candidate = utf8.encode(string);
+    if (candidate.length != data.length) {
+      fs.data[uri] = candidate;
+      if (!await crashesOnCompile(initialComponent)) {
+        // For some reason that didn't work.
+        fs.data[uri] = data;
+      }
+    }
+
+    // Try to load json and remove blocks.
+    try {
+      Map json = jsonDecode(utf8.decode(data));
+      Map jsonModified = new Map.from(json);
+      List packages = json["packages"];
+      List packagesModified = new List.from(packages);
+      jsonModified["packages"] = packagesModified;
+      int i = 0;
+      print("Note there's ${packagesModified.length} packages in .json");
+      JsonEncoder jsonEncoder = new JsonEncoder.withIndent("  ");
+      while (i < packagesModified.length) {
+        var oldEntry = packagesModified.removeAt(i);
+        String jsonString = jsonEncoder.convert(jsonModified);
+        candidate = utf8.encode(jsonString);
+        Uint8List previous = fs.data[uri];
+        fs.data[uri] = candidate;
+        if (!await crashesOnCompile(initialComponent)) {
+          // Couldn't remove that part.
+          fs.data[uri] = previous;
+          packagesModified.insert(i, oldEntry);
+          i++;
+        } else {
+          print(
+              "Removed package from .json (${packagesModified.length} left).");
+        }
+      }
+    } catch (e) {
+      // Couldn't decode it, so don't try to do anything.
+    }
+    return;
+  }
+  if (!uri.toString().endsWith(".dart")) return;
+
+  Uint8List data = fs.data[uri];
+  DirectParserASTContentCompilationUnitEnd ast = getAST(data,
+      includeBody: true,
+      includeComments: false,
+      enableExtensionMethods: true,
+      enableNonNullable: nnbd);
+
+  CompilationHelperClass helper = new CompilationHelperClass(data);
+
+  // Try to remove top level things on at a time.
+  for (DirectParserASTContent child in ast.children) {
+    bool shouldCompile = false;
+    String what = "";
+    if (child.isClass()) {
+      DirectParserASTContentClassDeclarationEnd cls = child.asClass();
+      helper.replacements.add(
+          new Replacement(cls.beginToken.offset - 1, cls.endToken.offset + 1));
+      shouldCompile = true;
+      what = "class";
+    } else if (child.isMixinDeclaration()) {
+      DirectParserASTContentMixinDeclarationEnd decl =
+          child.asMixinDeclaration();
+      helper.replacements.add(new Replacement(
+          decl.mixinKeyword.offset - 1, decl.endToken.offset + 1));
+      shouldCompile = true;
+      what = "mixin";
+    } else if (child.isNamedMixinDeclaration()) {
+      DirectParserASTContentNamedMixinApplicationEnd decl =
+          child.asNamedMixinDeclaration();
+      helper.replacements.add(
+          new Replacement(decl.begin.offset - 1, decl.endToken.offset + 1));
+      shouldCompile = true;
+      what = "named mixin";
+    } else if (child.isExtension()) {
+      DirectParserASTContentExtensionDeclarationEnd decl = child.asExtension();
+      helper.replacements.add(new Replacement(
+          decl.extensionKeyword.offset - 1, decl.endToken.offset + 1));
+      shouldCompile = true;
+      what = "extension";
+    } else if (child.isTopLevelFields()) {
+      DirectParserASTContentTopLevelFieldsEnd decl = child.asTopLevelFields();
+      helper.replacements.add(new Replacement(
+          decl.beginToken.offset - 1, decl.endToken.offset + 1));
+      shouldCompile = true;
+      what = "toplevel fields";
+    } else if (child.isTopLevelMethod()) {
+      DirectParserASTContentTopLevelMethodEnd decl = child.asTopLevelMethod();
+      helper.replacements.add(new Replacement(
+          decl.beginToken.offset - 1, decl.endToken.offset + 1));
+      shouldCompile = true;
+      what = "toplevel method";
+    } else if (child.isEnum()) {
+      DirectParserASTContentEnumEnd decl = child.asEnum();
+      helper.replacements.add(new Replacement(
+          decl.enumKeyword.offset - 1, decl.leftBrace.endGroup.offset + 1));
+      shouldCompile = true;
+      what = "enum";
+    } else if (child.isTypedef()) {
+      DirectParserASTContentFunctionTypeAliasEnd decl = child.asTypedef();
+      helper.replacements.add(new Replacement(
+          decl.typedefKeyword.offset - 1, decl.endToken.offset + 1));
+      shouldCompile = true;
+      what = "typedef";
+    } else if (child.isMetadata()) {
+      DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
+      List<DirectParserASTContentMetadataEnd> metadata =
+          decl.getMetadataEntries();
+      if (metadata.isNotEmpty) {
+        helper.replacements.add(new Replacement(
+            metadata.first.beginToken.offset - 1,
+            metadata.last.endToken.offset));
+        shouldCompile = true;
+      }
+      what = "metadata";
+    } else if (child.isImport()) {
+      DirectParserASTContentImportEnd decl = child.asImport();
+      helper.replacements.add(new Replacement(
+          decl.importKeyword.offset - 1, decl.semicolon.offset + 1));
+      shouldCompile = true;
+      what = "import";
+    } else if (child.isExport()) {
+      DirectParserASTContentExportEnd decl = child.asExport();
+      helper.replacements.add(new Replacement(
+          decl.exportKeyword.offset - 1, decl.semicolon.offset + 1));
+      shouldCompile = true;
+      what = "export";
+    } else if (child.isLibraryName()) {
+      DirectParserASTContentLibraryNameEnd decl = child.asLibraryName();
+      helper.replacements.add(new Replacement(
+          decl.libraryKeyword.offset - 1, decl.semicolon.offset + 1));
+      shouldCompile = true;
+      what = "library name";
+    } else if (child.isPart()) {
+      DirectParserASTContentPartEnd decl = child.asPart();
+      helper.replacements.add(new Replacement(
+          decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
+      shouldCompile = true;
+      what = "part";
+    } else if (child.isPartOf()) {
+      DirectParserASTContentPartOfEnd decl = child.asPartOf();
+      helper.replacements.add(new Replacement(
+          decl.partKeyword.offset - 1, decl.semicolon.offset + 1));
+      shouldCompile = true;
+      what = "part of";
+    } else if (child.isScript()) {
+      var decl = child.asScript();
+      helper.replacements.add(new Replacement(
+          decl.token.offset - 1, decl.token.offset + decl.token.length));
+      shouldCompile = true;
+      what = "script";
+    }
+
+    if (shouldCompile) {
+      bool success =
+          await _tryReplaceAndCompile(helper, uri, initialComponent, what);
+      if (helper.shouldQuit) return;
+      if (!success) {
+        if (child.isClass()) {
+          // Also try to remove all content of the class.
+          DirectParserASTContentClassDeclarationEnd decl = child.asClass();
+          DirectParserASTContentClassOrMixinBodyEnd body =
+              decl.getClassOrMixinBody();
+          if (body.beginToken.offset + 2 < body.endToken.offset) {
+            helper.replacements.add(
+                new Replacement(body.beginToken.offset, body.endToken.offset));
+            what = "class body";
+            success = await _tryReplaceAndCompile(
+                helper, uri, initialComponent, what);
+            if (helper.shouldQuit) return;
+          }
+
+          if (!success) {
+            // Also try to remove members one at a time.
+            for (DirectParserASTContent child in body.children) {
+              shouldCompile = false;
+              if (child is DirectParserASTContentMemberEnd) {
+                if (child.isClassConstructor()) {
+                  DirectParserASTContentClassConstructorEnd memberDecl =
+                      child.getClassConstructor();
+                  helper.replacements.add(new Replacement(
+                      memberDecl.beginToken.offset - 1,
+                      memberDecl.endToken.offset + 1));
+                  what = "class constructor";
+                  shouldCompile = true;
+                } else if (child.isClassFields()) {
+                  DirectParserASTContentClassFieldsEnd memberDecl =
+                      child.getClassFields();
+                  helper.replacements.add(new Replacement(
+                      memberDecl.beginToken.offset - 1,
+                      memberDecl.endToken.offset + 1));
+                  what = "class fields";
+                  shouldCompile = true;
+                } else if (child.isClassMethod()) {
+                  DirectParserASTContentClassMethodEnd memberDecl =
+                      child.getClassMethod();
+                  helper.replacements.add(new Replacement(
+                      memberDecl.beginToken.offset - 1,
+                      memberDecl.endToken.offset + 1));
+                  what = "class method";
+                  shouldCompile = true;
+                } else if (child.isClassFactoryMethod()) {
+                  DirectParserASTContentClassFactoryMethodEnd memberDecl =
+                      child.getClassFactoryMethod();
+                  helper.replacements.add(new Replacement(
+                      memberDecl.beginToken.offset - 1,
+                      memberDecl.endToken.offset + 1));
+                  what = "class factory method";
+                  shouldCompile = true;
+                } else {
+                  // throw "$child --- ${child.children}";
+                  continue;
+                }
+              } else if (child.isMetadata()) {
+                DirectParserASTContentMetadataStarEnd decl = child.asMetadata();
+                List<DirectParserASTContentMetadataEnd> metadata =
+                    decl.getMetadataEntries();
+                if (metadata.isNotEmpty) {
+                  helper.replacements.add(new Replacement(
+                      metadata.first.beginToken.offset - 1,
+                      metadata.last.endToken.offset));
+                  shouldCompile = true;
+                }
+                what = "metadata";
+              }
+              if (shouldCompile) {
+                success = await _tryReplaceAndCompile(
+                    helper, uri, initialComponent, what);
+                if (helper.shouldQuit) return;
+                if (!success) {
+                  DirectParserASTContentBlockFunctionBodyEnd decl;
+                  if (child is DirectParserASTContentMemberEnd) {
+                    if (child.isClassMethod()) {
+                      decl = child.getClassMethod().getBlockFunctionBody();
+                    } else if (child.isClassConstructor()) {
+                      decl = child.getClassConstructor().getBlockFunctionBody();
+                    }
+                  }
+                  if (decl != null &&
+                      decl.beginToken.offset + 2 < decl.endToken.offset) {
+                    helper.replacements.add(new Replacement(
+                        decl.beginToken.offset, decl.endToken.offset));
+                    what = "class member content";
+                    await _tryReplaceAndCompile(
+                        helper, uri, initialComponent, what);
+                    if (helper.shouldQuit) return;
+                  }
+                }
+              }
+            }
+          }
+
+          // Try to remove "extends", "implements" etc.
+          if (decl.getClassExtends().extendsKeyword != null) {
+            helper.replacements.add(new Replacement(
+                decl.getClassExtends().extendsKeyword.offset - 1,
+                body.beginToken.offset));
+            what = "class extends";
+            success = await _tryReplaceAndCompile(
+                helper, uri, initialComponent, what);
+            if (helper.shouldQuit) return;
+          }
+          if (decl.getClassImplements().implementsKeyword != null) {
+            helper.replacements.add(new Replacement(
+                decl.getClassImplements().implementsKeyword.offset - 1,
+                body.beginToken.offset));
+            what = "class implements";
+            success = await _tryReplaceAndCompile(
+                helper, uri, initialComponent, what);
+            if (helper.shouldQuit) return;
+          }
+          if (decl.getClassWithClause() != null) {
+            helper.replacements.add(new Replacement(
+                decl.getClassWithClause().withKeyword.offset - 1,
+                body.beginToken.offset));
+            what = "class with clause";
+            success = await _tryReplaceAndCompile(
+                helper, uri, initialComponent, what);
+            if (helper.shouldQuit) return;
+          }
+        }
+      }
+    }
+  }
+}
+
+class CompilationHelperClass {
+  int compileTry = 0;
+  bool shouldQuit = false;
+  List<Replacement> replacements = [];
+  Uint8List latestCrashData;
+  final Uint8List originalData;
+
+  CompilationHelperClass(this.originalData) : latestCrashData = originalData;
+}
+
+Future<bool> _tryReplaceAndCompile(CompilationHelperClass data, Uri uri,
+    Component initialComponent, String what) async {
+  if (await shouldQuit()) {
+    data.shouldQuit = true;
+    return false;
+  }
+  stdout.write(".");
+  data.compileTry++;
+  if (data.compileTry % 50 == 0) {
+    stdout.write("(at ${data.compileTry})\n");
+  }
+  Uint8List candidate = _replaceRange(data.replacements, data.originalData);
+
+  fs.data[uri] = candidate;
+  if (await crashesOnCompile(initialComponent)) {
+    print("Found $what from "
+        "${data.replacements.last.from} to "
+        "${data.replacements.last.to} "
+        "that can be removed.");
+    data.latestCrashData = candidate;
+    return true;
+  } else {
+    // Couldn't delete that.
+    data.replacements.removeLast();
+    fs.data[uri] = data.latestCrashData;
+    return false;
+  }
+}
+
+class Replacement implements Comparable<Replacement> {
+  final int from;
+  final int to;
+  final Uint8List nullOrReplacement;
+
+  Replacement(this.from, this.to, {this.nullOrReplacement});
+
+  @override
+  int compareTo(Replacement other) {
+    return from - other.from;
+  }
+}
+
+Uint8List _replaceRange(
+    List<Replacement> unsortedReplacements, Uint8List data) {
+  // The below assumes these are sorted.
+  List<Replacement> sortedReplacements =
+      new List<Replacement>.from(unsortedReplacements)..sort();
+  final BytesBuilder builder = new BytesBuilder();
+  int prev = 0;
+  for (int i = 0; i < sortedReplacements.length; i++) {
+    Replacement replacement = sortedReplacements[i];
+    for (int j = prev; j <= replacement.from; j++) {
+      builder.addByte(data[j]);
+    }
+    if (replacement.nullOrReplacement != null) {
+      builder.add(replacement.nullOrReplacement);
+    }
+    prev = replacement.to;
+  }
+  for (int j = prev; j < data.length; j++) {
+    builder.addByte(data[j]);
+  }
+  Uint8List candidate = builder.takeBytes();
+  return candidate;
+}
+
+const int $LF = 10;
+
+void deleteEmptyLines(Uri uri, Component initialComponent) async {
+  Uint8List data = fs.data[uri];
+  List<Uint8List> lines = [];
+  int start = 0;
+  for (int i = 0; i < data.length; i++) {
+    if (data[i] == $LF) {
+      if (i - start > 0) {
+        lines.add(sublist(data, start, i));
+      }
+      start = i + 1;
+    }
+  }
+  if (data.length - start > 0) {
+    lines.add(sublist(data, start, data.length));
+  }
+
+  final BytesBuilder builder = new BytesBuilder();
+  for (int j = 0; j < lines.length; j++) {
+    if (builder.isNotEmpty) {
+      builder.addByte($LF);
+    }
+    builder.add(lines[j]);
+  }
+  Uint8List candidate = builder.takeBytes();
+  if (candidate.length == data.length) return;
+
+  if (await shouldQuit()) return;
+  fs.data[uri] = candidate;
+  if (!await crashesOnCompile(initialComponent)) {
+    // For some reason the empty lines are important.
     fs.data[uri] = data;
   } else {
-    print("Can delete all content of file $uri");
-    await _tryToRemoveUnreferencedFileContent(initialComponent);
+    print("\nDeleted empty lines.");
   }
 }
 
 void deleteLines(Uri uri, Component initialComponent) async {
   // Try to delete "lines".
   Uint8List data = fs.data[uri];
-  const int $LF = 10;
   List<Uint8List> lines = [];
   int start = 0;
   for (int i = 0; i < data.length; i++) {
@@ -423,6 +1505,15 @@
   int length = 1;
   int i = 0;
   while (i < lines.length) {
+    if (await shouldQuit()) break;
+    if (skip) {
+      skip = false;
+      break;
+    }
+    stdout.write(".");
+    if (i % 50 == 0) {
+      stdout.write("(at $i of ${lines.length})\n");
+    }
     if (i + length > lines.length) {
       length = lines.length - i;
     }
@@ -432,10 +1523,10 @@
     final BytesBuilder builder = new BytesBuilder();
     for (int j = 0; j < lines.length; j++) {
       if (include[j]) {
-        builder.add(lines[j]);
-        if (j + 1 < lines.length) {
+        if (builder.isNotEmpty) {
           builder.addByte($LF);
         }
+        builder.add(lines[j]);
       }
     }
     Uint8List candidate = builder.takeBytes();
@@ -461,7 +1552,7 @@
         i++;
       }
     } else {
-      print("Can delete line $i (inclusive) - ${i + length} (exclusive) "
+      print("\nCan delete line $i (inclusive) - ${i + length} (exclusive) "
           "(of ${lines.length})");
       latestCrashData = candidate;
       i += length;
@@ -472,6 +1563,7 @@
 }
 
 Component _latestComponent;
+IncrementalCompiler _latestIncrementalCompiler;
 
 Future<bool> crashesOnCompile(Component initialComponent) async {
   IncrementalCompiler incrementalCompiler;
@@ -481,12 +1573,25 @@
     incrementalCompiler = new IncrementalCompiler.fromComponent(
         setupCompilerContext(), initialComponent);
   }
+  _latestIncrementalCompiler = incrementalCompiler;
   incrementalCompiler.invalidate(mainUri);
   try {
     _latestComponent = await incrementalCompiler.computeDelta();
+    if (serialize) {
+      ByteSink sink = new ByteSink();
+      BinaryPrinter printer = new BinaryPrinter(sink);
+      printer.writeComponentFile(_latestComponent);
+      sink.builder.takeBytes();
+    }
     for (Uri uri in invalidate) {
       incrementalCompiler.invalidate(uri);
-      await incrementalCompiler.computeDelta();
+      Component delta = await incrementalCompiler.computeDelta();
+      if (serialize) {
+        ByteSink sink = new ByteSink();
+        BinaryPrinter printer = new BinaryPrinter(sink);
+        printer.writeComponentFile(delta);
+        sink.builder.takeBytes();
+      }
     }
     _latestComponent = null; // if it didn't crash this isn't relevant.
     return false;
@@ -515,9 +1620,9 @@
     } else if (foundLine == expectedCrashLine) {
       return true;
     } else {
-      print("Crashed, but another place: $foundLine");
       if (askAboutRedirectCrashTarget &&
           !askedAboutRedirect.contains(foundLine)) {
+        print("Crashed, but another place: $foundLine");
         while (true) {
           askedAboutRedirect.add(foundLine);
           print(eWithSt);
@@ -552,6 +1657,11 @@
   if (nnbd) {
     options.explicitExperimentalFlags = {ExperimentalFlag.nonNullable: true};
   }
+  if (experimentalInvalidation) {
+    options.explicitExperimentalFlags ??= {};
+    options.explicitExperimentalFlags[
+        ExperimentalFlag.alternativeInvalidationStrategy] = true;
+  }
 
   TargetFlags targetFlags = new TargetFlags(
       enableNullSafety: nnbd, trackWidgetCreation: widgetTransformation);
@@ -616,12 +1726,52 @@
 
 class FakeFileSystem extends FileSystem {
   bool _redirectAndRecord = true;
+  bool _initialized = false;
   final Map<Uri, Uint8List> data = {};
 
   @override
   FileSystemEntity entityForUri(Uri uri) {
     return new FakeFileSystemEntity(this, uri);
   }
+
+  initializeFromJson(Map<String, dynamic> json) {
+    _initialized = true;
+    _redirectAndRecord = json['_redirectAndRecord'];
+    data.clear();
+    List tmp = json['data'];
+    for (int i = 0; i < tmp.length; i += 2) {
+      Uri key = tmp[i] == null ? null : Uri.parse(tmp[i]);
+      if (tmp[i + 1] == null) {
+        data[key] = null;
+      } else if (tmp[i + 1] is String) {
+        data[key] = utf8.encode(tmp[i + 1]);
+      } else {
+        data[key] = Uint8List.fromList(new List<int>.from(tmp[i + 1]));
+      }
+    }
+  }
+
+  Map<String, dynamic> toJson() {
+    List tmp = [];
+    for (var entry in data.entries) {
+      if (entry.value == null) continue;
+      tmp.add(entry.key == null ? null : entry.key.toString());
+      dynamic out = entry.value;
+      if (entry.value != null && entry.value.isNotEmpty) {
+        try {
+          String string = utf8.decode(entry.value);
+          out = string;
+        } catch (e) {
+          // not a string...
+        }
+      }
+      tmp.add(out);
+    }
+    return {
+      '_redirectAndRecord': _redirectAndRecord,
+      'data': tmp,
+    };
+  }
 }
 
 class FakeFileSystemEntity extends FileSystemEntity {
@@ -631,6 +1781,7 @@
 
   void _ensureCachedIfOk() {
     if (fs.data.containsKey(uri)) return;
+    if (fs._initialized) return;
     if (!fs._redirectAndRecord) {
       throw "Asked for file in non-recording mode that wasn't known";
     }
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index d67f2a8..ef0b0df 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -25,6 +25,7 @@
 amortized
 analyses
 animal
+annoying
 anon
 aoo
 approval
@@ -347,6 +348,7 @@
 increments
 indents
 initializer2
+inlinable
 instance2
 insufficient
 intdiv
@@ -448,6 +450,7 @@
 mf
 micro
 minimize
+minimizer
 mintty
 minutes
 mismatched
@@ -491,6 +494,7 @@
 out1
 out2
 outbound
+outlined
 overlay
 ox
 pack
@@ -559,6 +563,7 @@
 referring
 reflectee
 refusing
+regards
 regenerate
 regressions
 reify
@@ -568,6 +573,8 @@
 rendition
 repaint
 repro
+reproduce
+reproduction
 response
 result1
 result2
@@ -666,11 +673,13 @@
 test3b
 theoretically
 thereof
+thread
 timed
 timeout
 timer
 timings
 tinv
+told
 tpt
 transitively
 translators
@@ -693,6 +702,7 @@
 unawaited
 unbreak
 unconverted
+uncovers
 underline
 unpacked
 unpatched
diff --git a/sdk/lib/core/list.dart b/sdk/lib/core/list.dart
index ecd4c97..8cb3cc8 100644
--- a/sdk/lib/core/list.dart
+++ b/sdk/lib/core/list.dart
@@ -485,7 +485,7 @@
   void clear();
 
   /**
-   * Inserts the object at position [index] in this list.
+   * Inserts [element] at position [index] in this list.
    *
    * This increases the length of the list by one and shifts all objects
    * at or after the index towards the end of the list.
diff --git a/sdk/lib/core/set.dart b/sdk/lib/core/set.dart
index 6f47e07..edb05cf 100644
--- a/sdk/lib/core/set.dart
+++ b/sdk/lib/core/set.dart
@@ -136,7 +136,7 @@
   Iterator<E> get iterator;
 
   /**
-   * Returns true if [value] is in the set.
+   * Returns `true` if [value] is in the set.
    */
   bool contains(Object? value);
 
@@ -172,9 +172,9 @@
   void addAll(Iterable<E> elements);
 
   /**
-   * Removes [value] from the set. Returns true if [value] was
-   * in the set. Returns false otherwise. The method has no effect
-   * if [value] value was not in the set.
+   * Removes [value] from the set. Returns `true` if [value] was
+   * in the set. Returns `false` otherwise. The method has no effect
+   * if [value] was not in the set.
    */
   bool remove(Object? value);
 
@@ -205,7 +205,7 @@
    * Checks for each element of [elements] whether there is an element in this
    * set that is equal to it (according to `this.contains`), and if so, the
    * equal element in this set is retained, and elements that are not equal
-   * to any element in `elements` are removed.
+   * to any element in [elements] are removed.
    */
   void retainAll(Iterable<Object?> elements);
 
diff --git a/tools/VERSION b/tools/VERSION
index 1fe6ee9..a477cf6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 109
+PRERELEASE 110
 PRERELEASE_PATCH 0
\ No newline at end of file