blob: 702a24f8d53121e33fb64911ac2768f650dc2d81 [file] [log] [blame]
// Copyright (c) 2022, 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/lsp/constants.dart';
import 'package:analysis_server/src/lsp/error_or.dart';
import 'package:analysis_server/src/lsp/handlers/commands/simple_edit_handler.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/progress.dart';
import 'package:analysis_server/src/lsp/source_edits.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_context.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_processor.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_producer.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
/// A command handler for any of the commands used to implement refactorings.
class RefactorCommandHandler extends SimpleEditCommandHandler {
@override
final String commandName;
final RefactoringProducerGenerator generator;
RefactorCommandHandler(super.server, this.commandName, this.generator);
@override
Future<ErrorOr<void>> handle(
MessageInfo message,
Map<String, Object?> parameters,
ProgressReporter progress,
CancellationToken cancellationToken,
) async {
var filePath = parameters['filePath'];
var offset = parameters['selectionOffset'];
var length = parameters['selectionLength'];
var arguments = _validateArguments(parameters['arguments']);
if (filePath is! String ||
offset is! int ||
length is! int ||
arguments == null) {
return ErrorOr.error(ResponseError(
code: ServerErrorCodes.InvalidCommandArguments,
message: 'Refactoring operations require 4 parameters: '
'filePath: String, '
'offset: int, '
'length: int, '
'arguments: List'));
}
// Use the editor capabilities, since we're building edits to send to the
// editor regardless of who called us.
var clientCapabilities = server.editorClientCapabilities;
if (clientCapabilities == null) {
// This should not happen unless a client misbehaves.
return serverNotInitializedError;
}
var library = await requireResolvedLibrary(filePath);
return library.mapResult((library) async {
var unit = library.unitWithPath(filePath);
if (unit == null) {
return error(ErrorCodes.InternalError,
'The library containing a path did not contain the path.');
}
var context = RefactoringContext(
server: server,
startSessions: await server.currentSessions,
resolvedLibraryResult: library,
resolvedUnitResult: unit,
clientCapabilities: clientCapabilities,
selectionOffset: offset,
selectionLength: length,
includeExperimental:
server.lspClientConfiguration.global.experimentalRefactors,
);
var producer = generator(context);
var builder = ChangeBuilder(
workspace: context.workspace, eol: context.utils.endOfLine);
var status = await producer.compute(arguments, builder);
if (status is ComputeStatusFailure) {
var reason = status.reason ?? 'Cannot compute the change. No details.';
return ErrorOr.error(
ResponseError(
code: ServerErrorCodes.RefactoringComputeStatusFailure,
message: reason,
),
);
}
var edits = builder.sourceChange.edits;
if (edits.isEmpty) {
return success(null);
}
var fileEdits = <FileEditInformation>[];
for (var edit in edits) {
var path = edit.file;
var fileResult = context.session.getFile(path);
if (fileResult is! FileResult) {
return ErrorOr.error(ResponseError(
code: ServerErrorCodes.FileAnalysisFailed,
message: 'Could not access "$path".'));
}
var docIdentifier = server.getVersionedDocumentIdentifier(path);
fileEdits.add(FileEditInformation(
docIdentifier, fileResult.lineInfo, edit.edits,
newFile: edit.fileStamp == -1));
}
var workspaceEdit = toWorkspaceEdit(clientCapabilities, fileEdits);
return sendWorkspaceEditToClient(workspaceEdit);
});
}
/// If the [arguments] is a list, then return it. Otherwise, return `null`
/// to indicate that they aren't what we were expecting.
List<Object?>? _validateArguments(Object? arguments) {
if (arguments is! List<Object?>) {
return null;
}
return arguments;
}
}