c5fb640747
This updates the `showUserPrompt` method to accept a cancellation token so that if requests are cancelled (by the client, or something like a second refactor cancelling the first), the prompt is also cancelled (and when the response arrives, it can be ignored). Note: Unfortunately the protocol (and VS Code) don't actually allow for the prompt to be cancelled/hidden from the user, this is mainly to avoid us keeping the request "alive" on the server if the user ignores a prompt and we know the parent request was cancelled anyway. Fixes https://github.com/dart-lang/sdk/issues/63285 Change-Id: Ia77880a749b284a2ff31c8cf560307ad908d2175 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499360 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
1610 lines
39 KiB
Dart
1610 lines
39 KiB
Dart
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:analysis_server/lsp_protocol/protocol.dart';
|
|
import 'package:analysis_server/src/lsp/constants.dart';
|
|
import 'package:analyzer/src/test_utilities/platform.dart';
|
|
import 'package:analyzer/src/test_utilities/test_code_format.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../tool/lsp_spec/matchers.dart';
|
|
import '../utils/test_code_extensions.dart';
|
|
import 'change_verifier.dart';
|
|
import 'server_abstract.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(RenameTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class RenameTest extends AbstractLspAnalysisServerTest {
|
|
/// A completer that completes when an outbound rename request is sent.
|
|
///
|
|
/// This is used in cancellation tests that need to know when the request has
|
|
/// been sent and access the ID to cancel.
|
|
Completer<RequestMessage> outboundRenameRequestCompleter = Completer();
|
|
|
|
@override
|
|
RequestMessage makeRenameRequest(
|
|
int? version,
|
|
Uri uri,
|
|
Position pos,
|
|
String newName,
|
|
) {
|
|
var request = super.makeRenameRequest(version, uri, pos, newName);
|
|
outboundRenameRequestCompleter.complete(request);
|
|
return request;
|
|
}
|
|
|
|
Future<void> test_prepare_class() {
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = new [!My^Class!]();
|
|
''';
|
|
|
|
return _test_prepare(content, 'MyClass');
|
|
}
|
|
|
|
Future<void> test_prepare_class_newKeyword() {
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = n^ew [!MyClass!]();
|
|
''';
|
|
|
|
return _test_prepare(content, 'MyClass');
|
|
}
|
|
|
|
Future<void> test_prepare_class_startOfParameterList() {
|
|
const content = '''
|
|
class [!MyClass^!]<T> {}
|
|
''';
|
|
|
|
return _test_prepare(content, 'MyClass');
|
|
}
|
|
|
|
Future<void> test_prepare_class_typeParameter_atDeclaration() {
|
|
const content = '''
|
|
class A<[!T^!]> {
|
|
final List<T> values = [];
|
|
}
|
|
''';
|
|
|
|
return _test_prepare(content, 'T');
|
|
}
|
|
|
|
Future<void> test_prepare_class_typeParameter_atReference() {
|
|
const content = '''
|
|
class A<T> {
|
|
final List<[!T^!]> values = [];
|
|
}
|
|
''';
|
|
|
|
return _test_prepare(content, 'T');
|
|
}
|
|
|
|
Future<void> test_prepare_enum() {
|
|
const content = '''
|
|
enum [!My^Enum!] { one }
|
|
''';
|
|
|
|
return _test_prepare(content, 'MyEnum');
|
|
}
|
|
|
|
Future<void> test_prepare_enumMember() {
|
|
const content = '''
|
|
enum MyEnum { [!o^ne!] }
|
|
''';
|
|
|
|
return _test_prepare(content, 'one');
|
|
}
|
|
|
|
Future<void> test_prepare_enumMember_reference() {
|
|
const content = '''
|
|
enum MyEnum { one }
|
|
final a = MyEnum.[!o^ne!];
|
|
''';
|
|
|
|
return _test_prepare(content, 'one');
|
|
}
|
|
|
|
Future<void> test_prepare_extensionType_onName_begin() {
|
|
const content = '''
|
|
extension type [!^MyType!](int it) {}
|
|
''';
|
|
|
|
return _test_prepare(content, 'MyType');
|
|
}
|
|
|
|
Future<void> test_prepare_extensionType_onName_end() {
|
|
const content = '''
|
|
extension type [!MyType^!](int it) {}
|
|
''';
|
|
|
|
return _test_prepare(content, 'MyType');
|
|
}
|
|
|
|
Future<void> test_prepare_extensionType_onName_inside() {
|
|
const content = '''
|
|
extension type [!My^Type!](int it) {}
|
|
''';
|
|
|
|
return _test_prepare(content, 'MyType');
|
|
}
|
|
|
|
Future<void> test_prepare_function_startOfParameterList() {
|
|
const content = '''
|
|
void [!aaaa!]^() {}
|
|
''';
|
|
|
|
return _test_prepare(content, 'aaaa');
|
|
}
|
|
|
|
Future<void> test_prepare_function_startOfTypeParameterList() {
|
|
const content = '''
|
|
void [!aaaa!]^<T>() {}
|
|
''';
|
|
|
|
return _test_prepare(content, 'aaaa');
|
|
}
|
|
|
|
Future<void> test_prepare_importPrefix() {
|
|
const content = '''
|
|
import 'dart:async' as [!myPr^efix!];
|
|
''';
|
|
|
|
return _test_prepare(content, 'myPrefix');
|
|
}
|
|
|
|
Future<void> test_prepare_importWithoutPrefix() {
|
|
const content = '''
|
|
imp[!^!]ort 'dart:async';
|
|
''';
|
|
|
|
return _test_prepare(content, '');
|
|
}
|
|
|
|
Future<void> test_prepare_importWithPrefix() {
|
|
const content = '''
|
|
imp^ort 'dart:async' as [!myPrefix!];
|
|
''';
|
|
|
|
return _test_prepare(content, 'myPrefix');
|
|
}
|
|
|
|
Future<void> test_prepare_invalidRenameLocation() {
|
|
const content = '''
|
|
void f() {
|
|
// comm^ent
|
|
}
|
|
''';
|
|
|
|
return _test_prepare(content, null);
|
|
}
|
|
|
|
Future<void> test_prepare_method_startOfParameterList() {
|
|
const content = '''
|
|
class A {
|
|
void [!aaaa!]^() {}
|
|
}
|
|
''';
|
|
|
|
return _test_prepare(content, 'aaaa');
|
|
}
|
|
|
|
Future<void> test_prepare_method_startOfTypeParameterList() {
|
|
const content = '''
|
|
class A {
|
|
void [!aaaa!]^<T>() {}
|
|
}
|
|
''';
|
|
|
|
return _test_prepare(content, 'aaaa');
|
|
}
|
|
|
|
Future<void> test_prepare_sdkClass() async {
|
|
const content = '''
|
|
final a = new [!Ob^ject!]();
|
|
''';
|
|
var code = TestCode.parse(content);
|
|
await initialize();
|
|
await openFile(mainFileUri, code.code);
|
|
|
|
var request = makeRequest(
|
|
Method.textDocument_prepareRename,
|
|
TextDocumentPositionParams(
|
|
textDocument: TextDocumentIdentifier(uri: mainFileUri),
|
|
position: code.position.position,
|
|
),
|
|
);
|
|
var response = await channel.sendRequestToServer(request);
|
|
|
|
expect(response.id, equals(request.id));
|
|
expect(response.result, isNull);
|
|
expect(response.error, isNotNull);
|
|
expect(response.error!.code, ServerErrorCodes.renameNotValid);
|
|
expect(response.error!.message, contains('is defined in the SDK'));
|
|
}
|
|
|
|
Future<void> test_prepare_variable() {
|
|
const content = '''
|
|
void f() {
|
|
var variable = 0;
|
|
print([!vari^able!]);
|
|
}
|
|
''';
|
|
|
|
return _test_prepare(content, 'variable');
|
|
}
|
|
|
|
Future<void> test_prepare_variable_forEach_statement() {
|
|
const content = '''
|
|
void f(List<int> values) {
|
|
for (final [!value^!] in values) {
|
|
value;
|
|
}
|
|
}
|
|
''';
|
|
|
|
return _test_prepare(content, 'value');
|
|
}
|
|
|
|
Future<void> test_rename_class() {
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = new [!My^Class!]();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewClass {}
|
|
final a = new MyNewClass();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewClass',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_class_doesNotRenameFile_disabled() async {
|
|
const content = '''
|
|
class Main {}
|
|
final a = new [!Ma^in!]();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewMain {}
|
|
final a = new MyNewMain();
|
|
''';
|
|
await _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewMain',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void>
|
|
test_rename_class_doesNotRenameFile_showMessageRequestUnsupportedPreventsPrompt() async {
|
|
const content = '''
|
|
class Main {}
|
|
final a = new [!Ma^in!]();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewMain {}
|
|
final a = new MyNewMain();
|
|
''';
|
|
|
|
// Do the rename with setting enabled, but showMessageRequest not supported.
|
|
await provideConfig(
|
|
() => _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewMain',
|
|
expectedContent,
|
|
// showMessageRequest not supported.
|
|
supportsWindowShowMessageRequest: false,
|
|
),
|
|
// Rename files with prompt enabled.
|
|
{'renameFilesWithClasses': 'prompt'},
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_class_doesRenameFile_afterPrompt() {
|
|
const content = '''
|
|
class Main {}
|
|
final a = new [!Ma^in!]();
|
|
''';
|
|
const expectedContent = '''
|
|
>>>>>>>>>> lib/main.dart renamed to lib/my_new_main.dart
|
|
>>>>>>>>>> lib/my_new_main.dart renamed from lib/main.dart
|
|
class MyNewMain {}
|
|
final a = new MyNewMain();
|
|
''';
|
|
var newMainFilePath = join(projectFolderPath, 'lib', 'my_new_main.dart');
|
|
|
|
/// Helper that will respond to the window/showMessageRequest request from
|
|
/// the server when prompted about renaming the file.
|
|
Future<MessageActionItem> promptHandler(
|
|
ShowMessageRequestParams params,
|
|
) async {
|
|
// Ensure the prompt is as expected.
|
|
expect(params.type, equals(MessageType.Info));
|
|
expect(
|
|
params.message,
|
|
equals("Rename 'main.dart' to 'my_new_main.dart'?"),
|
|
);
|
|
expect(params.actions, hasLength(2));
|
|
expect(
|
|
params.actions![0],
|
|
equals(MessageActionItem(title: UserPromptActions.yes)),
|
|
);
|
|
expect(
|
|
params.actions![1],
|
|
equals(MessageActionItem(title: UserPromptActions.no)),
|
|
);
|
|
|
|
// Respond to the request with the required action.
|
|
return params.actions!.first;
|
|
}
|
|
|
|
// Run the test and provide the config + prompt handling function.
|
|
return handleExpectedRequest(
|
|
Method.window_showMessageRequest,
|
|
ShowMessageRequestParams.fromJson,
|
|
() => provideConfig(
|
|
() => _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewMain',
|
|
expectedContent,
|
|
expectedFilePath: newMainFilePath,
|
|
),
|
|
{'renameFilesWithClasses': 'prompt'},
|
|
),
|
|
handler: promptHandler,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_class_doesRenameFile_enabledWithoutPrompt() async {
|
|
const content = '''
|
|
class Main {}
|
|
final a = new [!Ma^in!]();
|
|
''';
|
|
const expectedContent = '''
|
|
>>>>>>>>>> lib/main.dart renamed to lib/my_new_main.dart
|
|
>>>>>>>>>> lib/my_new_main.dart renamed from lib/main.dart
|
|
class MyNewMain {}
|
|
final a = new MyNewMain();
|
|
''';
|
|
var newMainFilePath = join(projectFolderPath, 'lib', 'my_new_main.dart');
|
|
await provideConfig(
|
|
() => _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewMain',
|
|
expectedContent,
|
|
expectedFilePath: newMainFilePath,
|
|
),
|
|
{'renameFilesWithClasses': 'always'},
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_class_doesRenameFile_renamedFromOtherFile() async {
|
|
const mainContent = '''
|
|
class Main {}
|
|
''';
|
|
const otherContent = '''
|
|
import 'main.dart';
|
|
|
|
final a = Ma^in();
|
|
''';
|
|
// Since we don't actually perform the file rename (we only include an
|
|
// instruction for the client to do so), the import will not be updated
|
|
// by us. Instead, the client will send the rename event back to the server
|
|
// and it would be handled normally as if the user had done it locally.
|
|
const expectedContent = '''
|
|
>>>>>>>>>> lib/main.dart renamed to lib/my_new_main.dart
|
|
>>>>>>>>>> lib/my_new_main.dart renamed from lib/main.dart
|
|
class MyNewMain {}
|
|
>>>>>>>>>> lib/other.dart
|
|
import 'main.dart';
|
|
|
|
final a = MyNewMain();
|
|
''';
|
|
|
|
var otherFilePath = join(projectFolderPath, 'lib', 'other.dart');
|
|
var code = TestCode.parse(mainContent);
|
|
newFile(mainFilePath, code.code);
|
|
await pumpEventQueue(times: 5000);
|
|
await provideConfig(
|
|
() => _test_rename_withDocumentChanges(
|
|
otherContent,
|
|
'MyNewMain',
|
|
expectedContent,
|
|
filePath: otherFilePath,
|
|
),
|
|
{'renameFilesWithClasses': 'always'},
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_class_typeParameter_atDeclaration() {
|
|
const content = '''
|
|
class A<[!T^!]> {
|
|
final List<T> values = [];
|
|
}
|
|
''';
|
|
|
|
const expectedContent = '''
|
|
class A<U> {
|
|
final List<U> values = [];
|
|
}
|
|
''';
|
|
|
|
return _test_rename_withDocumentChanges(content, 'U', expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_classNewKeyword() {
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewClass {}
|
|
final a = new MyNewClass();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewClass',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_constructor_named_new_declaration() {
|
|
const content = '''
|
|
class MyClass {
|
|
new [!na^med!]();
|
|
}
|
|
final a = new MyClass.named();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass {
|
|
new newName();
|
|
}
|
|
final a = new MyClass.newName();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_constructor_named_new_declaration_removeName() {
|
|
const content = '''
|
|
class MyClass {
|
|
new [!na^med!]();
|
|
}
|
|
final a = new MyClass.named();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass {
|
|
new();
|
|
}
|
|
final a = new MyClass();
|
|
''';
|
|
return _test_rename_withDocumentChanges(content, '', expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_constructor_named_new_invocation() {
|
|
const content = '''
|
|
class MyClass {
|
|
new named();
|
|
}
|
|
final a = new MyClass.[!na^med!]();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass {
|
|
new newName();
|
|
}
|
|
final a = new MyClass.newName();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_duplicateName_applyAfterDocumentChanges() async {
|
|
// Perform a refactor that results in a prompt to the user, but then modify
|
|
// the document before accepting/rejecting to make the rename invalid.
|
|
const content = '''
|
|
class MyOtherClass {}
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
var result = await _test_rename_prompt(
|
|
content,
|
|
'MyOtherClass',
|
|
expectedMessage:
|
|
'Library already declares class with name \'MyOtherClass\'.',
|
|
action: UserPromptActions.renameAnyway,
|
|
beforeResponding: () => replaceFile(999, mainFileUri, '/*Updated*/'),
|
|
);
|
|
expect(result.result, isNull);
|
|
expect(result.error, isNotNull);
|
|
expect(result.error, isResponseError(ErrorCodes.ContentModified));
|
|
}
|
|
|
|
Future<void> test_rename_duplicateName_applyAnyway() async {
|
|
const content = '''
|
|
class MyOtherClass {}
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
const expectedContent = '''
|
|
>>>>>>>>>> lib/main.dart
|
|
class MyOtherClass {}
|
|
class MyOtherClass {}
|
|
final a = new MyOtherClass();
|
|
''';
|
|
var response = await _test_rename_prompt(
|
|
content,
|
|
'MyOtherClass',
|
|
expectedMessage:
|
|
'Library already declares class with name \'MyOtherClass\'.',
|
|
action: UserPromptActions.renameAnyway,
|
|
);
|
|
|
|
var error = response.error;
|
|
if (error != null) {
|
|
throw error;
|
|
}
|
|
|
|
var result = WorkspaceEdit.fromJson(
|
|
response.result as Map<String, Object?>,
|
|
);
|
|
|
|
verifyEdit(result, expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_duplicateName_cancelWhilePromptPending() async {
|
|
const content = '''
|
|
class MyOtherClass {}
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
// Pass a completer as beforeResponding so we can hold off responding to
|
|
// the prompt reverse-request.
|
|
var reverseRequestStartedCompleter = Completer<void>();
|
|
var reverseRequestCompleter = Completer<void>();
|
|
var responseFuture = _test_rename_prompt(
|
|
content,
|
|
'MyOtherClass',
|
|
expectedMessage:
|
|
'Library already declares class with name \'MyOtherClass\'.',
|
|
// When we complete, we will send renameAnyway, but since we
|
|
// cancelled the request in the meantime, we expect a cancellation
|
|
// result.
|
|
action: UserPromptActions.renameAnyway,
|
|
beforeResponding: () {
|
|
// Mark that we started, so the test can continue.
|
|
reverseRequestStartedCompleter.complete();
|
|
// But wait for us to trigger the end.
|
|
return reverseRequestCompleter.future;
|
|
},
|
|
);
|
|
|
|
// Wait for the server to send the reverse-request so we don't cancel too
|
|
// early.
|
|
await reverseRequestStartedCompleter.future;
|
|
|
|
// Now, cancel the request.
|
|
await sendNotificationToServer(
|
|
makeNotification(
|
|
Method.cancelRequest,
|
|
CancelParams(id: (await outboundRenameRequestCompleter.future).id),
|
|
),
|
|
);
|
|
|
|
// Expect that the response completes and returns a cancelled state.
|
|
var response = await responseFuture;
|
|
expect(response.result, isNull);
|
|
expect(response.error, isNotNull);
|
|
expect(response.error, isResponseError(ErrorCodes.RequestCancelled));
|
|
|
|
// Unblock the reverse-request.
|
|
reverseRequestCompleter.complete();
|
|
}
|
|
|
|
Future<void> test_rename_duplicateName_hover_beforeResponding() async {
|
|
const content = '''
|
|
class MyOtherClass {}
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
|
|
var code = TestCode.parse(content);
|
|
const expectedContent = '''
|
|
>>>>>>>>>> lib/main.dart
|
|
class MyOtherClass {}
|
|
class MyOtherClass {}
|
|
final a = new MyOtherClass();
|
|
''';
|
|
var response = await _test_rename_prompt(
|
|
content,
|
|
'MyOtherClass',
|
|
expectedMessage:
|
|
'Library already declares class with name \'MyOtherClass\'.',
|
|
action: UserPromptActions.renameAnyway,
|
|
beforeResponding: () => getHover(mainFileUri, code.position.position),
|
|
);
|
|
|
|
var error = response.error;
|
|
if (error != null) {
|
|
throw error;
|
|
}
|
|
|
|
var result = WorkspaceEdit.fromJson(
|
|
response.result as Map<String, Object?>,
|
|
);
|
|
|
|
verifyEdit(result, expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_duplicateName_reject() async {
|
|
const content = '''
|
|
class MyOtherClass {}
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
var response = await _test_rename_prompt(
|
|
content,
|
|
'MyOtherClass',
|
|
expectedMessage:
|
|
'Library already declares class with name \'MyOtherClass\'.',
|
|
action: UserPromptActions.cancel,
|
|
);
|
|
// Expect a successful empty response if cancelled.
|
|
expect(response.error, isNull);
|
|
expect(
|
|
WorkspaceEdit.fromJson(response.result as Map<String, Object?>),
|
|
equals(emptyWorkspaceEdit),
|
|
);
|
|
}
|
|
|
|
Future<void>
|
|
test_rename_duplicateName_showMessageRequestUnsupportedPreventsPrompt() async {
|
|
const content = '''
|
|
class MyOtherClass {}
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
var error = await _test_rename_failure(
|
|
content,
|
|
'MyOtherClass',
|
|
supportsWindowShowMessageRequest: false,
|
|
);
|
|
expect(error.code, equals(ServerErrorCodes.renameNotValid));
|
|
expect(
|
|
error.message,
|
|
contains('Library already declares class with name \'MyOtherClass\'.'),
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_enum() {
|
|
const content = '''
|
|
enum MyEnum { one }
|
|
final a = MyE^num.one;
|
|
''';
|
|
const expectedContent = '''
|
|
enum MyNewEnum { one }
|
|
final a = MyNewEnum.one;
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewEnum',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_enumMember() {
|
|
const content = '''
|
|
enum MyEnum { one }
|
|
final a = MyEnum.o^ne;
|
|
''';
|
|
const expectedContent = '''
|
|
enum MyEnum { newOne }
|
|
final a = MyEnum.newOne;
|
|
''';
|
|
return _test_rename_withDocumentChanges(content, 'newOne', expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_extensionType_onName_begin() {
|
|
const content = '''
|
|
extension type ^MyType(int it) {}
|
|
void f(MyType x) {}
|
|
''';
|
|
const expectedContent = '''
|
|
extension type MyNewType(int it) {}
|
|
void f(MyNewType x) {}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewType',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_extensionType_onName_end() {
|
|
const content = '''
|
|
extension type MyType^(int it) {}
|
|
void f(MyType x) {}
|
|
''';
|
|
const expectedContent = '''
|
|
extension type MyNewType(int it) {}
|
|
void f(MyNewType x) {}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewType',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_extensionType_onName_inside() {
|
|
const content = '''
|
|
extension type My^Type(int it) {}
|
|
void f(MyType x) {}
|
|
''';
|
|
const expectedContent = '''
|
|
extension type MyNewType(int it) {}
|
|
void f(MyNewType x) {}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewType',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_forEachElement_blockBody() {
|
|
const content = '''
|
|
void f(List<int> values) {
|
|
[for (final val^ue in values) value * 2];
|
|
}
|
|
''';
|
|
const expectedContent = '''
|
|
void f(List<int> values) {
|
|
[for (final newName in values) newName * 2];
|
|
}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_forEachElement_expressionBody() {
|
|
const content = '''
|
|
Object f() => [for (final val^ue in []) value * 2];
|
|
''';
|
|
const expectedContent = '''
|
|
Object f() => [for (final newName in []) newName * 2];
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_forEachElement_topLevel() {
|
|
const content = '''
|
|
final a = [for (final val^ue in []) value * 2];
|
|
''';
|
|
const expectedContent = '''
|
|
final a = [for (final newName in []) newName * 2];
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_function_startOfParameterList() {
|
|
const content = '''
|
|
void f^() {}
|
|
''';
|
|
const expectedContent = '''
|
|
void newName() {}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_function_startOfTypeParameterList() {
|
|
const content = '''
|
|
void f^<T>() {}
|
|
''';
|
|
const expectedContent = '''
|
|
void newName<T>() {}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_importPrefix() {
|
|
const content = '''
|
|
import 'dart:async' as myPr^efix;
|
|
''';
|
|
const expectedContent = '''
|
|
import 'dart:async' as myNewPrefix;
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'myNewPrefix',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_importWithoutPrefix() {
|
|
const content = '''
|
|
imp^ort 'dart:async';
|
|
''';
|
|
const expectedContent = '''
|
|
import 'dart:async' as myAddedPrefix;
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'myAddedPrefix',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_importWithPrefix() {
|
|
const content = '''
|
|
imp^ort 'dart:async' as myPrefix;
|
|
''';
|
|
const expectedContent = '''
|
|
import 'dart:async' as myNewPrefix;
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'myNewPrefix',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_importWithPrefix_atUse() {
|
|
const content = '''
|
|
import 'dart:async' as asyync;
|
|
|
|
void f(asy^ync.Future f1, asyync.Future f2) {}
|
|
''';
|
|
const expectedContent = '''
|
|
import 'dart:async' as async;
|
|
|
|
void f(async.Future f1, async.Future f2) {}
|
|
''';
|
|
return _test_rename_withDocumentChanges(content, 'async', expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_invalidRenameLocation() {
|
|
const content = '''
|
|
void f() {
|
|
// comm^ent
|
|
}
|
|
''';
|
|
return _test_rename_withDocumentChanges(content, 'MyNewClass', null);
|
|
}
|
|
|
|
Future<void> test_rename_method_startOfParameterList() {
|
|
const content = '''
|
|
class MyClass {
|
|
void m^() {}
|
|
}
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass {
|
|
void newName() {}
|
|
}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_method_startOfTypeParameterList() {
|
|
const content = '''
|
|
class MyClass {
|
|
void m^<T>() {}
|
|
}
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass {
|
|
void newName<T>() {}
|
|
}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_multipleFiles() async {
|
|
var referencedFilePath = join(projectFolderPath, 'lib', 'referenced.dart');
|
|
var referencedFileUri = pathContext.toUri(referencedFilePath);
|
|
const mainContent = '''
|
|
import 'referenced.dart';
|
|
final a = new My^Class();
|
|
''';
|
|
const referencedContent = '''
|
|
class MyClass {}
|
|
''';
|
|
const expectedContent = '''
|
|
>>>>>>>>>> lib/main.dart
|
|
import 'referenced.dart';
|
|
final a = new MyNewClass();
|
|
>>>>>>>>>> lib/referenced.dart
|
|
class MyNewClass {}
|
|
''';
|
|
const mainVersion = 111;
|
|
const referencedVersion = 222;
|
|
|
|
var mainCode = TestCode.parseNormalized(mainContent);
|
|
var referencedCode = TestCode.parseNormalized(referencedContent);
|
|
|
|
// Create initial files (to avoid diagnostics).
|
|
newFile(mainFilePath, mainCode.code);
|
|
newFile(referencedFilePath, referencedCode.code);
|
|
|
|
setDocumentChangesSupport();
|
|
await initialize();
|
|
|
|
// Open files to assign versions.
|
|
await openFile(mainFileUri, mainCode.code, version: mainVersion);
|
|
await openFile(
|
|
referencedFileUri,
|
|
referencedCode.code,
|
|
version: referencedVersion,
|
|
);
|
|
|
|
var result = (await rename(
|
|
mainFileUri,
|
|
mainVersion,
|
|
mainCode.position.position,
|
|
'MyNewClass',
|
|
))!;
|
|
|
|
var expectedVersions = {
|
|
mainFileUri: mainVersion,
|
|
referencedFileUri: referencedVersion,
|
|
};
|
|
verifyEdit(result, expectedContent, expectedVersions: expectedVersions);
|
|
}
|
|
|
|
Future<void> test_rename_nonClass_doesNotRenameFile() async {
|
|
const content = '''
|
|
final Ma^in = 'test';
|
|
''';
|
|
const expectedContent = '''
|
|
final MyNewMain = 'test';
|
|
''';
|
|
await provideConfig(
|
|
() => _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewMain',
|
|
expectedContent,
|
|
),
|
|
{'renameFilesWithClasses': 'always'},
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_className_declaration() {
|
|
const content = '''
|
|
class [!My^Class!]();
|
|
final a = new MyClass();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewClass();
|
|
final a = new MyNewClass();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewClass',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_className_invocation() {
|
|
const content = '''
|
|
class MyClass();
|
|
final a = new [!My^Class!]();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewClass();
|
|
final a = new MyNewClass();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewClass',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_named_className_declaration() {
|
|
const content = '''
|
|
class [!My^Class!].named();
|
|
final a = new MyClass.named();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewClass.named();
|
|
final a = new MyNewClass.named();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewClass',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_named_className_invocation() {
|
|
const content = '''
|
|
class MyClass.named();
|
|
final a = new [!My^Class!].named();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewClass.named();
|
|
final a = new MyNewClass.named();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewClass',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_named_name_declaration() {
|
|
const content = '''
|
|
class MyClass.[!na^med!]();
|
|
final a = new MyClass.named();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass.newName();
|
|
final a = new MyClass.newName();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_named_name_invocation() {
|
|
const content = '''
|
|
class MyClass.named();
|
|
final a = new MyClass.[!na^med!]();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass.newName();
|
|
final a = new MyClass.newName();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_param_declaring_declaration() {
|
|
const content = '''
|
|
class MyClass({required final int [!myPa^ram!]}) {
|
|
this {
|
|
myParam;
|
|
}
|
|
}
|
|
final a = new MyClass(myParam: 1);
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass({required final int myNewParam}) {
|
|
this {
|
|
myNewParam;
|
|
}
|
|
}
|
|
final a = new MyClass(myNewParam: 1);
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'myNewParam',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_param_declaring_reference() {
|
|
const content = '''
|
|
class MyClass({required final int myParam}) {
|
|
this {
|
|
[!myPa^ram!];
|
|
}
|
|
}
|
|
final a = new MyClass(myParam: 1);
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass({required final int myNewParam}) {
|
|
this {
|
|
myNewParam;
|
|
}
|
|
}
|
|
final a = new MyClass(myNewParam: 1);
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'myNewParam',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_param_nonDeclaring_declaration() {
|
|
const content = '''
|
|
class MyClass({required int [!myPa^ram!]}) {
|
|
this {
|
|
myParam;
|
|
}
|
|
}
|
|
final a = new MyClass(myParam: 1);
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass({required int myNewParam}) {
|
|
this {
|
|
myNewParam;
|
|
}
|
|
}
|
|
final a = new MyClass(myNewParam: 1);
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'myNewParam',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_primaryConstructor_param_nonDeclaring_reference() {
|
|
const content = '''
|
|
class MyClass({required int myParam}) {
|
|
this {
|
|
[!myPa^ram!];
|
|
}
|
|
}
|
|
final a = new MyClass(myParam: 1);
|
|
''';
|
|
const expectedContent = '''
|
|
class MyClass({required int myNewParam}) {
|
|
this {
|
|
myNewParam;
|
|
}
|
|
}
|
|
final a = new MyClass(myNewParam: 1);
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'myNewParam',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_rejectedForBadName() async {
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
var error = await _test_rename_failure(content, 'not a valid class name');
|
|
expect(error.code, equals(ServerErrorCodes.renameNotValid));
|
|
expect(error.message, contains('name must not contain'));
|
|
}
|
|
|
|
Future<void> test_rename_rejectedForSameName() async {
|
|
const content = '''
|
|
class My^Class {}
|
|
''';
|
|
var error = await _test_rename_failure(content, 'MyClass');
|
|
expect(error.code, equals(ServerErrorCodes.renameNotValid));
|
|
expect(
|
|
error.message,
|
|
contains('new name must be different than the current name'),
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_rejectedForStaleDocument() async {
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = n^ew MyClass();
|
|
''';
|
|
var error = await _test_rename_failure(
|
|
content,
|
|
'MyNewClass',
|
|
openFileVersion: 111,
|
|
);
|
|
expect(error.code, equals(ErrorCodes.ContentModified));
|
|
expect(error.message, contains('Document was modified'));
|
|
}
|
|
|
|
Future<void> test_rename_rejectionsDoNotCrashServer() async {
|
|
// Checks that a rename failure does not stop the server from responding
|
|
// as was previously the case in https://github.com/dart-lang/sdk/issues/42573
|
|
// because the error code was duplicated/reused for ClientServerInconsistentState.
|
|
const content = '''
|
|
/// Test Class
|
|
class My^Class {}
|
|
''';
|
|
var code = TestCode.parse(content);
|
|
var error = await _test_rename_failure(content, 'MyClass');
|
|
expect(error.code, isNotNull);
|
|
|
|
// Send any other request to ensure the server is still responsive.
|
|
var hover = await getHover(mainFileUri, code.position.position);
|
|
expect(hover?.contents, isNotNull);
|
|
}
|
|
|
|
Future<void> test_rename_sdkClass() async {
|
|
const content = '''
|
|
final a = new [!Ob^ject!]();
|
|
''';
|
|
var code = TestCode.parse(content);
|
|
|
|
newFile(mainFilePath, code.code);
|
|
await initialize();
|
|
|
|
var request = makeRequest(
|
|
Method.textDocument_rename,
|
|
RenameParams(
|
|
newName: 'Object2',
|
|
textDocument: TextDocumentIdentifier(uri: mainFileUri),
|
|
position: code.position.position,
|
|
),
|
|
);
|
|
var response = await channel.sendRequestToServer(request);
|
|
|
|
expect(response.id, equals(request.id));
|
|
expect(response.result, isNull);
|
|
expect(response.error, isNotNull);
|
|
expect(response.error!.code, ServerErrorCodes.renameNotValid);
|
|
expect(response.error!.message, contains('is defined in the SDK'));
|
|
}
|
|
|
|
/// Unrelated dartdoc references should not be renamed.
|
|
///
|
|
/// https://github.com/Dart-Code/Dart-Code/issues/4131
|
|
Future<void> test_rename_updatesCorrectDartdocReferences() {
|
|
const content = '''
|
|
class A {
|
|
int? origi^nalName;
|
|
}
|
|
|
|
class B {
|
|
int? originalName;
|
|
}
|
|
|
|
/// [A.originalName]
|
|
/// [B.originalName]
|
|
/// [C.originalName]
|
|
var a;
|
|
''';
|
|
const expectedContent = '''
|
|
class A {
|
|
int? newName;
|
|
}
|
|
|
|
class B {
|
|
int? originalName;
|
|
}
|
|
|
|
/// [A.newName]
|
|
/// [B.originalName]
|
|
/// [C.originalName]
|
|
var a;
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_usingLegacyChangeInterface() async {
|
|
// This test initializes without support for DocumentChanges (versioning)
|
|
// whereas the other tests all use DocumentChanges support (preferred).
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = new My^Class();
|
|
''';
|
|
const expectedContent = '''
|
|
>>>>>>>>>> lib/main.dart
|
|
class MyNewClass {}
|
|
final a = new MyNewClass();
|
|
''';
|
|
var code = TestCode.parseNormalized(content);
|
|
|
|
await initialize();
|
|
await openFile(mainFileUri, code.code, version: 222);
|
|
|
|
var result = (await rename(
|
|
mainFileUri,
|
|
222,
|
|
code.position.position,
|
|
'MyNewClass',
|
|
))!;
|
|
|
|
verifyEdit(result, expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_variable() {
|
|
const content = '''
|
|
void f() {
|
|
var variable = 0;
|
|
print([!vari^able!]);
|
|
}
|
|
''';
|
|
const expectedContent = '''
|
|
void f() {
|
|
var foo = 0;
|
|
print(foo);
|
|
}
|
|
''';
|
|
return _test_rename_withDocumentChanges(content, 'foo', expectedContent);
|
|
}
|
|
|
|
Future<void> test_rename_variable_forEach_statement() {
|
|
const content = '''
|
|
void f(List<int> values) {
|
|
for (final [!value^!] in values) {
|
|
value;
|
|
}
|
|
}
|
|
''';
|
|
const expectedContent = '''
|
|
void f(List<int> values) {
|
|
for (final newName in values) {
|
|
newName;
|
|
}
|
|
}
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'newName',
|
|
expectedContent,
|
|
);
|
|
}
|
|
|
|
/// Local variable renames shouldn't fail to rename references to them in
|
|
/// workspaces with overlapped roots and `driverMap` being sorted such that
|
|
/// the first driver containing a file is ancestor.
|
|
Future<void> test_rename_variable_issueDartCode5548() async {
|
|
// Use hard-coded paths because the failure here may depend on the hash
|
|
// codes of the folder paths (as stored in `driverMap`) to reproduce.
|
|
var rootPath = convertPath('/dart_code_test');
|
|
var nestedProjectPath = join(rootPath, 'packages', 'test_package');
|
|
var testFilePath = join(nestedProjectPath, 'foo.dart');
|
|
|
|
// Force an additional context for the nested project.
|
|
newFile(join(nestedProjectPath, 'analysis_options.yaml'), '');
|
|
|
|
const content = '''
|
|
void f() {
|
|
var [!^b!] = 1;
|
|
print(b);
|
|
}
|
|
''';
|
|
const expectedContent = '''
|
|
void f() {
|
|
var a = 1;
|
|
print(a);
|
|
}
|
|
''';
|
|
await _test_rename_withDocumentChanges(
|
|
filePath: testFilePath,
|
|
content,
|
|
'a',
|
|
expectedContent,
|
|
workspaceFolders: [
|
|
pathContext.toUri(nestedProjectPath),
|
|
pathContext.toUri(rootPath),
|
|
],
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_withoutVersionedIdentifier() {
|
|
// Without sending a document version, the rename should still work because
|
|
// the server should use the version it had at the start of the rename
|
|
// operation.
|
|
const content = '''
|
|
class MyClass {}
|
|
final a = new [!My^Class!]();
|
|
''';
|
|
const expectedContent = '''
|
|
class MyNewClass {}
|
|
final a = new MyNewClass();
|
|
''';
|
|
return _test_rename_withDocumentChanges(
|
|
content,
|
|
'MyNewClass',
|
|
expectedContent,
|
|
sendRenameVersion: false,
|
|
);
|
|
}
|
|
|
|
Future<void> _test_prepare(
|
|
String content,
|
|
String? expectedPlaceholder,
|
|
) async {
|
|
var code = TestCode.parse(content);
|
|
await initialize();
|
|
await openFile(mainFileUri, code.code);
|
|
|
|
var result = await prepareRename(mainFileUri, code.position.position);
|
|
|
|
if (expectedPlaceholder == null) {
|
|
expect(result, isNull);
|
|
} else {
|
|
expect(result!.range, equals(code.range.range));
|
|
expect(result.placeholder, equals(expectedPlaceholder));
|
|
}
|
|
}
|
|
|
|
Future<ResponseError> _test_rename_failure(
|
|
String content,
|
|
String newName, {
|
|
int openFileVersion = 222,
|
|
int renameRequestFileVersion = 222,
|
|
bool supportsWindowShowMessageRequest = true,
|
|
}) async {
|
|
setDocumentChangesSupport();
|
|
|
|
var code = TestCode.parse(content);
|
|
await initialize(
|
|
experimentalCapabilities: supportsWindowShowMessageRequest
|
|
? const {'supportsWindowShowMessageRequest': true}
|
|
: null,
|
|
);
|
|
await openFile(mainFileUri, code.code, version: openFileVersion);
|
|
|
|
var result = await renameRaw(
|
|
mainFileUri,
|
|
renameRequestFileVersion,
|
|
code.position.position,
|
|
newName,
|
|
);
|
|
|
|
expect(result.result, isNull);
|
|
expect(result.error, isNotNull);
|
|
return result.error!;
|
|
}
|
|
|
|
/// Tests a rename that is expected to cause an error, which will trigger
|
|
/// a ShowMessageRequest from the server to the client to allow the refactor
|
|
/// to be continued or rejected.
|
|
Future<ResponseMessage> _test_rename_prompt(
|
|
String content,
|
|
String newName, {
|
|
required String expectedMessage,
|
|
Future<void> Function()? beforeResponding,
|
|
required String action,
|
|
int openFileVersion = 222,
|
|
int renameRequestFileVersion = 222,
|
|
bool supportsWindowShowMessageRequest = true,
|
|
}) async {
|
|
setDocumentChangesSupport();
|
|
|
|
var code = TestCode.parseNormalized(content);
|
|
await initialize(
|
|
experimentalCapabilities: supportsWindowShowMessageRequest
|
|
? const {'supportsWindowShowMessageRequest': true}
|
|
: null,
|
|
);
|
|
await openFile(mainFileUri, code.code, version: openFileVersion);
|
|
|
|
// Expect the server to call us back with a ShowMessageRequest prompt about
|
|
// the errors for us to accept/reject.
|
|
return handleExpectedRequest(
|
|
Method.window_showMessageRequest,
|
|
ShowMessageRequestParams.fromJson,
|
|
() => renameRaw(
|
|
mainFileUri,
|
|
renameRequestFileVersion,
|
|
code.position.position,
|
|
newName,
|
|
),
|
|
handler: (ShowMessageRequestParams params) async {
|
|
// Ensure the warning prompt is as expected.
|
|
expect(params.type, equals(MessageType.Warning));
|
|
expect(params.message, equals(expectedMessage));
|
|
expect(params.actions, hasLength(2));
|
|
expect(
|
|
params.actions![0],
|
|
equals(MessageActionItem(title: UserPromptActions.renameAnyway)),
|
|
);
|
|
expect(
|
|
params.actions![1],
|
|
equals(MessageActionItem(title: UserPromptActions.cancel)),
|
|
);
|
|
|
|
// Allow the test to run some code before we send the response.
|
|
await beforeResponding?.call();
|
|
|
|
// Respond to the request with the required action.
|
|
return MessageActionItem(title: action);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _test_rename_withDocumentChanges(
|
|
String content,
|
|
String newName,
|
|
String? expectedContent, {
|
|
String? filePath,
|
|
String? expectedFilePath,
|
|
bool sendRenameVersion = true,
|
|
bool supportsWindowShowMessageRequest = true,
|
|
List<Uri>? workspaceFolders,
|
|
}) async {
|
|
content = normalizeNewlinesForPlatform(content);
|
|
if (expectedContent != null) {
|
|
expectedContent = normalizeNewlinesForPlatform(expectedContent);
|
|
}
|
|
|
|
filePath ??= mainFilePath;
|
|
expectedFilePath ??= filePath;
|
|
var fileUri = pathContext.toUri(filePath);
|
|
|
|
// The specific number doesn't matter here, it's just a placeholder to confirm
|
|
// the values match.
|
|
var documentVersion = 222;
|
|
var expectedVersions = {fileUri: documentVersion};
|
|
|
|
setDocumentChangesSupport();
|
|
setFileRenameSupport();
|
|
|
|
var code = TestCode.parse(content);
|
|
await initialize(
|
|
experimentalCapabilities: supportsWindowShowMessageRequest
|
|
? const {'supportsWindowShowMessageRequest': true}
|
|
: null,
|
|
workspaceFolders: workspaceFolders,
|
|
);
|
|
await openFile(fileUri, code.code, version: documentVersion);
|
|
await initialAnalysis;
|
|
|
|
var result = await rename(
|
|
fileUri,
|
|
sendRenameVersion ? documentVersion : null,
|
|
code.position.position,
|
|
newName,
|
|
);
|
|
|
|
if (expectedContent == null) {
|
|
expect(result, isNull);
|
|
} else {
|
|
// For convenience, if a test doesn't provide a full set of edits
|
|
// we assume only a single edit of the file that was being modified.
|
|
if (!expectedContent.startsWith(LspChangeVerifier.editMarkerStart)) {
|
|
expectedContent =
|
|
'''
|
|
${LspChangeVerifier.editMarkerStart} ${relativePath(filePath)}
|
|
$expectedContent''';
|
|
}
|
|
verifyEdit(result!, expectedContent, expectedVersions: expectedVersions);
|
|
}
|
|
}
|
|
}
|