blob: ca1dbc08877a1fa771b385d22fc2d6ac394e4e57 [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 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.dart';
import 'package:analysis_server/src/lsp/constants.dart';
import 'package:analysis_server/src/services/correction/fix_internal.dart';
import 'package:linter/src/rules.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../shared/shared_code_actions_source_tests.dart';
import 'code_actions_mixin.dart';
import 'server_abstract.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(SourceCodeActionsTest);
defineReflectiveTests(SortMembersSourceCodeActionsTest);
defineReflectiveTests(OrganizeImportsSourceCodeActionsTest);
defineReflectiveTests(FixAllSourceCodeActionsTest);
});
}
abstract class AbstractSourceCodeActionsTest
extends AbstractLspAnalysisServerTest
with
LspSharedTestMixin,
SharedSourceCodeActionsTestMixin,
CodeActionsTestMixin {}
@reflectiveTest
class FixAllSourceCodeActionsTest extends AbstractSourceCodeActionsTest {
@override
Future<void> setUp() async {
await super.setUp();
// Fix tests are likely to have diagnostics that need fixing.
failTestOnErrorDiagnostic = false;
registerBuiltInFixGenerators();
}
Future<void> test_appliesCorrectEdits() async {
const analysisOptionsContent = '''
linter:
rules:
- unnecessary_new
- prefer_collection_literals
''';
const content = '''
final a = new Object();
final b = new Set<String>();
''';
const expectedContent = '''
final a = Object();
final b = <String>{};
''';
registerLintRules();
newFile(analysisOptionsPath, analysisOptionsContent);
newFile(mainFilePath, content);
await verifyCodeActionLiteralEdits(
content,
expectedContent,
command: Commands.fixAll,
);
}
Future<void> test_multipleIterations_noOverlay() async {
const analysisOptionsContent = '''
linter:
rules:
- prefer_final_locals
- prefer_const_declarations
''';
const content = '''
void f() {
var a = 'test';
}
''';
const expectedContent = '''
void f() {
const a = 'test';
}
''';
registerLintRules();
newFile(analysisOptionsPath, analysisOptionsContent);
newFile(mainFilePath, content);
await verifyCodeActionLiteralEdits(
content,
expectedContent,
command: Commands.fixAll,
);
}
Future<void> test_multipleIterations_overlay() async {
const analysisOptionsContent = '''
linter:
rules:
- prefer_final_locals
- prefer_const_declarations
''';
const content = '''
void f() {
var a = 'test';
}
''';
const expectedContent = '''
void f() {
const a = 'test';
}
''';
registerLintRules();
newFile(analysisOptionsPath, analysisOptionsContent);
await verifyCodeActionLiteralEdits(
content,
expectedContent,
command: Commands.fixAll,
);
}
Future<void> test_multipleIterations_withClientModification() async {
const analysisOptionsContent = '''
linter:
rules:
- prefer_final_locals
- prefer_const_declarations
''';
const content = '''
void f() {
var a = 'test';
}
''';
registerLintRules();
newFile(analysisOptionsPath, analysisOptionsContent);
var codeAction = await expectCodeActionLiteral(
content,
command: Commands.fixAll,
);
var command = codeAction.command!;
// Files must be open to apply edits.
await openFile(testFileUri, content);
// Execute the command with a modification and capture the edit that is
// sent back to us.
ApplyWorkspaceEditParams? editParams;
await handleExpectedRequest<
Object?,
ApplyWorkspaceEditParams,
ApplyWorkspaceEditResult
>(
Method.workspace_applyEdit,
ApplyWorkspaceEditParams.fromJson,
() async {
// Apply the command and immediately modify a file afterwards without
// awaiting.
var commandFuture = executeCommand(command);
await replaceFile(12345, testFileUri, 'client-modified-content');
return commandFuture;
},
handler: (edit) {
// When the server sends the edit back, just keep a copy and say we
// applied successfully (we'll verify the actual edit below).
editParams = edit;
return ApplyWorkspaceEditResult(applied: true);
},
);
// Extract the text edit from the 'workspace/applyEdit' params the server
// sent us.
var change = editParams?.edit.documentChanges!.single;
var edit = change!.map(
(create) => throw 'Expected edit, got create',
(delete) => throw 'Expected edit, got delete',
(rename) => throw 'Expected edit, got rename',
(edit) => edit,
);
// Ensure the edit says that it was based on version 1 (the original
// version) and not the updated 12345 version we sent.
expect(edit.textDocument.version, 1);
}
Future<void> test_part() async {
var containerFilePath = join(projectFolderPath, 'lib', 'container.dart');
var partFilePath = join(projectFolderPath, 'lib', 'part.dart');
const analysisOptionsContent = '''
linter:
rules:
- unnecessary_new
- prefer_collection_literals
''';
const containerFileContent = '''
part 'part.dart';
''';
const content = '''
part of 'container.dart';
final a = new Object();
final b = new Set<String>();
''';
const expectedContent = '''
part of 'container.dart';
final a = Object();
final b = <String>{};
''';
registerLintRules();
newFile(analysisOptionsPath, analysisOptionsContent);
newFile(containerFilePath, containerFileContent);
newFile(partFilePath, content);
await verifyCodeActionLiteralEdits(
filePath: partFilePath,
content,
expectedContent,
command: Commands.fixAll,
);
}
Future<void> test_privateUnusedParameters_notRemovedIfSave() async {
const content = '''
class _MyClass {
int? _param;
_MyClass({
this._param,
});
}
''';
var codeAction = await expectCodeActionLiteral(
content,
command: Commands.fixAll,
triggerKind: CodeActionTriggerKind.Automatic,
);
var command = codeAction.command!;
// We should not get an applyEdit call during the command execution because
// no edits should be produced.
var applyEditSubscription = requestsFromServer
.where((n) => n.method == Method.workspace_applyEdit)
.listen((_) => throw 'workspace/applyEdit was unexpectedly called');
var commandResponse = await executeCommand(command);
expect(commandResponse, isNull);
await pumpEventQueue();
await applyEditSubscription.cancel();
}
Future<void> test_privateUnusedParameters_removedByDefault() async {
const content = '''
class _MyClass {
int? param;
_MyClass({
this.param,
});
}
''';
const expectedContent = '''
class _MyClass {
int? param;
_MyClass();
}
''';
await verifyCodeActionLiteralEdits(
content,
expectedContent,
command: Commands.fixAll,
);
}
Future<void> test_unavailable_outsideAnalysisRoot() async {
var otherFile = convertPath('/other/file.dart');
var content = '';
await expectNoAction(
filePath: otherFile,
content,
command: Commands.organizeImports,
);
}
Future<void> test_unusedUsings_notRemovedIfSave() async {
const content = '''
import 'dart:async';
int? a;
''';
var codeAction = await expectCodeActionLiteral(
content,
command: Commands.fixAll,
triggerKind: CodeActionTriggerKind.Automatic,
);
var command = codeAction.command!;
// We should not get an applyEdit call during the command execution because
// no edits should be produced.
var applyEditSubscription = requestsFromServer
.where((n) => n.method == Method.workspace_applyEdit)
.listen((_) => throw 'workspace/applyEdit was unexpectedly called');
var commandResponse = await executeCommand(command);
expect(commandResponse, isNull);
await pumpEventQueue();
await applyEditSubscription.cancel();
}
Future<void> test_unusedUsings_removedByDefault() async {
const content = '''
import 'dart:async';
int? a;
''';
const expectedContent = '''
int? a;
''';
await verifyCodeActionLiteralEdits(
content,
expectedContent,
command: Commands.fixAll,
);
}
}
@reflectiveTest
class OrganizeImportsSourceCodeActionsTest extends AbstractSourceCodeActionsTest
with
SharedSourceCodeActionsTestMixin,
// Tests are defined in a shared mixin.
SharedOrganizeImportsSourceCodeActionsTests {}
@reflectiveTest
class SortMembersSourceCodeActionsTest extends AbstractSourceCodeActionsTest
with
SharedSourceCodeActionsTestMixin,
// Tests are defined in a shared mixin.
SharedSortMembersSourceCodeActionsTests {}
@reflectiveTest
class SourceCodeActionsTest extends AbstractSourceCodeActionsTest
with
SharedSourceCodeActionsTestMixin,
// Tests are defined in a shared mixin.
SharedSourceCodeActionsTests {}