diff --git a/pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart b/pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart index 7f58600057f..92c50907e30 100644 --- a/pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart +++ b/pkg/analysis_server/lib/src/services/refactoring/add_constructor_name.dart @@ -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 get parameters => []; + @override String get title => constTitle; + /// Builds the [InteractiveForm] to collect input for this refactor. + @override + ErrorOr 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 compute( List 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(); diff --git a/pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart b/pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart index fd2b4b32065..a8d9b12474f 100644 --- a/pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart +++ b/pkg/analysis_server/test/src/services/refactoring/add_constructor_name_test.dart @@ -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 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 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()); + } + Future test_primary() async { var originalSource = ''' class C^() {}