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