Version 2.16.0-15.0.dev
Merge commit 'ffcc3e667eaf0272c703435bd7796a6d6aded531' into 'dev'
diff --git a/DEPS b/DEPS
index a79507d..a4e2e0f 100644
--- a/DEPS
+++ b/DEPS
@@ -39,7 +39,7 @@
# Checked-in SDK version. The checked-in SDK is a Dart SDK distribution in a
# cipd package used to run Dart scripts in the build and test infrastructure.
- "sdk_tag": "version:2.15.0-82.0.dev",
+ "sdk_tag": "version:2.15.0-268.8.beta",
# co19 is a cipd package. Use update.sh in tests/co19[_2] to update these
# hashes. It requires access to the dart-build-access group, which EngProd
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index 2689506..85a4474 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -141,6 +141,7 @@
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_MAX_RESULTS = 'maxResults';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_OFFSET = 'offset';
+const String COMPLETION_REQUEST_GET_SUGGESTIONS2_TIMEOUT = 'timeout';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS =
diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index 8cd904d..082036f 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -4698,7 +4698,14 @@
/// to true.
int maxResults;
- CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults);
+ /// The approximate time in milliseconds that the server should spend. The
+ /// server will perform some steps anyway, even if it takes longer than the
+ /// specified timeout. This field is intended to be used for benchmarking,
+ /// and usually should not be provided, so that the default timeout is used.
+ int? timeout;
+
+ CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults,
+ {this.timeout});
factory CompletionGetSuggestions2Params.fromJson(
JsonDecoder jsonDecoder, String jsonPath, Object? json) {
@@ -4723,7 +4730,12 @@
} else {
throw jsonDecoder.mismatch(jsonPath, 'maxResults');
}
- return CompletionGetSuggestions2Params(file, offset, maxResults);
+ int? timeout;
+ if (json.containsKey('timeout')) {
+ timeout = jsonDecoder.decodeInt(jsonPath + '.timeout', json['timeout']);
+ }
+ return CompletionGetSuggestions2Params(file, offset, maxResults,
+ timeout: timeout);
} else {
throw jsonDecoder.mismatch(
jsonPath, 'completion.getSuggestions2 params', json);
@@ -4741,6 +4753,10 @@
result['file'] = file;
result['offset'] = offset;
result['maxResults'] = maxResults;
+ var timeout = this.timeout;
+ if (timeout != null) {
+ result['timeout'] = timeout;
+ }
return result;
}
@@ -4757,7 +4773,8 @@
if (other is CompletionGetSuggestions2Params) {
return file == other.file &&
offset == other.offset &&
- maxResults == other.maxResults;
+ maxResults == other.maxResults &&
+ timeout == other.timeout;
}
return false;
}
@@ -4767,6 +4784,7 @@
file,
offset,
maxResults,
+ timeout,
);
}
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index cfc49eb..60c81b6 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -267,12 +267,17 @@
/// Implement the 'completion.getSuggestions2' request.
void getSuggestions2(Request request) async {
- var budget = CompletionBudget(budgetDuration);
-
var params = CompletionGetSuggestions2Params.fromRequest(request);
var file = params.file;
var offset = params.offset;
+ var timeoutMilliseconds = params.timeout;
+ var budget = CompletionBudget(
+ timeoutMilliseconds != null
+ ? Duration(milliseconds: timeoutMilliseconds)
+ : budgetDuration,
+ );
+
var provider = server.resourceProvider;
var pathContext = provider.pathContext;
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index a09c3b7..ede2ade 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -7,10 +7,13 @@
import 'package:analysis_server/src/services/correction/change_workspace.dart';
import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
+import 'package:analysis_server/src/services/correction/dart/organize_imports.dart';
+import 'package:analysis_server/src/services/correction/dart/remove_unused_import.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix/data_driven/transform_override_set.dart';
import 'package:analysis_server/src/services/correction/fix/data_driven/transform_override_set_parser.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/error/error.dart';
@@ -226,28 +229,107 @@
return builder;
}
+ Future<void> _applyProducer(
+ CorrectionProducerContext context, CorrectionProducer producer) async {
+ producer.configure(context);
+ try {
+ var localBuilder = builder.copy();
+ await producer.compute(localBuilder);
+ builder = localBuilder;
+ } on ConflictingEditException {
+ // If a conflicting edit was added in [compute], then the [localBuilder]
+ // is discarded and we revert to the previous state of the builder.
+ }
+ }
+
/// Use the change [builder] to create fixes for the diagnostics in the
/// library associated with the analysis [result].
Future<void> _fixErrorsInLibrary(ResolvedLibraryResult result) async {
var analysisOptions = result.session.analysisContext.analysisOptions;
- for (var unitResult in result.units) {
- var overrideSet = _readOverrideSet(unitResult);
- var errors = List.from(unitResult.errors, growable: false);
+ Iterable<AnalysisError> filteredErrors(ResolvedUnitResult result) sync* {
+ var errors = result.errors.toList();
errors.sort((a, b) => a.offset.compareTo(b.offset));
-
+ // Only fix errors not filtered out in analysis options.
for (var error in errors) {
var processor = ErrorProcessor.getProcessor(analysisOptions, error);
- // Only fix errors not filtered out in analysis options.
if (processor == null || processor.severity != null) {
- final fixContext = DartFixContextImpl(
- instrumentationService,
- workspace,
- unitResult,
- error,
- (name) => [],
- );
- await _fixSingleError(fixContext, unitResult, error, overrideSet);
+ yield error;
+ }
+ }
+ }
+
+ DartFixContextImpl fixContext(
+ ResolvedUnitResult result, AnalysisError diagnostic) {
+ return DartFixContextImpl(
+ instrumentationService,
+ workspace,
+ result,
+ diagnostic,
+ (name) => [],
+ );
+ }
+
+ CorrectionProducerContext? correctionContext(
+ ResolvedUnitResult result, AnalysisError diagnostic) {
+ var overrideSet = _readOverrideSet(result);
+ return CorrectionProducerContext.create(
+ applyingBulkFixes: true,
+ dartFixContext: fixContext(result, diagnostic),
+ diagnostic: diagnostic,
+ overrideSet: overrideSet,
+ resolvedResult: result,
+ selectionOffset: diagnostic.offset,
+ selectionLength: diagnostic.length,
+ workspace: workspace,
+ );
+ }
+
+ //
+ // Attempt to apply the fixes that aren't related to directives.
+ //
+ for (var unitResult in result.units) {
+ var overrideSet = _readOverrideSet(unitResult);
+ for (var error in filteredErrors(unitResult)) {
+ await _fixSingleError(
+ fixContext(unitResult, error), unitResult, error, overrideSet);
+ }
+ }
+ //
+ // If there are no such fixes in the defining compilation unit, then apply
+ // the fixes related to directives.
+ //
+ var definingUnit = result.units[0];
+ AnalysisError? directivesOrderingError;
+ var unusedImportErrors = <AnalysisError>[];
+ if (!builder.hasEditsFor(definingUnit.path)) {
+ for (var error in filteredErrors(definingUnit)) {
+ var errorCode = error.errorCode;
+ if (errorCode is LintCode) {
+ var lintName = errorCode.name;
+ if (lintName == LintNames.directives_ordering) {
+ directivesOrderingError = error;
+ break;
+ }
+ } else if (errorCode == HintCode.DUPLICATE_IMPORT ||
+ errorCode == HintCode.UNNECESSARY_IMPORT ||
+ errorCode == HintCode.UNUSED_IMPORT) {
+ unusedImportErrors.add(error);
+ }
+ }
+ if (directivesOrderingError != null) {
+ // `OrganizeImports` will also remove some of the unused imports, so we
+ // apply it first.
+ var context = correctionContext(definingUnit, directivesOrderingError);
+ if (context != null) {
+ await _applyProducer(context, OrganizeImports.newInstance());
+ }
+ } else {
+ for (var error in unusedImportErrors) {
+ var context = correctionContext(definingUnit, error);
+ if (context != null) {
+ await _applyProducer(context, RemoveUnusedImport.newInstance());
+ }
}
}
}
@@ -275,23 +357,11 @@
return;
}
- Future<void> compute(CorrectionProducer producer) async {
- producer.configure(context);
- try {
- var localBuilder = builder.copy();
- await producer.compute(localBuilder);
- builder = localBuilder;
- } on ConflictingEditException {
- // If a conflicting edit was added in [compute], then the [localBuilder]
- // is discarded and we revert to the previous state of the builder.
- }
- }
-
int computeChangeHash() => (builder as ChangeBuilderImpl).changeHash;
Future<void> generate(CorrectionProducer producer, String code) async {
var oldHash = computeChangeHash();
- await compute(producer);
+ await _applyProducer(context, producer);
var newHash = computeChangeHash();
if (newHash != oldHash) {
changeMap.add(result.path, code);
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart b/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart
index d5cf7c0..d655cc3 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/organize_imports.dart
@@ -11,7 +11,8 @@
class OrganizeImports extends CorrectionProducer {
@override
- bool get canBeAppliedInBulk => true;
+ // Bulk application is supported by a distinct import cleanup fix phase.
+ bool get canBeAppliedInBulk => false;
@override
// The fix is to sort all the directives, which will already fix all of the
diff --git a/pkg/analysis_server/test/integration/support/integration_test_methods.dart b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
index 9a707cf..aa0d470 100644
--- a/pkg/analysis_server/test/integration/support/integration_test_methods.dart
+++ b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
@@ -1020,9 +1020,11 @@
/// True if the number of suggestions after filtering was greater than the
/// requested maxResults.
Future<CompletionGetSuggestions2Result> sendCompletionGetSuggestions2(
- String file, int offset, int maxResults) async {
- var params =
- CompletionGetSuggestions2Params(file, offset, maxResults).toJson();
+ String file, int offset, int maxResults,
+ {int? timeout}) async {
+ var params = CompletionGetSuggestions2Params(file, offset, maxResults,
+ timeout: timeout)
+ .toJson();
var result = await server.send('completion.getSuggestions2', params);
var decoder = ResponseDecoder(null);
return CompletionGetSuggestions2Result.fromJson(decoder, 'result', result);
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index 5fd8c3c..70848d4 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -2139,7 +2139,8 @@
/// }
final Matcher isCompletionGetSuggestions2Params = LazyMatcher(() =>
MatchesJsonObject('completion.getSuggestions2 params',
- {'file': isFilePath, 'offset': isInt, 'maxResults': isInt}));
+ {'file': isFilePath, 'offset': isInt, 'maxResults': isInt},
+ optionalFields: {'timeout': isInt}));
/// completion.getSuggestions2 result
///
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
index cb280c7..6fc6671 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test.dart
@@ -1169,7 +1169,9 @@
import '$importUri';
void f(Old p) {}
''');
- await assertNoFix();
+ await assertHasFix('''
+void f(Old p) {}
+''');
}
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
index 6fbc9d8..c486e53 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_unused_import_test.dart
@@ -11,34 +11,85 @@
void main() {
defineReflectiveSuite(() {
- defineReflectiveTests(RemoveUnusedImportTest);
+ defineReflectiveTests(RemoveUnusedImportBulkTest);
defineReflectiveTests(RemoveUnusedImportMultiTest);
+ defineReflectiveTests(RemoveUnusedImportTest);
});
}
@reflectiveTest
+class RemoveUnusedImportBulkTest extends BulkFixProcessorTest {
+ @FailingTest(reason: 'multiple deletions conflict')
+ Future<void> test_multipleOnSingleLine() async {
+ // TODO(brianwilkerson) Remove test_multipleOnSingleLine_temporary when this
+ // test starts to pass.
+ await resolveTestCode('''
+import 'dart:collection'; import 'dart:math'; import 'dart:async';
+void f() {}
+''');
+ await assertHasFix('''
+
+void f() {}
+''');
+ }
+
+ Future<void> test_multipleOnSingleLine_temporary() async {
+ await resolveTestCode('''
+import 'dart:collection'; import 'dart:math'; import 'dart:async';
+void f() {}
+''');
+ await assertHasFix('''
+import 'dart:math';
+void f() {}
+''');
+ }
+
+ Future<void> test_multipleUnused() async {
+ await resolveTestCode('''
+import 'dart:collection';
+import 'dart:math';
+import 'dart:async';
+void f() {}
+''');
+ await assertHasFix('''
+void f() {}
+''');
+ }
+
+ Future<void> test_usedAndUnused() async {
+ await resolveTestCode('''
+import 'dart:async';
+import 'dart:math' as math;
+import 'dart:async';
+
+var tau = math.pi * 2;
+
+void f() {}
+''');
+ await assertHasFix('''
+import 'dart:math' as math;
+
+var tau = math.pi * 2;
+
+void f() {}
+''');
+ }
+}
+
+@reflectiveTest
class RemoveUnusedImportMultiTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.REMOVE_UNUSED_IMPORT_MULTI;
- @override
- void setUp() {
- super.setUp();
- // TODO(dantup): Get these tests passing with either line ending.
- useLineEndingsForPlatform = false;
- }
-
Future<void> test_all_diverseImports() async {
await resolveTestCode('''
import 'dart:math';
import 'dart:math';
import 'dart:async';
-main() {
-}
+void f() {}
''');
await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
-main() {
-}
+void f() {}
''');
}
@@ -50,29 +101,39 @@
var tau = math.pi * 2;
-main() {
-}
+void f() {}
''');
await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
import 'dart:math' as math;
var tau = math.pi * 2;
-main() {
-}
+void f() {}
''');
}
- @FailingTest(reason: 'one unused import remains unremoved')
+ @FailingTest(reason: 'multiple deletions conflict')
Future<void> test_all_singleLine() async {
+ // TODO(brianwilkerson) Remove test_multipleOnSingleLine_temporary when this
+ // test starts to pass.
await resolveTestCode('''
import 'dart:math'; import 'dart:math'; import 'dart:math';
-main() {
-}
+void f() {}
''');
await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
-main() {
-}
+
+void f() {}
+''');
+ }
+
+ Future<void> test_all_singleLine_temporary() async {
+ await resolveTestCode('''
+import 'dart:math'; import 'dart:math'; import 'dart:math';
+void f() {}
+''');
+ await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
+import 'dart:math';
+void f() {}
''');
}
@@ -81,12 +142,10 @@
import 'dart:math';
import 'dart:math';
import 'dart:math';
-main() {
-}
+void f() {}
''');
await assertHasFixAllFix(HintCode.UNUSED_IMPORT, '''
-main() {
-}
+void f() {}
''');
}
}
@@ -96,13 +155,6 @@
@override
FixKind get kind => DartFixKind.REMOVE_UNUSED_IMPORT;
- @override
- void setUp() {
- super.setUp();
- // TODO(dantup): Get these tests passing with either line ending.
- useLineEndingsForPlatform = false;
- }
-
Future<void> test_anotherImportOnLine() async {
await resolveTestCode('''
import 'dart:math'; import 'dart:async';
@@ -125,14 +177,14 @@
import 'dart:math';
import 'dart:math';
-main() {
+void f() {
print(min(0, 1));
}
''');
await assertHasFix('''
import 'dart:math';
-main() {
+void f() {
print(min(0, 1));
}
''');
@@ -142,11 +194,11 @@
await resolveTestCode('''
import
'dart:math';
-main() {
+void f() {
}
''');
await assertHasFix('''
-main() {
+void f() {
}
''');
}
@@ -154,11 +206,11 @@
Future<void> test_single() async {
await resolveTestCode('''
import 'dart:math';
-main() {
+void f() {
}
''');
await assertHasFix('''
-main() {
+void f() {
}
''');
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart b/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart
index 49f9041..e3245f3 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/replace_with_tear_off_test.dart
@@ -82,7 +82,6 @@
''');
}
- // @soloTest
Future<void> test_constructorTearOff_nameUnnamed() async {
await resolveTestCode('''
class C {
diff --git a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
index 9f7e268..2108516 100644
--- a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
+++ b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
@@ -441,8 +441,12 @@
* @param offset The offset within the file at which suggestions are to be made.
* @param maxResults The maximum number of suggestions to return. If the number of suggestions
* after filtering is greater than the maxResults, then isIncomplete is set to true.
+ * @param timeout The approximate time in milliseconds that the server should spend. The server
+ * will perform some steps anyway, even if it takes longer than the specified timeout. This
+ * field is intended to be used for benchmarking, and usually should not be provided, so
+ * that the default timeout is used.
*/
- public void completion_getSuggestions2(String file, int offset, int maxResults, GetSuggestions2Consumer consumer);
+ public void completion_getSuggestions2(String file, int offset, int maxResults, int timeout, GetSuggestions2Consumer consumer);
/**
* {@code completion.registerLibraryPaths}
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index aa08d74..d89a44b 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -1482,6 +1482,16 @@
then <tt>isIncomplete</tt> is set to <tt>true</tt>.
</p>
</field>
+ <field name="timeout" experimental="true" optional="true">
+ <ref>int</ref>
+ <p>
+ The approximate time in milliseconds that the server should spend.
+ The server will perform some steps anyway, even if it takes longer
+ than the specified timeout. This field is intended to be used for
+ benchmarking, and usually should not be provided, so that the
+ default timeout is used.
+ </p>
+ </field>
</params>
<result>
<field name="replacementOffset">
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
index 2689506..85a4474 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
@@ -141,6 +141,7 @@
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_MAX_RESULTS = 'maxResults';
const String COMPLETION_REQUEST_GET_SUGGESTIONS2_OFFSET = 'offset';
+const String COMPLETION_REQUEST_GET_SUGGESTIONS2_TIMEOUT = 'timeout';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_FILE = 'file';
const String COMPLETION_REQUEST_GET_SUGGESTIONS_OFFSET = 'offset';
const String COMPLETION_REQUEST_GET_SUGGESTION_DETAILS =
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
index 2dcbd32..2ebd67f 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
@@ -4698,7 +4698,14 @@
/// to true.
int maxResults;
- CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults);
+ /// The approximate time in milliseconds that the server should spend. The
+ /// server will perform some steps anyway, even if it takes longer than the
+ /// specified timeout. This field is intended to be used for benchmarking,
+ /// and usually should not be provided, so that the default timeout is used.
+ int? timeout;
+
+ CompletionGetSuggestions2Params(this.file, this.offset, this.maxResults,
+ {this.timeout});
factory CompletionGetSuggestions2Params.fromJson(
JsonDecoder jsonDecoder, String jsonPath, Object? json) {
@@ -4723,7 +4730,12 @@
} else {
throw jsonDecoder.mismatch(jsonPath, 'maxResults');
}
- return CompletionGetSuggestions2Params(file, offset, maxResults);
+ int? timeout;
+ if (json.containsKey('timeout')) {
+ timeout = jsonDecoder.decodeInt(jsonPath + '.timeout', json['timeout']);
+ }
+ return CompletionGetSuggestions2Params(file, offset, maxResults,
+ timeout: timeout);
} else {
throw jsonDecoder.mismatch(
jsonPath, 'completion.getSuggestions2 params', json);
@@ -4741,6 +4753,10 @@
result['file'] = file;
result['offset'] = offset;
result['maxResults'] = maxResults;
+ var timeout = this.timeout;
+ if (timeout != null) {
+ result['timeout'] = timeout;
+ }
return result;
}
@@ -4757,7 +4773,8 @@
if (other is CompletionGetSuggestions2Params) {
return file == other.file &&
offset == other.offset &&
- maxResults == other.maxResults;
+ maxResults == other.maxResults &&
+ timeout == other.timeout;
}
return false;
}
@@ -4767,6 +4784,7 @@
file,
offset,
maxResults,
+ timeout,
);
}
diff --git a/pkg/analyzer/lib/src/dart/micro/library_graph.dart b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
index 7bd4630..581a44e 100644
--- a/pkg/analyzer/lib/src/dart/micro/library_graph.dart
+++ b/pkg/analyzer/lib/src/dart/micro/library_graph.dart
@@ -428,11 +428,7 @@
}
String? getPathForUri(Uri uri) {
- var source = _sourceFactory.forUri2(uri);
- if (source == null) {
- return null;
- }
- return source.fullName;
+ return _sourceFactory.forUri2(uri)?.fullName;
}
/// Computes the set of [FileState]'s used/not used to analyze the given
@@ -712,19 +708,28 @@
return _fsState._resourceProvider.getFile(path);
}
+ Uri? resolveRelativeUriStr(String relativeUriStr) {
+ if (relativeUriStr.isEmpty) {
+ return null;
+ }
+
+ Uri relativeUri;
+ try {
+ relativeUri = Uri.parse(relativeUriStr);
+ } on FormatException {
+ return null;
+ }
+
+ return resolveRelativeUri(uri, relativeUri);
+ }
+
FileState? _fileForRelativeUri({
FileState? containingLibrary,
required String relativeUri,
required OperationPerformanceImpl performance,
}) {
- if (relativeUri.isEmpty) {
- return null;
- }
-
- Uri absoluteUri;
- try {
- absoluteUri = resolveRelativeUri(uri, Uri.parse(relativeUri));
- } on FormatException {
+ var absoluteUri = resolveRelativeUriStr(relativeUri);
+ if (absoluteUri == null) {
return null;
}
@@ -900,41 +905,35 @@
}
void _prefetchDirectReferences() {
- if (location._fsState.prefetchFiles == null) {
+ var prefetchFiles = location._fsState.prefetchFiles;
+ if (prefetchFiles == null) {
return;
}
var paths = <String>{};
- /// TODO(scheglov) This is duplicate.
- void findPathForUri(String relativeUri) {
- if (relativeUri.isEmpty) {
- return;
- }
- Uri absoluteUri;
- try {
- absoluteUri = resolveRelativeUri(location.uri, Uri.parse(relativeUri));
- } on FormatException {
- return;
- }
- var p = location._fsState.getPathForUri(absoluteUri);
- if (p != null) {
- paths.add(p);
+ void addRelativeUri(String relativeUri) {
+ var absoluteUri = location.resolveRelativeUriStr(relativeUri);
+ if (absoluteUri != null) {
+ var path = location._fsState.getPathForUri(absoluteUri);
+ if (path != null) {
+ paths.add(path);
+ }
}
}
var unlinkedUnit = unlinked.unit;
for (var directive in unlinkedUnit.imports) {
- findPathForUri(directive.uri);
+ addRelativeUri(directive.uri);
}
for (var directive in unlinkedUnit.exports) {
- findPathForUri(directive.uri);
+ addRelativeUri(directive.uri);
}
for (var uri in unlinkedUnit.parts) {
- findPathForUri(uri);
+ addRelativeUri(uri);
}
- location._fsState.prefetchFiles!(paths.toList());
+ prefetchFiles(paths.toList());
}
static CompilationUnitImpl parse(AnalysisErrorListener errorListener,
diff --git a/pkg/analyzer/lib/src/lint/pub.dart b/pkg/analyzer/lib/src/lint/pub.dart
index 86d7c17..be237ff 100644
--- a/pkg/analyzer/lib/src/lint/pub.dart
+++ b/pkg/analyzer/lib/src/lint/pub.dart
@@ -75,17 +75,6 @@
return null;
}
-PSNodeList? _processList(
- YamlScalar key, YamlNode v, ResourceProvider? resourceProvider) {
- if (v is! YamlList) {
- return null;
- }
- YamlList nodeList = v;
-
- return _PSNodeList(_PSNode(key, resourceProvider),
- nodeList.nodes.map((n) => _PSNode(n, resourceProvider)));
-}
-
PSEntry? _processScalar(
YamlScalar key, YamlNode value, ResourceProvider? resourceProvider) {
if (value is! YamlScalar) {
@@ -96,6 +85,20 @@
_PSNode(key, resourceProvider), _PSNode(value, resourceProvider));
}
+PSNodeList? _processScalarList(
+ YamlScalar key, YamlNode v, ResourceProvider? resourceProvider) {
+ if (v is! YamlList) {
+ return null;
+ }
+ YamlList nodeList = v;
+
+ return _PSNodeList(
+ _PSNode(key, resourceProvider),
+ nodeList.nodes
+ .whereType<YamlScalar>()
+ .map((n) => _PSNode(n, resourceProvider)));
+}
+
/// Representation of a key/value pair a map from package name to
/// _package description_.
///
@@ -166,9 +169,22 @@
PSEntry? get url;
}
+/// Representation of a leaf-node from `pubspec.yaml`.
abstract class PSNode {
Source get source;
SourceSpan get span;
+
+ /// String value of the node, or `null` if value in pubspec.yaml is `null` or
+ /// omitted.
+ ///
+ /// **Example**
+ /// ```
+ /// name: foo
+ /// version:
+ /// ```
+ /// In the example above the [PSNode] for `foo` will have [text] "foo", and
+ /// the [PSNode] for `version` will have not have [text] as `null`, as empty
+ /// value or `"null"` is the same in YAML.
String? get text;
}
@@ -353,7 +369,7 @@
final ResourceProvider? resourceProvider;
- _PSNode(YamlNode node, this.resourceProvider)
+ _PSNode(YamlScalar node, this.resourceProvider)
: text = node.value?.toString(),
span = node.span;
@@ -481,7 +497,7 @@
author = _processScalar(key, v, resourceProvider);
break;
case 'authors':
- authors = _processList(key, v, resourceProvider);
+ authors = _processScalarList(key, v, resourceProvider);
break;
case 'homepage':
homepage = _processScalar(key, v, resourceProvider);
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
index a9597f7..d2d24b8 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
@@ -242,6 +242,13 @@
}
@override
+ bool hasEditsFor(String path) {
+ return _dartFileEditBuilders.containsKey(path) ||
+ _genericFileEditBuilders.containsKey(path) ||
+ _yamlFileEditBuilders.containsKey(path);
+ }
+
+ @override
void setSelection(Position position) {
_selection = position;
}
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
index e407543..804c3c4c 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_core.dart
@@ -63,6 +63,10 @@
/// that changes to the copy will not effect this change builder.
ChangeBuilder copy();
+ /// Return `true` if this builder already has edits for the file with the
+ /// given [path].
+ bool hasEditsFor(String path);
+
/// Set the selection for the change being built to the given [position].
void setSelection(Position position);
}
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 91c66d3..cd26c6b 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -189,11 +189,12 @@
};
}
-// Creates a lazy final field that uses non-nullable initialization semantics.
+// Creates a lazy final static field that uses non-nullable initialization
+// semantics.
//
-// A lazy field has a storage entry, [name], which holds the value, and a
-// getter ([getterName]) to access the field. If the field wasn't set before
-// the first access, it is initialized with the [initializer].
+// A lazy final field has a storage entry, [name], which holds the value, and a
+// getter ([getterName]) to access the field. The field is initialized on first
+// access with the [initializer].
function lazyFinal(holder, name, getterName, initializer) {
var uninitializedSentinel = holder;
holder[name] = uninitializedSentinel;
@@ -201,12 +202,21 @@
if (holder[name] === uninitializedSentinel) {
var value = initializer();
if (holder[name] !== uninitializedSentinel) {
+ // Since there is no setter, the only way to get here is via bounded
+ // recursion, where `initializer` calls the lazy final getter.
#throwLateFieldADI(name);
}
holder[name] = value;
}
- holder[getterName] = function() { return this[name]; };
- return holder[name];
+ // TODO(sra): Does the value need to be stored in the holder at all?
+ // Potentially a call to the getter could be replaced with an access to
+ // holder slot if dominated by a previous call to the getter. Does the
+ // optimizer do this? If so, within a single function, the dominance
+ // relations should allow a local copy via GVN, so it is not that valuable
+ // an optimization for a `final` static variable.
+ var finalValue = holder[name];
+ holder[getterName] = function() { return finalValue; };
+ return finalValue;
};
}
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 655d646..5f70e3b 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -53,6 +53,13 @@
/// Typedef for handlers of VM Service stream events.
typedef _StreamEventHandler<T> = FutureOr<void> Function(T data);
+/// A null result passed to `sendResponse` functions when there is no result.
+///
+/// Because the signature of `sendResponse` is generic, an argument must be
+/// provided even when the generic type is `void`. This value is used to make
+/// it clearer in calling code that the result is unused.
+const _noResult = null;
+
/// Pattern for extracting useful error messages from an evaluation exception.
final _evalErrorMessagePattern = RegExp('Error: (.*)');
@@ -661,26 +668,26 @@
) async {
switch (request.command) {
- /// Used by tests to validate available protocols (e.g. DDS). There may be
- /// value in making this available to clients in future, but for now it's
- /// internal.
+ // Used by tests to validate available protocols (e.g. DDS). There may be
+ // value in making this available to clients in future, but for now it's
+ // internal.
case '_getSupportedProtocols':
final protocols = await vmService?.getSupportedProtocols();
sendResponse(protocols?.toJson());
break;
- /// Used to toggle debug settings such as whether SDK/Packages are
- /// debuggable while the session is in progress.
+ // Used to toggle debug settings such as whether SDK/Packages are
+ // debuggable while the session is in progress.
case 'updateDebugOptions':
if (args != null) {
await _updateDebugOptions(args.args);
}
- sendResponse(null);
+ sendResponse(_noResult);
break;
- /// Allows an editor to call a service/service extension that it was told
- /// about via a custom 'dart.serviceRegistered' or
- /// 'dart.serviceExtensionAdded' event.
+ // Allows an editor to call a service/service extension that it was told
+ // about via a custom 'dart.serviceRegistered' or
+ // 'dart.serviceExtensionAdded' event.
case 'callService':
final method = args?.args['method'] as String?;
if (method == null) {
@@ -696,6 +703,15 @@
sendResponse(response?.json);
break;
+ // Used to reload sources for all isolates. This supports Hot Reload for
+ // Dart apps. Flutter's DAP handles this command itself (and sends it
+ // through the run daemon) as it needs to perform additional work to
+ // rebuild widgets afterwards.
+ case 'hotReload':
+ await _isolateManager.reloadSources();
+ sendResponse(_noResult);
+ break;
+
default:
await super.customRequest(request, args, sendResponse);
}
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 2d82bbd..215a975 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -376,7 +376,7 @@
/// passes an unused arg so that `Function()` can be passed to a function
/// accepting `Function<T>(T x)` where `T` happens to be `void`.
///
- /// This allows handlers to simple call sendResponse() where they have no
+ /// This allows handlers to simply call sendResponse() where they have no
/// return value but need to send a valid response.
_VoidArgRequestHandler<TArg> _withVoidResponse<TArg>(
_VoidNoArgRequestHandler<TArg> handler,
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index fd2d342..47a8dde 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -136,10 +136,7 @@
vm.Event event, {
bool resumeIfStarting = true,
}) async {
- final isolateId = event.isolate?.id;
- if (isolateId == null) {
- return;
- }
+ final isolateId = event.isolate?.id!;
final eventKind = event.kind;
if (eventKind == vm.EventKind.kIsolateStart ||
@@ -203,12 +200,16 @@
return info;
}
+ /// Calls reloadSources for all isolates.
+ Future<void> reloadSources() async {
+ await Future.wait(_threadsByThreadId.values.map(
+ (isolate) => _reloadSources(isolate.isolate),
+ ));
+ }
+
Future<void> resumeIsolate(vm.IsolateRef isolateRef,
[String? resumeType]) async {
- final isolateId = isolateRef.id;
- if (isolateId == null) {
- return;
- }
+ final isolateId = isolateRef.id!;
final thread = _threadsByIsolateId[isolateId];
if (thread == null) {
@@ -527,6 +528,18 @@
}
}
+ /// Calls reloadSources for the given isolate.
+ Future<void> _reloadSources(vm.IsolateRef isolateRef) async {
+ final service = _adapter.vmService;
+ if (!debug || service == null) {
+ return;
+ }
+
+ final isolateId = isolateRef.id!;
+
+ await service.reloadSources(isolateId);
+ }
+
/// Sets breakpoints for an individual isolate.
///
/// If [uri] is provided, only breakpoints for that URI will be sent (used
@@ -592,10 +605,7 @@
return;
}
- final isolateId = isolateRef.id;
- if (isolateId == null) {
- return;
- }
+ final isolateId = isolateRef.id!;
final isolate = await service.getIsolate(isolateId);
final libraries = isolate.libraries;
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index 12f17b4..d6df8e6 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -173,6 +173,34 @@
unawaited(dap.client.event('thread').then((_) => dap.client.terminate()));
await dap.client.start(file: testFile);
});
+
+ test('can hot reload', () async {
+ const originalText = 'ORIGINAL TEXT';
+ const newText = 'NEW TEXT';
+
+ // Create a script that prints 'ORIGINAL TEXT'.
+ final testFile = dap.createTestFile(stringPrintingProgram(originalText));
+
+ // Start the program and wait for 'ORIGINAL TEXT' to be printed.
+ await Future.wait([
+ dap.client.initialize(),
+ dap.client.launch(testFile.path),
+ ], eagerError: true);
+
+ // Expect the original text.
+ await dap.client.outputEvents
+ .firstWhere((event) => event.output.trim() == originalText);
+
+ // Update the file and hot reload.
+ testFile.writeAsStringSync(stringPrintingProgram(newText));
+ await dap.client.hotReload();
+
+ // Expect the new text.
+ await dap.client.outputEvents
+ .firstWhere((event) => event.output.trim() == newText);
+
+ await dap.client.terminate();
+ });
// These tests can be slow due to starting up the external server process.
}, timeout: Timeout.none);
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index af40d1c..55f21c0 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -148,7 +148,7 @@
sendRequest(ContinueArguments(threadId: threadId));
/// Sends a custom request to the server and waits for a response.
- Future<Response> custom(String name, Object? args) async {
+ Future<Response> custom(String name, [Object? args]) async {
return sendRequest(args, overrideCommand: name);
}
@@ -191,6 +191,11 @@
_serverRequestHandlers[request] = handler;
}
+ /// Send a custom 'hotReload' request to the server.
+ Future<Response> hotReload() async {
+ return custom('hotReload');
+ }
+
/// Send an initialize request to the server.
///
/// This occurs before the request to start running/debugging a script and is
diff --git a/pkg/dds/test/dap/integration/test_scripts.dart b/pkg/dds/test/dap/integration/test_scripts.dart
index 2b24727..59bc718 100644
--- a/pkg/dds/test/dap/integration/test_scripts.dart
+++ b/pkg/dds/test/dap/integration/test_scripts.dart
@@ -2,6 +2,8 @@
// 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';
+
import 'package:test/test.dart';
/// A marker used in some test scripts/tests for where to set breakpoints.
@@ -53,6 +55,24 @@
}
''';
+/// Returns a simple Dart script that prints the provided string repeatedly.
+String stringPrintingProgram(String text) {
+ // jsonEncode the string to get it into a quoted/escaped form that can be
+ // embedded in the string.
+ final encodedTextString = jsonEncode(text);
+ return '''
+ import 'dart:async';
+
+ main() async {
+ Timer.periodic(Duration(milliseconds: 10), (_) => printSomething());
+ }
+
+ void printSomething() {
+ print($encodedTextString);
+ }
+''';
+}
+
/// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
/// will contain multiple stack frames across some async boundaries.
const simpleAsyncProgram = '''
diff --git a/pkg/dds/tool/dap/README.md b/pkg/dds/tool/dap/README.md
index a65f707..ac86324 100644
--- a/pkg/dds/tool/dap/README.md
+++ b/pkg/dds/tool/dap/README.md
@@ -74,6 +74,17 @@
}
```
+### `hotReload`
+
+`hotReload` calls the VM's `reloadSources` service for each active isolate, reloading all modified source files.
+
+```
+{
+ "method": "hotReload",
+ "params": null
+}
+```
+
## Custom Events
The debug adapter may emit several custom events that are useful to clients.
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index 3619d46..e35f2a1 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -6,6 +6,11 @@
# Kernel ASTs directly, that is, code in pkg/fasta/lib/src/kernel/ with
# strong-mode enabled.
+dart2js/late_fields: SemiFuzzFailure
+dart2js/late_from_dill/main: SemiFuzzFailure
+dart2js/late_statics: SemiFuzzFailure
+static_field_lowering/opt_in: SemiFuzzFailure
+
constructor_tearoffs/call_instantiation: TypeCheckError
constructor_tearoffs/lowering/invalid_redirect: VerificationError
extension_types/access_setter_as_getter: ExpectationFileMismatchSerialized # Expected.
diff --git a/pkg/front_end/testcases/weak.status b/pkg/front_end/testcases/weak.status
index 3b2aaee..a4258c6 100644
--- a/pkg/front_end/testcases/weak.status
+++ b/pkg/front_end/testcases/weak.status
@@ -4,11 +4,20 @@
# Status file for the weak_suite.dart test suite.
-general/platform_invalid_uris/main: SemiFuzzFailure
+dart2js/late_fields: SemiFuzzFailure
+dart2js/late_from_dill/main: SemiFuzzFailure
+dart2js/late_statics: SemiFuzzFailure
+dart2js/mixin_from_opt_in/main: SemiFuzzFailure
+dart2js/mixin_from_opt_in/main.no_link: SemiFuzzFailure
general/error_recovery/issue_39058_prime.crash: SemiFuzzFailure
general/error_recovery/issue_39202.crash: SemiFuzzCrash
general/error_recovery/issue_39058.crash: SemiFuzzFailure
+general/issue47339: SemiFuzzCrash
+general/platform_invalid_uris/main: SemiFuzzFailure
+nnbd_mixed/mixin_from_opt_in/main: SemiFuzzFailure
+nnbd_mixed/mixin_from_opt_in/main.no_link: SemiFuzzFailure
regress/utf_16_le_content.crash: SemiFuzzCrash
+static_field_lowering/opt_in: SemiFuzzFailure
constructor_tearoffs/call_instantiation: TypeCheckError
constructor_tearoffs/lowering/invalid_redirect: VerificationError
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 34521bc..515d1bd 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2449,7 +2449,18 @@
// static
void Isolate::NotifyLowMemory() {
- Isolate::KillAllIsolates(Isolate::kLowMemoryMsg);
+ IsolateGroup::ForEach([](IsolateGroup* group) { group->NotifyLowMemory(); });
+}
+
+void IsolateGroup::NotifyLowMemory() {
+ SafepointReadRwLocker ml(Thread::Current(), isolates_lock_.get());
+ MonitorLocker ml2(Isolate::isolate_creation_monitor_);
+ for (Isolate* isolate : isolates_) {
+ if (isolate->AcceptsMessagesLocked()) {
+ isolate->KillLocked(Isolate::kLowMemoryMsg);
+ return; // Only wake up one member of the group.
+ }
+ }
}
void Isolate::LowLevelShutdown() {
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index f9616f3..2c531f0 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -597,6 +597,8 @@
return deferred_marking_stack_;
}
+ void NotifyLowMemory();
+
// Runs the given [function] on every isolate in the isolate group.
//
// During the duration of this function, no new isolates can be added or
diff --git a/tools/VERSION b/tools/VERSION
index 746a27a..0fd0143 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 16
PATCH 0
-PRERELEASE 14
+PRERELEASE 15
PRERELEASE_PATCH 0
\ No newline at end of file