6a678d1f49
Closes https://github.com/dart-lang/sdk/pull/63534 GitOrigin-RevId: 345471bc33c002bbca9cea9c6626a05f481f70e3 Change-Id: If8d533ab0bbb4e6dff51a4256101fb8cb0490fb4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509680 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com>
3156 lines
68 KiB
Dart
3156 lines
68 KiB
Dart
// Copyright (c) 2014, 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/protocol/protocol.dart';
|
|
import 'package:analysis_server/protocol/protocol_generated.dart';
|
|
import 'package:analysis_server/src/services/refactoring/legacy/refactoring_manager.dart';
|
|
import 'package:analyzer/src/test_utilities/platform.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../analysis_server_base.dart';
|
|
import '../mocks.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(ConvertGetterMethodToMethodTest);
|
|
defineReflectiveTests(ConvertMethodToGetterTest);
|
|
defineReflectiveTests(ExtractLocalVariableTest);
|
|
defineReflectiveTests(ExtractMethodTest);
|
|
defineReflectiveTests(GetAvailableRefactoringsTest);
|
|
defineReflectiveTests(InlineLocalTest);
|
|
defineReflectiveTests(InlineMethodTest);
|
|
defineReflectiveTests(MoveFileTest);
|
|
defineReflectiveTests(RenameTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class ConvertGetterMethodToMethodTest extends _AbstractGetRefactoring_Test {
|
|
Future<void> test_function() {
|
|
addTestFile('''
|
|
int get test => 42;
|
|
void f() {
|
|
var a = 1 + test;
|
|
var b = 2 + test;
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendConvertRequest('test =>');
|
|
},
|
|
'''
|
|
int test() => 42;
|
|
void f() {
|
|
var a = 1 + test();
|
|
var b = 2 + test();
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_init_fatalError_notExplicit() {
|
|
addTestFile('''
|
|
int test = 42;
|
|
void f() {
|
|
var v = test;
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return _sendConvertRequest('test;');
|
|
}).then((result) {
|
|
assertResultProblemsFatal(
|
|
result.initialProblems,
|
|
'Only explicit getters can be converted to methods.',
|
|
);
|
|
// ...there is no any change
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_method() {
|
|
addTestFile('''
|
|
class A {
|
|
int get test => 1;
|
|
}
|
|
class B extends A {
|
|
int get test => 2;
|
|
}
|
|
class C extends B {
|
|
int get test => 3;
|
|
}
|
|
class D extends A {
|
|
int get test => 4;
|
|
}
|
|
void f(A a, B b, C c, D d) {
|
|
var va = a.test;
|
|
var vb = b.test;
|
|
var vc = c.test;
|
|
var vd = d.test;
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendConvertRequest('test => 2');
|
|
},
|
|
'''
|
|
class A {
|
|
int test() => 1;
|
|
}
|
|
class B extends A {
|
|
int test() => 2;
|
|
}
|
|
class C extends B {
|
|
int test() => 3;
|
|
}
|
|
class D extends A {
|
|
int test() => 4;
|
|
}
|
|
void f(A a, B b, C c, D d) {
|
|
var va = a.test();
|
|
var vb = b.test();
|
|
var vc = c.test();
|
|
var vd = d.test();
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<Response> _sendConvertRequest(String search) {
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.CONVERT_GETTER_TO_METHOD,
|
|
testFile.path,
|
|
findOffset(search),
|
|
0,
|
|
false,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class ConvertMethodToGetterTest extends _AbstractGetRefactoring_Test {
|
|
Future<void> test_function() {
|
|
addTestFile('''
|
|
int test() => 42;
|
|
void f() {
|
|
var a = 1 + test();
|
|
var b = 2 + test();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendConvertRequest('test() =>');
|
|
},
|
|
'''
|
|
int get test => 42;
|
|
void f() {
|
|
var a = 1 + test;
|
|
var b = 2 + test;
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_init_fatalError_hasParameters() {
|
|
addTestFile('''
|
|
int test(p) => p + 1;
|
|
void f() {
|
|
var v = test(2);
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return _sendConvertRequest('test(p)');
|
|
}).then((result) {
|
|
assertResultProblemsFatal(
|
|
result.initialProblems,
|
|
'Only methods without parameters can be converted to getters.',
|
|
);
|
|
// ...there is no any change
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_init_fatalError_notExecutableElement() {
|
|
addTestFile('''
|
|
void f() {
|
|
int abc = 1;
|
|
print(abc);
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return _sendConvertRequest('abc');
|
|
}).then((result) {
|
|
assertResultProblemsFatal(
|
|
result.initialProblems,
|
|
'Unable to create a refactoring',
|
|
);
|
|
// ...there is no any change
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_method() {
|
|
addTestFile('''
|
|
class A {
|
|
int test() => 1;
|
|
}
|
|
class B extends A {
|
|
int test() => 2;
|
|
}
|
|
class C extends B {
|
|
int test() => 3;
|
|
}
|
|
class D extends A {
|
|
int test() => 4;
|
|
}
|
|
void f(A a, B b, C c, D d) {
|
|
var va = a.test();
|
|
var vb = b.test();
|
|
var vc = c.test();
|
|
var vd = d.test();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendConvertRequest('test() => 2');
|
|
},
|
|
'''
|
|
class A {
|
|
int get test => 1;
|
|
}
|
|
class B extends A {
|
|
int get test => 2;
|
|
}
|
|
class C extends B {
|
|
int get test => 3;
|
|
}
|
|
class D extends A {
|
|
int get test => 4;
|
|
}
|
|
void f(A a, B b, C c, D d) {
|
|
var va = a.test;
|
|
var vb = b.test;
|
|
var vc = c.test;
|
|
var vd = d.test;
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<Response> _sendConvertRequest(String search) {
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.CONVERT_METHOD_TO_GETTER,
|
|
testFile.path,
|
|
findOffset(search),
|
|
0,
|
|
false,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class ExtractLocalVariableTest extends _AbstractGetRefactoring_Test {
|
|
Future<Response> sendExtractRequest(
|
|
int offset,
|
|
int length,
|
|
String? name,
|
|
bool extractAll,
|
|
) {
|
|
var kind = RefactoringKind.EXTRACT_LOCAL_VARIABLE;
|
|
var options = name != null
|
|
? ExtractLocalVariableOptions(name, extractAll)
|
|
: null;
|
|
return sendRequest(kind, offset, length, options);
|
|
}
|
|
|
|
Future<Response> sendStringRequest(
|
|
String search,
|
|
String name,
|
|
bool extractAll,
|
|
) {
|
|
var offset = findOffset(search);
|
|
var length = search.length;
|
|
return sendExtractRequest(offset, length, name, extractAll);
|
|
}
|
|
|
|
Future<Response> sendStringSuffixRequest(
|
|
String search,
|
|
String suffix,
|
|
String? name,
|
|
bool extractAll,
|
|
) {
|
|
var offset = findOffset(search + suffix);
|
|
var length = search.length;
|
|
return sendExtractRequest(offset, length, name, extractAll);
|
|
}
|
|
|
|
@override
|
|
Future<void> tearDown() async {
|
|
test_simulateRefactoringException_init = false;
|
|
test_simulateRefactoringException_final = false;
|
|
test_simulateRefactoringException_change = false;
|
|
await super.tearDown();
|
|
}
|
|
|
|
Future<void> test_analysis_onlyOneFile() async {
|
|
shouldWaitForFullAnalysis = false;
|
|
newFile('$testPackageLibPath/other.dart', r'''
|
|
foo(int myName) {}
|
|
''');
|
|
addTestFile('''
|
|
import 'other.dart';
|
|
void f() {
|
|
foo(1 + 2);
|
|
}
|
|
''');
|
|
// Start refactoring.
|
|
var result = await getRefactoringResult(() {
|
|
return sendStringRequest('1 + 2', 'res', true);
|
|
});
|
|
// We get the refactoring feedback....
|
|
var feedback = result.feedback as ExtractLocalVariableFeedback;
|
|
expect(feedback.names, contains('myName'));
|
|
}
|
|
|
|
Future<void> test_coveringExpressions() {
|
|
addTestFile('''
|
|
void f() {
|
|
var v = 111 + 222 + 333;
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendExtractRequest(
|
|
testFileContent.indexOf('222 +'),
|
|
0,
|
|
'res',
|
|
true,
|
|
);
|
|
}).then((result) {
|
|
var feedback = result.feedback as ExtractLocalVariableFeedback;
|
|
expect(feedback.coveringExpressionOffsets, [
|
|
testFileContent.indexOf('222 +'),
|
|
testFileContent.indexOf('111 +'),
|
|
testFileContent.indexOf('111 +'),
|
|
]);
|
|
expect(feedback.coveringExpressionLengths, [
|
|
'222'.length,
|
|
'111 + 222'.length,
|
|
'111 + 222 + 333'.length,
|
|
]);
|
|
});
|
|
}
|
|
|
|
Future<void> test_extractAll() {
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendStringRequest('1 + 2', 'res', true);
|
|
},
|
|
'''
|
|
void f() {
|
|
var res = 1 + 2;
|
|
print(res);
|
|
print(res);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extractOne() {
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
print(1 + 2); // marker
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendStringSuffixRequest('1 + 2', '); // marker', 'res', false);
|
|
},
|
|
'''
|
|
void f() {
|
|
print(1 + 2);
|
|
var res = 1 + 2;
|
|
print(res); // marker
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notAbsolute() async {
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.EXTRACT_LOCAL_VARIABLE,
|
|
'test.dart',
|
|
0,
|
|
0,
|
|
true,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
var response = await handleRequest(request);
|
|
assertResponseFailure(
|
|
response,
|
|
requestId: '0',
|
|
errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notNormalized() async {
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.EXTRACT_LOCAL_VARIABLE,
|
|
convertPath('/foo/../bar/test.dart'),
|
|
0,
|
|
0,
|
|
true,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
var response = await handleRequest(request);
|
|
assertResponseFailure(
|
|
response,
|
|
requestId: '0',
|
|
errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
|
|
);
|
|
}
|
|
|
|
Future<void> test_names() async {
|
|
addTestFile('''
|
|
class TreeItem {}
|
|
TreeItem getSelectedItem() => null;
|
|
void f() {
|
|
var a = getSelectedItem();
|
|
}
|
|
''');
|
|
var result = await getRefactoringResult(() {
|
|
return sendStringSuffixRequest('getSelectedItem()', ';', null, true);
|
|
});
|
|
var feedback = result.feedback as ExtractLocalVariableFeedback;
|
|
expect(
|
|
feedback.names,
|
|
unorderedEquals(['treeItem', 'item', 'selectedItem']),
|
|
);
|
|
expect(result.change, isNull);
|
|
}
|
|
|
|
Future<void> test_nameWarning() async {
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
var result = await getRefactoringResult(() {
|
|
return sendStringRequest('1 + 2', 'Name', true);
|
|
});
|
|
assertResultProblemsWarning(
|
|
result.optionsProblems,
|
|
'Variable name should start with a lowercase letter.',
|
|
);
|
|
// ...but there is still a change
|
|
assertTestRefactoringResult(result, '''
|
|
void f() {
|
|
var Name = 1 + 2;
|
|
print(Name);
|
|
}
|
|
''');
|
|
}
|
|
|
|
Future<void> test_offsetsLengths() {
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendStringRequest('1 + 2', 'res', true);
|
|
}).then((result) {
|
|
var feedback = result.feedback as ExtractLocalVariableFeedback;
|
|
expect(feedback.offsets, [findOffset('1 + 2'), findOffset('1 + 2')]);
|
|
expect(feedback.lengths, [5, 6]);
|
|
});
|
|
}
|
|
|
|
Future<void> test_resetOnAnalysisSetChanged_overlay() async {
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2); // 0
|
|
}
|
|
''');
|
|
|
|
Future<void> checkUpdate(void Function() doUpdate) async {
|
|
await getRefactoringResult(() {
|
|
return sendStringRequest('1 + 2', 'res', true);
|
|
});
|
|
var initialResetCount = test_resetCount;
|
|
doUpdate();
|
|
await pumpEventQueue();
|
|
expect(test_resetCount, initialResetCount + 1);
|
|
}
|
|
|
|
await checkUpdate(() {
|
|
server.updateContent('u1', {
|
|
testFile.path: AddContentOverlay('''
|
|
void f() {
|
|
print(1 + 2); // 1
|
|
}
|
|
'''),
|
|
});
|
|
});
|
|
|
|
await checkUpdate(() {
|
|
server.updateContent('u2', {
|
|
testFile.path: ChangeContentOverlay([
|
|
SourceEdit(0, 0, '''
|
|
void f() {
|
|
print(1 + 2); // 2
|
|
}
|
|
'''),
|
|
]),
|
|
});
|
|
});
|
|
|
|
await checkUpdate(() {
|
|
server.updateContent('u3', {testFile.path: RemoveContentOverlay()});
|
|
});
|
|
}
|
|
|
|
Future<void> test_resetOnAnalysisSetChanged_watch_otherFile() async {
|
|
var otherFile = join('$testPackageLibPath/other.dart');
|
|
newFile(otherFile, '// other 1');
|
|
addTestFile('''
|
|
void f() {
|
|
foo(1 + 2);
|
|
}
|
|
foo(int myName) {}
|
|
''');
|
|
// Send the first request.
|
|
{
|
|
var result = await getRefactoringResult(() {
|
|
return sendStringRequest('1 + 2', 'res', true);
|
|
});
|
|
var feedback = result.feedback as ExtractLocalVariableFeedback;
|
|
expect(feedback.names, contains('myName'));
|
|
}
|
|
var initialResetCount = test_resetCount;
|
|
// Update the other.dart file.
|
|
// The refactoring is reset, even though it's a different file. It is up to
|
|
// analyzer to track dependencies and provide resolved units fast when
|
|
// possible.
|
|
newFile(otherFile, '// other 2');
|
|
await pumpEventQueue();
|
|
expect(test_resetCount, initialResetCount + 1);
|
|
}
|
|
|
|
Future<void> test_resetOnAnalysisSetChanged_watch_thisFile() async {
|
|
addTestFile('''
|
|
void f() {
|
|
foo(1 + 2);
|
|
}
|
|
foo(int myName) {}
|
|
''');
|
|
// Send the first request.
|
|
{
|
|
var result = await getRefactoringResult(() {
|
|
return sendStringRequest('1 + 2', 'res', true);
|
|
});
|
|
var feedback = result.feedback as ExtractLocalVariableFeedback;
|
|
expect(feedback.names, contains('myName'));
|
|
}
|
|
var initialResetCount = test_resetCount;
|
|
// Update the test.dart file.
|
|
modifyTestFile('''
|
|
void f() {
|
|
foo(1 + 2);
|
|
}
|
|
foo(int otherName) {}
|
|
''');
|
|
// The refactoring was reset.
|
|
await pumpEventQueue();
|
|
expect(test_resetCount, initialResetCount + 1);
|
|
// Send the second request, with the same kind, file and offset.
|
|
{
|
|
var result = await getRefactoringResult(() {
|
|
return sendStringRequest('1 + 2', 'res', true);
|
|
});
|
|
var feedback = result.feedback as ExtractLocalVariableFeedback;
|
|
// The refactoring was reset, so we don't get stale results.
|
|
expect(feedback.names, contains('otherName'));
|
|
}
|
|
}
|
|
|
|
Future<void> test_serverError_change() {
|
|
test_simulateRefactoringException_change = true;
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
return waitForTasksFinished().then((_) {
|
|
return sendStringRequest('1 + 2', 'res', true).then((response) {
|
|
var error = response.error!;
|
|
expect(error.code, RequestErrorCode.SERVER_ERROR);
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<void> test_serverError_final() {
|
|
test_simulateRefactoringException_final = true;
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
return waitForTasksFinished().then((_) {
|
|
return sendStringRequest('1 + 2', 'res', true).then((response) {
|
|
var error = response.error!;
|
|
expect(error.code, RequestErrorCode.SERVER_ERROR);
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<void> test_serverError_init() {
|
|
test_simulateRefactoringException_init = true;
|
|
addTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
return waitForTasksFinished().then((_) {
|
|
return sendStringRequest('1 + 2', 'res', true).then((response) {
|
|
var error = response.error!;
|
|
expect(error.code, RequestErrorCode.SERVER_ERROR);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class ExtractMethodTest extends _AbstractGetRefactoring_Test {
|
|
late int offset;
|
|
late int length;
|
|
String name = 'res';
|
|
ExtractMethodOptions? options;
|
|
|
|
Future<void> test_expression() {
|
|
addTestFile('''
|
|
void f() {
|
|
print([!1 + 2!]);
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return assertSuccessfulRefactoring(_computeChange, '''
|
|
void f() {
|
|
print(res());
|
|
print(res());
|
|
}
|
|
|
|
int res() => 1 + 2;
|
|
''');
|
|
}
|
|
|
|
Future<void> test_expression_hasParameters() {
|
|
addTestFile('''
|
|
void f() {
|
|
int a = 1;
|
|
int b = 2;
|
|
print([!a + b!]);
|
|
print(a + b);
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return assertSuccessfulRefactoring(_computeChange, '''
|
|
void f() {
|
|
int a = 1;
|
|
int b = 2;
|
|
print(res(a, b));
|
|
print(res(a, b));
|
|
}
|
|
|
|
int res(int a, int b) => a + b;
|
|
''');
|
|
}
|
|
|
|
Future<void> test_expression_updateParameters() async {
|
|
addTestFile('''
|
|
void f() {
|
|
int a = 1;
|
|
int b = 2;
|
|
print([!a + b!]);
|
|
print(a + b);
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
var result = await getRefactoringResult(_computeChange);
|
|
var feedback = result.feedback as ExtractMethodFeedback;
|
|
var parameters = feedback.parameters;
|
|
parameters[0].name = 'aaa';
|
|
parameters[1].name = 'bbb';
|
|
parameters[1].type = 'num';
|
|
parameters.insert(0, parameters.removeLast());
|
|
options!.parameters = parameters;
|
|
return assertSuccessfulRefactoring(_sendExtractRequest, '''
|
|
void f() {
|
|
int a = 1;
|
|
int b = 2;
|
|
print(res(b, a));
|
|
print(res(b, a));
|
|
}
|
|
|
|
int res(num bbb, int aaa) => aaa + bbb;
|
|
''');
|
|
}
|
|
|
|
Future<void> test_init_fatalError_invalidStatement() {
|
|
addTestFile('''
|
|
void f(bool b) {
|
|
[!if (b) {
|
|
print(1);!]
|
|
print(2);
|
|
}
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return waitForTasksFinished()
|
|
.then((_) {
|
|
return _sendExtractRequest();
|
|
})
|
|
.then((Response response) {
|
|
var result = EditGetRefactoringResult.fromResponse(
|
|
response,
|
|
clientUriConverter: server.uriConverter,
|
|
);
|
|
assertResultProblemsFatal(result.initialProblems);
|
|
// ...there is no any feedback
|
|
expect(result.feedback, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_long_expression() {
|
|
addTestFile('''
|
|
void f() {
|
|
print([!1 +
|
|
2!]);
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return assertSuccessfulRefactoring(_computeChange, '''
|
|
void f() {
|
|
print(res());
|
|
}
|
|
|
|
int res() {
|
|
return 1 +
|
|
2;
|
|
}
|
|
''');
|
|
}
|
|
|
|
Future<void> test_names() {
|
|
addTestFile('''
|
|
class TreeItem {}
|
|
TreeItem getSelectedItem() => null;
|
|
void f() {
|
|
var a = [!getSelectedItem()!];
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return _computeInitialFeedback().then((feedback) {
|
|
expect(
|
|
feedback.names,
|
|
unorderedEquals(['treeItem', 'item', 'selectedItem']),
|
|
);
|
|
expect(feedback.returnType, 'TreeItem');
|
|
});
|
|
}
|
|
|
|
Future<void> test_offsetsLengths() {
|
|
addTestFile('''
|
|
class TreeItem {}
|
|
TreeItem getSelectedItem() => null;
|
|
void f() {
|
|
var a = [!1 + 2!];
|
|
var b = 1 + 2;
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return _computeInitialFeedback().then((feedback) {
|
|
expect(feedback.offsets, [findOffset('1 + 2'), findOffset('1 + 2')]);
|
|
expect(feedback.lengths, [5, 6]);
|
|
});
|
|
}
|
|
|
|
Future<void> test_statements() {
|
|
addTestFile('''
|
|
void f() {
|
|
int a = 1;
|
|
int b = 2;
|
|
[!print(a + b);!]
|
|
print(a + b);
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return assertSuccessfulRefactoring(_computeChange, '''
|
|
void f() {
|
|
int a = 1;
|
|
int b = 2;
|
|
res(a, b);
|
|
res(a, b);
|
|
}
|
|
|
|
void res(int a, int b) {
|
|
print(a + b);
|
|
}
|
|
''');
|
|
}
|
|
|
|
Future<void> test_statements_nullableReturnType() {
|
|
addTestFile('''
|
|
void foo(int b) {
|
|
[!int? x;
|
|
if (b < 2) {
|
|
x = 42;
|
|
}
|
|
if (b >= 2) {
|
|
x = 43;
|
|
}!]
|
|
print(x!);
|
|
}
|
|
''');
|
|
_setOffsetLengthFromMarkdown();
|
|
return assertSuccessfulRefactoring(_computeChange, '''
|
|
void foo(int b) {
|
|
int? x = res(b);
|
|
print(x!);
|
|
}
|
|
|
|
int? res(int b) {
|
|
int? x;
|
|
if (b < 2) {
|
|
x = 42;
|
|
}
|
|
if (b >= 2) {
|
|
x = 43;
|
|
}
|
|
return x;
|
|
}
|
|
''');
|
|
}
|
|
|
|
Future<Response> _computeChange() async {
|
|
await _prepareOptions();
|
|
// send request with the options
|
|
return _sendExtractRequest();
|
|
}
|
|
|
|
Future<ExtractMethodFeedback> _computeInitialFeedback() async {
|
|
await waitForTasksFinished();
|
|
var response = await _sendExtractRequest();
|
|
var result = EditGetRefactoringResult.fromResponse(
|
|
response,
|
|
clientUriConverter: server.uriConverter,
|
|
);
|
|
return result.feedback as ExtractMethodFeedback;
|
|
}
|
|
|
|
Future<void> _prepareOptions() {
|
|
return getRefactoringResult(() {
|
|
// get initial feedback
|
|
return _sendExtractRequest();
|
|
}).then((result) {
|
|
assertResultProblemsOK(result);
|
|
// fill options from result
|
|
var feedback = result.feedback as ExtractMethodFeedback;
|
|
options = ExtractMethodOptions(
|
|
feedback.returnType,
|
|
false,
|
|
name,
|
|
feedback.parameters,
|
|
true,
|
|
);
|
|
// done
|
|
return Future.value();
|
|
});
|
|
}
|
|
|
|
Future<Response> _sendExtractRequest() {
|
|
var kind = RefactoringKind.EXTRACT_METHOD;
|
|
return sendRequest(kind, offset, length, options);
|
|
}
|
|
|
|
void _setOffsetLengthFromMarkdown() {
|
|
if (parsedTestCode.ranges.isNotEmpty) {
|
|
if (parsedTestCode.positions.isNotEmpty) {
|
|
fail('Expected a single range.');
|
|
}
|
|
var range = parsedTestCode.range.sourceRange;
|
|
offset = range.offset;
|
|
length = range.length;
|
|
} else {
|
|
fail('Expected a single range.');
|
|
}
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class GetAvailableRefactoringsTest extends PubPackageAnalysisServerTest {
|
|
late List<RefactoringKind> kinds;
|
|
|
|
void addFlutterPackage() {
|
|
writeTestPackageConfig(flutter: true);
|
|
}
|
|
|
|
/// Tests that there is refactoring of the given [kind] is available at the
|
|
/// [search] offset.
|
|
Future<void> assertHasKind(
|
|
String code,
|
|
String search,
|
|
RefactoringKind kind,
|
|
bool expected,
|
|
) async {
|
|
addTestFile(code);
|
|
await waitForTasksFinished();
|
|
await getRefactoringsAtString(search);
|
|
// verify
|
|
var matcher = contains(kind);
|
|
if (!expected) {
|
|
matcher = isNot(matcher);
|
|
}
|
|
expect(kinds, matcher);
|
|
}
|
|
|
|
/// Tests that there is a RENAME refactoring available at the [search] offset.
|
|
Future<void> assertHasRenameRefactoring(String code, String search) {
|
|
return assertHasKind(code, search, RefactoringKind.RENAME, true);
|
|
}
|
|
|
|
/// Returns the list of available refactorings for the given [offset] and
|
|
/// [length].
|
|
Future<void> getRefactorings(int offset, int length) async {
|
|
var request = EditGetAvailableRefactoringsParams(
|
|
testFile.path,
|
|
offset,
|
|
length,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
var response = await serverChannel.simulateRequestFromClient(request);
|
|
var result = EditGetAvailableRefactoringsResult.fromResponse(
|
|
response,
|
|
clientUriConverter: server.uriConverter,
|
|
);
|
|
kinds = result.kinds;
|
|
}
|
|
|
|
/// Returns the list of available refactorings at the offset of [search].
|
|
Future<void> getRefactoringsAtString(String search) {
|
|
var offset = findOffset(search);
|
|
return getRefactorings(offset, 0);
|
|
}
|
|
|
|
Future<void> getRefactoringsForString(String search) {
|
|
var offset = findOffset(search);
|
|
return getRefactorings(offset, search.length);
|
|
}
|
|
|
|
@override
|
|
Future<void> setUp() async {
|
|
super.setUp();
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
}
|
|
|
|
Future<void> test_convertMethodToGetter_hasElement() {
|
|
return assertHasKind(
|
|
'''
|
|
int getValue() => 42;
|
|
''',
|
|
'getValue',
|
|
RefactoringKind.CONVERT_METHOD_TO_GETTER,
|
|
true,
|
|
);
|
|
}
|
|
|
|
Future<void> test_extractLocal() async {
|
|
addTestFile('''
|
|
void f() {
|
|
var a = 1 + 2;
|
|
}
|
|
''');
|
|
await waitForTasksFinished();
|
|
await getRefactoringsForString('1 + 2');
|
|
expect(kinds, contains(RefactoringKind.EXTRACT_LOCAL_VARIABLE));
|
|
expect(kinds, contains(RefactoringKind.EXTRACT_METHOD));
|
|
}
|
|
|
|
Future<void> test_extractLocal_withoutSelection() async {
|
|
addTestFile('''
|
|
void f() {
|
|
var a = 1 + 2;
|
|
}
|
|
''');
|
|
await waitForTasksFinished();
|
|
await getRefactoringsAtString('1 + 2');
|
|
expect(kinds, contains(RefactoringKind.EXTRACT_LOCAL_VARIABLE));
|
|
expect(kinds, contains(RefactoringKind.EXTRACT_METHOD));
|
|
}
|
|
|
|
Future<void> test_extractWidget() async {
|
|
addFlutterPackage();
|
|
addTestFile('''
|
|
import 'package:flutter/material.dart';
|
|
|
|
class MyWidget extends StatelessWidget {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Text('AAA');
|
|
}
|
|
}
|
|
''');
|
|
await waitForTasksFinished();
|
|
await getRefactoringsForString('Text');
|
|
expect(kinds, contains(RefactoringKind.EXTRACT_WIDGET));
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notAbsolute() async {
|
|
var request = EditGetAvailableRefactoringsParams(
|
|
'test.dart',
|
|
0,
|
|
0,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
var response = await handleRequest(request);
|
|
assertResponseFailure(
|
|
response,
|
|
requestId: '0',
|
|
errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notNormalized() async {
|
|
var request = EditGetAvailableRefactoringsParams(
|
|
convertPath('/foo/../bar/test.dart'),
|
|
0,
|
|
0,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
var response = await handleRequest(request);
|
|
assertResponseFailure(
|
|
response,
|
|
requestId: '0',
|
|
errorCode: RequestErrorCode.INVALID_FILE_PATH_FORMAT,
|
|
);
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_class() {
|
|
return assertHasRenameRefactoring('''
|
|
class Test {}
|
|
void f() {
|
|
Test v;
|
|
}
|
|
''', 'Test v');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_constructor() {
|
|
return assertHasRenameRefactoring('''
|
|
class A {
|
|
A.test() {}
|
|
}
|
|
void f() {
|
|
A.test();
|
|
}
|
|
''', 'test();');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_function() {
|
|
return assertHasRenameRefactoring('''
|
|
void f() {
|
|
test();
|
|
}
|
|
test() {}
|
|
''', 'test();');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_importElement_directive() {
|
|
return assertHasRenameRefactoring('''
|
|
import 'dart:math' as math;
|
|
void f() {
|
|
math.PI;
|
|
}
|
|
''', 'import ');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_importElement_prefixDecl() {
|
|
return assertHasRenameRefactoring('''
|
|
import 'dart:math' as math;
|
|
void f() {
|
|
math.PI;
|
|
}
|
|
''', 'math;');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_importElement_prefixRef() {
|
|
return assertHasRenameRefactoring('''
|
|
import 'dart:async' as test;
|
|
import 'dart:math' as test;
|
|
void f() {
|
|
test.pi;
|
|
}
|
|
''', 'test.pi;');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_instanceGetter() {
|
|
return assertHasRenameRefactoring('''
|
|
class A {
|
|
get test => 0;
|
|
}
|
|
void f(A a) {
|
|
a.test;
|
|
}
|
|
''', 'test;');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_instanceSetter() {
|
|
return assertHasRenameRefactoring('''
|
|
class A {
|
|
set test(x) {}
|
|
}
|
|
void f(A a) {
|
|
a.test = 2;
|
|
}
|
|
''', 'test = 2;');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_library() {
|
|
return assertHasRenameRefactoring('''
|
|
library my.lib;
|
|
''', 'library ');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_localVariable() {
|
|
return assertHasRenameRefactoring('''
|
|
void f() {
|
|
int test = 0;
|
|
print(test);
|
|
}
|
|
''', 'test = 0;');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_localVariable_forEach_statement() {
|
|
return assertHasRenameRefactoring('''
|
|
void f(List<int> values) {
|
|
for (final value in values) {
|
|
value;
|
|
}
|
|
}
|
|
''', 'value in');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_method() {
|
|
return assertHasRenameRefactoring('''
|
|
class A {
|
|
test() {}
|
|
}
|
|
void f(A a) {
|
|
a.test();
|
|
}
|
|
''', 'test();');
|
|
}
|
|
|
|
Future<void> test_rename_hasElement_typeParameter_class() {
|
|
return assertHasRenameRefactoring('''
|
|
class A<T> {}
|
|
''', 'T> {}');
|
|
}
|
|
|
|
Future<void> test_rename_noElement() async {
|
|
addTestFile('''
|
|
void f() {
|
|
// not an element
|
|
}
|
|
''');
|
|
await waitForTasksFinished();
|
|
await getRefactoringsAtString('// not an element');
|
|
expect(kinds, isNot(contains(RefactoringKind.RENAME)));
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class InlineLocalTest extends _AbstractGetRefactoring_Test {
|
|
Future<void> test_analysis_onlyOneFile() async {
|
|
shouldWaitForFullAnalysis = false;
|
|
newFile('$testPackageLibPath/other.dart', r'''
|
|
foo(int p) {}
|
|
''');
|
|
addTestFile('''
|
|
import 'other.dart';
|
|
void f() {
|
|
int res = 1 + 2;
|
|
foo(res);
|
|
foo(res);
|
|
}
|
|
''');
|
|
// Start refactoring.
|
|
await setPriorityFiles2([testFile]);
|
|
var result = await getRefactoringResult(() {
|
|
return _sendInlineRequest('res =');
|
|
});
|
|
// We get the refactoring feedback....
|
|
var feedback = result.feedback as InlineLocalVariableFeedback;
|
|
expect(feedback.occurrences, 2);
|
|
}
|
|
|
|
Future<void> test_feedback() {
|
|
setPriorityFiles([testFile]);
|
|
addTestFile('''
|
|
void f() {
|
|
int test = 42;
|
|
print(test);
|
|
print(test);
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return _sendInlineRequest('test =');
|
|
}).then((result) {
|
|
var feedback = result.feedback as InlineLocalVariableFeedback;
|
|
expect(feedback.name, 'test');
|
|
expect(feedback.occurrences, 2);
|
|
});
|
|
}
|
|
|
|
Future<void> test_init_fatalError_notVariable() {
|
|
addTestFile('void f() {}');
|
|
return getRefactoringResult(() {
|
|
return _sendInlineRequest('void f() {}');
|
|
}).then((result) {
|
|
assertResultProblemsFatal(
|
|
result.initialProblems,
|
|
'Local variable declaration or reference must be selected to activate this refactoring.',
|
|
);
|
|
// ...there is no any change
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_OK() {
|
|
setPriorityFiles([testFile]);
|
|
addTestFile('''
|
|
void f() {
|
|
int test = 42;
|
|
int a = test + 2;
|
|
print(test);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendInlineRequest('test + 2');
|
|
},
|
|
'''
|
|
void f() {
|
|
int a = 42 + 2;
|
|
print(42);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_resetOnAnalysisSetChanged() async {
|
|
newFile('$testPackageLibPath/other.dart', '// other 1');
|
|
addTestFile('''
|
|
void f() {
|
|
int res = 1 + 2;
|
|
print(res);
|
|
}
|
|
''');
|
|
// Send the first request.
|
|
await getRefactoringResult(() {
|
|
return _sendInlineRequest('res = ');
|
|
});
|
|
var initialResetCount = test_resetCount;
|
|
// Update the test.dart file.
|
|
modifyTestFile('''
|
|
void f() {
|
|
print(1 + 2);
|
|
}
|
|
''');
|
|
// The refactoring was reset.
|
|
await pumpEventQueue();
|
|
expect(test_resetCount, initialResetCount + 1);
|
|
}
|
|
|
|
Future<Response> _sendInlineRequest(String search) {
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.INLINE_LOCAL_VARIABLE,
|
|
testFile.path,
|
|
findOffset(search),
|
|
0,
|
|
false,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class InlineMethodTest extends _AbstractGetRefactoring_Test {
|
|
InlineMethodOptions options = InlineMethodOptions(true, true);
|
|
|
|
Future<void> test_feedback() {
|
|
addTestFile('''
|
|
class A {
|
|
int f;
|
|
test(int p) {
|
|
print(f + p);
|
|
}
|
|
void f() {
|
|
test(1);
|
|
}
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return _sendInlineRequest('test(int p)');
|
|
}).then((result) {
|
|
var feedback = result.feedback as InlineMethodFeedback;
|
|
expect(feedback.className, 'A');
|
|
expect(feedback.methodName, 'test');
|
|
expect(feedback.isDeclaration, isTrue);
|
|
});
|
|
}
|
|
|
|
Future<void> test_init_fatalError_noMethod() {
|
|
addTestFile('// nothing to inline');
|
|
return getRefactoringResult(() {
|
|
return _sendInlineRequest('// nothing');
|
|
}).then((result) {
|
|
assertResultProblemsFatal(
|
|
result.initialProblems,
|
|
'Method declaration or reference must be selected to activate this refactoring.',
|
|
);
|
|
// ...there is no any change
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_method() {
|
|
addTestFile('''
|
|
class A {
|
|
int f;
|
|
test(int p) {
|
|
print(f + p);
|
|
}
|
|
void f() {
|
|
test(1);
|
|
}
|
|
}
|
|
void f(A a) {
|
|
a.test(2);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendInlineRequest('test(int p)');
|
|
},
|
|
'''
|
|
class A {
|
|
int f;
|
|
void f() {
|
|
print(f + 1);
|
|
}
|
|
}
|
|
void f(A a) {
|
|
print(a.f + 2);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_topLevelFunction() {
|
|
addTestFile('''
|
|
test(a, b) {
|
|
print(a + b);
|
|
}
|
|
void f() {
|
|
test(1, 2);
|
|
test(10, 20);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendInlineRequest('test(a');
|
|
},
|
|
'''
|
|
void f() {
|
|
print(1 + 2);
|
|
print(10 + 20);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_topLevelFunction_async() {
|
|
addTestFile('''
|
|
Future<int> a() async => 3;
|
|
Future<int> b() async => await a();
|
|
Future<int> c() async => await b();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendInlineRequest('b(');
|
|
},
|
|
'''
|
|
Future<int> a() async => 3;
|
|
Future<int> c() async => await a();
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_topLevelFunction_oneInvocation() {
|
|
addTestFile('''
|
|
test(a, b) {
|
|
print(a + b);
|
|
}
|
|
void f() {
|
|
test(1, 2);
|
|
test(10, 20);
|
|
}
|
|
''');
|
|
options.deleteSource = false;
|
|
options.inlineAll = false;
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendInlineRequest('test(10,');
|
|
},
|
|
'''
|
|
test(a, b) {
|
|
print(a + b);
|
|
}
|
|
void f() {
|
|
test(1, 2);
|
|
print(10 + 20);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<Response> _sendInlineRequest(String search) {
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.INLINE_METHOD,
|
|
testFile.path,
|
|
findOffset(search),
|
|
0,
|
|
false,
|
|
options: options,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class MoveFileTest extends _AbstractGetRefactoring_Test {
|
|
late MoveFileOptions options;
|
|
|
|
Future<void> test_file_OK() {
|
|
newFile('$testPackageLibPath/a.dart', '');
|
|
addTestFile('''
|
|
import 'dart:math';
|
|
import 'a.dart';
|
|
''');
|
|
_setOptions('$testPackageRootPath/test.dart');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendMoveRequest(testFile.path);
|
|
},
|
|
'''
|
|
import 'dart:math';
|
|
import 'lib/a.dart';
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_folder_cancel() {
|
|
newFile('$testPackageLibPath/original_folder/file.dart', '');
|
|
addTestFile('''
|
|
import 'dart:math';
|
|
import 'original_folder/file.dart';
|
|
''');
|
|
_setOptions('$testPackageLibPath/new_folder');
|
|
return assertEmptySuccessfulRefactoring(() {
|
|
return _sendAndCancelMoveRequest(
|
|
getFolder('$testPackageLibPath/original_folder').path,
|
|
);
|
|
});
|
|
}
|
|
|
|
Future<void> test_folder_OK() {
|
|
newFile('$testPackageLibPath/original_folder/file.dart', '');
|
|
addTestFile('''
|
|
import 'dart:math';
|
|
import 'original_folder/file.dart';
|
|
''');
|
|
_setOptions('$testPackageLibPath/new_folder');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return _sendMoveRequest(
|
|
getFolder('$testPackageLibPath/original_folder').path,
|
|
);
|
|
},
|
|
'''
|
|
import 'dart:math';
|
|
import 'new_folder/file.dart';
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<Response> _cancelMoveRequest() {
|
|
// 0 is the id from _sendMoveRequest
|
|
// 1 is another arbitrary id for the cancel request
|
|
var request = ServerCancelRequestParams(
|
|
'0',
|
|
).toRequest('1', clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
|
|
Future<Response> _sendAndCancelMoveRequest(String item) async {
|
|
var responses = await Future.wait([
|
|
_sendMoveRequest(item),
|
|
_cancelMoveRequest(),
|
|
]);
|
|
return responses.first;
|
|
}
|
|
|
|
Future<Response> _sendMoveRequest(String item) {
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.MOVE_FILE,
|
|
item,
|
|
0,
|
|
0,
|
|
false,
|
|
options: options,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
|
|
void _setOptions(String newFile) {
|
|
options = MoveFileOptions(convertPath(newFile));
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class RenameTest extends _AbstractGetRefactoring_Test {
|
|
Future<Response> sendRenameRequest(
|
|
String search,
|
|
String? newName, {
|
|
String id = '0',
|
|
bool validateOnly = false,
|
|
}) {
|
|
var options = newName != null ? RenameOptions(newName) : null;
|
|
var request = EditGetRefactoringParams(
|
|
RefactoringKind.RENAME,
|
|
testFile.path,
|
|
findOffset(search),
|
|
0,
|
|
validateOnly,
|
|
options: options,
|
|
).toRequest(id, clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
|
|
@override
|
|
Future<void> tearDown() async {
|
|
test_simulateRefactoringReset_afterInitialConditions = false;
|
|
test_simulateRefactoringReset_afterFinalConditions = false;
|
|
test_simulateRefactoringReset_afterCreateChange = false;
|
|
await super.tearDown();
|
|
}
|
|
|
|
Future<void> test_cancelPendingRequest() async {
|
|
addTestFile('''
|
|
void f() {
|
|
int test = 0;
|
|
print(test);
|
|
}
|
|
''');
|
|
// send the "1" request, but don't wait for it
|
|
var futureA = sendRenameRequest('test =', 'nameA', id: '1');
|
|
// send the "2" request and wait for it
|
|
var responseB = await sendRenameRequest('test =', 'nameB', id: '2');
|
|
// wait for the (delayed) "1" response
|
|
var responseA = await futureA;
|
|
// "1" was cancelled
|
|
// "2" is successful
|
|
expect(
|
|
responseA,
|
|
isResponseFailure('1', RequestErrorCode.REFACTORING_REQUEST_CANCELLED),
|
|
);
|
|
expect(responseB, isResponseSuccess('2'));
|
|
}
|
|
|
|
/// We shouldn't send a cancellation for a previous refactor request that
|
|
/// errored, since it is complete.
|
|
Future<void> test_cancelPendingRequest_notForErrored_fatalError() async {
|
|
addTestFile('''
|
|
void f() {
|
|
int test = 0;
|
|
print(test);
|
|
}
|
|
''');
|
|
|
|
// Send an invalid params request.
|
|
test_simulateRefactoringException_final = true;
|
|
await sendRenameRequest('test =', 'nameB', id: '1');
|
|
test_simulateRefactoringException_final = false;
|
|
|
|
// Then send a successful request
|
|
await sendRenameRequest('test =', 'nameB', id: '2');
|
|
|
|
// Expect exactly one response for each ID.
|
|
expect(
|
|
serverChannel.responsesReceived.where((response) => response.id == '1'),
|
|
[isResponseFailure('1', RequestErrorCode.SERVER_ERROR)],
|
|
);
|
|
expect(
|
|
serverChannel.responsesReceived.where((response) => response.id == '2'),
|
|
[isResponseSuccess('2')],
|
|
);
|
|
}
|
|
|
|
/// We shouldn't send a cancellation for a previous refactor request that
|
|
/// errored, since it is complete.
|
|
Future<void> test_cancelPendingRequest_notForErrored_invalidPath() async {
|
|
addTestFile('''
|
|
void f() {
|
|
int test = 0;
|
|
print(test);
|
|
}
|
|
''');
|
|
|
|
// Send an invalid path request.
|
|
var invalidRequest = EditGetRefactoringParams(
|
|
RefactoringKind.RENAME,
|
|
'INVALID_FILE_PATH',
|
|
-1,
|
|
0,
|
|
false,
|
|
).toRequest('1', clientUriConverter: server.uriConverter);
|
|
await serverChannel.simulateRequestFromClient(invalidRequest);
|
|
// Then send a successful request
|
|
await sendRenameRequest('test =', 'nameB', id: '2');
|
|
|
|
// Expect exactly one response for each ID.
|
|
expect(
|
|
serverChannel.responsesReceived.where((response) => response.id == '1'),
|
|
[isResponseFailure('1', RequestErrorCode.INVALID_FILE_PATH_FORMAT)],
|
|
);
|
|
expect(
|
|
serverChannel.responsesReceived.where((response) => response.id == '2'),
|
|
[isResponseSuccess('2')],
|
|
);
|
|
}
|
|
|
|
Future<void> test_class() {
|
|
addTestFile('''
|
|
class Test {
|
|
Test() {}
|
|
Test.named() {}
|
|
}
|
|
void f() {
|
|
Test v;
|
|
Test();
|
|
Test.named();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test {', 'NewName');
|
|
},
|
|
'''
|
|
class NewName {
|
|
NewName() {}
|
|
NewName.named() {}
|
|
}
|
|
void f() {
|
|
NewName v;
|
|
NewName();
|
|
NewName.named();
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_fromFactoryRedirectingConstructor() {
|
|
addTestFile('''
|
|
class A {
|
|
A() = [!Test!].named;
|
|
}
|
|
class Test {
|
|
Test.named() {}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test.named;', 'NewName');
|
|
},
|
|
'''
|
|
class A {
|
|
A() = NewName.named;
|
|
}
|
|
class NewName {
|
|
NewName.named() {}
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_fromInstanceCreation() {
|
|
addTestFile('''
|
|
class Test {
|
|
Test() {}
|
|
}
|
|
void f() {
|
|
[!Test!]();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test();', 'NewName');
|
|
},
|
|
'''
|
|
class NewName {
|
|
NewName() {}
|
|
}
|
|
void f() {
|
|
NewName();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_fromInstanceCreation_namedConstructor() {
|
|
addTestFile('''
|
|
class Test {
|
|
Test.named() {}
|
|
}
|
|
void f() {
|
|
[!Test!].named();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test.named();', 'NewName');
|
|
},
|
|
'''
|
|
class NewName {
|
|
NewName.named() {}
|
|
}
|
|
void f() {
|
|
NewName.named();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_fromInstanceCreation_onNew() {
|
|
addTestFile('''
|
|
class Test {
|
|
Test() {}
|
|
}
|
|
void f() {
|
|
new [!Test!]();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('new Test();', 'NewName');
|
|
},
|
|
'''
|
|
class NewName {
|
|
NewName() {}
|
|
}
|
|
void f() {
|
|
new NewName();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_fromInstanceCreation_onNew_namedConstructor() {
|
|
addTestFile('''
|
|
class Test {
|
|
Test.named() {}
|
|
}
|
|
void f() {
|
|
new [!Test!].named();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('new Test.named();', 'NewName');
|
|
},
|
|
'''
|
|
class NewName {
|
|
NewName.named() {}
|
|
}
|
|
void f() {
|
|
new NewName.named();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_getter_in_objectPattern() {
|
|
addTestFile('''
|
|
void f(Object? x) {
|
|
if (x case A(test: 0)) {}
|
|
if (x case A(: var test)) {}
|
|
}
|
|
|
|
class A {
|
|
int get test => 0;
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test =>', 'newName');
|
|
},
|
|
'''
|
|
void f(Object? x) {
|
|
if (x case A(newName: 0)) {}
|
|
if (x case A(newName: var test)) {}
|
|
}
|
|
|
|
class A {
|
|
int get newName => 0;
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_macros() {
|
|
addTestFile('''
|
|
import 'macros.dart';
|
|
|
|
@DeclareInType(' C.named();')
|
|
class C {}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('C {', 'NewName');
|
|
},
|
|
'''
|
|
import 'macros.dart';
|
|
|
|
@DeclareInType(' C.named();')
|
|
class NewName {}
|
|
''',
|
|
changeEdits: 1,
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_method_in_objectPattern() {
|
|
addTestFile('''
|
|
void f(Object? x) {
|
|
if (x case A(test: _)) {}
|
|
if (x case A(: var test)) {}
|
|
}
|
|
|
|
class A {
|
|
void test() {}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test() {}', 'newName');
|
|
},
|
|
'''
|
|
void f(Object? x) {
|
|
if (x case A(newName: _)) {}
|
|
if (x case A(newName: var test)) {}
|
|
}
|
|
|
|
class A {
|
|
void newName() {}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_options_fatalError() {
|
|
addTestFile('''
|
|
class Test {}
|
|
void f() {
|
|
Test v;
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('Test {}', '');
|
|
}).then((result) {
|
|
assertResultProblemsFatal(
|
|
result.optionsProblems,
|
|
'Class name must not be empty.',
|
|
);
|
|
// ...there is no any change
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_class_typeParameter_atDeclaration() {
|
|
addTestFile('''
|
|
class A<Test> {
|
|
void foo(Test a) {}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test> {', 'NewName');
|
|
},
|
|
'''
|
|
class A<NewName> {
|
|
void foo(NewName a) {}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_typeParameter_atReference() {
|
|
addTestFile('''
|
|
class A<Test> {
|
|
final List<Test> values = [];
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test> values', 'NewName');
|
|
},
|
|
'''
|
|
class A<NewName> {
|
|
final List<NewName> values = [];
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_class_validateOnly() {
|
|
addTestFile('''
|
|
class Test {}
|
|
void f() {
|
|
Test v;
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('Test {}', 'NewName', validateOnly: true);
|
|
}).then((result) {
|
|
var feedback = result.feedback as RenameFeedback;
|
|
assertResultProblemsOK(result);
|
|
expect(feedback.elementKindName, 'class');
|
|
expect(feedback.oldName, 'Test');
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_class_warning() {
|
|
addTestFile('''
|
|
class Test {}
|
|
void f() {
|
|
Test v;
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('Test {}', 'newName');
|
|
})
|
|
.then((result) {
|
|
assertResultProblemsWarning(
|
|
result.optionsProblems,
|
|
'Class name should start with an uppercase letter.',
|
|
);
|
|
// ...but there is still a change
|
|
assertTestRefactoringResult(result, '''
|
|
class newName {}
|
|
void f() {
|
|
newName v;
|
|
}
|
|
''');
|
|
})
|
|
.then((_) {
|
|
// "NewName" is a perfectly valid name
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('Test {}', 'NewName');
|
|
}).then((result) {
|
|
assertResultProblemsOK(result);
|
|
// ...and there is a new change
|
|
assertTestRefactoringResult(result, '''
|
|
class NewName {}
|
|
void f() {
|
|
NewName v;
|
|
}
|
|
''');
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<void> test_classMember_field() {
|
|
addTestFile('''
|
|
class A {
|
|
var test = 0;
|
|
A(this.test);
|
|
void f() {
|
|
print(test);
|
|
}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test = 0', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
var newName = 0;
|
|
A(this.newName);
|
|
void f() {
|
|
print(newName);
|
|
}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_classMember_field_onFieldFormalParameter() {
|
|
addTestFile('''
|
|
class A {
|
|
var test = 0;
|
|
A(this.test);
|
|
void f() {
|
|
print(test);
|
|
}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test);', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
var newName = 0;
|
|
A(this.newName);
|
|
void f() {
|
|
print(newName);
|
|
}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_classMember_field_onFieldFormalParameter_named() {
|
|
addTestFile('''
|
|
class A {
|
|
final int test;
|
|
A({this.test: 0});
|
|
}
|
|
void f() {
|
|
A(test: 42);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test: 42', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
final int newName;
|
|
A({this.newName: 0});
|
|
}
|
|
void f() {
|
|
A(newName: 42);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_classMember_getter() {
|
|
addTestFile('''
|
|
class A {
|
|
get test => 0;
|
|
void f() {
|
|
print(test);
|
|
}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test =>', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
get newName => 0;
|
|
void f() {
|
|
print(newName);
|
|
}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_classMember_method() {
|
|
addTestFile('''
|
|
class A {
|
|
test() {}
|
|
void f() {
|
|
test();
|
|
}
|
|
}
|
|
void f(A a) {
|
|
a.test();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test() {}', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
newName() {}
|
|
void f() {
|
|
newName();
|
|
}
|
|
}
|
|
void f(A a) {
|
|
a.newName();
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_classMember_method_potential() {
|
|
addTestFile('''
|
|
class A {
|
|
test() {}
|
|
}
|
|
void f(A a, a2) {
|
|
a.test();
|
|
a2.test(); // a2
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('test() {}', 'newName');
|
|
}).then((result) {
|
|
assertResultProblemsOK(result);
|
|
// prepare potential edit ID
|
|
var potentialIds = result.potentialEdits!;
|
|
expect(potentialIds, hasLength(1));
|
|
var potentialId = potentialIds[0];
|
|
// find potential edit
|
|
var change = result.change!;
|
|
var potentialEdit = _findEditWithId(change, potentialId)!;
|
|
expect(potentialEdit.offset, findOffset('test(); // a2'));
|
|
expect(potentialEdit.length, 4);
|
|
});
|
|
}
|
|
|
|
Future<void> test_classMember_setter() {
|
|
addTestFile('''
|
|
class A {
|
|
set test(x) {}
|
|
void f() {
|
|
test = 0;
|
|
}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test = 0', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
set newName(x) {}
|
|
void f() {
|
|
newName = 0;
|
|
}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_constructor_fromFactoryRedirectingConstructor() {
|
|
addTestFile('''
|
|
class A {
|
|
A() = B.[!test!];
|
|
}
|
|
class B {
|
|
B.test() {}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test;', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
A() = B.newName;
|
|
}
|
|
class B {
|
|
B.newName() {}
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_constructor_fromInstanceCreation() {
|
|
addTestFile('''
|
|
class A {
|
|
A.test() {}
|
|
}
|
|
void f() {
|
|
A.[!test!]();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test();', 'newName');
|
|
},
|
|
'''
|
|
class A {
|
|
A.newName() {}
|
|
}
|
|
void f() {
|
|
A.newName();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_enum_constructor_add_toSynthetic() {
|
|
addTestFile('''
|
|
enum E {
|
|
v1, v2.[!new!]()
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('new()', 'newName');
|
|
},
|
|
'''
|
|
enum E {
|
|
v1.newName(), v2.newName();
|
|
|
|
const new newName();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_enum_constructor_change() {
|
|
addTestFile('''
|
|
enum E {
|
|
v1.test(), v2.[!test!]();
|
|
|
|
const E.test();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test();', 'newName');
|
|
},
|
|
'''
|
|
enum E {
|
|
v1.newName(), v2.newName();
|
|
|
|
const E.newName();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_enum_typeParameter_atDeclaration() {
|
|
addTestFile('''
|
|
enum E2<Test> {
|
|
v<int>();
|
|
void foo(Test a) {}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test> {', 'NewName');
|
|
},
|
|
'''
|
|
enum E2<NewName> {
|
|
v<int>();
|
|
void foo(NewName a) {}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extension_atDeclaration() {
|
|
addTestFile('''
|
|
extension [!Test!] on int {
|
|
void foo() {}
|
|
}
|
|
void f() {
|
|
Test(0).foo();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test on int', 'NewName');
|
|
},
|
|
'''
|
|
extension NewName on int {
|
|
void foo() {}
|
|
}
|
|
void f() {
|
|
NewName(0).foo();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_extension_atReference() {
|
|
addTestFile('''
|
|
extension Test on int {
|
|
void foo() {}
|
|
}
|
|
void f() {
|
|
[!Test!](0).foo();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test(0)', 'NewName');
|
|
},
|
|
'''
|
|
extension NewName on int {
|
|
void foo() {}
|
|
}
|
|
void f() {
|
|
NewName(0).foo();
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_extension_typeParameter_atDeclaration() {
|
|
addTestFile('''
|
|
extension E<Test> on int {
|
|
void foo(Test a) {}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test> on', 'NewName');
|
|
},
|
|
'''
|
|
extension E<NewName> on int {
|
|
void foo(NewName a) {}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extensionType_field_representation() {
|
|
addTestFile('''
|
|
extension type E(int test) {}
|
|
|
|
void f(E e) {
|
|
e.test;
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test) {}', 'newName');
|
|
},
|
|
'''
|
|
extension type E(int newName) {}
|
|
|
|
void f(E e) {
|
|
e.newName;
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extensionType_method() {
|
|
addTestFile('''
|
|
extension type E(int it) {
|
|
void test() {}
|
|
}
|
|
|
|
void f(E e) {
|
|
e.test();
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test() {}', 'newName');
|
|
},
|
|
'''
|
|
extension type E(int it) {
|
|
void newName() {}
|
|
}
|
|
|
|
void f(E e) {
|
|
e.newName();
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extensionType_name_end() {
|
|
addTestFile('''
|
|
extension type Test(int it) {}
|
|
|
|
void f(Test x) {}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('(int', 'NewName');
|
|
},
|
|
'''
|
|
extension type NewName(int it) {}
|
|
|
|
void f(NewName x) {}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extensionType_name_inside() {
|
|
addTestFile('''
|
|
extension type Test(int it) {}
|
|
|
|
void f(Test x) {}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('est(int', 'NewName');
|
|
},
|
|
'''
|
|
extension type NewName(int it) {}
|
|
|
|
void f(NewName x) {}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extensionType_name_start() {
|
|
addTestFile('''
|
|
extension type Test(int it) {}
|
|
|
|
void f(Test x) {}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test(int', 'NewName');
|
|
},
|
|
'''
|
|
extension type NewName(int it) {}
|
|
|
|
void f(NewName x) {}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extensionType_primaryConstructor_atDeclaration() {
|
|
addTestFile('''
|
|
extension type E.test(int it) {}
|
|
|
|
void f() {
|
|
E.test(0);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test(int', 'newName');
|
|
},
|
|
'''
|
|
extension type E.newName(int it) {}
|
|
|
|
void f() {
|
|
E.newName(0);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_extensionType_primaryConstructor_atInvocation() {
|
|
addTestFile('''
|
|
extension type E.test(int it) {}
|
|
|
|
void f() {
|
|
E.test(0);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test(0', 'newName');
|
|
},
|
|
'''
|
|
extension type E.newName(int it) {}
|
|
|
|
void f() {
|
|
E.newName(0);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_feedback() {
|
|
setPriorityFiles([testFile]);
|
|
addTestFile('''
|
|
class Test {}
|
|
void f() {
|
|
Test v;
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('st v;', 'NewName');
|
|
}).then((result) {
|
|
var feedback = result.feedback as RenameFeedback;
|
|
expect(feedback, isNotNull);
|
|
expect(feedback.offset, findOffset('Test v;'));
|
|
expect(feedback.length, 'Test'.length);
|
|
});
|
|
}
|
|
|
|
Future<void> test_formalParameter_named_ofConstructor_genericClass() {
|
|
addTestFile('''
|
|
class A<T> {
|
|
A({T test});
|
|
}
|
|
|
|
void f() {
|
|
A(test: 0);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test: 0', 'newName');
|
|
},
|
|
'''
|
|
class A<T> {
|
|
A({T newName});
|
|
}
|
|
|
|
void f() {
|
|
A(newName: 0);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_formalParameter_named_ofMethod_genericClass() {
|
|
addTestFile('''
|
|
class A<T> {
|
|
void foo({T test}) {}
|
|
}
|
|
|
|
void f(A<int> a) {
|
|
a.foo(test: 0);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test: 0', 'newName');
|
|
},
|
|
'''
|
|
class A<T> {
|
|
void foo({T newName}) {}
|
|
}
|
|
|
|
void f(A<int> a) {
|
|
a.foo(newName: 0);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_function() {
|
|
addTestFile('''
|
|
test() {}
|
|
void f() {
|
|
test();
|
|
print(test);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test() {}', 'newName');
|
|
},
|
|
'''
|
|
newName() {}
|
|
void f() {
|
|
newName();
|
|
print(newName);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_importPrefix_add() {
|
|
addTestFile('''
|
|
import 'dart:math';
|
|
import 'dart:async';
|
|
void f() {
|
|
Random r;
|
|
Future f;
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest("import 'dart:async';", 'new_name');
|
|
},
|
|
'''
|
|
import 'dart:math';
|
|
import 'dart:async' as new_name;
|
|
void f() {
|
|
Random r;
|
|
new_name.Future f;
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, -1);
|
|
expect(renameFeedback.length, 0);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_importPrefix_remove() {
|
|
addTestFile('''
|
|
import 'dart:math' as test;
|
|
import 'dart:async' as [!test!];
|
|
void f() {
|
|
test.Random r;
|
|
test.Future f;
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest("import 'dart:async' as test;", '');
|
|
},
|
|
'''
|
|
import 'dart:math' as test;
|
|
import 'dart:async';
|
|
void f() {
|
|
test.Random r;
|
|
Future f;
|
|
}
|
|
''',
|
|
feedbackValidator: (feedback) {
|
|
var renameFeedback = feedback as RenameFeedback;
|
|
expect(renameFeedback.offset, parsedSourceRange.offset);
|
|
expect(renameFeedback.length, parsedSourceRange.length);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> test_init_fatalError_noElement() {
|
|
addTestFile('// nothing to rename');
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('// nothing', null);
|
|
}).then((result) {
|
|
assertResultProblemsFatal(
|
|
result.initialProblems,
|
|
'Unable to create a refactoring',
|
|
);
|
|
// ...there is no any change
|
|
expect(result.change, isNull);
|
|
});
|
|
}
|
|
|
|
Future<void> test_library_libraryDirective() {
|
|
addTestFile('''
|
|
library aaa.bbb.ccc;
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('library aaa', 'my.new_name');
|
|
},
|
|
'''
|
|
library my.new_name;
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_library_libraryDirective_name() {
|
|
addTestFile('''
|
|
library aaa.bbb.ccc;
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('aaa', 'my.new_name');
|
|
},
|
|
'''
|
|
library my.new_name;
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_library_libraryDirective_nameDot() {
|
|
addTestFile('''
|
|
library aaa.bbb.ccc;
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('.bbb', 'my.new_name');
|
|
},
|
|
'''
|
|
library my.new_name;
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_localVariable() {
|
|
setPriorityFiles([testFile]);
|
|
addTestFile('''
|
|
void f() {
|
|
int test = 0;
|
|
test = 1;
|
|
test += 2;
|
|
print(test);
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test = 1', 'newName');
|
|
},
|
|
'''
|
|
void f() {
|
|
int newName = 0;
|
|
newName = 1;
|
|
newName += 2;
|
|
print(newName);
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_localVariable_finalCheck_shadowError() {
|
|
setPriorityFiles([testFile]);
|
|
addTestFile('''
|
|
void f() {
|
|
var newName;
|
|
int test = 0;
|
|
print(test);
|
|
}
|
|
''');
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('test = 0', 'newName');
|
|
}).then((result) {
|
|
var problems = result.finalProblems;
|
|
expect(problems, hasLength(1));
|
|
assertResultProblemsError(
|
|
problems,
|
|
"Duplicate local variable of name 'newName' at f in 'test.dart'.",
|
|
);
|
|
});
|
|
}
|
|
|
|
Future<void> test_mixin_typeParameter_atDeclaration() {
|
|
addTestFile('''
|
|
mixin M<Test> {
|
|
final List<Test> values = [];
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('Test> {', 'NewName');
|
|
},
|
|
'''
|
|
mixin M<NewName> {
|
|
final List<NewName> values = [];
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_parameter_onDefaultParameter() {
|
|
addTestFile('''
|
|
class A {
|
|
final int test;
|
|
A({int t = 0}) : test = t;
|
|
}
|
|
void f() {
|
|
A(t: 42);
|
|
}
|
|
''');
|
|
|
|
return getRefactoringResult(() {
|
|
return sendRenameRequest('t: 42', '_new');
|
|
}).then((result) {
|
|
var problems = result.finalProblems;
|
|
expect(problems, hasLength(1));
|
|
assertResultProblemsError(
|
|
problems,
|
|
"The parameter 't' is named and can not be private.",
|
|
);
|
|
});
|
|
}
|
|
|
|
Future<void> test_patternVariable_ifCase() {
|
|
addTestFile('''
|
|
void f(Object? x) {
|
|
if (x case int test) {
|
|
test;
|
|
test = 1;
|
|
test += 2;
|
|
}
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test) {', 'newName');
|
|
},
|
|
'''
|
|
void f(Object? x) {
|
|
if (x case int newName) {
|
|
newName;
|
|
newName = 1;
|
|
newName += 2;
|
|
}
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_patternVariable_patternAssignment() {
|
|
setPriorityFiles([testFile]);
|
|
addTestFile('''
|
|
void f(Object? x) {
|
|
int test;
|
|
(test, _) = (0, 1);
|
|
test;
|
|
}
|
|
''');
|
|
return assertSuccessfulRefactoring(
|
|
() {
|
|
return sendRenameRequest('test,', 'newName');
|
|
},
|
|
'''
|
|
void f(Object? x) {
|
|
int newName;
|
|
(newName, _) = (0, 1);
|
|
newName;
|
|
}
|
|
''',
|
|
);
|
|
}
|
|
|
|
Future<void> test_reset_afterCreateChange() {
|
|
test_simulateRefactoringReset_afterCreateChange = true;
|
|
addTestFile('''
|
|
test() {}
|
|
void f() {
|
|
test();
|
|
}
|
|
''');
|
|
return waitForTasksFinished().then((_) {
|
|
return sendRenameRequest('test() {}', 'newName').then((response) {
|
|
_expectRefactoringRequestCancelled(response);
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<void> test_reset_afterFinalConditions() {
|
|
test_simulateRefactoringReset_afterFinalConditions = true;
|
|
addTestFile('''
|
|
test() {}
|
|
void f() {
|
|
test();
|
|
}
|
|
''');
|
|
return waitForTasksFinished().then((_) {
|
|
return sendRenameRequest('test() {}', 'newName').then((response) {
|
|
_expectRefactoringRequestCancelled(response);
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<void> test_reset_afterInitialConditions() {
|
|
test_simulateRefactoringReset_afterInitialConditions = true;
|
|
addTestFile('''
|
|
test() {}
|
|
void f() {
|
|
test();
|
|
}
|
|
''');
|
|
return waitForTasksFinished().then((_) {
|
|
return sendRenameRequest('test() {}', 'newName').then((response) {
|
|
_expectRefactoringRequestCancelled(response);
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<void> test_resetOnAnalysis() async {
|
|
addTestFile('''
|
|
void f() {
|
|
int initialName = 0;
|
|
print(initialName);
|
|
}
|
|
''');
|
|
// send the first request
|
|
var result = await getRefactoringResult(() {
|
|
return sendRenameRequest('initialName =', 'newName', validateOnly: true);
|
|
});
|
|
_validateFeedback(result, oldName: 'initialName');
|
|
// update the file
|
|
modifyTestFile('''
|
|
void f() {
|
|
int otherName = 0;
|
|
print(otherName);
|
|
}
|
|
''');
|
|
unawaited(
|
|
server.getAnalysisDriver(testFile.path)!.getResolvedUnit(testFile.path),
|
|
);
|
|
// send the second request, with the same kind, file and offset
|
|
await waitForTasksFinished();
|
|
result = await getRefactoringResult(() {
|
|
return sendRenameRequest('otherName =', 'newName', validateOnly: true);
|
|
});
|
|
// the refactoring was reset, so we don't get a stale result
|
|
_validateFeedback(result, oldName: 'otherName');
|
|
}
|
|
|
|
void _expectRefactoringRequestCancelled(Response response) {
|
|
expect(response.error, isNotNull);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.REFACTORING_REQUEST_CANCELLED),
|
|
);
|
|
}
|
|
|
|
SourceEdit? _findEditWithId(SourceChange change, String id) {
|
|
SourceEdit? potentialEdit;
|
|
for (var fileEdit in change.edits) {
|
|
for (var edit in fileEdit.edits) {
|
|
if (edit.id == id) {
|
|
potentialEdit = edit;
|
|
}
|
|
}
|
|
}
|
|
return potentialEdit;
|
|
}
|
|
|
|
void _validateFeedback(EditGetRefactoringResult result, {String? oldName}) {
|
|
var feedback = result.feedback as RenameFeedback;
|
|
if (oldName != null) {
|
|
expect(feedback.oldName, oldName);
|
|
}
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class _AbstractGetRefactoring_Test extends PubPackageAnalysisServerTest {
|
|
bool shouldWaitForFullAnalysis = true;
|
|
|
|
Future<void> assertEmptySuccessfulRefactoring(
|
|
Future<Response> Function() requestSender, {
|
|
void Function(RefactoringFeedback?)? feedbackValidator,
|
|
}) async {
|
|
var result = await getRefactoringResult(requestSender);
|
|
assertResultProblemsOK(result);
|
|
if (feedbackValidator != null) {
|
|
feedbackValidator(result.feedback);
|
|
}
|
|
assertNoTestRefactoringResult(result);
|
|
}
|
|
|
|
/// Asserts that the given [EditGetRefactoringResult] does not have a change
|
|
/// for [testFile].
|
|
void assertNoTestRefactoringResult(EditGetRefactoringResult result) {
|
|
var change = result.change!;
|
|
if (change.edits.any((edit) => edit.file == testFile.path)) {
|
|
fail('Found a SourceFileEdit for $testFile in $change');
|
|
}
|
|
}
|
|
|
|
/// Asserts that [problems] has a single ERROR problem.
|
|
void assertResultProblemsError(
|
|
List<RefactoringProblem> problems, [
|
|
String? message,
|
|
]) {
|
|
var problem = problems[0];
|
|
expect(
|
|
problem.severity,
|
|
RefactoringProblemSeverity.ERROR,
|
|
reason: problem.toString(),
|
|
);
|
|
if (message != null) {
|
|
expect(problem.message, message);
|
|
}
|
|
}
|
|
|
|
/// Asserts that [result] has a single FATAL problem.
|
|
void assertResultProblemsFatal(
|
|
List<RefactoringProblem> problems, [
|
|
String? message,
|
|
]) {
|
|
var problem = problems[0];
|
|
expect(problems, hasLength(1));
|
|
expect(
|
|
problem.severity,
|
|
RefactoringProblemSeverity.FATAL,
|
|
reason: problem.toString(),
|
|
);
|
|
if (message != null) {
|
|
expect(problem.message, message);
|
|
}
|
|
}
|
|
|
|
/// Asserts that [result] has no problems at all.
|
|
void assertResultProblemsOK(EditGetRefactoringResult result) {
|
|
expect(result.initialProblems, isEmpty);
|
|
expect(result.optionsProblems, isEmpty);
|
|
expect(result.finalProblems, isEmpty);
|
|
}
|
|
|
|
/// Asserts that [result] has a single WARNING problem.
|
|
void assertResultProblemsWarning(
|
|
List<RefactoringProblem> problems, [
|
|
String? message,
|
|
]) {
|
|
var problem = problems[0];
|
|
expect(problems, hasLength(1));
|
|
expect(
|
|
problem.severity,
|
|
RefactoringProblemSeverity.WARNING,
|
|
reason: problem.toString(),
|
|
);
|
|
if (message != null) {
|
|
expect(problem.message, message);
|
|
}
|
|
}
|
|
|
|
Future<void> assertSuccessfulRefactoring(
|
|
Future<Response> Function() requestSender,
|
|
String expectedCode, {
|
|
void Function(RefactoringFeedback?)? feedbackValidator,
|
|
int changeEdits = 0,
|
|
}) async {
|
|
var result = await getRefactoringResult(requestSender);
|
|
assertResultProblemsOK(result);
|
|
if (feedbackValidator != null) {
|
|
feedbackValidator(result.feedback);
|
|
}
|
|
assertTestRefactoringResult(result, expectedCode, changeEdits: changeEdits);
|
|
}
|
|
|
|
/// Asserts that the given [EditGetRefactoringResult] has a [testFile] change
|
|
/// which results in the [expectedCode].
|
|
void assertTestRefactoringResult(
|
|
EditGetRefactoringResult result,
|
|
String expectedCode, {
|
|
int changeEdits = 0,
|
|
}) {
|
|
expectedCode = normalizeNewlinesForPlatform(expectedCode);
|
|
var change = result.change!;
|
|
if (changeEdits != 0) {
|
|
expect(change.edits.length, changeEdits);
|
|
}
|
|
for (var fileEdit in change.edits) {
|
|
if (fileEdit.file == testFile.path) {
|
|
var actualCode = SourceEdit.applySequence(
|
|
testFileContent,
|
|
fileEdit.edits,
|
|
);
|
|
expect(actualCode, expectedCode);
|
|
return;
|
|
}
|
|
}
|
|
fail('No SourceFileEdit for $testFile in $change');
|
|
}
|
|
|
|
Future<EditGetRefactoringResult> getRefactoringResult(
|
|
Future<Response> Function() requestSender,
|
|
) async {
|
|
if (shouldWaitForFullAnalysis) {
|
|
await waitForTasksFinished();
|
|
}
|
|
var response = await requestSender();
|
|
return EditGetRefactoringResult.fromResponse(
|
|
response,
|
|
clientUriConverter: server.uriConverter,
|
|
);
|
|
}
|
|
|
|
Future<Response> sendRequest(
|
|
RefactoringKind kind,
|
|
int offset,
|
|
int length,
|
|
RefactoringOptions? options, [
|
|
bool validateOnly = false,
|
|
]) {
|
|
var request = EditGetRefactoringParams(
|
|
kind,
|
|
testFile.path,
|
|
offset,
|
|
length,
|
|
validateOnly,
|
|
options: options,
|
|
).toRequest('0', clientUriConverter: server.uriConverter);
|
|
return serverChannel.simulateRequestFromClient(request);
|
|
}
|
|
|
|
@override
|
|
Future<void> setUp() async {
|
|
super.setUp();
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
}
|
|
}
|