blob: ced554ed05e9ad8a508941455871228b37984330 [file]
// Copyright (c) 2026, 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/src/lsp/constants.dart';
import 'package:analysis_server/src/lsp/error_or.dart';
import 'package:analysis_server/src/services/interactive_forms/interactive_forms.dart';
import 'package:analysis_server/src/services/refactoring/client_side_validators.dart';
import 'package:analysis_server/src/services/refactoring/framework/refactoring_producer.dart';
import 'package:analysis_server/src/services/refactoring/legacy/naming_conventions.dart';
import 'package:analysis_server/src/services/refactoring/legacy/refactoring.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:language_server_protocol/protocol_custom_generated.dart';
import 'package:language_server_protocol/protocol_generated.dart';
/// The refactoring that extracts an expression or statements into a new method.
class ExtractMethod extends ParameterizedRefactoringProducer {
static const String commandName = 'dart.refactor.extract_method';
static const String constTitle = 'Extract Method';
new(super.context);
@override
bool get isExperimental => false;
@override
CodeActionKind get kind => CodeActionKind.RefactorExtract;
@override
/// This refactor supports input using the new system (see
/// [buildInteractiveForm]) but not using the old one, so there are no
/// parameters.
List<CommandParameter> get parameters => [];
@override
String get title => constTitle;
/// Builds the [InteractiveForm] to collect input for this refactor.
@override
Future<ErrorOr<InteractiveForm>> buildInteractiveForm() async {
var refactoring = _createRefactoring();
var status = await refactoring.checkInitialConditions();
if (status.hasFatalError) {
return error(
ServerErrorCodes.refactoringComputeStatusFailure,
status.message!,
);
}
var validators = ClientSideValidators(
refactoringContext.clientCapabilities,
);
var nameField = ValidatableFormField(
id: 'name',
description: 'Method Name',
required: true,
defaultValue: refactoring.names.firstOrNull ?? 'newMethod',
type: FormFieldTypeString(validators: validators.methodName),
validate: wrapRefactorValidationFunction(validateMethodName),
);
return success(createForm([nameField]));
}
@override
Future<ComputeStatus> compute(
List<Object?> commandArguments,
ChangeBuilder builder,
) async {
var refactoring = _createRefactoring();
refactoring.name = switch (commandArguments) {
[String name] => name,
_ => refactoring.names.firstOrNull ?? 'newMethod',
};
var status = await refactoring.checkAllConditions();
if (status.hasFatalError) {
return ComputeStatusFailure(reason: status.message);
}
// For non-fatal errors and warnings, we must still compute the change to
// give the user the choice to proceed.
await refactoring.createChange(builder: builder);
if (status.hasError || status.hasWarning) {
return ComputeStatusWarning(status.message);
}
return ComputeStatusSuccess();
}
@override
bool isAvailable() {
if (refactoringContext.server.prefersLegacyRefactors) {
// The legacy refactor will show up in this case.
return false;
}
return _createRefactoring().isAvailable();
}
ExtractMethodRefactoring _createRefactoring() {
return ExtractMethodRefactoring(
searchEngine,
unitResult,
selectionOffset,
selectionLength,
)
// Defaults to true, but may be surprising if users didn't have an option
// to opt in.
..extractAll = false;
}
}