| // 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; | |
| @override | |
| bool get addFlutterPackageDep => 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 { | |
| 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: []); | |
| } | |
| } |