blob: b696ea80c78562ec589c0a306eef55e165e399d4 [file] [log] [blame]
// 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/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/flutter_util.dart' as flutter;
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 {
test_function() {
addTestFile('''
int get test => 42;
main() {
var a = 1 + test;
var b = 2 + test;
}
''');
return assertSuccessfulRefactoring(() {
return _sendConvertRequest('test =>');
}, '''
int test() => 42;
main() {
var a = 1 + test();
var b = 2 + test();
}
''');
}
test_init_fatalError_notExplicit() {
addTestFile('''
int test = 42;
main() {
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);
});
}
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;
}
main(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;
}
main(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) {
Request request = new EditGetRefactoringParams(
RefactoringKind.CONVERT_GETTER_TO_METHOD,
testFile,
findOffset(search),
0,
false)
.toRequest('0');
return serverChannel.sendRequest(request);
}
}
@reflectiveTest
class ConvertMethodToGetterTest extends _AbstractGetRefactoring_Test {
test_function() {
addTestFile('''
int test() => 42;
main() {
var a = 1 + test();
var b = 2 + test();
}
''');
return assertSuccessfulRefactoring(() {
return _sendConvertRequest('test() =>');
}, '''
int get test => 42;
main() {
var a = 1 + test;
var b = 2 + test;
}
''');
}
test_init_fatalError_hasParameters() {
addTestFile('''
int test(p) => p + 1;
main() {
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);
});
}
test_init_fatalError_notExecutableElement() {
addTestFile('''
main() {
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);
});
}
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;
}
main(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;
}
main(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) {
Request request = new 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) {
RefactoringKind kind = RefactoringKind.EXTRACT_LOCAL_VARIABLE;
ExtractLocalVariableOptions options =
name != null ? new ExtractLocalVariableOptions(name, extractAll) : null;
return sendRequest(kind, offset, length, options, false);
}
Future<Response> sendStringRequest(
String search, String name, bool extractAll) {
int offset = findOffset(search);
int length = search.length;
return sendExtractRequest(offset, length, name, extractAll);
}
Future<Response> sendStringSuffixRequest(
String search, String suffix, String name, bool extractAll) {
int offset = findOffset(search + suffix);
int length = search.length;
return sendExtractRequest(offset, length, name, extractAll);
}
void tearDown() {
test_simulateRefactoringException_init = false;
test_simulateRefactoringException_final = false;
test_simulateRefactoringException_change = false;
super.tearDown();
}
test_analysis_onlyOneFile() async {
shouldWaitForFullAnalysis = false;
newFile(join(testFolder, 'other.dart'), content: r'''
foo(int myName) {}
''');
addTestFile('''
import 'other.dart';
main() {
foo(1 + 2);
}
''');
// Start refactoring.
EditGetRefactoringResult result = await getRefactoringResult(() {
return sendStringRequest('1 + 2', 'res', true);
});
// We get the refactoring feedback....
ExtractLocalVariableFeedback feedback = result.feedback;
expect(feedback.names, contains('myName'));
}
test_coveringExpressions() {
addTestFile('''
main() {
var v = 111 + 222 + 333;
}
''');
return getRefactoringResult(() {
return sendExtractRequest(testCode.indexOf('222 +'), 0, 'res', true);
}).then((result) {
ExtractLocalVariableFeedback feedback = result.feedback;
expect(feedback.coveringExpressionOffsets, [
testCode.indexOf('222 +'),
testCode.indexOf('111 +'),
testCode.indexOf('111 +')
]);
expect(feedback.coveringExpressionLengths,
['222'.length, '111 + 222'.length, '111 + 222 + 333'.length]);
});
}
test_extractAll() {
addTestFile('''
main() {
print(1 + 2);
print(1 + 2);
}
''');
return assertSuccessfulRefactoring(() {
return sendStringRequest('1 + 2', 'res', true);
}, '''
main() {
var res = 1 + 2;
print(res);
print(res);
}
''');
}
test_extractOne() {
addTestFile('''
main() {
print(1 + 2);
print(1 + 2); // marker
}
''');
return assertSuccessfulRefactoring(() {
return sendStringSuffixRequest('1 + 2', '); // marker', 'res', false);
}, '''
main() {
print(1 + 2);
var res = 1 + 2;
print(res); // marker
}
''');
}
test_names() async {
addTestFile('''
class TreeItem {}
TreeItem getSelectedItem() => null;
main() {
var a = getSelectedItem();
}
''');
EditGetRefactoringResult result = await getRefactoringResult(() {
return sendStringSuffixRequest('getSelectedItem()', ';', null, true);
});
ExtractLocalVariableFeedback feedback = result.feedback;
expect(
feedback.names, unorderedEquals(['treeItem', 'item', 'selectedItem']));
expect(result.change, isNull);
}
test_nameWarning() async {
addTestFile('''
main() {
print(1 + 2);
}
''');
EditGetRefactoringResult 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, '''
main() {
var Name = 1 + 2;
print(Name);
}
''');
}
test_offsetsLengths() {
addTestFile('''
main() {
print(1 + 2);
print(1 + 2);
}
''');
return getRefactoringResult(() {
return sendStringRequest('1 + 2', 'res', true);
}).then((result) {
ExtractLocalVariableFeedback feedback = result.feedback;
expect(feedback.offsets, [findOffset('1 + 2'), findOffset('1 + 2')]);
expect(feedback.lengths, [5, 6]);
});
}
test_resetOnAnalysisSetChanged_overlay() async {
addTestFile('''
main() {
print(1 + 2); // 0
}
''');
Future<void> checkUpdate(doUpdate()) async {
await getRefactoringResult(() {
return sendStringRequest('1 + 2', 'res', true);
});
int initialResetCount = test_resetCount;
doUpdate();
await pumpEventQueue();
expect(test_resetCount, initialResetCount + 1);
}
await checkUpdate(() {
server.updateContent('u1', {
testFile: new AddContentOverlay('''
main() {
print(1 + 2); // 1
}
''')
});
});
await checkUpdate(() {
server.updateContent('u2', {
testFile: new ChangeContentOverlay([
new SourceEdit(0, 0, '''
main() {
print(1 + 2); // 2
}
''')
])
});
});
await checkUpdate(() {
server.updateContent('u3', {testFile: new RemoveContentOverlay()});
});
}
test_resetOnAnalysisSetChanged_watch_otherFile() async {
String otherFile = join(testFolder, 'other.dart');
newFile(otherFile, content: '// other 1');
addTestFile('''
main() {
foo(1 + 2);
}
foo(int myName) {}
''');
// Send the first request.
{
EditGetRefactoringResult result = await getRefactoringResult(() {
return sendStringRequest('1 + 2', 'res', true);
});
ExtractLocalVariableFeedback feedback = result.feedback;
expect(feedback.names, contains('myName'));
}
int 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);
}
test_resetOnAnalysisSetChanged_watch_thisFile() async {
addTestFile('''
main() {
foo(1 + 2);
}
foo(int myName) {}
''');
// Send the first request.
{
EditGetRefactoringResult result = await getRefactoringResult(() {
return sendStringRequest('1 + 2', 'res', true);
});
ExtractLocalVariableFeedback feedback = result.feedback;
expect(feedback.names, contains('myName'));
}
int initialResetCount = test_resetCount;
// Update the test.dart file.
modifyTestFile('''
main() {
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.
{
EditGetRefactoringResult result = await getRefactoringResult(() {
return sendStringRequest('1 + 2', 'res', true);
});
ExtractLocalVariableFeedback feedback = result.feedback;
// The refactoring was reset, so we don't get stale results.
expect(feedback.names, contains('otherName'));
}
}
test_serverError_change() {
test_simulateRefactoringException_change = true;
addTestFile('''
main() {
print(1 + 2);
}
''');
return waitForTasksFinished().then((_) {
return sendStringRequest('1 + 2', 'res', true).then((response) {
expect(response.error, isNotNull);
expect(response.error.code, RequestErrorCode.SERVER_ERROR);
});
});
}
test_serverError_final() {
test_simulateRefactoringException_final = true;
addTestFile('''
main() {
print(1 + 2);
}
''');
return waitForTasksFinished().then((_) {
return sendStringRequest('1 + 2', 'res', true).then((response) {
expect(response.error, isNotNull);
expect(response.error.code, RequestErrorCode.SERVER_ERROR);
});
});
}
test_serverError_init() {
test_simulateRefactoringException_init = true;
addTestFile('''
main() {
print(1 + 2);
}
''');
return waitForTasksFinished().then((_) {
return sendStringRequest('1 + 2', 'res', true).then((response) {
expect(response.error, isNotNull);
expect(response.error.code, RequestErrorCode.SERVER_ERROR);
});
});
}
}
@reflectiveTest
class ExtractMethodTest extends _AbstractGetRefactoring_Test {
int offset;
int length;
String name = 'res';
ExtractMethodOptions options;
test_expression() {
addTestFile('''
main() {
print(1 + 2);
print(1 + 2);
}
''');
_setOffsetLengthForString('1 + 2');
return assertSuccessfulRefactoring(_computeChange, '''
main() {
print(res());
print(res());
}
int res() => 1 + 2;
''');
}
test_expression_hasParameters() {
addTestFile('''
main() {
int a = 1;
int b = 2;
print(a + b);
print(a + b);
}
''');
_setOffsetLengthForString('a + b');
return assertSuccessfulRefactoring(_computeChange, '''
main() {
int a = 1;
int b = 2;
print(res(a, b));
print(res(a, b));
}
int res(int a, int b) => a + b;
''');
}
test_expression_updateParameters() {
addTestFile('''
main() {
int a = 1;
int b = 2;
print(a + b);
print(a + b);
}
''');
_setOffsetLengthForString('a + b');
return getRefactoringResult(_computeChange).then((result) {
ExtractMethodFeedback feedback = result.feedback;
List<RefactoringMethodParameter> 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, '''
main() {
int a = 1;
int b = 2;
print(res(b, a));
print(res(b, a));
}
int res(num bbb, int aaa) => aaa + bbb;
''');
});
}
test_init_fatalError_invalidStatement() {
addTestFile('''
main(bool b) {
// start
if (b) {
print(1);
// end
print(2);
}
}
''');
_setOffsetLengthForStartEnd();
return waitForTasksFinished().then((_) {
return _sendExtractRequest();
}).then((Response response) {
var result = new EditGetRefactoringResult.fromResponse(response);
assertResultProblemsFatal(result.initialProblems);
// ...there is no any feedback
expect(result.feedback, isNull);
});
}
test_long_expression() {
addTestFile('''
main() {
print(1 +
2);
}
''');
_setOffsetLengthForString('1 +\n 2');
return assertSuccessfulRefactoring(_computeChange, '''
main() {
print(res());
}
int res() {
return 1 +
2;
}
''');
}
test_names() {
addTestFile('''
class TreeItem {}
TreeItem getSelectedItem() => null;
main() {
var a = getSelectedItem( );
}
''');
_setOffsetLengthForString('getSelectedItem( )');
return _computeInitialFeedback().then((feedback) {
expect(feedback.names,
unorderedEquals(['treeItem', 'item', 'selectedItem']));
expect(feedback.returnType, 'TreeItem');
});
}
test_offsetsLengths() {
addTestFile('''
class TreeItem {}
TreeItem getSelectedItem() => null;
main() {
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]);
});
}
test_statements() {
addTestFile('''
main() {
int a = 1;
int b = 2;
// start
print(a + b);
// end
print(a + b);
}
''');
_setOffsetLengthForStartEnd();
return assertSuccessfulRefactoring(_computeChange, '''
main() {
int a = 1;
int b = 2;
// start
res(a, b);
// end
res(a, b);
}
void res(int a, int b) {
print(a + b);
}
''');
}
Future<Response> _computeChange() async {
await _prepareOptions();
// send request with the options
return _sendExtractRequest();
}
Future<ExtractMethodFeedback> _computeInitialFeedback() async {
await waitForTasksFinished();
Response response = await _sendExtractRequest();
var result = new EditGetRefactoringResult.fromResponse(response);
return result.feedback;
}
Future _prepareOptions() {
return getRefactoringResult(() {
// get initial feedback
return _sendExtractRequest();
}).then((result) {
assertResultProblemsOK(result);
// fill options from result
ExtractMethodFeedback feedback = result.feedback;
options = new ExtractMethodOptions(
feedback.returnType, false, name, feedback.parameters, true);
// done
return new Future.value();
});
}
Future<Response> _sendExtractRequest() {
RefactoringKind 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 {
List<RefactoringKind> kinds;
void addFlutterPackage() {
var libFolder = flutter.configureFlutterPackage(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
Matcher 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 {
Request request =
new EditGetAvailableRefactoringsParams(testFile, offset, length)
.toRequest('0');
serverChannel.sendRequest(request);
var response = await serverChannel.waitForResponse(request);
var result = new EditGetAvailableRefactoringsResult.fromResponse(response);
kinds = result.kinds;
}
/**
* Returns the list of available refactorings at the offset of [search].
*/
Future getRefactoringsAtString(String search) {
int offset = findOffset(search);
return getRefactorings(offset, 0);
}
Future getRefactoringsForString(String search) {
int offset = findOffset(search);
return getRefactorings(offset, search.length);
}
@override
void setUp() {
super.setUp();
createProject();
handler = new 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('''
main() {
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('''
main() {
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 test_rename_hasElement_class() {
return assertHasRenameRefactoring('''
class Test {}
main() {
Test v;
}
''', 'Test v');
}
Future test_rename_hasElement_constructor() {
return assertHasRenameRefactoring('''
class A {
A.test() {}
}
main() {
new A.test();
}
''', 'test();');
}
Future test_rename_hasElement_function() {
return assertHasRenameRefactoring('''
main() {
test();
}
test() {}
''', 'test();');
}
Future test_rename_hasElement_importElement_directive() {
return assertHasRenameRefactoring('''
import 'dart:math' as math;
main() {
math.PI;
}
''', 'import ');
}
Future test_rename_hasElement_importElement_prefixDecl() {
return assertHasRenameRefactoring('''
import 'dart:math' as math;
main() {
math.PI;
}
''', 'math;');
}
Future test_rename_hasElement_importElement_prefixRef() {
return assertHasRenameRefactoring('''
import 'dart:async' as test;
import 'dart:math' as test;
main() {
test.PI;
}
''', 'test.PI;');
}
Future test_rename_hasElement_instanceGetter() {
return assertHasRenameRefactoring('''
class A {
get test => 0;
}
main(A a) {
a.test;
}
''', 'test;');
}
Future test_rename_hasElement_instanceSetter() {
return assertHasRenameRefactoring('''
class A {
set test(x) {}
}
main(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('''
main() {
int test = 0;
print(test);
}
''', 'test = 0;');
}
Future test_rename_hasElement_method() {
return assertHasRenameRefactoring('''
class A {
test() {}
}
main(A a) {
a.test();
}
''', 'test();');
}
Future test_rename_noElement() async {
addTestFile('''
main() {
// not an element
}
''');
await waitForTasksFinished();
await getRefactoringsAtString('// not an element');
expect(kinds, isNot(contains(RefactoringKind.RENAME)));
}
}
@reflectiveTest
class InlineLocalTest extends _AbstractGetRefactoring_Test {
test_analysis_onlyOneFile() async {
shouldWaitForFullAnalysis = false;
String otherFile = join(testFolder, 'other.dart');
newFile(otherFile, content: r'''
foo(int p) {}
''');
addTestFile('''
import 'other.dart';
main() {
int res = 1 + 2;
foo(res);
foo(res);
}
''');
// Start refactoring.
EditGetRefactoringResult result = await getRefactoringResult(() {
return _sendInlineRequest('res =');
});
// We get the refactoring feedback....
InlineLocalVariableFeedback feedback = result.feedback;
expect(feedback.occurrences, 2);
}
test_feedback() {
addTestFile('''
main() {
int test = 42;
print(test);
print(test);
}
''');
return getRefactoringResult(() {
return _sendInlineRequest('test =');
}).then((result) {
InlineLocalVariableFeedback feedback = result.feedback;
expect(feedback.name, 'test');
expect(feedback.occurrences, 2);
});
}
test_init_fatalError_notVariable() {
addTestFile('main() {}');
return getRefactoringResult(() {
return _sendInlineRequest('main() {}');
}).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);
});
}
test_OK() {
addTestFile('''
main() {
int test = 42;
int a = test + 2;
print(test);
}
''');
return assertSuccessfulRefactoring(() {
return _sendInlineRequest('test + 2');
}, '''
main() {
int a = 42 + 2;
print(42);
}
''');
}
test_resetOnAnalysisSetChanged() async {
newFile(join(testFolder, 'other.dart'), content: '// other 1');
addTestFile('''
main() {
int res = 1 + 2;
print(res);
}
''');
// Send the first request.
await getRefactoringResult(() {
return _sendInlineRequest('res = ');
});
int initialResetCount = test_resetCount;
// Update the test.dart file.
modifyTestFile('''
main() {
print(1 + 2);
}
''');
// The refactoring was reset.
await pumpEventQueue();
expect(test_resetCount, initialResetCount + 1);
}
Future<Response> _sendInlineRequest(String search) {
Request request = new EditGetRefactoringParams(
RefactoringKind.INLINE_LOCAL_VARIABLE,
testFile,
findOffset(search),
0,
false)
.toRequest('0');
return serverChannel.sendRequest(request);
}
}
@reflectiveTest
class InlineMethodTest extends _AbstractGetRefactoring_Test {
InlineMethodOptions options = new InlineMethodOptions(true, true);
test_feedback() {
addTestFile('''
class A {
int f;
test(int p) {
print(f + p);
}
main() {
test(1);
}
}
''');
return getRefactoringResult(() {
return _sendInlineRequest('test(int p)');
}).then((result) {
InlineMethodFeedback feedback = result.feedback;
expect(feedback.className, 'A');
expect(feedback.methodName, 'test');
expect(feedback.isDeclaration, isTrue);
});
}
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);
});
}
test_method() {
addTestFile('''
class A {
int f;
test(int p) {
print(f + p);
}
main() {
test(1);
}
}
main(A a) {
a.test(2);
}
''');
return assertSuccessfulRefactoring(() {
return _sendInlineRequest('test(int p)');
}, '''
class A {
int f;
main() {
print(f + 1);
}
}
main(A a) {
print(a.f + 2);
}
''');
}
test_topLevelFunction() {
addTestFile('''
test(a, b) {
print(a + b);
}
main() {
test(1, 2);
test(10, 20);
}
''');
return assertSuccessfulRefactoring(() {
return _sendInlineRequest('test(a');
}, '''
main() {
print(1 + 2);
print(10 + 20);
}
''');
}
test_topLevelFunction_oneInvocation() {
addTestFile('''
test(a, b) {
print(a + b);
}
main() {
test(1, 2);
test(10, 20);
}
''');
options.deleteSource = false;
options.inlineAll = false;
return assertSuccessfulRefactoring(() {
return _sendInlineRequest('test(10,');
}, '''
test(a, b) {
print(a + b);
}
main() {
test(1, 2);
print(10 + 20);
}
''');
}
Future<Response> _sendInlineRequest(String search) {
Request request = new EditGetRefactoringParams(
RefactoringKind.INLINE_METHOD,
testFile,
findOffset(search),
0,
false,
options: options)
.toRequest('0');
return serverChannel.sendRequest(request);
}
}
@reflectiveTest
class MoveFileTest extends _AbstractGetRefactoring_Test {
MoveFileOptions options;
@failingTest
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() {
Request request = new EditGetRefactoringParams(
RefactoringKind.MOVE_FILE, testFile, 0, 0, false,
options: options)
.toRequest('0');
return serverChannel.sendRequest(request);
}
void _setOptions(String newFile) {
options = new MoveFileOptions(newFile);
}
}
@reflectiveTest
class RenameTest extends _AbstractGetRefactoring_Test {
Future<Response> sendRenameRequest(String search, String newName,
{String id: '0', bool validateOnly: false}) {
RenameOptions options = newName != null ? new RenameOptions(newName) : null;
Request request = new EditGetRefactoringParams(RefactoringKind.RENAME,
testFile, findOffset(search), 0, validateOnly,
options: options)
.toRequest(id);
return serverChannel.sendRequest(request);
}
void tearDown() {
test_simulateRefactoringReset_afterInitialConditions = false;
test_simulateRefactoringReset_afterFinalConditions = false;
test_simulateRefactoringReset_afterCreateChange = false;
super.tearDown();
}
test_cancelPendingRequest() async {
addTestFile('''
main() {
int test = 0;
print(test);
}
''');
// send the "1" request, but don't wait for it
Future<Response> futureA = sendRenameRequest('test =', 'nameA', id: '1');
// send the "2" request and wait for it
Response responseB = await sendRenameRequest('test =', 'nameB', id: '2');
// wait for the (delayed) "1" response
Response responseA = await futureA;
// "1" was cancelled
// "2" is successful
expect(responseA,
isResponseFailure('1', RequestErrorCode.REFACTORING_REQUEST_CANCELLED));
expect(responseB, isResponseSuccess('2'));
}
test_class() {
addTestFile('''
class Test {
Test() {}
Test.named() {}
}
main() {
Test v;
new Test();
new Test.named();
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('Test {', 'NewName');
}, '''
class NewName {
NewName() {}
NewName.named() {}
}
main() {
NewName v;
new NewName();
new NewName.named();
}
''');
}
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) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 18);
expect(renameFeedback.length, 4);
},
);
}
test_class_fromInstanceCreation() {
addTestFile('''
class Test {
Test() {}
}
main() {
new Test();
}
''');
return assertSuccessfulRefactoring(
() {
return sendRenameRequest('Test();', 'NewName');
},
'''
class NewName {
NewName() {}
}
main() {
new NewName();
}
''',
feedbackValidator: (feedback) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 42);
expect(renameFeedback.length, 4);
},
);
}
test_class_fromInstanceCreation_namedConstructor() {
addTestFile('''
class Test {
Test.named() {}
}
main() {
new Test.named();
}
''');
return assertSuccessfulRefactoring(
() {
return sendRenameRequest('Test.named();', 'NewName');
},
'''
class NewName {
NewName.named() {}
}
main() {
new NewName.named();
}
''',
feedbackValidator: (feedback) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 48);
expect(renameFeedback.length, 4);
},
);
}
test_class_fromInstanceCreation_onNew() {
addTestFile('''
class Test {
Test() {}
}
main() {
new Test();
}
''');
return assertSuccessfulRefactoring(
() {
return sendRenameRequest('new Test();', 'NewName');
},
'''
class NewName {
NewName() {}
}
main() {
new NewName();
}
''',
feedbackValidator: (feedback) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 42);
expect(renameFeedback.length, 4);
},
);
}
test_class_fromInstanceCreation_onNew_namedConstructor() {
addTestFile('''
class Test {
Test.named() {}
}
main() {
new Test.named();
}
''');
return assertSuccessfulRefactoring(
() {
return sendRenameRequest('new Test.named();', 'NewName');
},
'''
class NewName {
NewName.named() {}
}
main() {
new NewName.named();
}
''',
feedbackValidator: (feedback) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 48);
expect(renameFeedback.length, 4);
},
);
}
test_class_options_fatalError() {
addTestFile('''
class Test {}
main() {
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);
});
}
test_class_validateOnly() {
addTestFile('''
class Test {}
main() {
Test v;
}
''');
return getRefactoringResult(() {
return sendRenameRequest('Test {}', 'NewName', validateOnly: true);
}).then((result) {
RenameFeedback feedback = result.feedback;
assertResultProblemsOK(result);
expect(feedback.elementKindName, 'class');
expect(feedback.oldName, 'Test');
expect(result.change, isNull);
});
}
test_class_warning() {
addTestFile('''
class Test {}
main() {
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 {}
main() {
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 {}
main() {
NewName v;
}
''');
});
});
}
test_classMember_field() {
addTestFile('''
class A {
var test = 0;
A(this.test);
main() {
print(test);
}
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test = 0', 'newName');
}, '''
class A {
var newName = 0;
A(this.newName);
main() {
print(newName);
}
}
''');
}
test_classMember_field_onFieldFormalParameter() {
addTestFile('''
class A {
var test = 0;
A(this.test);
main() {
print(test);
}
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test);', 'newName');
}, '''
class A {
var newName = 0;
A(this.newName);
main() {
print(newName);
}
}
''');
}
test_classMember_field_onFieldFormalParameter_named() {
addTestFile('''
class A {
final int test;
A({this.test: 0});
}
main() {
new A(test: 42);
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test: 42', 'newName');
}, '''
class A {
final int newName;
A({this.newName: 0});
}
main() {
new A(newName: 42);
}
''');
}
test_classMember_getter() {
addTestFile('''
class A {
get test => 0;
main() {
print(test);
}
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test =>', 'newName');
}, '''
class A {
get newName => 0;
main() {
print(newName);
}
}
''');
}
test_classMember_method() {
addTestFile('''
class A {
test() {}
main() {
test();
}
}
main(A a) {
a.test();
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test() {}', 'newName');
}, '''
class A {
newName() {}
main() {
newName();
}
}
main(A a) {
a.newName();
}
''');
}
test_classMember_method_potential() {
addTestFile('''
class A {
test() {}
}
main(A a, a2) {
a.test();
a2.test(); // a2
}
''');
return getRefactoringResult(() {
return sendRenameRequest('test() {}', 'newName');
}).then((result) {
assertResultProblemsOK(result);
// prepare potential edit ID
List<String> potentialIds = result.potentialEdits;
expect(potentialIds, hasLength(1));
String potentialId = potentialIds[0];
// find potential edit
SourceChange change = result.change;
SourceEdit potentialEdit = _findEditWithId(change, potentialId);
expect(potentialEdit, isNotNull);
expect(potentialEdit.offset, findOffset('test(); // a2'));
expect(potentialEdit.length, 4);
});
}
test_classMember_setter() {
addTestFile('''
class A {
set test(x) {}
main() {
test = 0;
}
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test = 0', 'newName');
}, '''
class A {
set newName(x) {}
main() {
newName = 0;
}
}
''');
}
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) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 20);
expect(renameFeedback.length, 4);
},
);
}
test_constructor_fromInstanceCreation() {
addTestFile('''
class A {
A.test() {}
}
main() {
new A.test();
}
''');
return assertSuccessfulRefactoring(
() {
return sendRenameRequest('test();', 'newName');
},
'''
class A {
A.newName() {}
}
main() {
new A.newName();
}
''',
feedbackValidator: (feedback) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 43);
expect(renameFeedback.length, 4);
},
);
}
test_feedback() {
addTestFile('''
class Test {}
main() {
Test v;
}
''');
return getRefactoringResult(() {
return sendRenameRequest('st v;', 'NewName');
}).then((result) {
RenameFeedback feedback = result.feedback;
expect(feedback, isNotNull);
expect(feedback.offset, findOffset('Test v;'));
expect(feedback.length, 'Test'.length);
});
}
test_function() {
addTestFile('''
test() {}
main() {
test();
print(test);
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test() {}', 'newName');
}, '''
newName() {}
main() {
newName();
print(newName);
}
''');
}
test_importPrefix_add() {
addTestFile('''
import 'dart:math';
import 'dart:async';
main() {
Random r;
Future f;
}
''');
return assertSuccessfulRefactoring(
() {
return sendRenameRequest("import 'dart:async';", 'new_name');
},
'''
import 'dart:math';
import 'dart:async' as new_name;
main() {
Random r;
new_name.Future f;
}
''',
feedbackValidator: (feedback) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, -1);
expect(renameFeedback.length, 0);
});
}
test_importPrefix_remove() {
addTestFile('''
import 'dart:math' as test;
import 'dart:async' as test;
main() {
test.Random r;
test.Future f;
}
''');
return assertSuccessfulRefactoring(
() {
return sendRenameRequest("import 'dart:async' as test;", '');
},
'''
import 'dart:math' as test;
import 'dart:async';
main() {
test.Random r;
Future f;
}
''',
feedbackValidator: (feedback) {
RenameFeedback renameFeedback = feedback;
expect(renameFeedback.offset, 51);
expect(renameFeedback.length, 4);
});
}
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);
});
}
test_library_libraryDirective() {
addTestFile('''
library aaa.bbb.ccc;
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('library aaa', 'my.new_name');
}, '''
library my.new_name;
''');
}
test_library_libraryDirective_name() {
addTestFile('''
library aaa.bbb.ccc;
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('aaa', 'my.new_name');
}, '''
library my.new_name;
''');
}
test_library_libraryDirective_nameDot() {
addTestFile('''
library aaa.bbb.ccc;
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('.bbb', 'my.new_name');
}, '''
library my.new_name;
''');
}
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;
''');
}
test_localVariable() {
addTestFile('''
main() {
int test = 0;
test = 1;
test += 2;
print(test);
}
''');
return assertSuccessfulRefactoring(() {
return sendRenameRequest('test = 1', 'newName');
}, '''
main() {
int newName = 0;
newName = 1;
newName += 2;
print(newName);
}
''');
}
test_localVariable_finalCheck_shadowError() {
addTestFile('''
main() {
var newName;
int test = 0;
print(test);
}
''');
return getRefactoringResult(() {
return sendRenameRequest('test = 0', 'newName');
}).then((result) {
List<RefactoringProblem> problems = result.finalProblems;
expect(problems, hasLength(1));
assertResultProblemsError(
problems, "Duplicate local variable 'newName'.");
});
}
test_reset_afterCreateChange() {
test_simulateRefactoringReset_afterCreateChange = true;
addTestFile('''
test() {}
main() {
test();
}
''');
return waitForTasksFinished().then((_) {
return sendRenameRequest('test() {}', 'newName').then((response) {
_expectRefactoringRequestCancelled(response);
});
});
}
test_reset_afterFinalConditions() {
test_simulateRefactoringReset_afterFinalConditions = true;
addTestFile('''
test() {}
main() {
test();
}
''');
return waitForTasksFinished().then((_) {
return sendRenameRequest('test() {}', 'newName').then((response) {
_expectRefactoringRequestCancelled(response);
});
});
}
test_reset_afterInitialConditions() {
test_simulateRefactoringReset_afterInitialConditions = true;
addTestFile('''
test() {}
main() {
test();
}
''');
return waitForTasksFinished().then((_) {
return sendRenameRequest('test() {}', 'newName').then((response) {
_expectRefactoringRequestCancelled(response);
});
});
}
test_resetOnAnalysis() async {
addTestFile('''
main() {
int initialName = 0;
print(initialName);
}
''');
// send the first request
EditGetRefactoringResult result = await getRefactoringResult(() {
return sendRenameRequest('initialName =', 'newName', validateOnly: true);
});
_validateFeedback(result, oldName: 'initialName');
// update the file
modifyTestFile('''
main() {
int otherName = 0;
print(otherName);
}
''');
server.getAnalysisDriver(testFile).getResult(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}) {
RenameFeedback feedback = result.feedback;
expect(feedback, isNotNull);
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]) {
RefactoringProblem 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]) {
RefactoringProblem 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]) {
RefactoringProblem 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> requestSender(), String expectedCode,
{void Function(RefactoringFeedback) feedbackValidator}) async {
EditGetRefactoringResult 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) {
SourceChange change = result.change;
expect(change, isNotNull);
for (SourceFileEdit fileEdit in change.edits) {
if (fileEdit.file == testFile) {
String actualCode = SourceEdit.applySequence(testCode, fileEdit.edits);
expect(actualCode, expectedCode);
return;
}
}
fail('No SourceFileEdit for $testFile in $change');
}
Future<EditGetRefactoringResult> getRefactoringResult(
Future<Response> requestSender()) async {
if (shouldWaitForFullAnalysis) {
await waitForTasksFinished();
}
Response response = await requestSender();
return new EditGetRefactoringResult.fromResponse(response);
}
Future<Response> sendRequest(
RefactoringKind kind, int offset, int length, RefactoringOptions options,
[bool validateOnly = false]) {
Request request = new EditGetRefactoringParams(
kind, testFile, offset, length, validateOnly,
options: options)
.toRequest('0');
return serverChannel.sendRequest(request);
}
@override
void setUp() {
super.setUp();
createProject();
handler = new EditDomainHandler(server);
server.handlers = [handler];
}
}