mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 15:01:29 +00:00
85d455fbad
Change-Id: I2deac95e665a19065a7ef1d8c1f79485497c9de2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/205680 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
398 lines
13 KiB
Dart
398 lines
13 KiB
Dart
// 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/lsp_protocol/protocol_generated.dart';
|
|
import 'package:analysis_server/src/lsp/constants.dart';
|
|
import 'package:linter/src/rules.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../tool/lsp_spec/matchers.dart';
|
|
import 'code_actions_abstract.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(SortMembersSourceCodeActionsTest);
|
|
defineReflectiveTests(OrganizeImportsSourceCodeActionsTest);
|
|
defineReflectiveTests(FixAllSourceCodeActionsTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class FixAllSourceCodeActionsTest extends AbstractCodeActionsTest {
|
|
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, content: analysisOptionsContent);
|
|
newFile(mainFilePath, content: content);
|
|
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.fixAll)!;
|
|
|
|
await verifyCodeActionEdits(codeAction, content, expectedContent);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class OrganizeImportsSourceCodeActionsTest extends AbstractCodeActionsTest {
|
|
Future<void> test_appliesCorrectEdits_withDocumentChangesSupport() async {
|
|
const content = '''
|
|
import 'dart:math';
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
Completer foo;
|
|
int minified(int x, int y) => min(x, y);
|
|
''';
|
|
const expectedContent = '''
|
|
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
Completer foo;
|
|
int minified(int x, int y) => min(x, y);
|
|
''';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities: withApplyEditSupport(
|
|
withDocumentChangesSupport(emptyWorkspaceClientCapabilities)));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.organizeImports)!;
|
|
|
|
await verifyCodeActionEdits(codeAction, content, expectedContent,
|
|
expectDocumentChanges: true);
|
|
}
|
|
|
|
Future<void> test_appliesCorrectEdits_withoutDocumentChangesSupport() async {
|
|
const content = '''
|
|
import 'dart:math';
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
Completer foo;
|
|
int minified(int x, int y) => min(x, y);
|
|
''';
|
|
const expectedContent = '''
|
|
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
Completer foo;
|
|
int minified(int x, int y) => min(x, y);
|
|
''';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.organizeImports)!;
|
|
|
|
await verifyCodeActionEdits(codeAction, content, expectedContent);
|
|
}
|
|
|
|
Future<void> test_availableAsCodeActionLiteral() async {
|
|
newFile(mainFilePath);
|
|
await initialize(
|
|
textDocumentCapabilities: withCodeActionKinds(
|
|
emptyTextDocumentClientCapabilities, [CodeActionKind.Source]),
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
await checkCodeActionAvailable(
|
|
mainFileUri,
|
|
Commands.organizeImports,
|
|
'Organize Imports',
|
|
asCodeActionLiteral: true,
|
|
);
|
|
}
|
|
|
|
Future<void> test_availableAsCommand() async {
|
|
newFile(mainFilePath);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
await checkCodeActionAvailable(
|
|
mainFileUri,
|
|
Commands.organizeImports,
|
|
'Organize Imports',
|
|
asCommand: true,
|
|
);
|
|
}
|
|
|
|
Future<void> test_failsSilentlyIfFileHasErrors() async {
|
|
final content = 'invalid dart code';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.organizeImports)!;
|
|
|
|
final command = codeAction.map(
|
|
(command) => command,
|
|
(codeAction) => codeAction.command!,
|
|
);
|
|
|
|
final commandResponse = await executeCommand(command);
|
|
// Invalid code returns an empty success() response to avoid triggering
|
|
// errors in the editor if run automatically on every save.
|
|
expect(commandResponse, isNull);
|
|
}
|
|
|
|
Future<void> test_filtersCorrectly() async {
|
|
newFile(mainFilePath, content: '');
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final ofKind = (CodeActionKind kind) => getCodeActions(
|
|
mainFileUri.toString(),
|
|
kinds: [kind],
|
|
);
|
|
|
|
expect(await ofKind(CodeActionKind.Source), hasLength(3));
|
|
expect(await ofKind(CodeActionKind.SourceOrganizeImports), hasLength(1));
|
|
expect(await ofKind(DartCodeActionKind.SortMembers), hasLength(1));
|
|
expect(await ofKind(DartCodeActionKind.FixAll), hasLength(1));
|
|
expect(await ofKind(CodeActionKind('source.foo')), isEmpty);
|
|
expect(await ofKind(CodeActionKind.Refactor), isEmpty);
|
|
}
|
|
|
|
Future<void> test_noEdits() async {
|
|
const content = '''
|
|
import 'dart:async';
|
|
import 'dart:math';
|
|
|
|
Completer foo;
|
|
int minified(int x, int y) => min(x, y);
|
|
''';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.organizeImports)!;
|
|
|
|
final command = codeAction.map(
|
|
(command) => command,
|
|
(codeAction) => codeAction.command!,
|
|
);
|
|
|
|
// Execute the command and it should return without needing us to process
|
|
// a workspace/applyEdit command because there were no edits.
|
|
final commandResponse = await executeCommand(command);
|
|
// Successful edits return an empty success() response.
|
|
expect(commandResponse, isNull);
|
|
}
|
|
|
|
Future<void> test_unavailableWhenNotRequested() async {
|
|
newFile(mainFilePath);
|
|
await initialize(
|
|
textDocumentCapabilities: withCodeActionKinds(
|
|
emptyTextDocumentClientCapabilities, [CodeActionKind.Refactor]),
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.organizeImports);
|
|
expect(codeAction, isNull);
|
|
}
|
|
|
|
Future<void> test_unavailableWithoutApplyEditSupport() async {
|
|
newFile(mainFilePath);
|
|
await initialize();
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.organizeImports);
|
|
expect(codeAction, isNull);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class SortMembersSourceCodeActionsTest extends AbstractCodeActionsTest {
|
|
Future<void> test_appliesCorrectEdits_withDocumentChangesSupport() async {
|
|
const content = '''
|
|
String b;
|
|
String a;
|
|
''';
|
|
const expectedContent = '''
|
|
String a;
|
|
String b;
|
|
''';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities: withApplyEditSupport(
|
|
withDocumentChangesSupport(emptyWorkspaceClientCapabilities)));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.sortMembers)!;
|
|
|
|
await verifyCodeActionEdits(codeAction, content, expectedContent,
|
|
expectDocumentChanges: true);
|
|
}
|
|
|
|
Future<void> test_appliesCorrectEdits_withoutDocumentChangesSupport() async {
|
|
const content = '''
|
|
String b;
|
|
String a;
|
|
''';
|
|
const expectedContent = '''
|
|
String a;
|
|
String b;
|
|
''';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.sortMembers)!;
|
|
|
|
await verifyCodeActionEdits(codeAction, content, expectedContent);
|
|
}
|
|
|
|
Future<void> test_availableAsCodeActionLiteral() async {
|
|
newFile(mainFilePath);
|
|
await initialize(
|
|
textDocumentCapabilities: withCodeActionKinds(
|
|
emptyTextDocumentClientCapabilities, [CodeActionKind.Source]),
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
await checkCodeActionAvailable(
|
|
mainFileUri,
|
|
Commands.sortMembers,
|
|
'Sort Members',
|
|
asCodeActionLiteral: true,
|
|
);
|
|
}
|
|
|
|
Future<void> test_availableAsCommand() async {
|
|
newFile(mainFilePath);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
await checkCodeActionAvailable(
|
|
mainFileUri,
|
|
Commands.sortMembers,
|
|
'Sort Members',
|
|
asCommand: true,
|
|
);
|
|
}
|
|
|
|
Future<void> test_failsIfClientDoesntApplyEdits() async {
|
|
const content = '''
|
|
String b;
|
|
String a;
|
|
''';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.sortMembers)!;
|
|
|
|
final command = codeAction.map(
|
|
(command) => command,
|
|
(codeAction) => codeAction.command!,
|
|
);
|
|
|
|
final commandResponse = handleExpectedRequest<Object?,
|
|
ApplyWorkspaceEditParams, ApplyWorkspaceEditResponse>(
|
|
Method.workspace_applyEdit,
|
|
ApplyWorkspaceEditParams.fromJson,
|
|
() => executeCommand(command),
|
|
// Claim that we failed tpo apply the edits. This is what the client
|
|
// would do if the edits provided were for an old version of the
|
|
// document.
|
|
handler: (edit) => ApplyWorkspaceEditResponse(
|
|
applied: false, failureReason: 'Document changed'),
|
|
);
|
|
|
|
// Ensure the request returned an error (error repsonses are thrown by
|
|
// the test helper to make consuming success results simpler).
|
|
await expectLater(commandResponse,
|
|
throwsA(isResponseError(ServerErrorCodes.ClientFailedToApplyEdit)));
|
|
}
|
|
|
|
Future<void> test_failsIfFileHasErrors() async {
|
|
final content = 'invalid dart code';
|
|
newFile(mainFilePath, content: content);
|
|
await initialize(
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.sortMembers)!;
|
|
|
|
final command = codeAction.map(
|
|
(command) => command,
|
|
(codeAction) => codeAction.command!,
|
|
);
|
|
|
|
// Ensure the request returned an error (error repsonses are thrown by
|
|
// the test helper to make consuming success results simpler).
|
|
await expectLater(executeCommand(command),
|
|
throwsA(isResponseError(ServerErrorCodes.FileHasErrors)));
|
|
}
|
|
|
|
Future<void> test_nonDartFile() async {
|
|
newFile(pubspecFilePath, content: simplePubspecContent);
|
|
await initialize(
|
|
textDocumentCapabilities: withCodeActionKinds(
|
|
emptyTextDocumentClientCapabilities, [CodeActionKind.Source]),
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions =
|
|
await getCodeActions(pubspecFileUri.toString(), range: startOfDocRange);
|
|
expect(codeActions, isEmpty);
|
|
}
|
|
|
|
Future<void> test_unavailableWhenNotRequested() async {
|
|
newFile(mainFilePath);
|
|
await initialize(
|
|
textDocumentCapabilities: withCodeActionKinds(
|
|
emptyTextDocumentClientCapabilities, [CodeActionKind.Refactor]),
|
|
workspaceCapabilities:
|
|
withApplyEditSupport(emptyWorkspaceClientCapabilities));
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.sortMembers);
|
|
expect(codeAction, isNull);
|
|
}
|
|
|
|
Future<void> test_unavailableWithoutApplyEditSupport() async {
|
|
newFile(mainFilePath);
|
|
await initialize();
|
|
|
|
final codeActions = await getCodeActions(mainFileUri.toString());
|
|
final codeAction = findCommand(codeActions, Commands.sortMembers);
|
|
expect(codeAction, isNull);
|
|
}
|
|
}
|