[analysis_server] Support prompting for name for "Add Constructor Name" refactor

Similar to the previous CL for Add Import Prefix, this adds support for prompting for a name for "Add Constructor Name" if Interactive Forms are available.

Change-Id: I952695df03016a549345f329ac04961d7c279af1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509380
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Danny Tuppeny
2026-06-04 10:41:49 -07:00
committed by Brian Wilkerson
parent 8f30cb7e43
commit dbe61d2ece
2 changed files with 112 additions and 3 deletions
@@ -3,17 +3,21 @@
// 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/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:analysis_server/src/services/search/search_engine_internal.dart';
import 'package:analysis_server/src/utilities/extensions/selection.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.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 adds a name to an unnamed constructor.
class AddConstructorName extends RefactoringProducer {
class AddConstructorName extends ParameterizedRefactoringProducer {
static const String commandName = 'dart.refactor.add_constructor_name';
static const String constTitle = 'Add a name to the constructor';
@@ -26,14 +30,51 @@ class AddConstructorName extends RefactoringProducer {
@override
CodeActionKind get kind => DartCodeActionKind.refactorAdd;
@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
ErrorOr<InteractiveForm> buildInteractiveForm() {
var element = selection?.constructor(mustNotHaveName: true);
if (element == null) {
// We shouldn't have gotten here if the selection was not valid for this
// refactor, but return a useful error to aid debugging if so.
return error(
ErrorCodes.InvalidParams,
'The selection is not valid for adding a constructor name',
);
}
var nameField = ValidatableFormField(
id: 'name',
description: 'Constructor Name',
required: true,
defaultValue: _computeName(element),
type: FormFieldTypeString(),
validate: wrapRefactorValidationFunction(validateConstructorName),
);
return success(createForm([nameField]));
}
@override
Future<ComputeStatus> compute(
List<Object?> commandArguments,
ChangeBuilder builder,
) async {
// Handle optional name in the arguments (if Interactive Forms were used).
var constructorName = switch (commandArguments) {
[String name] => name,
_ => null,
};
var element = selection?.constructor(mustNotHaveName: true);
if (element == null) {
// This should never happen because `isAvailable` would have returned
@@ -45,7 +86,8 @@ class AddConstructorName extends RefactoringProducer {
if (refactoring == null) {
return ComputeStatusFailure();
}
refactoring.newName = _computeName(element);
constructorName ??= _computeName(element);
refactoring.newName = constructorName;
var status = await refactoring.checkAllConditions();
if (status.hasError) {
return ComputeStatusFailure();
@@ -3,9 +3,13 @@
// 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/extensions/code_action.dart';
import 'package:analysis_server/src/services/refactoring/add_constructor_name.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../support/interactive_forms.dart';
import '../../../utils/lsp_protocol_extensions.dart';
import 'refactoring_test_support.dart';
void main() {
@@ -17,7 +21,70 @@ void main() {
}
@reflectiveTest
class AddConstructorNameInClassTest extends _AddConstructorNameTest {
class AddConstructorNameInClassTest extends _AddConstructorNameTest
with InteractiveFormsTestMixin {
Future<void> test_interactiveForm_clientModifiedValues() async {
setSupportedInteractiveFormInputKinds({'string'});
var originalSource = '''
class C^() {}
void f() {
C();
}
''';
var expected = '''
>>>>>>>>>> lib/main.dart
class C.customName() {}
void f() {
C.customName();
}
''';
addTestSource(originalSource);
await initializeServer(experimentalInteractiveForms: true);
var action = await expectCodeActionWithTitle(refactoringTitle);
var completedCommand = await completeInteractiveForm(action.command!, {
'name': 'customName',
});
await verifyCommandEdits(completedCommand, expected);
}
Future<void> test_interactiveForm_expectedFields() async {
setSupportedInteractiveFormInputKinds({'string'});
var originalSource = '''
class C^() {}
void f() {
C();
}
''';
addTestSource(originalSource);
await initializeServer(experimentalInteractiveForms: true);
var action = await expectCodeActionWithTitle(refactoringTitle);
var command = action.asCommand;
var interactiveCommand = await resolveCommand(
ExecuteCommandParams(
command: command.command,
arguments: command.arguments,
),
);
expect(interactiveCommand.formFields, hasLength(1));
var field = interactiveCommand.formFields!.single;
expect(field.id, 'name');
expect(field.description, 'Constructor Name');
expect(field.defaultValue, 'name');
expect(field.error, isNull);
expect(field.type, isA<FormFieldTypeString>());
}
Future<void> test_primary() async {
var originalSource = '''
class C^() {}