blob: 5f889d913b5042fd8ba45443d31b18982940a38d [file] [log] [blame]
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/edit/edit_dartfix.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:linter/src/rules.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'analysis_abstract.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(EditDartfixDomainHandlerTest);
});
}
@reflectiveTest
class EditDartfixDomainHandlerTest extends AbstractAnalysisTest {
int requestId = 30;
String get nextRequestId => (++requestId).toString();
void expectEdits(List<SourceFileEdit> fileEdits, String expectedSource) {
expect(fileEdits, hasLength(1));
expect(fileEdits[0].file, testFile);
expectFileEdits(testCode, fileEdits[0], expectedSource);
}
void expectFileEdits(
String originalSource, SourceFileEdit fileEdit, String expectedSource) {
var source = SourceEdit.applySequence(originalSource, fileEdit.edits);
expect(source, expectedSource);
}
void expectSuggestion(DartFixSuggestion suggestion, String partialText,
[int? offset, int? length]) {
expect(suggestion.description, contains(partialText));
if (offset == null) {
expect(suggestion.location, isNull);
} else {
var location = suggestion.location!;
expect(location.offset, offset);
expect(location.length, length);
}
}
Future<EditDartfixResult> performFix(
{List<String>? includedFixes, bool? pedantic}) async {
var response =
await performFixRaw(includedFixes: includedFixes, pedantic: pedantic);
expect(response.error, isNull);
return EditDartfixResult.fromResponse(response);
}
Future<Response> performFixRaw(
{List<String>? includedFixes,
List<String>? excludedFixes,
bool? pedantic}) async {
final id = nextRequestId;
final params = EditDartfixParams([projectPath]);
params.includedFixes = includedFixes;
params.excludedFixes = excludedFixes;
params.includePedanticFixes = pedantic;
final request = Request(id, 'edit.dartfix', params.toJson());
var fix = EditDartFix(server, request);
final response = await fix.compute();
expect(response.id, id);
return response;
}
@override
void setUp() {
super.setUp();
registerLintRules();
testFile = resourceProvider.convertPath('/project/lib/fileToBeFixed.dart');
}
Future<void> test_collection_if_elements() async {
addTestFile('''
f(bool b) {
return ['a', b ? 'c' : 'd', 'e'];
}
''');
createProject();
var result = await performFix(
includedFixes: ['prefer_if_elements_to_conditional_expressions']);
expect(result.suggestions.length, greaterThanOrEqualTo(1));
expect(result.hasErrors, isFalse);
expectEdits(result.edits, '''
f(bool b) {
return ['a', if (b) 'c' else 'd', 'e'];
}
''');
}
Future<void> test_excludedFix_invalid() async {
addTestFile('''
const double myDouble = 42.0;
''');
createProject();
final result = await performFixRaw(excludedFixes: ['not_a_fix']);
expect(result.error, isNotNull);
}
Future<void> test_excludedSource() async {
// Add analysis options to exclude the lib directory then reanalyze
newAnalysisOptionsYamlFile('/project', content: '''
analyzer:
exclude:
- lib/**
''');
addTestFile('''
const double myDouble = 42.0;
''');
createProject();
// Assert no suggestions now that source has been excluded
final result = await performFix(includedFixes: ['prefer_int_literals']);
expect(result.suggestions, hasLength(0));
expect(result.edits, hasLength(0));
}
Future<void> test_fixNamedConstructorTypeArgs() async {
addTestFile('''
class A<T> {
A.from(Object obj);
}
void f() {
print(A.from<String>([]));
}
''');
createProject();
var result = await performFix(
includedFixes: ['wrong_number_of_type_arguments_constructor']);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], 'type arguments', 62, 8);
expectEdits(result.edits, '''
class A<T> {
A.from(Object obj);
}
void f() {
print(A<String>.from([]));
}
''');
}
Future<void> test_includedFix_invalid() async {
addTestFile('''
const double myDouble = 42.0;
''');
createProject();
final result = await performFixRaw(includedFixes: ['not_a_fix']);
expect(result.error, isNotNull);
}
Future<void> test_partFile() async {
newFile('/project/lib/lib.dart', content: '''
library lib2;
part 'fileToBeFixed.dart';
''');
addTestFile('''
part of lib2;
const double myDouble = 42.0;
''');
createProject();
// Assert dartfix suggestions
var result = await performFix(includedFixes: ['prefer_int_literals']);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], 'int literal', 38, 4);
expectEdits(result.edits, '''
part of lib2;
const double myDouble = 42;
''');
}
Future<void> test_partFile_loose() async {
addTestFile('''
part of lib2;
const double myDouble = 42.0;
''');
createProject();
// Assert dartfix suggestions
var result = await performFix(includedFixes: ['prefer_int_literals']);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], 'int literal', 38, 4);
expectEdits(result.edits, '''
part of lib2;
const double myDouble = 42;
''');
}
Future<void> test_pedantic() async {
addTestFile('void f(List args) { if (args.length == 0) { } }');
createProject();
var result = await performFix(pedantic: true);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], "Replace with 'isEmpty'", 24, 16);
expect(result.hasErrors, isFalse);
expectEdits(result.edits, 'void f(List args) { if (args.isEmpty) { } }');
}
Future<void> test_preferEqualForDefaultValues() async {
// Add analysis options to enable ui as code
addTestFile('f({a: 1}) { }');
createProject();
var result =
await performFix(includedFixes: ['prefer_equal_for_default_values']);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], "Replace ':' with '='", 4, 1);
expect(result.hasErrors, isFalse);
expectEdits(result.edits, 'f({a = 1}) { }');
}
Future<void> test_preferForElementsToMapFromIterable() async {
addTestFile('''
var m =
Map<int, int>.fromIterable([1, 2, 3], key: (i) => i, value: (i) => i * 2);
''');
createProject();
var result = await performFix(
includedFixes: ['prefer_for_elements_to_map_fromIterable']);
expect(result.suggestions, hasLength(1));
expectSuggestion(
result.suggestions[0], "Convert to a 'for' element", 10, 73);
expectEdits(result.edits, '''
var m =
{ for (var i in [1, 2, 3]) i : i * 2 };
''');
}
Future<void> test_preferIfElementsToConditionalExpressions() async {
addTestFile('''
f(bool b) => ['a', b ? 'c' : 'd', 'e'];
''');
createProject();
var result = await performFix(
includedFixes: ['prefer_if_elements_to_conditional_expressions']);
expect(result.suggestions, hasLength(1));
expectSuggestion(
result.suggestions[0], "Convert to an 'if' element", 19, 13);
expectEdits(result.edits, '''
f(bool b) => ['a', if (b) 'c' else 'd', 'e'];
''');
}
Future<void> test_preferIntLiterals() async {
addTestFile('''
const double myDouble = 42.0;
''');
createProject();
var result = await performFix(includedFixes: ['prefer_int_literals']);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], 'int literal', 24, 4);
expectEdits(result.edits, '''
const double myDouble = 42;
''');
}
Future<void> test_preferIsEmpty() async {
addTestFile('void f(List<String> args) { if (args.length == 0) { } }');
createProject();
var result = await performFix(includedFixes: ['prefer_is_empty']);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], "Replace with 'isEmpty'", 32, 16);
expect(result.hasErrors, isFalse);
expectEdits(
result.edits, 'void f(List<String> args) { if (args.isEmpty) { } }');
}
Future<void> test_preferMixin() async {
addTestFile('''
class A {}
class B extends A {}
class C with B {}
''');
createProject();
var result = await performFix(includedFixes: ['convert_class_to_mixin']);
expect(result.suggestions, hasLength(1));
expectSuggestion(result.suggestions[0], 'mixin', 17, 1);
expectEdits(result.edits, '''
class A {}
mixin B implements A {}
class C with B {}
''');
}
Future<void> test_preferSingleQuotes() async {
addTestFile('''
var l = [
"abc",
'def',
"'g'",
"""hij""",
\'''klm\''',
];
''');
createProject();
var result = await performFix(includedFixes: ['prefer_single_quotes']);
expect(result.suggestions, hasLength(2));
expectSuggestion(
result.suggestions[0], 'Convert to single quoted string', 12, 5);
expectSuggestion(
result.suggestions[1], 'Convert to single quoted string', 39, 9);
expect(result.hasErrors, isFalse);
expectEdits(result.edits, '''
var l = [
'abc',
'def',
"'g'",
\'''hij\''',
\'''klm\''',
];
''');
}
Future<void> test_preferSpreadCollections() async {
addTestFile('''
var l1 = ['b'];
var l2 = ['a']..addAll(l1);
''');
createProject();
var result = await performFix(includedFixes: ['prefer_spread_collections']);
expect(result.suggestions.length, greaterThanOrEqualTo(1));
expect(result.hasErrors, isFalse);
expectEdits(result.edits, '''
var l1 = ['b'];
var l2 = ['a', ...l1];
''');
}
}