| // 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.dart'; |
| import 'package:analysis_server/src/services/correction/fix_internal.dart'; |
| import 'package:analyzer/src/test_utilities/test_code_format.dart'; |
| import 'package:collection/collection.dart'; |
| import 'package:test/test.dart'; |
| |
| import '../utils/test_code_extensions.dart'; |
| import 'change_verifier.dart'; |
| import 'server_abstract.dart'; |
| |
| abstract class AbstractCodeActionsTest extends AbstractLspAnalysisServerTest { |
| /// Initializes the server with some basic configuration and expects to find |
| /// a [CodeAction] with [kind]/[command]/[title]. |
| Future<CodeAction> expectAction( |
| String content, { |
| CodeActionKind? kind, |
| String? command, |
| String? title, |
| CodeActionTriggerKind? triggerKind, |
| String? filePath, |
| bool openTargetFile = false, |
| }) async { |
| filePath ??= mainFilePath; |
| var code = TestCode.parse(content); |
| newFile(filePath, code.code); |
| |
| await initialize(); |
| |
| var fileUri = uriConverter.toClientUri(filePath); |
| if (openTargetFile) { |
| await openFile(fileUri, code.code); |
| } |
| |
| var codeActions = await getCodeActions( |
| fileUri, |
| position: code.positions.isNotEmpty ? code.position.position : null, |
| range: code.ranges.isNotEmpty ? code.range.range : null, |
| triggerKind: triggerKind, |
| ); |
| |
| return findAction(codeActions, kind: kind, command: command, title: title)!; |
| } |
| |
| /// Expects that command [commandName] was logged to the analytics manager. |
| void expectCommandLogged(String commandName) { |
| expect( |
| server.analyticsManager |
| .getRequestData(Method.workspace_executeCommand.toString()) |
| .additionalEnumCounts['command']! |
| .keys, |
| contains(commandName), |
| ); |
| } |
| |
| /// Initializes the server with some basic configuration and expects not to |
| /// find a [CodeAction] with [kind]/[command]/[title]. |
| Future<void> expectNoAction( |
| String content, { |
| String? filePath, |
| CodeActionKind? kind, |
| String? command, |
| String? title, |
| ProgressToken? workDoneToken, |
| }) async { |
| filePath ??= mainFilePath; |
| var code = TestCode.parse(content); |
| newFile(filePath, code.code); |
| |
| if (workDoneToken != null) { |
| setWorkDoneProgressSupport(); |
| } |
| await initialize(); |
| |
| var codeActions = await getCodeActions( |
| uriConverter.toClientUri(filePath), |
| position: code.positions.isNotEmpty ? code.position.position : null, |
| range: code.ranges.isNotEmpty ? code.range.range : null, |
| workDoneToken: workDoneToken, |
| ); |
| |
| expect( |
| findAction(codeActions, kind: kind, command: command, title: title), |
| isNull, |
| ); |
| } |
| |
| /// Finds the single action matching [title], [kind] and [command]. |
| /// |
| /// Throws if zero or more than one actions match. |
| CodeAction? findAction(List<Either2<Command, CodeAction>> actions, |
| {String? title, CodeActionKind? kind, String? command}) { |
| return findActions(actions, title: title, kind: kind, command: command) |
| .singleOrNull; |
| } |
| |
| List<CodeAction> findActions(List<Either2<Command, CodeAction>> actions, |
| {String? title, CodeActionKind? kind, String? command}) { |
| return actions |
| .map((action) => action.map((cmd) => null, (action) => action)) |
| .where((action) => title == null || action?.title == title) |
| .where((action) => kind == null || action?.kind == kind) |
| .where( |
| (action) => command == null || action?.command?.command == command) |
| .map((action) { |
| // Always expect a command (either to execute, or for logging) |
| assert(action!.command != null); |
| // Expect an edit if we weren't looking for a command-action. |
| if (command == null) { |
| assert(action!.edit != null); |
| } |
| return action; |
| }) |
| .nonNulls |
| .toList(); |
| } |
| |
| Either2<Command, CodeAction>? findCommand( |
| List<Either2<Command, CodeAction>> actions, String commandID, |
| [String? wantedTitle]) { |
| for (var codeAction in actions) { |
| var id = codeAction.map( |
| (cmd) => cmd.command, |
| (action) => action.command?.command, |
| ); |
| var title = codeAction.map( |
| (cmd) => cmd.title, |
| (action) => action.title, |
| ); |
| if (id == commandID && (wantedTitle == null || wantedTitle == title)) { |
| return codeAction; |
| } |
| } |
| return null; |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| |
| // Fix tests are likely to have diagnostics that need fixing. |
| failTestOnErrorDiagnostic = false; |
| |
| // Some defaults that most tests use. Tests can opt-out by overwriting these |
| // before initializing. |
| setApplyEditSupport(); |
| setDocumentChangesSupport(); |
| registerBuiltInProducers(); |
| } |
| |
| /// Initializes the server with some basic configuration and expects to find |
| /// a [CodeAction] with [kind]/[title] that applies edits resulting in |
| /// [expected]. |
| Future<LspChangeVerifier> verifyActionEdits( |
| String content, |
| String expected, { |
| String? filePath, |
| CodeActionKind? kind, |
| String? command, |
| String? title, |
| ProgressToken? commandWorkDoneToken, |
| }) async { |
| filePath ??= mainFilePath; |
| |
| // For convenience, if a test doesn't provide an full set of edits |
| // we assume only a single edit of the file that was being modified. |
| if (!expected.startsWith(LspChangeVerifier.editMarkerStart)) { |
| expected = ''' |
| ${LspChangeVerifier.editMarkerStart} ${relativePath(filePath)} |
| $expected'''; |
| } |
| |
| var action = await expectAction( |
| filePath: filePath, |
| content, |
| kind: kind, |
| command: command, |
| title: title, |
| ); |
| |
| // Verify the edits either by executing the command we expected, or |
| // the edits attached directly to the code action. |
| if (command != null) { |
| return await verifyCommandEdits( |
| action.command!, |
| expected, |
| workDoneToken: commandWorkDoneToken, |
| ); |
| } else { |
| var edit = action.edit!; |
| return verifyEdit(edit, expected); |
| } |
| } |
| } |