From dd9a061f4e6ca3db3b81b8c274bd50b631045bde Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Sat, 18 Jan 2025 10:59:08 -0800 Subject: [PATCH] [analysis_server] Enable "EditArgument" request to work over the legacy protocol + move all related tests to a shared mixin so they run for both servers. Change-Id: I853e8f24948c07fdde2a5d6a64ddc65962357b6d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404841 Commit-Queue: Brian Wilkerson Reviewed-by: Brian Wilkerson Reviewed-by: Phil Quitslund --- .../lib/src/analysis_server.dart | 5 + .../lib/src/legacy_analysis_server.dart | 1 + .../handler_edit_argument.dart | 9 - .../lib/src/lsp/lsp_analysis_server.dart | 4 +- .../test/lsp/edit_argument_test.dart | 718 +---------------- .../lsp_over_legacy/edit_argument_test.dart | 28 + .../test/lsp_over_legacy/test_all.dart | 2 + .../shared/shared_edit_argument_tests.dart | 724 ++++++++++++++++++ 8 files changed, 767 insertions(+), 724 deletions(-) create mode 100644 pkg/analysis_server/test/lsp_over_legacy/edit_argument_test.dart create mode 100644 pkg/analysis_server/test/shared/shared_edit_argument_tests.dart diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart index 84205ef0f822..d8d51deba229 100644 --- a/pkg/analysis_server/lib/src/analysis_server.dart +++ b/pkg/analysis_server/lib/src/analysis_server.dart @@ -1033,6 +1033,11 @@ abstract class AnalysisServer { /// 'lsp.notification' notification. void sendLspNotification(lsp.NotificationMessage notification); + /// Sends an LSP request with the given [params] to the client and waits for a + /// response. Completes with the raw [lsp.ResponseMessage] which could be an + /// error response. + Future sendLspRequest(lsp.Method method, Object params); + /// Sends an error notification to the user. void sendServerErrorNotification( String message, diff --git a/pkg/analysis_server/lib/src/legacy_analysis_server.dart b/pkg/analysis_server/lib/src/legacy_analysis_server.dart index 5ab95bd8196c..d0b21b618b44 100644 --- a/pkg/analysis_server/lib/src/legacy_analysis_server.dart +++ b/pkg/analysis_server/lib/src/legacy_analysis_server.dart @@ -719,6 +719,7 @@ class LegacyAnalysisServer extends AnalysisServer { /// Sends an LSP request to the server (wrapped in 'lsp.handle') and unwraps /// the LSP response from the result of the legacy response. + @override Future sendLspRequest( lsp.Method method, Object params, diff --git a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_edit_argument.dart b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_edit_argument.dart index 5a8a1991f1b7..265ec6383857 100644 --- a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_edit_argument.dart +++ b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_edit_argument.dart @@ -10,7 +10,6 @@ import 'package:analysis_server/src/lsp/constants.dart'; import 'package:analysis_server/src/lsp/error_or.dart'; import 'package:analysis_server/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart'; import 'package:analysis_server/src/lsp/handlers/handlers.dart'; -import 'package:analysis_server/src/lsp/lsp_analysis_server.dart'; import 'package:analysis_server/src/lsp/mapping.dart'; import 'package:analysis_server/src/lsp/source_edits.dart'; import 'package:analyzer/dart/analysis/results.dart'; @@ -286,14 +285,6 @@ class EditArgumentHandler extends SharedMessageHandler /// Sends [workspaceEdit] to the client and returns `null` if applied /// successfully or an error otherwise. Future> _sendEditToClient(WorkspaceEdit workspaceEdit) async { - var server = this.server; - if (server is! LspAnalysisServer) { - return error( - ErrorCodes.RequestFailed, - 'Sending edits is currently only supported for clients using LSP directly', - ); - } - var editDescription = 'Edit argument'; var editResponse = await server.sendLspRequest( Method.workspace_applyEdit, diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart index 0f3ce258fb6e..9357d9fa2a06 100644 --- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart +++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart @@ -861,9 +861,7 @@ class LspAnalysisServer extends AnalysisServer { channel.sendNotification(notification); } - /// Sends a request with the given [params] to the client and wait for a - /// response. Completes with the raw [ResponseMessage] which could be an - /// error response. + @override Future sendLspRequest(Method method, Object params) { var requestId = nextRequestId++; var completer = Completer(); diff --git a/pkg/analysis_server/test/lsp/edit_argument_test.dart b/pkg/analysis_server/test/lsp/edit_argument_test.dart index e7dac5aa8c8e..8b92399bc6a2 100644 --- a/pkg/analysis_server/test/lsp/edit_argument_test.dart +++ b/pkg/analysis_server/test/lsp/edit_argument_test.dart @@ -2,14 +2,11 @@ // 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/handlers/custom/editable_arguments/handler_edit_argument.dart'; -import 'package:analyzer/src/test_utilities/test_code_format.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; -import '../tool/lsp_spec/matchers.dart'; -import '../utils/test_code_extensions.dart'; +import '../shared/shared_edit_argument_tests.dart'; import 'server_abstract.dart'; // ignore_for_file: prefer_single_quotes @@ -218,718 +215,15 @@ class ComputeStringValueTest { } @reflectiveTest -class EditArgumentTest extends SharedAbstractLspAnalysisServerTest { - late TestCode code; - +class EditArgumentTest extends SharedAbstractLspAnalysisServerTest + // Tests are defined in SharedEditArgumentTests because they + // are shared and run for both LSP and Legacy servers. + with + SharedEditArgumentTests { @override void setUp() { super.setUp(); writeTestPackageConfig(flutter: true); } - - test_comma_addArg_addsIfExists() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y })', - originalArgs: '(x: 1,)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(x: 1, y: 2,)', - ); - } - - test_comma_addArg_doesNotAddIfNotExists() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(x: 1, y: 2)', - ); - } - - test_comma_editArg_doesNotAddIfNotExists() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y })', - originalArgs: '(x: 1, y: 1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(x: 1, y: 2)', - ); - } - - test_comma_editArg_retainsIfExists() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y })', - originalArgs: '(x: 1, y: 1,)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(x: 1, y: 2,)', - ); - } - - test_named_addAfterNamed() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(x: 1, y: 2)', - ); - } - - test_named_addAfterNamed_afterChildNotAtEnd() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y, Widget? child })', - originalArgs: '(child: null, x: 1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(child: null, x: 1, y: 2)', - ); - } - - test_named_addAfterNamed_beforeChildAtEnd() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y, Widget? child })', - originalArgs: '(x: 1, child: null)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(x: 1, y: 2, child: null)', - ); - } - - test_named_addAfterNamed_beforeChildrenAtEnd() async { - await _expectSimpleArgumentEdit( - params: '({ int? x, int? y, List? children })', - originalArgs: '(x: 1, children: [])', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(x: 1, y: 2, children: [])', - ); - } - - test_named_addAfterPositional() async { - await _expectSimpleArgumentEdit( - params: '(int? x, { int? y })', - originalArgs: '(1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(1, y: 2)', - ); - } - - test_named_addAfterPositional_afterChildNotAtEnd() async { - await _expectSimpleArgumentEdit( - params: '(int? x, { int? y, Widget? child })', - originalArgs: '(child: null, 1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(child: null, 1, y: 2)', - ); - } - - test_named_addAfterPositional_beforeChildAtEnd() async { - await _expectSimpleArgumentEdit( - params: '(int? x, { int? y, Widget? child })', - originalArgs: '(1, child: null)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(1, y: 2, child: null)', - ); - } - - test_named_addAfterPositional_beforeChildrenAtEnd() async { - await _expectSimpleArgumentEdit( - params: '(int? x, { int? y, List? children })', - originalArgs: '(1, children: [])', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(1, y: 2, children: [])', - ); - } - - test_optionalPositional_addAfterPositional() async { - await _expectSimpleArgumentEdit( - params: '([int? x, int? y])', - originalArgs: '(1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(1, 2)', - ); - } - - test_optionalPositional_notNext_afterPositional() async { - await _expectFailedEdit( - params: '([int? x, int y = 10, int? z])', - originalArgs: '(1)', - edit: ArgumentEdit(name: 'z', newValue: 2), - message: - "Parameter 'z' is not editable: " - "A value for the 3rd parameter can't be added until a value for all preceding positional parameters have been added.", - ); - } - - test_optionalPositional_notNext_solo() async { - await _expectFailedEdit( - params: '([int? x = 10, int? y])', - originalArgs: '()', - edit: ArgumentEdit(name: 'y', newValue: 2), - message: - "Parameter 'y' is not editable: " - "A value for the 2nd parameter can't be added until a value for all preceding positional parameters have been added.", - ); - } - - test_requiredPositional_addAfterNamed() async { - failTestOnErrorDiagnostic = false; // Tests with missing positional. - await _expectSimpleArgumentEdit( - params: '(int? x, { int? y })', - originalArgs: '(y: 1)', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(y: 1, 2)', - ); - } - - test_requiredPositional_addAfterPositional() async { - failTestOnErrorDiagnostic = false; // Tests with missing positional. - await _expectSimpleArgumentEdit( - params: '(int? x, int? y)', - originalArgs: '(1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - expectedArgs: '(1, 2)', - ); - } - - test_requiredPositional_notNext_afterPositional() async { - failTestOnErrorDiagnostic = false; // Tests with missing positional. - await _expectFailedEdit( - params: '(int? x, int? y, int? z)', - originalArgs: '(1)', - edit: ArgumentEdit(name: 'z', newValue: 2), - message: - "Parameter 'z' is not editable: " - "A value for the 3rd parameter can't be added until a value for all preceding positional parameters have been added.", - ); - } - - test_requiredPositional_notNext_noExisting() async { - failTestOnErrorDiagnostic = false; // Tests with missing positional. - await _expectFailedEdit( - params: '(int? x, int? y)', - originalArgs: '()', - edit: ArgumentEdit(name: 'y', newValue: 2), - message: - "Parameter 'y' is not editable: " - "A value for the 2nd parameter can't be added until a value for all preceding positional parameters have been added.", - ); - } - - test_requiredPositional_notNext_onlyNamed() async { - failTestOnErrorDiagnostic = false; // Tests with missing positional. - await _expectFailedEdit( - params: '(int? x, int? y, { int? z })', - originalArgs: '(z: 1)', - edit: ArgumentEdit(name: 'y', newValue: 2), - message: - "Parameter 'y' is not editable: " - "A value for the 2nd parameter can't be added until a value for all preceding positional parameters have been added.", - ); - } - - test_soloArgument_addNamed() async { - await _expectSimpleArgumentEdit( - params: '({int? x })', - originalArgs: '()', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(x: 2)', - ); - } - - test_soloArgument_addOptionalPositional() async { - await _expectSimpleArgumentEdit( - params: '([int? x])', - originalArgs: '()', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(2)', - ); - } - - test_soloArgument_addRequiredPositional() async { - failTestOnErrorDiagnostic = false; // Tests with missing positional. - await _expectSimpleArgumentEdit( - params: '(int? x)', - originalArgs: '()', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(2)', - ); - } - - test_soloArgument_editNamed() async { - await _expectSimpleArgumentEdit( - params: '({int? x })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(x: 2)', - ); - } - - test_soloArgument_editOptionalPositional() async { - await _expectSimpleArgumentEdit( - params: '([int? x])', - originalArgs: '(1)', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(2)', - ); - } - - test_soloArgument_editRequiredPositional() async { - await _expectSimpleArgumentEdit( - params: '(int? x)', - originalArgs: '(1)', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(2)', - ); - } - - test_type_bool_invalidType() async { - await _expectFailedEdit( - params: '({ bool? x })', - originalArgs: '(x: true)', - edit: ArgumentEdit(name: 'x', newValue: 'invalid'), - message: 'Value for parameter "x" should be bool? but was String', - ); - } - - test_type_bool_null_allowed() async { - await _expectSimpleArgumentEdit( - params: '({ bool? x })', - originalArgs: '(x: true)', - edit: ArgumentEdit(name: 'x'), - expectedArgs: '(x: null)', - ); - } - - test_type_bool_null_notAllowed() async { - await _expectFailedEdit( - params: '({ required bool x })', - originalArgs: '(x: true)', - edit: ArgumentEdit(name: 'x'), - message: 'Value for non-nullable parameter "x" cannot be null', - ); - } - - test_type_bool_replaceLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ bool? x })', - originalArgs: '(x: true)', - edit: ArgumentEdit(name: 'x', newValue: false), - expectedArgs: '(x: false)', - ); - } - - test_type_bool_replaceNonLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ bool? x })', - originalArgs: '(x: 1 == 1)', - edit: ArgumentEdit(name: 'x', newValue: false), - expectedArgs: '(x: false)', - ); - } - - test_type_double_invalidType() async { - await _expectFailedEdit( - params: '({ double? x })', - originalArgs: '(x: 1.1)', - edit: ArgumentEdit(name: 'x', newValue: 'invalid'), - message: 'Value for parameter "x" should be double? but was String', - ); - } - - test_type_double_null_allowed() async { - await _expectSimpleArgumentEdit( - params: '({ double? x })', - originalArgs: '(x: 1.0)', - edit: ArgumentEdit(name: 'x'), - expectedArgs: '(x: null)', - ); - } - - test_type_double_null_notAllowed() async { - await _expectFailedEdit( - params: '({ required double x })', - originalArgs: '(x: 1.0)', - edit: ArgumentEdit(name: 'x'), - message: 'Value for non-nullable parameter "x" cannot be null', - ); - } - - test_type_double_replaceInt() async { - await _expectSimpleArgumentEdit( - params: '({ double? x })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'x', newValue: 2.2), - expectedArgs: '(x: 2.2)', - ); - } - - test_type_double_replaceLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ double? x })', - originalArgs: '(x: 1.1)', - edit: ArgumentEdit(name: 'x', newValue: 2.2), - expectedArgs: '(x: 2.2)', - ); - } - - test_type_double_replaceNonLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ double? x })', - originalArgs: '(x: 1.1 + 0.1)', - edit: ArgumentEdit(name: 'x', newValue: 2.2), - expectedArgs: '(x: 2.2)', - ); - } - - test_type_double_replaceWithInt() async { - await _expectSimpleArgumentEdit( - params: '({ double? x })', - originalArgs: '(x: 1.1)', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(x: 2)', - ); - } - - test_type_enum_invalidType() async { - await _expectFailedEdit( - additionalCode: 'enum E { one, two }', - params: '({ E? x })', - originalArgs: '(x: E.one)', - edit: ArgumentEdit(name: 'x', newValue: 'invalid'), - message: - 'Value for parameter "x" should be one of "E.one", "E.two" but was "invalid"', - ); - } - - test_type_enum_null_allowed() async { - await _expectSimpleArgumentEdit( - additionalCode: 'enum E { one, two }', - params: '({ E? x })', - originalArgs: '(x: E.one)', - edit: ArgumentEdit(name: 'x'), - expectedArgs: '(x: null)', - ); - } - - test_type_enum_null_notAllowed() async { - await _expectFailedEdit( - additionalCode: 'enum E { one, two }', - params: '({ required E x })', - originalArgs: '(x: E.one)', - edit: ArgumentEdit(name: 'x'), - message: 'Value for non-nullable parameter "x" cannot be null', - ); - } - - test_type_enum_replaceLiteral() async { - await _expectSimpleArgumentEdit( - additionalCode: 'enum E { one, two }', - params: '({ E? x })', - originalArgs: '(x: E.one)', - edit: ArgumentEdit(name: 'x', newValue: 'E.two'), - expectedArgs: '(x: E.two)', - ); - } - - test_type_enum_replaceNonLiteral() async { - await _expectSimpleArgumentEdit( - additionalCode: ''' -enum E { one, two } -const myConst = E.one; -''', - params: '({ E? x })', - originalArgs: '(x: myConst)', - edit: ArgumentEdit(name: 'x', newValue: 'E.two'), - expectedArgs: '(x: E.two)', - ); - } - - test_type_int_invalidType() async { - await _expectFailedEdit( - params: '({ int? x })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'x', newValue: 'invalid'), - message: 'Value for parameter "x" should be int? but was String', - ); - } - - test_type_int_null_allowed() async { - await _expectSimpleArgumentEdit( - params: '({ int? x })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'x'), - expectedArgs: '(x: null)', - ); - } - - test_type_int_null_notAllowed() async { - await _expectFailedEdit( - params: '({ required int x })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'x'), - message: 'Value for non-nullable parameter "x" cannot be null', - ); - } - - test_type_int_replaceLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ int? x })', - originalArgs: '(x: 1)', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(x: 2)', - ); - } - - test_type_int_replaceNonLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ int? x })', - originalArgs: '(x: 1 + 0)', - edit: ArgumentEdit(name: 'x', newValue: 2), - expectedArgs: '(x: 2)', - ); - } - - test_type_string_containsBackslashes() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x', newValue: r'a\b'), - expectedArgs: r"(x: 'a\\b')", - ); - } - - test_type_string_containsBothQuotes() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x', newValue: '''a'b"c'''), - expectedArgs: r'''(x: 'a\'b"c')''', - ); - } - - test_type_string_containsSingleQuotes() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x', newValue: "a'b"), - expectedArgs: r'''(x: 'a\'b')''', - ); - } - - test_type_string_invalidType() async { - await _expectFailedEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x', newValue: 123), - message: 'Value for parameter "x" should be String? but was int', - ); - } - - test_type_string_multiline() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x', newValue: 'a\nb'), - expectedArgs: r'''(x: 'a\nb')''', - ); - } - - test_type_string_null_allowed() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x'), - expectedArgs: '(x: null)', - ); - } - - test_type_string_null_notAllowed() async { - await _expectFailedEdit( - params: '({ required String x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x'), - message: 'Value for non-nullable parameter "x" cannot be null', - ); - } - - test_type_string_quotes_dollar_escapedNonRaw() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: '')", - edit: ArgumentEdit(name: 'x', newValue: r'$'), - expectedArgs: r"(x: '\$')", - ); - } - - test_type_string_quotes_dollar_notEscapedRaw() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: r'')", - edit: ArgumentEdit(name: 'x', newValue: r'$'), - expectedArgs: r"(x: r'$')", - ); - } - - test_type_string_quotes_usesExistingDouble() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: '(x: "a")', - edit: ArgumentEdit(name: 'x', newValue: 'a'), - expectedArgs: '(x: "a")', - ); - } - - test_type_string_quotes_usesExistingSingle() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x', newValue: 'a'), - expectedArgs: "(x: 'a')", - ); - } - - test_type_string_quotes_usesExistingTripleDouble() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: '(x: """a""")', - edit: ArgumentEdit(name: 'x', newValue: 'a'), - expectedArgs: '(x: """a""")', - ); - } - - test_type_string_quotes_usesExistingTripleSingle() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: '''a''')", - edit: ArgumentEdit(name: 'x', newValue: 'a'), - expectedArgs: "(x: '''a''')", - ); - } - - test_type_string_replaceLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a')", - edit: ArgumentEdit(name: 'x', newValue: 'b'), - expectedArgs: "(x: 'b')", - ); - } - - test_type_string_replaceLiteral_raw() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: r'a')", - edit: ArgumentEdit(name: 'x', newValue: 'b'), - expectedArgs: "(x: r'b')", - ); - } - - test_type_string_replaceLiteral_tripleQuoted() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: '''a''')", - edit: ArgumentEdit(name: 'x', newValue: 'b'), - expectedArgs: "(x: '''b''')", - ); - } - - test_type_string_replaceNonLiteral() async { - await _expectSimpleArgumentEdit( - params: '({ String? x })', - originalArgs: "(x: 'a' + 'a')", - edit: ArgumentEdit(name: 'x', newValue: 'b'), - expectedArgs: "(x: 'b')", - ); - } - - /// Initializes the server with [content] and tries to apply the argument - /// [edit] at the marked location. Verifies the changes made match - /// [expectedContent]. - Future _expectArgumentEdit( - String content, - ArgumentEdit edit, - String expectedContent, { - bool open = true, - }) async { - code = TestCode.parse(content); - createFile(testFilePath, code.code); - await initializeServer(); - if (open) { - await openFile(testFileUri, code.code); - } - await currentAnalysis; - var verifier = await executeForEdits( - () => editArgument(testFileUri, code.position.position, edit), - ); - - verifier.verifyFiles(expectedContent); - } - - /// Initializes the server and verifies a simple argument edit fails with - /// a given message. - Future _expectFailedEdit({ - required String params, - required String originalArgs, - required ArgumentEdit edit, - required String message, - String? additionalCode, - }) async { - additionalCode ??= ''; - var content = ''' -import 'package:flutter/widgets.dart'; - -$additionalCode - -class MyWidget extends StatelessWidget { - const MyWidget$params; - - @override - Widget build(BuildContext context) => MyW^idget$originalArgs; -} -'''; - - code = TestCode.parse(content); - createFile(testFilePath, code.code); - await initializeServer(); - await currentAnalysis; - - await expectLater( - editArgument(testFileUri, code.position.position, edit), - throwsA(isResponseError(ErrorCodes.RequestFailed, message: message)), - ); - } - - /// Initializes the server and verifies a simple argument edit. - Future _expectSimpleArgumentEdit({ - required String params, - required String originalArgs, - required ArgumentEdit edit, - required String expectedArgs, - String? additionalCode, - }) async { - additionalCode ??= ''; - var content = ''' -import 'package:flutter/widgets.dart'; - -$additionalCode - -class MyWidget extends StatelessWidget { - const MyWidget$params; - - @override - Widget build(BuildContext context) => MyW^idget$originalArgs; -} -'''; - var expectedContent = ''' ->>>>>>>>>> lib/test.dart -import 'package:flutter/widgets.dart'; - -$additionalCode - -class MyWidget extends StatelessWidget { - const MyWidget$params; - - @override - Widget build(BuildContext context) => MyWidget$expectedArgs; -} -'''; - - await _expectArgumentEdit(content, edit, expectedContent); - } } diff --git a/pkg/analysis_server/test/lsp_over_legacy/edit_argument_test.dart b/pkg/analysis_server/test/lsp_over_legacy/edit_argument_test.dart new file mode 100644 index 000000000000..c3132e9ba36b --- /dev/null +++ b/pkg/analysis_server/test/lsp_over_legacy/edit_argument_test.dart @@ -0,0 +1,28 @@ +// Copyright (c) 2025, 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:test_reflective_loader/test_reflective_loader.dart'; + +import '../shared/shared_edit_argument_tests.dart'; +import 'abstract_lsp_over_legacy.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(EditArgumentTest); + }); +} + +@reflectiveTest +class EditArgumentTest extends SharedLspOverLegacyTest + with + // Tests are defined in SharedEditArgumentTests because they + // are shared and run for both LSP and Legacy servers. + SharedEditArgumentTests { + @override + Future setUp() async { + await super.setUp(); + + writeTestPackageConfig(flutter: true); + } +} diff --git a/pkg/analysis_server/test/lsp_over_legacy/test_all.dart b/pkg/analysis_server/test/lsp_over_legacy/test_all.dart index afc8547e3e10..504b2802372d 100644 --- a/pkg/analysis_server/test/lsp_over_legacy/test_all.dart +++ b/pkg/analysis_server/test/lsp_over_legacy/test_all.dart @@ -10,6 +10,7 @@ import 'dart_text_document_content_provider_test.dart' import 'document_color_test.dart' as document_color; import 'document_highlights_test.dart' as document_highlights; import 'document_symbols_test.dart' as document_symbols; +import 'edit_argument_test.dart' as edit_argument; import 'editable_arguments_test.dart' as editable_arguments; import 'format_test.dart' as format; import 'hover_test.dart' as hover; @@ -28,6 +29,7 @@ void main() { document_color.main; document_highlights.main(); document_symbols.main(); + edit_argument.main(); editable_arguments.main(); format.main(); hover.main(); diff --git a/pkg/analysis_server/test/shared/shared_edit_argument_tests.dart b/pkg/analysis_server/test/shared/shared_edit_argument_tests.dart new file mode 100644 index 000000000000..fc543b26d06b --- /dev/null +++ b/pkg/analysis_server/test/shared/shared_edit_argument_tests.dart @@ -0,0 +1,724 @@ +// Copyright (c) 2025, 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:analyzer/src/test_utilities/test_code_format.dart'; +import 'package:test/test.dart'; + +import '../lsp/request_helpers_mixin.dart'; +import '../tool/lsp_spec/matchers.dart'; +import '../utils/test_code_extensions.dart'; +import 'shared_test_interface.dart'; + +/// Shared edit argument tests that are used by both LSP + Legacy server +/// tests. +mixin SharedEditArgumentTests + on SharedTestInterface, LspRequestHelpersMixin, LspVerifyEditHelpersMixin { + late TestCode code; + + test_comma_addArg_addsIfExists() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y })', + originalArgs: '(x: 1,)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(x: 1, y: 2,)', + ); + } + + test_comma_addArg_doesNotAddIfNotExists() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(x: 1, y: 2)', + ); + } + + test_comma_editArg_doesNotAddIfNotExists() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y })', + originalArgs: '(x: 1, y: 1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(x: 1, y: 2)', + ); + } + + test_comma_editArg_retainsIfExists() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y })', + originalArgs: '(x: 1, y: 1,)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(x: 1, y: 2,)', + ); + } + + test_named_addAfterNamed() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(x: 1, y: 2)', + ); + } + + test_named_addAfterNamed_afterChildNotAtEnd() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y, Widget? child })', + originalArgs: '(child: null, x: 1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(child: null, x: 1, y: 2)', + ); + } + + test_named_addAfterNamed_beforeChildAtEnd() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y, Widget? child })', + originalArgs: '(x: 1, child: null)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(x: 1, y: 2, child: null)', + ); + } + + test_named_addAfterNamed_beforeChildrenAtEnd() async { + await _expectSimpleArgumentEdit( + params: '({ int? x, int? y, List? children })', + originalArgs: '(x: 1, children: [])', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(x: 1, y: 2, children: [])', + ); + } + + test_named_addAfterPositional() async { + await _expectSimpleArgumentEdit( + params: '(int? x, { int? y })', + originalArgs: '(1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(1, y: 2)', + ); + } + + test_named_addAfterPositional_afterChildNotAtEnd() async { + await _expectSimpleArgumentEdit( + params: '(int? x, { int? y, Widget? child })', + originalArgs: '(child: null, 1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(child: null, 1, y: 2)', + ); + } + + test_named_addAfterPositional_beforeChildAtEnd() async { + await _expectSimpleArgumentEdit( + params: '(int? x, { int? y, Widget? child })', + originalArgs: '(1, child: null)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(1, y: 2, child: null)', + ); + } + + test_named_addAfterPositional_beforeChildrenAtEnd() async { + await _expectSimpleArgumentEdit( + params: '(int? x, { int? y, List? children })', + originalArgs: '(1, children: [])', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(1, y: 2, children: [])', + ); + } + + test_optionalPositional_addAfterPositional() async { + await _expectSimpleArgumentEdit( + params: '([int? x, int? y])', + originalArgs: '(1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(1, 2)', + ); + } + + test_optionalPositional_notNext_afterPositional() async { + await _expectFailedEdit( + params: '([int? x, int y = 10, int? z])', + originalArgs: '(1)', + edit: ArgumentEdit(name: 'z', newValue: 2), + message: + "Parameter 'z' is not editable: " + "A value for the 3rd parameter can't be added until a value for all preceding positional parameters have been added.", + ); + } + + test_optionalPositional_notNext_solo() async { + await _expectFailedEdit( + params: '([int? x = 10, int? y])', + originalArgs: '()', + edit: ArgumentEdit(name: 'y', newValue: 2), + message: + "Parameter 'y' is not editable: " + "A value for the 2nd parameter can't be added until a value for all preceding positional parameters have been added.", + ); + } + + test_requiredPositional_addAfterNamed() async { + failTestOnErrorDiagnostic = false; // Tests with missing positional. + await _expectSimpleArgumentEdit( + params: '(int? x, { int? y })', + originalArgs: '(y: 1)', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(y: 1, 2)', + ); + } + + test_requiredPositional_addAfterPositional() async { + failTestOnErrorDiagnostic = false; // Tests with missing positional. + await _expectSimpleArgumentEdit( + params: '(int? x, int? y)', + originalArgs: '(1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + expectedArgs: '(1, 2)', + ); + } + + test_requiredPositional_notNext_afterPositional() async { + failTestOnErrorDiagnostic = false; // Tests with missing positional. + await _expectFailedEdit( + params: '(int? x, int? y, int? z)', + originalArgs: '(1)', + edit: ArgumentEdit(name: 'z', newValue: 2), + message: + "Parameter 'z' is not editable: " + "A value for the 3rd parameter can't be added until a value for all preceding positional parameters have been added.", + ); + } + + test_requiredPositional_notNext_noExisting() async { + failTestOnErrorDiagnostic = false; // Tests with missing positional. + await _expectFailedEdit( + params: '(int? x, int? y)', + originalArgs: '()', + edit: ArgumentEdit(name: 'y', newValue: 2), + message: + "Parameter 'y' is not editable: " + "A value for the 2nd parameter can't be added until a value for all preceding positional parameters have been added.", + ); + } + + test_requiredPositional_notNext_onlyNamed() async { + failTestOnErrorDiagnostic = false; // Tests with missing positional. + await _expectFailedEdit( + params: '(int? x, int? y, { int? z })', + originalArgs: '(z: 1)', + edit: ArgumentEdit(name: 'y', newValue: 2), + message: + "Parameter 'y' is not editable: " + "A value for the 2nd parameter can't be added until a value for all preceding positional parameters have been added.", + ); + } + + test_soloArgument_addNamed() async { + await _expectSimpleArgumentEdit( + params: '({int? x })', + originalArgs: '()', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(x: 2)', + ); + } + + test_soloArgument_addOptionalPositional() async { + await _expectSimpleArgumentEdit( + params: '([int? x])', + originalArgs: '()', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(2)', + ); + } + + test_soloArgument_addRequiredPositional() async { + failTestOnErrorDiagnostic = false; // Tests with missing positional. + await _expectSimpleArgumentEdit( + params: '(int? x)', + originalArgs: '()', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(2)', + ); + } + + test_soloArgument_editNamed() async { + await _expectSimpleArgumentEdit( + params: '({int? x })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(x: 2)', + ); + } + + test_soloArgument_editOptionalPositional() async { + await _expectSimpleArgumentEdit( + params: '([int? x])', + originalArgs: '(1)', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(2)', + ); + } + + test_soloArgument_editRequiredPositional() async { + await _expectSimpleArgumentEdit( + params: '(int? x)', + originalArgs: '(1)', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(2)', + ); + } + + test_type_bool_invalidType() async { + await _expectFailedEdit( + params: '({ bool? x })', + originalArgs: '(x: true)', + edit: ArgumentEdit(name: 'x', newValue: 'invalid'), + message: 'Value for parameter "x" should be bool? but was String', + ); + } + + test_type_bool_null_allowed() async { + await _expectSimpleArgumentEdit( + params: '({ bool? x })', + originalArgs: '(x: true)', + edit: ArgumentEdit(name: 'x'), + expectedArgs: '(x: null)', + ); + } + + test_type_bool_null_notAllowed() async { + await _expectFailedEdit( + params: '({ required bool x })', + originalArgs: '(x: true)', + edit: ArgumentEdit(name: 'x'), + message: 'Value for non-nullable parameter "x" cannot be null', + ); + } + + test_type_bool_replaceLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ bool? x })', + originalArgs: '(x: true)', + edit: ArgumentEdit(name: 'x', newValue: false), + expectedArgs: '(x: false)', + ); + } + + test_type_bool_replaceNonLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ bool? x })', + originalArgs: '(x: 1 == 1)', + edit: ArgumentEdit(name: 'x', newValue: false), + expectedArgs: '(x: false)', + ); + } + + test_type_double_invalidType() async { + await _expectFailedEdit( + params: '({ double? x })', + originalArgs: '(x: 1.1)', + edit: ArgumentEdit(name: 'x', newValue: 'invalid'), + message: 'Value for parameter "x" should be double? but was String', + ); + } + + test_type_double_null_allowed() async { + await _expectSimpleArgumentEdit( + params: '({ double? x })', + originalArgs: '(x: 1.0)', + edit: ArgumentEdit(name: 'x'), + expectedArgs: '(x: null)', + ); + } + + test_type_double_null_notAllowed() async { + await _expectFailedEdit( + params: '({ required double x })', + originalArgs: '(x: 1.0)', + edit: ArgumentEdit(name: 'x'), + message: 'Value for non-nullable parameter "x" cannot be null', + ); + } + + test_type_double_replaceInt() async { + await _expectSimpleArgumentEdit( + params: '({ double? x })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'x', newValue: 2.2), + expectedArgs: '(x: 2.2)', + ); + } + + test_type_double_replaceLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ double? x })', + originalArgs: '(x: 1.1)', + edit: ArgumentEdit(name: 'x', newValue: 2.2), + expectedArgs: '(x: 2.2)', + ); + } + + test_type_double_replaceNonLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ double? x })', + originalArgs: '(x: 1.1 + 0.1)', + edit: ArgumentEdit(name: 'x', newValue: 2.2), + expectedArgs: '(x: 2.2)', + ); + } + + test_type_double_replaceWithInt() async { + await _expectSimpleArgumentEdit( + params: '({ double? x })', + originalArgs: '(x: 1.1)', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(x: 2)', + ); + } + + test_type_enum_invalidType() async { + await _expectFailedEdit( + additionalCode: 'enum E { one, two }', + params: '({ E? x })', + originalArgs: '(x: E.one)', + edit: ArgumentEdit(name: 'x', newValue: 'invalid'), + message: + 'Value for parameter "x" should be one of "E.one", "E.two" but was "invalid"', + ); + } + + test_type_enum_null_allowed() async { + await _expectSimpleArgumentEdit( + additionalCode: 'enum E { one, two }', + params: '({ E? x })', + originalArgs: '(x: E.one)', + edit: ArgumentEdit(name: 'x'), + expectedArgs: '(x: null)', + ); + } + + test_type_enum_null_notAllowed() async { + await _expectFailedEdit( + additionalCode: 'enum E { one, two }', + params: '({ required E x })', + originalArgs: '(x: E.one)', + edit: ArgumentEdit(name: 'x'), + message: 'Value for non-nullable parameter "x" cannot be null', + ); + } + + test_type_enum_replaceLiteral() async { + await _expectSimpleArgumentEdit( + additionalCode: 'enum E { one, two }', + params: '({ E? x })', + originalArgs: '(x: E.one)', + edit: ArgumentEdit(name: 'x', newValue: 'E.two'), + expectedArgs: '(x: E.two)', + ); + } + + test_type_enum_replaceNonLiteral() async { + await _expectSimpleArgumentEdit( + additionalCode: ''' +enum E { one, two } +const myConst = E.one; +''', + params: '({ E? x })', + originalArgs: '(x: myConst)', + edit: ArgumentEdit(name: 'x', newValue: 'E.two'), + expectedArgs: '(x: E.two)', + ); + } + + test_type_int_invalidType() async { + await _expectFailedEdit( + params: '({ int? x })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'x', newValue: 'invalid'), + message: 'Value for parameter "x" should be int? but was String', + ); + } + + test_type_int_null_allowed() async { + await _expectSimpleArgumentEdit( + params: '({ int? x })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'x'), + expectedArgs: '(x: null)', + ); + } + + test_type_int_null_notAllowed() async { + await _expectFailedEdit( + params: '({ required int x })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'x'), + message: 'Value for non-nullable parameter "x" cannot be null', + ); + } + + test_type_int_replaceLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ int? x })', + originalArgs: '(x: 1)', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(x: 2)', + ); + } + + test_type_int_replaceNonLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ int? x })', + originalArgs: '(x: 1 + 0)', + edit: ArgumentEdit(name: 'x', newValue: 2), + expectedArgs: '(x: 2)', + ); + } + + test_type_string_containsBackslashes() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x', newValue: r'a\b'), + expectedArgs: r"(x: 'a\\b')", + ); + } + + test_type_string_containsBothQuotes() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x', newValue: '''a'b"c'''), + expectedArgs: r'''(x: 'a\'b"c')''', + ); + } + + test_type_string_containsSingleQuotes() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x', newValue: "a'b"), + expectedArgs: r'''(x: 'a\'b')''', + ); + } + + test_type_string_invalidType() async { + await _expectFailedEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x', newValue: 123), + message: 'Value for parameter "x" should be String? but was int', + ); + } + + test_type_string_multiline() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x', newValue: 'a\nb'), + expectedArgs: r'''(x: 'a\nb')''', + ); + } + + test_type_string_null_allowed() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x'), + expectedArgs: '(x: null)', + ); + } + + test_type_string_null_notAllowed() async { + await _expectFailedEdit( + params: '({ required String x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x'), + message: 'Value for non-nullable parameter "x" cannot be null', + ); + } + + test_type_string_quotes_dollar_escapedNonRaw() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: '')", + edit: ArgumentEdit(name: 'x', newValue: r'$'), + expectedArgs: r"(x: '\$')", + ); + } + + test_type_string_quotes_dollar_notEscapedRaw() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: r'')", + edit: ArgumentEdit(name: 'x', newValue: r'$'), + expectedArgs: r"(x: r'$')", + ); + } + + test_type_string_quotes_usesExistingDouble() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: '(x: "a")', + edit: ArgumentEdit(name: 'x', newValue: 'a'), + expectedArgs: '(x: "a")', + ); + } + + test_type_string_quotes_usesExistingSingle() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x', newValue: 'a'), + expectedArgs: "(x: 'a')", + ); + } + + test_type_string_quotes_usesExistingTripleDouble() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: '(x: """a""")', + edit: ArgumentEdit(name: 'x', newValue: 'a'), + expectedArgs: '(x: """a""")', + ); + } + + test_type_string_quotes_usesExistingTripleSingle() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: '''a''')", + edit: ArgumentEdit(name: 'x', newValue: 'a'), + expectedArgs: "(x: '''a''')", + ); + } + + test_type_string_replaceLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a')", + edit: ArgumentEdit(name: 'x', newValue: 'b'), + expectedArgs: "(x: 'b')", + ); + } + + test_type_string_replaceLiteral_raw() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: r'a')", + edit: ArgumentEdit(name: 'x', newValue: 'b'), + expectedArgs: "(x: r'b')", + ); + } + + test_type_string_replaceLiteral_tripleQuoted() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: '''a''')", + edit: ArgumentEdit(name: 'x', newValue: 'b'), + expectedArgs: "(x: '''b''')", + ); + } + + test_type_string_replaceNonLiteral() async { + await _expectSimpleArgumentEdit( + params: '({ String? x })', + originalArgs: "(x: 'a' + 'a')", + edit: ArgumentEdit(name: 'x', newValue: 'b'), + expectedArgs: "(x: 'b')", + ); + } + + /// Initializes the server with [content] and tries to apply the argument + /// [edit] at the marked location. Verifies the changes made match + /// [expectedContent]. + Future _expectArgumentEdit( + String content, + ArgumentEdit edit, + String expectedContent, { + bool open = true, + }) async { + code = TestCode.parse(content); + createFile(testFilePath, code.code); + await initializeServer(); + if (open) { + await openFile(testFileUri, code.code); + } + await currentAnalysis; + var verifier = await executeForEdits( + () => editArgument(testFileUri, code.position.position, edit), + ); + + verifier.verifyFiles(expectedContent); + } + + /// Initializes the server and verifies a simple argument edit fails with + /// a given message. + Future _expectFailedEdit({ + required String params, + required String originalArgs, + required ArgumentEdit edit, + required String message, + String? additionalCode, + }) async { + additionalCode ??= ''; + var content = ''' +import 'package:flutter/widgets.dart'; + +$additionalCode + +class MyWidget extends StatelessWidget { + const MyWidget$params; + + @override + Widget build(BuildContext context) => MyW^idget$originalArgs; +} +'''; + + code = TestCode.parse(content); + createFile(testFilePath, code.code); + await initializeServer(); + await currentAnalysis; + + await expectLater( + editArgument(testFileUri, code.position.position, edit), + throwsA(isResponseError(ErrorCodes.RequestFailed, message: message)), + ); + } + + /// Initializes the server and verifies a simple argument edit. + Future _expectSimpleArgumentEdit({ + required String params, + required String originalArgs, + required ArgumentEdit edit, + required String expectedArgs, + String? additionalCode, + }) async { + additionalCode ??= ''; + var content = ''' +import 'package:flutter/widgets.dart'; + +$additionalCode + +class MyWidget extends StatelessWidget { + const MyWidget$params; + + @override + Widget build(BuildContext context) => MyW^idget$originalArgs; +} +'''; + var expectedContent = ''' +>>>>>>>>>> lib/test.dart +import 'package:flutter/widgets.dart'; + +$additionalCode + +class MyWidget extends StatelessWidget { + const MyWidget$params; + + @override + Widget build(BuildContext context) => MyWidget$expectedArgs; +} +'''; + + await _expectArgumentEdit(content, edit, expectedContent); + } +}