| // 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 'package:analysis_server/protocol/protocol.dart'; |
| import 'package:analysis_server/protocol/protocol_generated.dart'; |
| import 'package:analysis_server/src/edit/edit_domain.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_abstract.dart'; |
| import '../mocks.dart'; |
| import '../src/utilities/mock_packages.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(ConvertGetterMethodToMethodTest); |
| defineReflectiveTests(ConvertMethodToGetterTest); |
| defineReflectiveTests(ExtractLocalVariableTest); |
| defineReflectiveTests(ExtractMethodTest); |
| defineReflectiveTests(GetAvailableRefactoringsTest); |
| defineReflectiveTests(InlineLocalTest); |
| defineReflectiveTests(InlineMethodTest); |
| defineReflectiveTests(MoveFileTest); |
| defineReflectiveTests(RenameTest); |
| }); |
| } |
| |
| /// Wrapper around the test package's `fail` function. |
| /// |
| /// Unlike the test package's `fail` function, this function is not annotated |
| /// with @alwaysThrows, so we can call it at the top of a test method without |
| /// causing the rest of the method to be flagged as dead code. |
| void _fail(String message) { |
| fail(message); |
| } |
| |
| @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, |
| findOffset(search), |
| 0, |
| false) |
| .toRequest('0'); |
| return serverChannel.sendRequest(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, |
| findOffset(search), |
| 0, |
| false) |
| .toRequest('0'); |
| return serverChannel.sendRequest(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, false); |
| } |
| |
| 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 |
| void tearDown() { |
| test_simulateRefactoringException_init = false; |
| test_simulateRefactoringException_final = false; |
| test_simulateRefactoringException_change = false; |
| super.tearDown(); |
| } |
| |
| Future<void> test_analysis_onlyOneFile() async { |
| shouldWaitForFullAnalysis = false; |
| newFile(join(testFolder, 'other.dart'), content: 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(testCode.indexOf('222 +'), 0, 'res', true); |
| }).then((result) { |
| var feedback = result.feedback as ExtractLocalVariableFeedback; |
| expect(feedback.coveringExpressionOffsets, [ |
| testCode.indexOf('222 +'), |
| testCode.indexOf('111 +'), |
| testCode.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'); |
| var response = await waitResponse(request); |
| expect( |
| response, |
| isResponseFailure('0', 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'); |
| var response = await waitResponse(request); |
| expect( |
| response, |
| isResponseFailure('0', 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: AddContentOverlay(''' |
| void f() { |
| print(1 + 2); // 1 |
| } |
| ''') |
| }); |
| }); |
| |
| await checkUpdate(() { |
| server.updateContent('u2', { |
| testFile: ChangeContentOverlay([ |
| SourceEdit(0, 0, ''' |
| void f() { |
| print(1 + 2); // 2 |
| } |
| ''') |
| ]) |
| }); |
| }); |
| |
| await checkUpdate(() { |
| server.updateContent('u3', {testFile: RemoveContentOverlay()}); |
| }); |
| } |
| |
| Future<void> test_resetOnAnalysisSetChanged_watch_otherFile() async { |
| var otherFile = join(testFolder, 'other.dart'); |
| newFile(otherFile, content: '// 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, content: '// 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); |
| } |
| '''); |
| _setOffsetLengthForString('1 + 2'); |
| 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); |
| } |
| '''); |
| _setOffsetLengthForString('a + b'); |
| 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); |
| } |
| '''); |
| _setOffsetLengthForString('a + b'); |
| 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) { |
| // start |
| if (b) { |
| print(1); |
| // end |
| print(2); |
| } |
| } |
| '''); |
| _setOffsetLengthForStartEnd(); |
| return waitForTasksFinished().then((_) { |
| return _sendExtractRequest(); |
| }).then((Response response) { |
| var result = EditGetRefactoringResult.fromResponse(response); |
| assertResultProblemsFatal(result.initialProblems); |
| // ...there is no any feedback |
| expect(result.feedback, isNull); |
| }); |
| } |
| |
| Future<void> test_long_expression() { |
| addTestFile(''' |
| void f() { |
| print(1 + |
| 2); |
| } |
| '''); |
| _setOffsetLengthForString('1 +\n 2'); |
| 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( ); |
| } |
| '''); |
| _setOffsetLengthForString('getSelectedItem( )'); |
| 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; |
| } |
| '''); |
| _setOffsetLengthForString('1 + 2'); |
| 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; |
| // start |
| print(a + b); |
| // end |
| print(a + b); |
| } |
| '''); |
| _setOffsetLengthForStartEnd(); |
| return assertSuccessfulRefactoring(_computeChange, ''' |
| void f() { |
| int a = 1; |
| int b = 2; |
| // start |
| res(a, b); |
| // end |
| res(a, b); |
| } |
| |
| void res(int a, int b) { |
| print(a + b); |
| } |
| '''); |
| } |
| |
| Future<void> test_statements_nullableReturnType() { |
| addTestFile(''' |
| void foo(int b) { |
| // start |
| int? x; |
| if (b < 2) { |
| x = 42; |
| } |
| if (b >= 2) { |
| x = 43; |
| } |
| // end |
| print(x!); |
| } |
| '''); |
| _setOffsetLengthForStartEnd(); |
| return assertSuccessfulRefactoring(_computeChange, ''' |
| void foo(int b) { |
| // start |
| int? x = res(b); |
| // end |
| 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); |
| return result.feedback as ExtractMethodFeedback; |
| } |
| |
| Future _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, false); |
| } |
| |
| void _setOffsetLengthForStartEnd() { |
| offset = findOffset('// start') + '// start\n'.length; |
| length = findOffset('// end') - offset; |
| } |
| |
| void _setOffsetLengthForString(String search) { |
| offset = findOffset(search); |
| length = search.length; |
| } |
| } |
| |
| @reflectiveTest |
| class GetAvailableRefactoringsTest extends AbstractAnalysisTest { |
| late List<RefactoringKind> kinds; |
| |
| void addFlutterPackage() { |
| var libFolder = MockPackages.instance.addFlutter(resourceProvider); |
| // Create .packages in the project. |
| newFile(join(projectPath, '.packages'), content: ''' |
| flutter:${libFolder.toUri()} |
| '''); |
| } |
| |
| /// Tests that there is refactoring of the given [kind] is available at the |
| /// [search] offset. |
| Future 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 assertHasRenameRefactoring(String code, String search) async { |
| return assertHasKind(code, search, RefactoringKind.RENAME, true); |
| } |
| |
| /// Returns the list of available refactorings for the given [offset] and |
| /// [length]. |
| Future getRefactorings(int offset, int length) async { |
| var request = EditGetAvailableRefactoringsParams(testFile, offset, length) |
| .toRequest('0'); |
| serverChannel.sendRequest(request); |
| var response = await serverChannel.waitForResponse(request); |
| var result = EditGetAvailableRefactoringsResult.fromResponse(response); |
| kinds = result.kinds; |
| } |
| |
| /// Returns the list of available refactorings at the offset of [search]. |
| Future getRefactoringsAtString(String search) { |
| var offset = findOffset(search); |
| return getRefactorings(offset, 0); |
| } |
| |
| Future getRefactoringsForString(String search) { |
| var offset = findOffset(search); |
| return getRefactorings(offset, search.length); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| createProject(); |
| handler = EditDomainHandler(server); |
| server.handlers = [handler]; |
| } |
| |
| Future test_convertMethodToGetter_hasElement() { |
| return assertHasKind(''' |
| int getValue() => 42; |
| ''', 'getValue', RefactoringKind.CONVERT_METHOD_TO_GETTER, true); |
| } |
| |
| Future 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 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 test_extractWidget() async { |
| addFlutterPackage(); |
| addTestFile(''' |
| import 'package:flutter/material.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return new Text('AAA'); |
| } |
| } |
| '''); |
| await waitForTasksFinished(); |
| await getRefactoringsForString('new Text'); |
| expect(kinds, contains(RefactoringKind.EXTRACT_WIDGET)); |
| } |
| |
| Future<void> test_invalidFilePathFormat_notAbsolute() async { |
| var request = |
| EditGetAvailableRefactoringsParams('test.dart', 0, 0).toRequest('0'); |
| var response = await waitResponse(request); |
| expect( |
| response, |
| isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT), |
| ); |
| } |
| |
| Future<void> test_invalidFilePathFormat_notNormalized() async { |
| var request = EditGetAvailableRefactoringsParams( |
| convertPath('/foo/../bar/test.dart'), 0, 0) |
| .toRequest('0'); |
| var response = await waitResponse(request); |
| expect( |
| response, |
| isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT), |
| ); |
| } |
| |
| Future test_rename_hasElement_class() { |
| return assertHasRenameRefactoring(''' |
| class Test {} |
| void f() { |
| Test v; |
| } |
| ''', 'Test v'); |
| } |
| |
| Future test_rename_hasElement_constructor() { |
| return assertHasRenameRefactoring(''' |
| class A { |
| A.test() {} |
| } |
| void f() { |
| new A.test(); |
| } |
| ''', 'test();'); |
| } |
| |
| Future test_rename_hasElement_function() { |
| return assertHasRenameRefactoring(''' |
| void f() { |
| test(); |
| } |
| test() {} |
| ''', 'test();'); |
| } |
| |
| Future test_rename_hasElement_importElement_directive() { |
| return assertHasRenameRefactoring(''' |
| import 'dart:math' as math; |
| void f() { |
| math.PI; |
| } |
| ''', 'import '); |
| } |
| |
| Future test_rename_hasElement_importElement_prefixDecl() { |
| return assertHasRenameRefactoring(''' |
| import 'dart:math' as math; |
| void f() { |
| math.PI; |
| } |
| ''', 'math;'); |
| } |
| |
| Future test_rename_hasElement_importElement_prefixRef() { |
| return assertHasRenameRefactoring(''' |
| import 'dart:async' as test; |
| import 'dart:math' as test; |
| void f() { |
| test.pi; |
| } |
| ''', 'test.pi;'); |
| } |
| |
| Future test_rename_hasElement_instanceGetter() { |
| return assertHasRenameRefactoring(''' |
| class A { |
| get test => 0; |
| } |
| void f(A a) { |
| a.test; |
| } |
| ''', 'test;'); |
| } |
| |
| Future test_rename_hasElement_instanceSetter() { |
| return assertHasRenameRefactoring(''' |
| class A { |
| set test(x) {} |
| } |
| void f(A a) { |
| a.test = 2; |
| } |
| ''', 'test = 2;'); |
| } |
| |
| Future test_rename_hasElement_library() { |
| return assertHasRenameRefactoring(''' |
| library my.lib; |
| ''', 'library '); |
| } |
| |
| Future test_rename_hasElement_localVariable() { |
| return assertHasRenameRefactoring(''' |
| void f() { |
| int test = 0; |
| print(test); |
| } |
| ''', 'test = 0;'); |
| } |
| |
| Future test_rename_hasElement_method() { |
| return assertHasRenameRefactoring(''' |
| class A { |
| test() {} |
| } |
| void f(A a) { |
| a.test(); |
| } |
| ''', 'test();'); |
| } |
| |
| Future 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; |
| var otherFile = join(testFolder, 'other.dart'); |
| newFile(otherFile, content: r''' |
| foo(int p) {} |
| '''); |
| addTestFile(''' |
| import 'other.dart'; |
| void f() { |
| int res = 1 + 2; |
| foo(res); |
| foo(res); |
| } |
| '''); |
| // Start refactoring. |
| 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() { |
| 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() { |
| 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(join(testFolder, 'other.dart'), content: '// 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, |
| findOffset(search), |
| 0, |
| false) |
| .toRequest('0'); |
| return serverChannel.sendRequest(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_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, findOffset(search), 0, false, |
| options: options) |
| .toRequest('0'); |
| return serverChannel.sendRequest(request); |
| } |
| } |
| |
| @reflectiveTest |
| class MoveFileTest extends _AbstractGetRefactoring_Test { |
| late MoveFileOptions options; |
| |
| @failingTest |
| Future<void> test_OK() { |
| _fail('The move file refactoring is not supported under the new driver'); |
| newFile('/project/bin/lib.dart'); |
| addTestFile(''' |
| import 'dart:math'; |
| import 'lib.dart'; |
| '''); |
| _setOptions('/project/test.dart'); |
| return assertSuccessfulRefactoring(() { |
| return _sendMoveRequest(); |
| }, ''' |
| import 'dart:math'; |
| import 'bin/lib.dart'; |
| '''); |
| } |
| |
| Future<Response> _sendMoveRequest() { |
| var request = EditGetRefactoringParams( |
| RefactoringKind.MOVE_FILE, testFile, 0, 0, false, |
| options: options) |
| .toRequest('0'); |
| return serverChannel.sendRequest(request); |
| } |
| |
| void _setOptions(String newFile) { |
| options = MoveFileOptions(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, |
| findOffset(search), 0, validateOnly, |
| options: options) |
| .toRequest(id); |
| return serverChannel.sendRequest(request); |
| } |
| |
| @override |
| void tearDown() { |
| test_simulateRefactoringReset_afterInitialConditions = false; |
| test_simulateRefactoringReset_afterFinalConditions = false; |
| test_simulateRefactoringReset_afterCreateChange = false; |
| 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')); |
| } |
| |
| Future<void> test_class() { |
| addTestFile(''' |
| class Test { |
| Test() {} |
| Test.named() {} |
| } |
| void f() { |
| Test v; |
| new Test(); |
| new Test.named(); |
| } |
| '''); |
| return assertSuccessfulRefactoring(() { |
| return sendRenameRequest('Test {', 'NewName'); |
| }, ''' |
| class NewName { |
| NewName() {} |
| NewName.named() {} |
| } |
| void f() { |
| NewName v; |
| new NewName(); |
| new 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, 18); |
| expect(renameFeedback.length, 4); |
| }, |
| ); |
| } |
| |
| Future<void> test_class_fromInstanceCreation() { |
| addTestFile(''' |
| class Test { |
| Test() {} |
| } |
| void f() { |
| new Test(); |
| } |
| '''); |
| return assertSuccessfulRefactoring( |
| () { |
| return sendRenameRequest('Test();', 'NewName'); |
| }, |
| ''' |
| class NewName { |
| NewName() {} |
| } |
| void f() { |
| new NewName(); |
| } |
| ''', |
| feedbackValidator: (feedback) { |
| var renameFeedback = feedback as RenameFeedback; |
| expect(renameFeedback.offset, 44); |
| expect(renameFeedback.length, 4); |
| }, |
| ); |
| } |
| |
| Future<void> test_class_fromInstanceCreation_namedConstructor() { |
| addTestFile(''' |
| class Test { |
| Test.named() {} |
| } |
| void f() { |
| new Test.named(); |
| } |
| '''); |
| return assertSuccessfulRefactoring( |
| () { |
| return sendRenameRequest('Test.named();', 'NewName'); |
| }, |
| ''' |
| class NewName { |
| NewName.named() {} |
| } |
| void f() { |
| new NewName.named(); |
| } |
| ''', |
| feedbackValidator: (feedback) { |
| var renameFeedback = feedback as RenameFeedback; |
| expect(renameFeedback.offset, 50); |
| expect(renameFeedback.length, 4); |
| }, |
| ); |
| } |
| |
| 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, 44); |
| expect(renameFeedback.length, 4); |
| }, |
| ); |
| } |
| |
| 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, 50); |
| expect(renameFeedback.length, 4); |
| }, |
| ); |
| } |
| |
| 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_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() { |
| new A(test: 42); |
| } |
| '''); |
| return assertSuccessfulRefactoring(() { |
| return sendRenameRequest('test: 42', 'newName'); |
| }, ''' |
| class A { |
| final int newName; |
| A({this.newName: 0}); |
| } |
| void f() { |
| new 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, 20); |
| expect(renameFeedback.length, 4); |
| }, |
| ); |
| } |
| |
| Future<void> test_constructor_fromInstanceCreation() { |
| addTestFile(''' |
| class A { |
| A.test() {} |
| } |
| void f() { |
| new A.test(); |
| } |
| '''); |
| return assertSuccessfulRefactoring( |
| () { |
| return sendRenameRequest('test();', 'newName'); |
| }, |
| ''' |
| class A { |
| A.newName() {} |
| } |
| void f() { |
| new A.newName(); |
| } |
| ''', |
| feedbackValidator: (feedback) { |
| var renameFeedback = feedback as RenameFeedback; |
| expect(renameFeedback.offset, 45); |
| expect(renameFeedback.length, 4); |
| }, |
| ); |
| } |
| |
| Future<void> test_feedback() { |
| 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, 51); |
| expect(renameFeedback.length, 4); |
| }); |
| } |
| |
| 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_library_partOfDirective() { |
| newFile(join(testFolder, 'my_lib.dart'), content: ''' |
| library aaa.bbb.ccc; |
| part 'test.dart'; |
| '''); |
| addTestFile(''' |
| part of aaa.bbb.ccc; |
| '''); |
| return assertSuccessfulRefactoring(() { |
| return sendRenameRequest('aaa.bb', 'my.new_name'); |
| }, ''' |
| part of my.new_name; |
| '''); |
| } |
| |
| Future<void> test_localVariable() { |
| 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() { |
| 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 '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); |
| } |
| '''); |
| server.getAnalysisDriver(testFile)!.getResult2(testFile); |
| // 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; |
| change.edits.forEach((fileEdit) { |
| fileEdit.edits.forEach((edit) { |
| 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 AbstractAnalysisTest { |
| bool shouldWaitForFullAnalysis = true; |
| |
| /// 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 assertSuccessfulRefactoring( |
| Future<Response> Function() requestSender, String expectedCode, |
| {void Function(RefactoringFeedback?)? feedbackValidator}) async { |
| var result = await getRefactoringResult(requestSender); |
| assertResultProblemsOK(result); |
| if (feedbackValidator != null) { |
| feedbackValidator(result.feedback); |
| } |
| assertTestRefactoringResult(result, expectedCode); |
| } |
| |
| /// Asserts that the given [EditGetRefactoringResult] has a [testFile] change |
| /// which results in the [expectedCode]. |
| void assertTestRefactoringResult( |
| EditGetRefactoringResult result, String expectedCode) { |
| var change = result.change!; |
| for (var fileEdit in change.edits) { |
| if (fileEdit.file == testFile) { |
| var actualCode = SourceEdit.applySequence(testCode, 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); |
| } |
| |
| Future<Response> sendRequest( |
| RefactoringKind kind, int offset, int length, RefactoringOptions? options, |
| [bool validateOnly = false]) { |
| var request = EditGetRefactoringParams( |
| kind, testFile, offset, length, validateOnly, |
| options: options) |
| .toRequest('0'); |
| return serverChannel.sendRequest(request); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| createProject(); |
| handler = EditDomainHandler(server); |
| server.handlers = [handler]; |
| } |
| } |