Version 2.15.0-264.0.dev
Merge commit 'a9752ad140eed5b48f1d23879758dae5c0e16f80' into 'dev'
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index 85c3ac9..b1b2b36 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -97,13 +97,9 @@
librariesToImport: librariesToImport,
);
- try {
- suggestions.addAll(
- await manager.computeSuggestions(request, performance),
- );
- } on AbortCompletion {
- suggestions.clear();
- }
+ suggestions.addAll(
+ await manager.computeSuggestions(request, performance),
+ );
});
// TODO (danrubel) if request is obsolete (processAnalysisRequest returns
// false) then send empty results
@@ -212,6 +208,63 @@
);
}
+ /// Process a `completion.getSuggestionDetails2` request.
+ void getSuggestionDetails2(Request request) async {
+ var params = CompletionGetSuggestionDetails2Params.fromRequest(request);
+
+ var file = params.file;
+ if (server.sendResponseErrorIfInvalidFilePath(request, file)) {
+ return;
+ }
+
+ var libraryUri = Uri.tryParse(params.libraryUri);
+ if (libraryUri == null) {
+ server.sendResponse(
+ Response.invalidParameter(request, 'libraryUri', 'Cannot parse'),
+ );
+ return;
+ }
+
+ var budget = CompletionBudget(
+ const Duration(milliseconds: 1000),
+ );
+ var id = ++_latestGetSuggestionDetailsId;
+ while (id == _latestGetSuggestionDetailsId && !budget.isEmpty) {
+ try {
+ var analysisDriver = server.getAnalysisDriver(file);
+ if (analysisDriver == null) {
+ server.sendResponse(Response.fileNotAnalyzed(request, file));
+ return;
+ }
+ var session = analysisDriver.currentSession;
+
+ var completion = params.completion;
+ var builder = ChangeBuilder(session: session);
+ await builder.addDartFileEdit(file, (builder) {
+ var result = builder.importLibraryElement(libraryUri);
+ if (result.prefix != null) {
+ completion = '${result.prefix}.$completion';
+ }
+ });
+
+ server.sendResponse(
+ CompletionGetSuggestionDetails2Result(
+ completion,
+ builder.sourceChange,
+ ).toResponse(request.id),
+ );
+ return;
+ } on InconsistentAnalysisException {
+ // Loop around to try again.
+ }
+ }
+
+ // Timeout or abort, send the empty response.
+ server.sendResponse(
+ CompletionGetSuggestionDetailsResult('').toResponse(request.id),
+ );
+ }
+
/// Implement the 'completion.getSuggestions2' request.
void getSuggestions2(Request request) async {
var budget = CompletionBudget(budgetDuration);
@@ -261,14 +314,28 @@
),
documentationCache: server.getDocumentationCacheFor(resolvedUnit),
);
+ setNewRequest(completionRequest);
var librariesToImport = <Uri>[];
- var suggestions = await computeSuggestions(
- budget: budget,
- performance: performance,
- request: completionRequest,
- librariesToImport: librariesToImport,
- );
+ var suggestions = <CompletionSuggestion>[];
+ try {
+ suggestions = await computeSuggestions(
+ budget: budget,
+ performance: performance,
+ request: completionRequest,
+ librariesToImport: librariesToImport,
+ );
+ } on AbortCompletion {
+ return server.sendResponse(
+ CompletionGetSuggestions2Result(
+ completionRequest.replacementOffset,
+ completionRequest.replacementLength,
+ [],
+ [],
+ true,
+ ).toResponse(request.id),
+ );
+ }
performance.run('filter', (performance) {
performance.getDataInt('count').add(suggestions.length);
@@ -311,6 +378,9 @@
if (requestName == COMPLETION_REQUEST_GET_SUGGESTION_DETAILS) {
getSuggestionDetails(request);
return Response.DELAYED_RESPONSE;
+ } else if (requestName == COMPLETION_REQUEST_GET_SUGGESTION_DETAILS2) {
+ getSuggestionDetails2(request);
+ return Response.DELAYED_RESPONSE;
} else if (requestName == COMPLETION_REQUEST_GET_SUGGESTIONS) {
processRequest(request);
return Response.DELAYED_RESPONSE;
@@ -436,14 +506,19 @@
// Compute suggestions in the background
try {
- var suggestions = await computeSuggestions(
- budget: budget,
- performance: perf,
- request: completionRequest,
- includedElementKinds: includedElementKinds,
- includedElementNames: includedElementNames,
- includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
- );
+ var suggestions = <CompletionSuggestion>[];
+ try {
+ suggestions = await computeSuggestions(
+ budget: budget,
+ performance: perf,
+ request: completionRequest,
+ includedElementKinds: includedElementKinds,
+ includedElementNames: includedElementNames,
+ includedSuggestionRelevanceTags: includedSuggestionRelevanceTags,
+ );
+ } on AbortCompletion {
+ // Continue with empty suggestions list.
+ }
String? libraryFile;
var includedSuggestionSets = <IncludedSuggestionSet>[];
if (includedElementKinds != null && includedElementNames != null) {
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
index 4fba896..938228c 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
@@ -16,9 +16,14 @@
import 'package:analyzer/src/lint/pub.dart';
import 'package:analyzer/src/workspace/pub.dart';
import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
/// A contributor of suggestions from not yet imported libraries.
class NotImportedContributor extends DartCompletionContributor {
+ /// Tests set this function to abort the current request.
+ @visibleForTesting
+ static void Function(FileState)? onFile;
+
final CompletionBudget budget;
final List<Uri> librariesToImport;
@@ -49,6 +54,9 @@
var knownFiles = fsState.knownFiles.toList();
for (var file in knownFiles) {
+ onFile?.call(file);
+ request.checkAborted();
+
if (budget.isEmpty) {
return;
}
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index ca7daef..615fc09 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.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:async';
+
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/domain_analysis.dart';
import 'package:analysis_server/src/domain_completion.dart';
@@ -9,6 +11,7 @@
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
+import 'package:analysis_server/src/services/completion/dart/not_imported_contributor.dart';
import 'package:analysis_server/src/utilities/mocks.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/instrumentation/service.dart';
@@ -27,74 +30,237 @@
void main() {
defineReflectiveSuite(() {
+ defineReflectiveTests(CompletionDomainHandlerGetSuggestionDetails2Test);
defineReflectiveTests(CompletionDomainHandlerGetSuggestions2Test);
defineReflectiveTests(CompletionDomainHandlerGetSuggestionsTest);
});
}
@reflectiveTest
-class CompletionDomainHandlerGetSuggestions2Test with ResourceProviderMixin {
- late final MockServerChannel serverChannel;
- late final AnalysisServer server;
+class CompletionDomainHandlerGetSuggestionDetails2Test
+ extends PubPackageAnalysisServerTest {
+ Future<void> test_alreadyImported() async {
+ await _configureWithWorkspaceRoot();
- AnalysisDomainHandler get analysisDomain {
- return server.handlers.whereType<AnalysisDomainHandler>().single;
+ var validator = await _getTestCodeDetails('''
+import 'dart:math';
+void f() {
+ Rand^
+}
+''', completion: 'Random', libraryUri: 'dart:math');
+ validator
+ ..hasCompletion('Random')
+ ..hasChange().assertNoFileEdits();
}
- CompletionDomainHandler get completionDomain {
- return server.handlers.whereType<CompletionDomainHandler>().single;
+ Future<void> test_import_dart() async {
+ await _configureWithWorkspaceRoot();
+
+ var validator = await _getTestCodeDetails('''
+void f() {
+ R^
+}
+''', completion: 'Random', libraryUri: 'dart:math');
+ validator
+ ..hasCompletion('Random')
+ ..hasChange()
+ .hasFileEdit(testFilePathPlatform)
+ .whenApplied(testFileContent, r'''
+import 'dart:math';
+
+void f() {
+ R
+}
+''');
}
- String get testFilePath => '$testPackageLibPath/test.dart';
+ Future<void> test_import_package_dependencies() async {
+ // TODO(scheglov) Switch to PubspecYamlFileConfig
+ newPubspecYamlFile(testPackageRootPath, r'''
+name: test
+dependencies:
+ aaa: any
+''');
- String get testPackageLibPath => '$testPackageRootPath/lib';
+ var aaaRoot = getFolder('$workspaceRootPath/packages/aaa');
+ newFile('${aaaRoot.path}/lib/f.dart', content: '''
+class Test {}
+''');
- Folder get testPackageRoot => getFolder(testPackageRootPath);
+ writeTestPackageConfig(
+ config: PackageConfigFileBuilder()
+ ..add(name: 'aaa', rootPath: aaaRoot.path),
+ );
- String get testPackageRootPath => '$workspaceRootPath/test';
+ await _configureWithWorkspaceRoot();
- String get testPackageTestPath => '$testPackageRootPath/test';
+ var validator = await _getTestCodeDetails('''
+void f() {
+ T^
+}
+''', completion: 'Test', libraryUri: 'package:aaa/a.dart');
+ validator
+ ..hasCompletion('Test')
+ ..hasChange()
+ .hasFileEdit(testFilePathPlatform)
+ .whenApplied(testFileContent, r'''
+import 'package:aaa/a.dart';
- String get workspaceRootPath => '/home';
+void f() {
+ T
+}
+''');
+ }
- Future<void> setRoots({
- required List<String> included,
- required List<String> excluded,
+ Future<void> test_import_package_this() async {
+ newFile('$testPackageLibPath/a.dart', content: '''
+class Test {}
+''');
+
+ await _configureWithWorkspaceRoot();
+
+ var validator = await _getTestCodeDetails('''
+void f() {
+ T^
+}
+''', completion: 'Test', libraryUri: 'package:test/a.dart');
+ validator
+ ..hasCompletion('Test')
+ ..hasChange()
+ .hasFileEdit(testFilePathPlatform)
+ .whenApplied(testFileContent, r'''
+import 'package:test/a.dart';
+
+void f() {
+ T
+}
+''');
+ }
+
+ Future<void> test_invalidLibraryUri() async {
+ await _configureWithWorkspaceRoot();
+
+ var request = CompletionGetSuggestionDetails2Params(
+ testFilePathPlatform, 0, 'Random', '[foo]:bar')
+ .toRequest('0');
+
+ var response = await _handleRequest(request);
+ expect(response.error?.code, RequestErrorCode.INVALID_PARAMETER);
+ // TODO(scheglov) Check that says "libraryUri".
+ }
+
+ Future<void> test_invalidPath() async {
+ await _configureWithWorkspaceRoot();
+
+ var request =
+ CompletionGetSuggestionDetails2Params('foo', 0, 'Random', 'dart:math')
+ .toRequest('0');
+
+ var response = await _handleRequest(request);
+ expect(response.error?.code, RequestErrorCode.INVALID_FILE_PATH_FORMAT);
+ }
+
+ Future<GetSuggestionDetails2Validator> _getCodeDetails({
+ required String path,
+ required String content,
+ required String completion,
+ required String libraryUri,
}) async {
- var includedConverted = included.map(convertPath).toList();
- var excludedConverted = excluded.map(convertPath).toList();
- await _handleSuccessfulRequest(
- AnalysisSetAnalysisRootsParams(
- includedConverted,
- excludedConverted,
- packageRoots: {},
- ).toRequest('0'),
+ var completionOffset = content.indexOf('^');
+ expect(completionOffset, isNot(equals(-1)), reason: 'missing ^');
+
+ var nextOffset = content.indexOf('^', completionOffset + 1);
+ expect(nextOffset, equals(-1), reason: 'too many ^');
+
+ newFile(path,
+ content: content.substring(0, completionOffset) +
+ content.substring(completionOffset + 1));
+
+ return await _getDetails(
+ path: path,
+ completionOffset: completionOffset,
+ completion: completion,
+ libraryUri: libraryUri,
);
}
+ Future<GetSuggestionDetails2Validator> _getDetails({
+ required String path,
+ required int completionOffset,
+ required String completion,
+ required String libraryUri,
+ }) async {
+ var request = CompletionGetSuggestionDetails2Params(
+ path,
+ completionOffset,
+ completion,
+ libraryUri,
+ ).toRequest('0');
+
+ var response = await _handleSuccessfulRequest(request);
+ var result = CompletionGetSuggestionDetails2Result.fromResponse(response);
+ return GetSuggestionDetails2Validator(result);
+ }
+
+ Future<GetSuggestionDetails2Validator> _getTestCodeDetails(
+ String content, {
+ required String completion,
+ required String libraryUri,
+ }) async {
+ return _getCodeDetails(
+ path: convertPath(testFilePath),
+ content: content,
+ completion: completion,
+ libraryUri: libraryUri,
+ );
+ }
+}
+
+@reflectiveTest
+class CompletionDomainHandlerGetSuggestions2Test
+ extends PubPackageAnalysisServerTest {
+ @override
void setUp() {
- serverChannel = MockServerChannel();
-
- var sdkRoot = newFolder('/sdk');
- createMockSdk(
- resourceProvider: resourceProvider,
- root: sdkRoot,
- );
-
- writeTestPackageConfig();
-
- server = AnalysisServer(
- serverChannel,
- resourceProvider,
- AnalysisServerOptions(),
- DartSdkManager(sdkRoot.path),
- CrashReportingAttachmentsBuilder.empty,
- InstrumentationService.NULL_SERVICE,
- );
-
+ super.setUp();
completionDomain.budgetDuration = const Duration(seconds: 30);
}
+ void tearDown() {
+ NotImportedContributor.onFile = null;
+ }
+
+ Future<void> test_notImported_abort() async {
+ await _configureWithWorkspaceRoot();
+
+ NotImportedContributor.onFile = (file) {
+ if (file.uriStr == 'dart:math') {
+ unawaited(
+ _getSuggestions(
+ path: convertPath(testFilePath),
+ completionOffset: 0,
+ maxResults: 100,
+ ),
+ );
+ }
+ };
+
+ var responseValidator = await _getTestCodeSuggestions('''
+void f() {
+ Rand^
+}
+''');
+
+ responseValidator
+ ..assertIncomplete()
+ ..assertReplacementBack(4)
+ ..assertLibrariesToImport(includes: [], excludes: [
+ 'dart:core',
+ 'dart:math',
+ ]);
+
+ responseValidator.suggestions.assertEmpty();
+ }
+
Future<void> test_notImported_emptyBudget() async {
await _configureWithWorkspaceRoot();
@@ -932,37 +1098,6 @@
suggestionsValidator.assertCompletions(['foo02', 'foo01']);
}
- void writePackageConfig(Folder root, PackageConfigFileBuilder config) {
- newPackageConfigJsonFile(
- root.path,
- content: config.toContent(toUriStr: toUriStr),
- );
- }
-
- void writeTestPackageConfig({
- PackageConfigFileBuilder? config,
- String? languageVersion,
- }) {
- if (config == null) {
- config = PackageConfigFileBuilder();
- } else {
- config = config.copy();
- }
-
- config.add(
- name: 'test',
- rootPath: testPackageRootPath,
- languageVersion: languageVersion,
- );
-
- writePackageConfig(testPackageRoot, config);
- }
-
- Future<void> _configureWithWorkspaceRoot() async {
- await setRoots(included: [workspaceRootPath], excluded: []);
- await server.onAnalysisComplete;
- }
-
Future<CompletionGetSuggestions2ResponseValidator> _getCodeSuggestions({
required String path,
required String content,
@@ -1011,13 +1146,6 @@
maxResults: maxResults,
);
}
-
- /// Validates that the given [request] is handled successfully.
- Future<Response> _handleSuccessfulRequest(Request request) async {
- var response = await serverChannel.sendRequest(request);
- expect(response, isResponseSuccess(request.id));
- return response;
- }
}
@reflectiveTest
@@ -1959,6 +2087,129 @@
}
}
+class GetSuggestionDetails2Validator {
+ final CompletionGetSuggestionDetails2Result result;
+
+ GetSuggestionDetails2Validator(this.result);
+
+ SourceChangeValidator hasChange() {
+ return SourceChangeValidator(result.change);
+ }
+
+ void hasCompletion(Object completion) {
+ expect(result.completion, completion);
+ }
+}
+
+class PubPackageAnalysisServerTest with ResourceProviderMixin {
+ late final MockServerChannel serverChannel;
+ late final AnalysisServer server;
+
+ AnalysisDomainHandler get analysisDomain {
+ return server.handlers.whereType<AnalysisDomainHandler>().single;
+ }
+
+ CompletionDomainHandler get completionDomain {
+ return server.handlers.whereType<CompletionDomainHandler>().single;
+ }
+
+ String get testFileContent => getFile(testFilePath).readAsStringSync();
+
+ String get testFilePath => '$testPackageLibPath/test.dart';
+
+ String get testFilePathPlatform => convertPath(testFilePath);
+
+ String get testPackageLibPath => '$testPackageRootPath/lib';
+
+ Folder get testPackageRoot => getFolder(testPackageRootPath);
+
+ String get testPackageRootPath => '$workspaceRootPath/test';
+
+ String get testPackageTestPath => '$testPackageRootPath/test';
+
+ String get workspaceRootPath => '/home';
+
+ Future<void> setRoots({
+ required List<String> included,
+ required List<String> excluded,
+ }) async {
+ var includedConverted = included.map(convertPath).toList();
+ var excludedConverted = excluded.map(convertPath).toList();
+ await _handleSuccessfulRequest(
+ AnalysisSetAnalysisRootsParams(
+ includedConverted,
+ excludedConverted,
+ packageRoots: {},
+ ).toRequest('0'),
+ );
+ }
+
+ void setUp() {
+ serverChannel = MockServerChannel();
+
+ var sdkRoot = newFolder('/sdk');
+ createMockSdk(
+ resourceProvider: resourceProvider,
+ root: sdkRoot,
+ );
+
+ writeTestPackageConfig();
+
+ server = AnalysisServer(
+ serverChannel,
+ resourceProvider,
+ AnalysisServerOptions(),
+ DartSdkManager(sdkRoot.path),
+ CrashReportingAttachmentsBuilder.empty,
+ InstrumentationService.NULL_SERVICE,
+ );
+
+ completionDomain.budgetDuration = const Duration(seconds: 30);
+ }
+
+ void writePackageConfig(Folder root, PackageConfigFileBuilder config) {
+ newPackageConfigJsonFile(
+ root.path,
+ content: config.toContent(toUriStr: toUriStr),
+ );
+ }
+
+ void writeTestPackageConfig({
+ PackageConfigFileBuilder? config,
+ String? languageVersion,
+ }) {
+ if (config == null) {
+ config = PackageConfigFileBuilder();
+ } else {
+ config = config.copy();
+ }
+
+ config.add(
+ name: 'test',
+ rootPath: testPackageRootPath,
+ languageVersion: languageVersion,
+ );
+
+ writePackageConfig(testPackageRoot, config);
+ }
+
+ Future<void> _configureWithWorkspaceRoot() async {
+ await setRoots(included: [workspaceRootPath], excluded: []);
+ await server.onAnalysisComplete;
+ }
+
+ Future<Response> _handleRequest(Request request) async {
+ return await serverChannel.sendRequest(request);
+ }
+
+ /// Validates that the given [request] is handled successfully.
+ Future<Response> _handleSuccessfulRequest(Request request) async {
+ var response = await _handleRequest(request);
+ expect(response, isResponseSuccess(request.id));
+ return response;
+ }
+}
+
class SingleSuggestionValidator {
final CompletionSuggestion suggestion;
final List<String>? libraryUrisToImport;
@@ -2003,6 +2254,32 @@
}
}
+class SourceChangeValidator {
+ final SourceChange change;
+
+ SourceChangeValidator(this.change);
+
+ void assertNoFileEdits() {
+ expect(change.edits, isEmpty);
+ }
+
+ SourceFileEditValidator hasFileEdit(String path) {
+ var edit = change.edits.singleWhere((e) => e.file == path);
+ return SourceFileEditValidator(edit);
+ }
+}
+
+class SourceFileEditValidator {
+ final SourceFileEdit edit;
+
+ SourceFileEditValidator(this.edit);
+
+ void whenApplied(String applyTo, Object expected) {
+ var actual = SourceEdit.applySequence(applyTo, edit.edits);
+ expect(actual, expected);
+ }
+}
+
class SuggestionsValidator {
final List<CompletionSuggestion> suggestions;
final List<String>? libraryUrisToImport;
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index af626d9..df6722b 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -3,6 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
complex_reload_test: Skip # http://dartbug.com/47130
+coverage_closure_call_test: Skip # https://github.com/dart-lang/sdk/issues/47568
valid_source_locations_test: Pass, Slow
[ $compiler == app_jitk ]
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index 322b922..5627906 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -3,6 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
complex_reload_test: Skip # http://dartbug.com/47130
+coverage_closure_call_test: Skip # https://github.com/dart-lang/sdk/issues/47568
valid_source_locations_test: Pass, Slow
[ $compiler == app_jitk ]
diff --git a/tools/VERSION b/tools/VERSION
index 54f7e08..298c9a4 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 263
+PRERELEASE 264
PRERELEASE_PATCH 0
\ No newline at end of file