[analysis_server] Move all EditableArgument tests to a shared mixin and run for legacy

This moves all tests (without any changes) to a shared mixin, and then applies that mixin to the original test class. It also adds a new test class for LSP-over-Legacy, meaning the tests will now run for both protocols.

There are two failing tests when using LSP-over-Legacy so they are overridden with `@FailingTest()` temporarily.

Change-Id: I8c7efcf8f5d85d874b3483f4f3953d4d8b68a31a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404621
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Phil Quitslund <pquitslund@google.com>
diff --git a/pkg/analysis_server/test/lsp/editable_arguments_test.dart b/pkg/analysis_server/test/lsp/editable_arguments_test.dart
index 844f0a8..5aa0c09 100644
--- a/pkg/analysis_server/test/lsp/editable_arguments_test.dart
+++ b/pkg/analysis_server/test/lsp/editable_arguments_test.dart
@@ -2,12 +2,9 @@
 // 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 'package:test_reflective_loader/test_reflective_loader.dart';
 
-import '../utils/test_code_extensions.dart';
+import '../shared/shared_editable_arguments_tests.dart';
 import 'server_abstract.dart';
 
 void main() {
@@ -17,1265 +14,12 @@
 }
 
 @reflectiveTest
-class EditableArgumentsTest extends AbstractLspAnalysisServerTest {
-  late TestCode code;
-
-  /// Initializes the server with [content] and fetches editable arguments.
-  Future<EditableArguments?> getEditableArgumentsFor(
-    String content, {
-    bool open = true,
-  }) async {
-    code = TestCode.parse('''
-import 'package:flutter/widgets.dart';
-
-$content
-''');
-    newFile(mainFilePath, code.code);
-    await initialize();
-    if (open) {
-      await openFile(mainFileUri, code.code);
-    }
-    await initialAnalysis;
-    return await getEditableArguments(mainFileUri, code.position.position);
-  }
-
-  Matcher hasArg(Matcher matcher) {
-    return hasArgs(contains(matcher));
-  }
-
-  Matcher hasArgNamed(String argumentName) {
-    return hasArg(isArg(argumentName));
-  }
-
-  Matcher hasArgs(Matcher matcher) {
-    return isA<EditableArguments>().having(
-      (arguments) => arguments.arguments,
-      'arguments',
-      matcher,
-    );
-  }
-
-  Matcher isArg(
-    String name, {
-    Object? type = anything,
-    Object? value = anything,
-    Object? displayValue = anything,
-    Object? hasArgument = anything,
-    Object? isDefault = anything,
-    Object? isRequired = anything,
-    Object? isNullable = anything,
-    Object? isEditable = anything,
-    Object? notEditableReason = anything,
-    Object? options = anything,
-  }) {
-    return isA<EditableArgument>()
-        .having((arg) => arg.name, 'name', name)
-        .having((arg) => arg.type, 'type', type)
-        .having((arg) => arg.value, 'value', value)
-        .having((arg) => arg.displayValue, 'displayValue', displayValue)
-        .having((arg) => arg.hasArgument, 'hasArgument', hasArgument)
-        .having((arg) => arg.isDefault, 'isDefault', isDefault)
-        .having((arg) => arg.isRequired, 'isRequired', isRequired)
-        .having((arg) => arg.isNullable, 'isNullable', isNullable)
-        .having((arg) => arg.isEditable, 'isEditable', isEditable)
-        .having(
-          (arg) => arg.notEditableReason,
-          'notEditableReason',
-          notEditableReason,
-        )
-        .having((arg) => arg.options, 'options', options)
-        // Some extra checks that should be true for all.
-        .having(
-          (arg) =>
-              arg.value == null ||
-              arg.value?.toString() != arg.displayValue?.toString(),
-          'different value and displayValues',
-          isTrue,
-        )
-        .having(
-          (arg) => (arg.notEditableReason == null) == arg.isEditable,
-          'notEditableReason must be supplied if isEditable=false',
-          isTrue,
-        )
-        .having(
-          (arg) => arg.value == null || arg.isEditable,
-          'isEditable must be true if there is a value',
-          isTrue,
-        )
-        .having(
-          (arg) =>
-              arg.type == 'enum'
-                  ? (arg.options?.isNotEmpty ?? false)
-                  : arg.options == null,
-          'enum types must have options / non-enums must not have options',
-          isTrue,
-        );
-  }
-
+class EditableArgumentsTest extends SharedAbstractLspAnalysisServerTest
+    with SharedEditableArgumentsTests {
   @override
   void setUp() {
     super.setUp();
 
     writeTestPackageConfig(flutter: true);
   }
-
-  test_hasArgument() async {
-    failTestOnErrorDiagnostic = false;
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget(
-    int? aPositionalSupplied,
-    int? aPositionalNotSupplied, {
-    int? aNamedSupplied,
-    int? aNamedNotSupplied,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(1, aNamedSupplied: 1);
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        unorderedEquals([
-          isArg('aPositionalSupplied', hasArgument: true),
-          isArg('aPositionalNotSupplied', hasArgument: false),
-          isArg('aNamedSupplied', hasArgument: true),
-          isArg('aNamedNotSupplied', hasArgument: false),
-        ]),
-      ),
-    );
-  }
-
-  test_isEditable_false_positional_optional() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget([int? a, int? b, int? c]);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(1);
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('a', isEditable: true),
-          isArg('b', isEditable: true),
-          isArg(
-            'c',
-            // c is not editable because it is not guaranteed that we can insert
-            // a default value for b (it could be a private value or require
-            // imports).
-            isEditable: false,
-            notEditableReason:
-                "A value for the 3rd parameter can't be added until a value "
-                'for all preceding positional parameters have been added.',
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_isEditable_false_positional_required1() async {
-    failTestOnErrorDiagnostic = false;
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(int a, int b);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget();
-}
-''');
-    expect(
-      result,
-      hasArg(
-        // b is not editable because there are missing required previous
-        // arguments (a).
-        isArg(
-          'b',
-          isEditable: false,
-          notEditableReason:
-              "A value for the 2nd parameter can't be added until a value "
-              'for all preceding positional parameters have been added.',
-        ),
-      ),
-    );
-  }
-
-  test_isEditable_false_positional_required2() async {
-    failTestOnErrorDiagnostic = false;
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(int a, int b, int c);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(1);
-}
-''');
-    expect(
-      result,
-      hasArg(
-        // c is not editable because there are missing required previous
-        // arguments (b).
-        isArg(
-          'c',
-          isEditable: false,
-          notEditableReason:
-              "A value for the 3rd parameter can't be added until a value "
-              'for all preceding positional parameters have been added.',
-        ),
-      ),
-    );
-  }
-
-  test_isEditable_false_string_adjacent() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String s);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget('a' 'b');
-}
-''');
-    expect(
-      result,
-      hasArg(
-        isArg(
-          's',
-          type: 'string',
-          value: isNull,
-          displayValue: 'ab',
-          isDefault: false,
-          isEditable: false,
-          notEditableReason: "Adjacent strings can't be edited",
-        ),
-      ),
-    );
-  }
-
-  test_isEditable_false_string_interpolated() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String s);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget('${context.runtimeType}');
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg(
-            's',
-            type: 'string',
-            value: isNull,
-            displayValue: r"'${context.runtimeType}'",
-            isEditable: false,
-            notEditableReason: "Interpolated strings can't be edited",
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_isEditable_false_string_withNewlines() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String sEscaped, String sLiteral);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    'a\nb',
-    """
-a
-b
-""",
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg(
-            'sEscaped',
-            type: 'string',
-            value: isNull,
-            displayValue: 'a\nb',
-            isEditable: false,
-            notEditableReason: "Strings containing newlines can't be edited",
-          ),
-          isArg(
-            'sLiteral',
-            type: 'string',
-            value: isNull,
-            displayValue: 'a\nb\n',
-            isEditable: false,
-            notEditableReason: "Strings containing newlines can't be edited",
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_isEditable_true_named() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget({int? a, int? b, int? c});
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(a: 1);
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('a', isEditable: true),
-          isArg('b', isEditable: true),
-          isArg('c', isEditable: true),
-        ]),
-      ),
-    );
-  }
-
-  test_isEditable_true_positional_required() async {
-    failTestOnErrorDiagnostic = false;
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(int a, int b);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(1);
-}
-''');
-    expect(
-      result,
-      hasArg(
-        isArg(
-          'b',
-          // b is editable because it's the next argument and we don't need
-          // to add anything additional.
-          isEditable: true,
-        ),
-      ),
-    );
-  }
-
-  test_isEditable_true_string_dollar_escaped() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String s);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget('\${1}');
-}
-''');
-    expect(
-      result,
-      hasArg(
-        isArg(
-          's',
-          type: 'string',
-          value: r'${1}',
-          displayValue: isNull,
-          isEditable: true,
-        ),
-      ),
-    );
-  }
-
-  test_isEditable_true_string_dollar_raw() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String s);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(r'${1}');
-}
-''');
-    expect(
-      result,
-      hasArg(
-        isArg(
-          's',
-          type: 'string',
-          value: r'${1}',
-          displayValue: isNull,
-          isEditable: true,
-        ),
-      ),
-    );
-  }
-
-  test_isEditable_true_string_tripleQuoted_withoutNewlines() async {
-    var result = await getEditableArgumentsFor(r'''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String s);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget("""string_value""");
-}
-''');
-    expect(
-      result,
-      hasArg(
-        isArg(
-          's',
-          type: 'string',
-          value: 'string_value',
-          displayValue: isNull,
-          isEditable: true,
-        ),
-      ),
-    );
-  }
-
-  test_isNullable() async {
-    failTestOnErrorDiagnostic = false;
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget(
-    int aPositional,
-    int? aPositionalNullable, {
-    int? aNamed,
-    required int aRequiredNamed,
-    required int? aRequiredNamedNullable
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget();
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('aPositional', isNullable: false),
-          isArg('aPositionalNullable', isNullable: true),
-          isArg('aNamed', isNullable: true),
-          isArg('aRequiredNamed', isNullable: false),
-          isArg('aRequiredNamedNullable', isNullable: true),
-        ]),
-      ),
-    );
-  }
-
-  test_isRequired() async {
-    failTestOnErrorDiagnostic = false;
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget(
-    int aPositional,
-    int? aPositionalNullable, {
-    int? aNamed,
-    required int aRequiredNamed,
-    required int? aRequiredNamedNullable
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget();
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('aPositional', isRequired: true),
-          isArg('aPositionalNullable', isRequired: true),
-          isArg('aNamed', isRequired: false),
-          isArg('aRequiredNamed', isRequired: true),
-          isArg('aRequiredNamedNullable', isRequired: true),
-        ]),
-      ),
-    );
-  }
-
-  test_location_bad_extensionMethod_noWidgetFactory() async {
-    var result = await getEditableArgumentsFor('''
-extension on MyWidget {
-  Widget padded(String a1) => this;
-}
-
-class MyWidget extends StatelessWidget {
-  const MyWidget();
-  const MyWidget.foo(String a1);
-
-  @override
-  Widget build(BuildContext context) => this.pad^ded('value1');
-}
-''');
-    expect(result, isNull);
-  }
-
-  test_location_bad_functionInvocation() async {
-    var result = await getEditableArgumentsFor('''
-MyWidget create(String a1) => throw '';
-
-class MyWidget extends StatelessWidget {
-  @override
-  Widget build(BuildContext context) => crea^te('value1');
-}
-''');
-    expect(result, isNull);
-  }
-
-  test_location_bad_methodInvocation() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  @widgetFactory
-  MyWidget create(String a1) => throw '';
-
-  @override
-  Widget build(BuildContext context) => crea^te('value1');
-}
-''');
-    expect(result, isNull);
-  }
-
-  test_location_bad_unnamedConstructor_notWidget() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget {
-  const MyWidget(String a1);
-
-  @override
-  MyWidget build(BuildContext context) => MyW^idget('value1');
-}
-''');
-    expect(result, isNull);
-  }
-
-  test_location_good_argumentList_argumentName() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({required String a1 });
-
-  @override
-  Widget build(BuildContext context) => MyWidget(a^1: 'value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_argumentList_literalValue() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({required String a1 });
-
-  @override
-  Widget build(BuildContext context) => MyWidget(a1: 'val^ue1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_argumentList_nestedInvocation() async {
-    var result = await getEditableArgumentsFor('''
-String getString() => '';
-
-class MyWidget extends StatelessWidget {
-  const MyWidget(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyWidget(getS^tring());
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_argumentList_nestedInvocation_arguments() async {
-    var result = await getEditableArgumentsFor('''
-String getString(String s) => s;
-
-class MyWidget extends StatelessWidget {
-  const MyWidget(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyWidget(getString('valu^e1'));
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_argumentList_parens_afterOpen() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({required String a1 });
-
-  @override
-  Widget build(BuildContext context) => MyWidget(^a1: 'value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_argumentList_parens_beforeClose() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({required String a1 });
-
-  @override
-  Widget build(BuildContext context) => MyWidget(a1: 'value1'^);
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_argumentList_parens_beforeOpen() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({required String a1 });
-
-  @override
-  Widget build(BuildContext context) => MyWidget^(a1: 'value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_extensionMethod_constructorTarget() async {
-    var result = await getEditableArgumentsFor('''
-extension on MyWidget {
-  @widgetFactory
-  Widget padded(String a1) => this;
-}
-
-class MyWidget extends StatelessWidget {
-  const MyWidget();
-  const MyWidget.foo(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyWidget().pad^ded('value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_extensionMethod_thisTarget() async {
-    var result = await getEditableArgumentsFor('''
-extension on MyWidget {
-  @widgetFactory
-  Widget padded(String a1) => this;
-}
-
-class MyWidget extends StatelessWidget {
-  const MyWidget.foo(String a1);
-
-  @override
-  Widget build(BuildContext context) => this.pad^ded('value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_extensionMethod_variableTarget() async {
-    var result = await getEditableArgumentsFor('''
-extension on MyWidget {
-  @widgetFactory
-  Widget padded(String a1) => this;
-}
-
-class MyWidget extends StatelessWidget {
-  const MyWidget();
-  const MyWidget.foo(String a1);
-
-  @override
-  Widget build(BuildContext context) {
-    MyWidget? foo;
-    return foo!.pad^ded('value1');
-  }
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_namedConstructor_className() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget.foo(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget.foo('value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_namedConstructor_constructorName() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget.foo(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyWidget.f^oo('value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  test_location_good_unnamedConstructor() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget('value1');
-}
-''');
-    expect(result, hasArgNamed('a1'));
-  }
-
-  /// Arguments should be returned in the order of the parameters in the source
-  /// code. This keeps things consistent across different instances of the same
-  /// Widget class and prevents the order from changing as a user adds/removes
-  /// arguments.
-  ///
-  /// If an editor wants to sort provided arguments first (and keep these stable
-  /// across add/removes) it could still do so client-side, whereas if server
-  /// orders them that way, the opposite (using source-order) is not possible.
-  test_order() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    int c1 = 0,
-    int c2 = 0,
-    int a1 = 0,
-    int a2 = 0,
-    int b1 = 0,
-    int b2 = 0,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(b1: 1, a1: 1, c1: 1);
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('c1'),
-          isArg('c2'),
-          isArg('a1'),
-          isArg('a2'),
-          isArg('b1'),
-          isArg('b2'),
-        ]),
-      ),
-    );
-  }
-
-  test_textDocument_closedFile() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget('value1');
-}
-''');
-
-    // Verify initial content of 1.
-    expect(
-      result!.textDocument,
-      isA<VersionedTextDocumentIdentifier>()
-          .having((td) => td.uri, 'uri', mainFileUri)
-          .having((td) => td.version, 'version', 1),
-    );
-
-    // Close the file.
-    await closeFile(mainFileUri);
-
-    // Verify new results have null version.
-    result = await getEditableArguments(mainFileUri, code.position.position);
-    expect(
-      result!.textDocument,
-      isA<OptionalVersionedTextDocumentIdentifier>()
-          .having((td) => td.uri, 'uri', mainFileUri)
-          .having((td) => td.version, 'version', isNull),
-    );
-  }
-
-  test_textDocument_unopenedFile() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget('value1');
-}
-''', open: false);
-
-    // Verify null version for unopened file.
-    expect(
-      result!.textDocument,
-      isA<OptionalVersionedTextDocumentIdentifier>()
-          .having((td) => td.uri, 'uri', mainFileUri)
-          .having((td) => td.version, 'version', null),
-    );
-  }
-
-  test_textDocument_versions() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget(String a1);
-
-  @override
-  Widget build(BuildContext context) => MyW^idget('value1');
-}
-''');
-
-    // Verify initial content of 1.
-    expect(
-      result!.textDocument,
-      isA<VersionedTextDocumentIdentifier>()
-          .having((td) => td.uri, 'uri', mainFileUri)
-          .having((td) => td.version, 'version', 1),
-    );
-
-    // Update the content to v5.
-    await replaceFile(5, mainFileUri, '${code.code}\n// extra comment');
-
-    // Verify new results have version 5.
-    result = await getEditableArguments(mainFileUri, code.position.position);
-    expect(
-      result!.textDocument,
-      isA<VersionedTextDocumentIdentifier>()
-          .having((td) => td.uri, 'uri', mainFileUri)
-          .having((td) => td.version, 'version', 5),
-    );
-  }
-
-  test_type_bool() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    bool supplied = true,
-    bool suppliedAsDefault = true,
-    bool notSupplied = true,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    supplied: false,
-    suppliedAsDefault: true,
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('supplied', type: 'bool', value: false, isDefault: false),
-          isArg(
-            'suppliedAsDefault',
-            type: 'bool',
-            value: true,
-            isDefault: true,
-          ),
-          isArg('notSupplied', type: 'bool', value: true, isDefault: true),
-        ]),
-      ),
-    );
-  }
-
-  test_type_bool_nonLiterals() async {
-    var result = await getEditableArgumentsFor('''
-var myVar = true;
-const myConst = true;
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    bool? aVar,
-    bool? aConst,
-    bool? aExpr,
-    bool? aConstExpr,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    aVar: myVar,
-    aConst: myConst,
-    aExpr: DateTime.now().isBefore(DateTime.now()),
-    aConstExpr: 1 == 2,
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('aVar', type: 'bool', value: isNull, displayValue: 'myVar'),
-          isArg('aConst', type: 'bool', value: true, displayValue: 'myConst'),
-          isArg(
-            'aExpr',
-            type: 'bool',
-            value: isNull,
-            displayValue: 'DateTime.now().isBefore(DateTime.now())',
-          ),
-          isArg(
-            'aConstExpr',
-            type: 'bool',
-            value: false,
-            displayValue: '1 == 2',
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_type_double() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    double supplied = 1.0,
-    double suppliedAsDefault = 1.0,
-    double notSupplied = 1.0,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    supplied: 2.0,
-    suppliedAsDefault: 1.0,
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('supplied', type: 'double', value: 2.0, isDefault: false),
-          isArg(
-            'suppliedAsDefault',
-            type: 'double',
-            value: 1.0,
-            isDefault: true,
-          ),
-          isArg('notSupplied', type: 'double', value: 1.0, isDefault: true),
-        ]),
-      ),
-    );
-  }
-
-  test_type_double_intLiterals() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    double supplied = 1,
-    double suppliedAsDefault = 1,
-    double notSupplied = 1,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    supplied: 2,
-    suppliedAsDefault: 1,
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('supplied', type: 'double', value: 2, isDefault: false),
-          isArg('suppliedAsDefault', type: 'double', value: 1, isDefault: true),
-          isArg('notSupplied', type: 'double', value: 1, isDefault: true),
-        ]),
-      ),
-    );
-  }
-
-  test_type_double_nonLiterals() async {
-    var result = await getEditableArgumentsFor('''
-var myVar = 1.0;
-const myConst = 1.0;
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    double? aVar,
-    double? aConst,
-    double? aExpr,
-    double? aConstExpr,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    aVar: myVar,
-    aConst: myConst,
-    aExpr: DateTime.now().millisecondsSinceEpoch.toDouble(),
-    aConstExpr: 1.0 + myConst,
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('aVar', type: 'double', value: isNull, displayValue: 'myVar'),
-          isArg('aConst', type: 'double', value: 1.0, displayValue: 'myConst'),
-          isArg(
-            'aExpr',
-            type: 'double',
-            value: isNull,
-            displayValue: 'DateTime.now().millisecondsSinceEpoch.toDouble()',
-          ),
-          isArg(
-            'aConstExpr',
-            type: 'double',
-            value: 2.0,
-            displayValue: '1.0 + myConst',
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_type_enum() async {
-    var result = await getEditableArgumentsFor('''
-enum E { one, two }
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    E supplied = E.one,
-    E suppliedAsDefault = E.one,
-    E notSupplied = E.one,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    supplied: E.two,
-    suppliedAsDefault: E.one,
-  );
-}
-''');
-
-    var optionsMatcher = equals(['E.one', 'E.two']);
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg(
-            'supplied',
-            type: 'enum',
-            value: 'E.two',
-            isDefault: false,
-            options: optionsMatcher,
-          ),
-          isArg(
-            'suppliedAsDefault',
-            type: 'enum',
-            value: 'E.one',
-            isDefault: true,
-            options: optionsMatcher,
-          ),
-          isArg(
-            'notSupplied',
-            type: 'enum',
-            value: 'E.one',
-            isDefault: true,
-            options: optionsMatcher,
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_type_enum_nonLiterals() async {
-    var result = await getEditableArgumentsFor('''
-enum E { one, two }
-var myVar = E.one;
-const myConst = E.one;
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    E? aVar,
-    E? aConst,
-    E? aExpr,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    aVar: myVar,
-    aConst: myConst,
-    aExpr: E.values.first,
-  );
-}
-''');
-
-    var optionsMatcher = equals(['E.one', 'E.two']);
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg(
-            'aVar',
-            type: 'enum',
-            value: isNull,
-            displayValue: 'myVar',
-            options: optionsMatcher,
-          ),
-          isArg(
-            'aConst',
-            type: 'enum',
-            value: 'E.one',
-            displayValue: 'myConst',
-            options: optionsMatcher,
-          ),
-          isArg(
-            'aExpr',
-            type: 'enum',
-            value: isNull,
-            displayValue: 'E.values.first',
-            options: optionsMatcher,
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_type_int() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    int supplied = 1,
-    int suppliedAsDefault = 1,
-    int notSupplied = 1,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    supplied: 2,
-    suppliedAsDefault: 1,
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('supplied', type: 'int', value: 2, isDefault: false),
-          isArg('suppliedAsDefault', type: 'int', value: 1, isDefault: true),
-          isArg('notSupplied', type: 'int', value: 1, isDefault: true),
-        ]),
-      ),
-    );
-  }
-
-  test_type_int_nonLiterals() async {
-    var result = await getEditableArgumentsFor('''
-var myVar = 1;
-const myConst = 1;
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    int? aVar,
-    int? aConst,
-    int? aExpr,
-    int? aConstExpr,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    aVar: myVar,
-    aConst: myConst,
-    aExpr: DateTime.now().millisecondsSinceEpoch,
-    aConstExpr: 1 + myConst,
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('aVar', type: 'int', value: isNull, displayValue: 'myVar'),
-          isArg('aConst', type: 'int', value: 1, displayValue: 'myConst'),
-          isArg(
-            'aExpr',
-            type: 'int',
-            value: isNull,
-            displayValue: 'DateTime.now().millisecondsSinceEpoch',
-          ),
-          isArg(
-            'aConstExpr',
-            type: 'int',
-            value: 2,
-            displayValue: '1 + myConst',
-          ),
-        ]),
-      ),
-    );
-  }
-
-  test_type_string() async {
-    var result = await getEditableArgumentsFor('''
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    String supplied = 'a',
-    String suppliedAsDefault = 'a',
-    String notSupplied = 'a',
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    supplied: 'b',
-    suppliedAsDefault: 'a',
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('supplied', type: 'string', value: 'b', isDefault: false),
-          isArg(
-            'suppliedAsDefault',
-            type: 'string',
-            value: 'a',
-            isDefault: true,
-          ),
-          isArg('notSupplied', type: 'string', value: 'a', isDefault: true),
-        ]),
-      ),
-    );
-  }
-
-  test_type_string_nonLiterals() async {
-    var result = await getEditableArgumentsFor('''
-var myVar = 'a';
-const myConst = 'a';
-class MyWidget extends StatelessWidget {
-  const MyWidget({
-    String? aVar,
-    String? aConst,
-    String? aExpr,
-    String? aConstExpr,
-  });
-
-  @override
-  Widget build(BuildContext context) => MyW^idget(
-    aVar: myVar,
-    aConst: myConst,
-    aExpr: DateTime.now().toString(),
-    aConstExpr: 'a' + 'b',
-  );
-}
-''');
-    expect(
-      result,
-      hasArgs(
-        orderedEquals([
-          isArg('aVar', type: 'string', value: isNull, displayValue: 'myVar'),
-          isArg('aConst', type: 'string', value: 'a', displayValue: 'myConst'),
-          isArg(
-            'aExpr',
-            type: 'string',
-            value: isNull,
-            displayValue: 'DateTime.now().toString()',
-          ),
-          isArg(
-            'aConstExpr',
-            type: 'string',
-            value: 'ab',
-            displayValue: "'a' + 'b'",
-          ),
-        ]),
-      ),
-    );
-  }
 }
diff --git a/pkg/analysis_server/test/lsp_over_legacy/editable_arguments_test.dart b/pkg/analysis_server/test/lsp_over_legacy/editable_arguments_test.dart
new file mode 100644
index 0000000..5e56fff
--- /dev/null
+++ b/pkg/analysis_server/test/lsp_over_legacy/editable_arguments_test.dart
@@ -0,0 +1,49 @@
+// 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_editable_arguments_tests.dart';
+import 'abstract_lsp_over_legacy.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(EditableArgumentsTest);
+  });
+}
+
+@reflectiveTest
+class EditableArgumentsTest extends SharedLspOverLegacyTest
+    with
+        // Tests are defined in SharedEditableArgumentsTests because they
+        // are shared and run for both LSP and Legacy servers.
+        SharedEditableArgumentsTests {
+  @override
+  Future<void> initializeServer() async {
+    await waitForTasksFinished();
+  }
+
+  @override
+  Future<void> setUp() async {
+    await super.setUp();
+
+    writeTestPackageConfig(flutter: true);
+  }
+
+  @override
+  @FailingTest(reason: 'Document versions not currently supported for legacy')
+  test_textDocument_closedFile() {
+    // TODO(dantup): Implement support for version numbers in the legacy
+    // protocol.
+    return super.test_textDocument_closedFile();
+  }
+
+  @override
+  @FailingTest(reason: 'Document versions not currently supported for legacy')
+  test_textDocument_versions() {
+    // TODO(dantup): Implement support for version numbers in the legacy
+    // protocol.
+    return super.test_textDocument_versions();
+  }
+}
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 bd04d65..afc8547 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 'document_color_test.dart' as document_color;
 import 'document_highlights_test.dart' as document_highlights;
 import 'document_symbols_test.dart' as document_symbols;
+import 'editable_arguments_test.dart' as editable_arguments;
 import 'format_test.dart' as format;
 import 'hover_test.dart' as hover;
 import 'implementation_test.dart' as implementation;
@@ -27,6 +28,7 @@
     document_color.main;
     document_highlights.main();
     document_symbols.main();
+    editable_arguments.main();
     format.main();
     hover.main();
     implementation.main();
diff --git a/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart b/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
new file mode 100644
index 0000000..dfa990a
--- /dev/null
+++ b/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
@@ -0,0 +1,1270 @@
+// 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 '../utils/test_code_extensions.dart';
+import 'shared_test_interface.dart';
+
+/// Shared editable arguments tests that are used by both LSP + Legacy server
+/// tests.
+mixin SharedEditableArgumentsTests
+    on SharedTestInterface, LspRequestHelpersMixin {
+  late TestCode code;
+
+  /// Initializes the server with [content] and fetches editable arguments.
+  Future<EditableArguments?> getEditableArgumentsFor(
+    String content, {
+    bool open = true,
+  }) async {
+    code = TestCode.parse('''
+import 'package:flutter/widgets.dart';
+
+$content
+''');
+    createFile(testFilePath, code.code);
+    await initializeServer();
+    if (open) {
+      await openFile(testFileUri, code.code);
+    }
+    await currentAnalysis;
+    return await getEditableArguments(testFileUri, code.position.position);
+  }
+
+  Matcher hasArg(Matcher matcher) {
+    return hasArgs(contains(matcher));
+  }
+
+  Matcher hasArgNamed(String argumentName) {
+    return hasArg(isArg(argumentName));
+  }
+
+  Matcher hasArgs(Matcher matcher) {
+    return isA<EditableArguments>().having(
+      (arguments) => arguments.arguments,
+      'arguments',
+      matcher,
+    );
+  }
+
+  Matcher isArg(
+    String name, {
+    Object? type = anything,
+    Object? value = anything,
+    Object? displayValue = anything,
+    Object? hasArgument = anything,
+    Object? isDefault = anything,
+    Object? isRequired = anything,
+    Object? isNullable = anything,
+    Object? isEditable = anything,
+    Object? notEditableReason = anything,
+    Object? options = anything,
+  }) {
+    return isA<EditableArgument>()
+        .having((arg) => arg.name, 'name', name)
+        .having((arg) => arg.type, 'type', type)
+        .having((arg) => arg.value, 'value', value)
+        .having((arg) => arg.displayValue, 'displayValue', displayValue)
+        .having((arg) => arg.hasArgument, 'hasArgument', hasArgument)
+        .having((arg) => arg.isDefault, 'isDefault', isDefault)
+        .having((arg) => arg.isRequired, 'isRequired', isRequired)
+        .having((arg) => arg.isNullable, 'isNullable', isNullable)
+        .having((arg) => arg.isEditable, 'isEditable', isEditable)
+        .having(
+          (arg) => arg.notEditableReason,
+          'notEditableReason',
+          notEditableReason,
+        )
+        .having((arg) => arg.options, 'options', options)
+        // Some extra checks that should be true for all.
+        .having(
+          (arg) =>
+              arg.value == null ||
+              arg.value?.toString() != arg.displayValue?.toString(),
+          'different value and displayValues',
+          isTrue,
+        )
+        .having(
+          (arg) => (arg.notEditableReason == null) == arg.isEditable,
+          'notEditableReason must be supplied if isEditable=false',
+          isTrue,
+        )
+        .having(
+          (arg) => arg.value == null || arg.isEditable,
+          'isEditable must be true if there is a value',
+          isTrue,
+        )
+        .having(
+          (arg) =>
+              arg.type == 'enum'
+                  ? (arg.options?.isNotEmpty ?? false)
+                  : arg.options == null,
+          'enum types must have options / non-enums must not have options',
+          isTrue,
+        );
+  }
+
+  test_hasArgument() async {
+    failTestOnErrorDiagnostic = false;
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget(
+    int? aPositionalSupplied,
+    int? aPositionalNotSupplied, {
+    int? aNamedSupplied,
+    int? aNamedNotSupplied,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(1, aNamedSupplied: 1);
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        unorderedEquals([
+          isArg('aPositionalSupplied', hasArgument: true),
+          isArg('aPositionalNotSupplied', hasArgument: false),
+          isArg('aNamedSupplied', hasArgument: true),
+          isArg('aNamedNotSupplied', hasArgument: false),
+        ]),
+      ),
+    );
+  }
+
+  test_isEditable_false_positional_optional() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget([int? a, int? b, int? c]);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(1);
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('a', isEditable: true),
+          isArg('b', isEditable: true),
+          isArg(
+            'c',
+            // c is not editable because it is not guaranteed that we can insert
+            // a default value for b (it could be a private value or require
+            // imports).
+            isEditable: false,
+            notEditableReason:
+                "A value for the 3rd parameter can't be added until a value "
+                'for all preceding positional parameters have been added.',
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_isEditable_false_positional_required1() async {
+    failTestOnErrorDiagnostic = false;
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(int a, int b);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget();
+}
+''');
+    expect(
+      result,
+      hasArg(
+        // b is not editable because there are missing required previous
+        // arguments (a).
+        isArg(
+          'b',
+          isEditable: false,
+          notEditableReason:
+              "A value for the 2nd parameter can't be added until a value "
+              'for all preceding positional parameters have been added.',
+        ),
+      ),
+    );
+  }
+
+  test_isEditable_false_positional_required2() async {
+    failTestOnErrorDiagnostic = false;
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(int a, int b, int c);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(1);
+}
+''');
+    expect(
+      result,
+      hasArg(
+        // c is not editable because there are missing required previous
+        // arguments (b).
+        isArg(
+          'c',
+          isEditable: false,
+          notEditableReason:
+              "A value for the 3rd parameter can't be added until a value "
+              'for all preceding positional parameters have been added.',
+        ),
+      ),
+    );
+  }
+
+  test_isEditable_false_string_adjacent() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String s);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget('a' 'b');
+}
+''');
+    expect(
+      result,
+      hasArg(
+        isArg(
+          's',
+          type: 'string',
+          value: isNull,
+          displayValue: 'ab',
+          isDefault: false,
+          isEditable: false,
+          notEditableReason: "Adjacent strings can't be edited",
+        ),
+      ),
+    );
+  }
+
+  test_isEditable_false_string_interpolated() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String s);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget('${context.runtimeType}');
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg(
+            's',
+            type: 'string',
+            value: isNull,
+            displayValue: r"'${context.runtimeType}'",
+            isEditable: false,
+            notEditableReason: "Interpolated strings can't be edited",
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_isEditable_false_string_withNewlines() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String sEscaped, String sLiteral);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    'a\nb',
+    """
+a
+b
+""",
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg(
+            'sEscaped',
+            type: 'string',
+            value: isNull,
+            displayValue: 'a\nb',
+            isEditable: false,
+            notEditableReason: "Strings containing newlines can't be edited",
+          ),
+          isArg(
+            'sLiteral',
+            type: 'string',
+            value: isNull,
+            displayValue: 'a\nb\n',
+            isEditable: false,
+            notEditableReason: "Strings containing newlines can't be edited",
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_isEditable_true_named() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget({int? a, int? b, int? c});
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(a: 1);
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('a', isEditable: true),
+          isArg('b', isEditable: true),
+          isArg('c', isEditable: true),
+        ]),
+      ),
+    );
+  }
+
+  test_isEditable_true_positional_required() async {
+    failTestOnErrorDiagnostic = false;
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(int a, int b);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(1);
+}
+''');
+    expect(
+      result,
+      hasArg(
+        isArg(
+          'b',
+          // b is editable because it's the next argument and we don't need
+          // to add anything additional.
+          isEditable: true,
+        ),
+      ),
+    );
+  }
+
+  test_isEditable_true_string_dollar_escaped() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String s);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget('\${1}');
+}
+''');
+    expect(
+      result,
+      hasArg(
+        isArg(
+          's',
+          type: 'string',
+          value: r'${1}',
+          displayValue: isNull,
+          isEditable: true,
+        ),
+      ),
+    );
+  }
+
+  test_isEditable_true_string_dollar_raw() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String s);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(r'${1}');
+}
+''');
+    expect(
+      result,
+      hasArg(
+        isArg(
+          's',
+          type: 'string',
+          value: r'${1}',
+          displayValue: isNull,
+          isEditable: true,
+        ),
+      ),
+    );
+  }
+
+  test_isEditable_true_string_tripleQuoted_withoutNewlines() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String s);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget("""string_value""");
+}
+''');
+    expect(
+      result,
+      hasArg(
+        isArg(
+          's',
+          type: 'string',
+          value: 'string_value',
+          displayValue: isNull,
+          isEditable: true,
+        ),
+      ),
+    );
+  }
+
+  test_isNullable() async {
+    failTestOnErrorDiagnostic = false;
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget(
+    int aPositional,
+    int? aPositionalNullable, {
+    int? aNamed,
+    required int aRequiredNamed,
+    required int? aRequiredNamedNullable
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget();
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('aPositional', isNullable: false),
+          isArg('aPositionalNullable', isNullable: true),
+          isArg('aNamed', isNullable: true),
+          isArg('aRequiredNamed', isNullable: false),
+          isArg('aRequiredNamedNullable', isNullable: true),
+        ]),
+      ),
+    );
+  }
+
+  test_isRequired() async {
+    failTestOnErrorDiagnostic = false;
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget(
+    int aPositional,
+    int? aPositionalNullable, {
+    int? aNamed,
+    required int aRequiredNamed,
+    required int? aRequiredNamedNullable
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget();
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('aPositional', isRequired: true),
+          isArg('aPositionalNullable', isRequired: true),
+          isArg('aNamed', isRequired: false),
+          isArg('aRequiredNamed', isRequired: true),
+          isArg('aRequiredNamedNullable', isRequired: true),
+        ]),
+      ),
+    );
+  }
+
+  test_location_bad_extensionMethod_noWidgetFactory() async {
+    var result = await getEditableArgumentsFor('''
+extension on MyWidget {
+  Widget padded(String a1) => this;
+}
+
+class MyWidget extends StatelessWidget {
+  const MyWidget();
+  const MyWidget.foo(String a1);
+
+  @override
+  Widget build(BuildContext context) => this.pad^ded('value1');
+}
+''');
+    expect(result, isNull);
+  }
+
+  test_location_bad_functionInvocation() async {
+    var result = await getEditableArgumentsFor('''
+MyWidget create(String a1) => throw '';
+
+class MyWidget extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) => crea^te('value1');
+}
+''');
+    expect(result, isNull);
+  }
+
+  test_location_bad_methodInvocation() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  @widgetFactory
+  MyWidget create(String a1) => throw '';
+
+  @override
+  Widget build(BuildContext context) => crea^te('value1');
+}
+''');
+    expect(result, isNull);
+  }
+
+  test_location_bad_unnamedConstructor_notWidget() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget {
+  const MyWidget(String a1);
+
+  @override
+  MyWidget build(BuildContext context) => MyW^idget('value1');
+}
+''');
+    expect(result, isNull);
+  }
+
+  test_location_good_argumentList_argumentName() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({required String a1 });
+
+  @override
+  Widget build(BuildContext context) => MyWidget(a^1: 'value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_argumentList_literalValue() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({required String a1 });
+
+  @override
+  Widget build(BuildContext context) => MyWidget(a1: 'val^ue1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_argumentList_nestedInvocation() async {
+    var result = await getEditableArgumentsFor('''
+String getString() => '';
+
+class MyWidget extends StatelessWidget {
+  const MyWidget(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyWidget(getS^tring());
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_argumentList_nestedInvocation_arguments() async {
+    var result = await getEditableArgumentsFor('''
+String getString(String s) => s;
+
+class MyWidget extends StatelessWidget {
+  const MyWidget(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyWidget(getString('valu^e1'));
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_argumentList_parens_afterOpen() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({required String a1 });
+
+  @override
+  Widget build(BuildContext context) => MyWidget(^a1: 'value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_argumentList_parens_beforeClose() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({required String a1 });
+
+  @override
+  Widget build(BuildContext context) => MyWidget(a1: 'value1'^);
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_argumentList_parens_beforeOpen() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({required String a1 });
+
+  @override
+  Widget build(BuildContext context) => MyWidget^(a1: 'value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_extensionMethod_constructorTarget() async {
+    var result = await getEditableArgumentsFor('''
+extension on MyWidget {
+  @widgetFactory
+  Widget padded(String a1) => this;
+}
+
+class MyWidget extends StatelessWidget {
+  const MyWidget();
+  const MyWidget.foo(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyWidget().pad^ded('value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_extensionMethod_thisTarget() async {
+    var result = await getEditableArgumentsFor('''
+extension on MyWidget {
+  @widgetFactory
+  Widget padded(String a1) => this;
+}
+
+class MyWidget extends StatelessWidget {
+  const MyWidget.foo(String a1);
+
+  @override
+  Widget build(BuildContext context) => this.pad^ded('value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_extensionMethod_variableTarget() async {
+    var result = await getEditableArgumentsFor('''
+extension on MyWidget {
+  @widgetFactory
+  Widget padded(String a1) => this;
+}
+
+class MyWidget extends StatelessWidget {
+  const MyWidget();
+  const MyWidget.foo(String a1);
+
+  @override
+  Widget build(BuildContext context) {
+    MyWidget? foo;
+    return foo!.pad^ded('value1');
+  }
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_namedConstructor_className() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget.foo(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget.foo('value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_namedConstructor_constructorName() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget.foo(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyWidget.f^oo('value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  test_location_good_unnamedConstructor() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget('value1');
+}
+''');
+    expect(result, hasArgNamed('a1'));
+  }
+
+  /// Arguments should be returned in the order of the parameters in the source
+  /// code. This keeps things consistent across different instances of the same
+  /// Widget class and prevents the order from changing as a user adds/removes
+  /// arguments.
+  ///
+  /// If an editor wants to sort provided arguments first (and keep these stable
+  /// across add/removes) it could still do so client-side, whereas if server
+  /// orders them that way, the opposite (using source-order) is not possible.
+  test_order() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    int c1 = 0,
+    int c2 = 0,
+    int a1 = 0,
+    int a2 = 0,
+    int b1 = 0,
+    int b2 = 0,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(b1: 1, a1: 1, c1: 1);
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('c1'),
+          isArg('c2'),
+          isArg('a1'),
+          isArg('a2'),
+          isArg('b1'),
+          isArg('b2'),
+        ]),
+      ),
+    );
+  }
+
+  test_textDocument_closedFile() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget('value1');
+}
+''');
+
+    // Verify initial content of 1.
+    expect(
+      result!.textDocument,
+      isA<VersionedTextDocumentIdentifier>()
+          .having((td) => td.uri, 'uri', testFileUri)
+          .having((td) => td.version, 'version', 1),
+    );
+
+    // Close the file.
+    await closeFile(testFileUri);
+
+    // Verify new results have null version.
+    result = await getEditableArguments(testFileUri, code.position.position);
+    expect(
+      result!.textDocument,
+      isA<OptionalVersionedTextDocumentIdentifier>()
+          .having((td) => td.uri, 'uri', testFileUri)
+          .having((td) => td.version, 'version', isNull),
+    );
+  }
+
+  test_textDocument_unopenedFile() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget('value1');
+}
+''', open: false);
+
+    // Verify null version for unopened file.
+    expect(
+      result!.textDocument,
+      isA<OptionalVersionedTextDocumentIdentifier>()
+          .having((td) => td.uri, 'uri', testFileUri)
+          .having((td) => td.version, 'version', null),
+    );
+  }
+
+  test_textDocument_versions() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget(String a1);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget('value1');
+}
+''');
+
+    // Verify initial content of 1.
+    expect(
+      result!.textDocument,
+      isA<VersionedTextDocumentIdentifier>()
+          .having((td) => td.uri, 'uri', testFileUri)
+          .having((td) => td.version, 'version', 1),
+    );
+
+    // Update the content to v5.
+    await replaceFile(5, testFileUri, '${code.code}\n// extra comment');
+
+    // Verify new results have version 5.
+    result = await getEditableArguments(testFileUri, code.position.position);
+    expect(
+      result!.textDocument,
+      isA<VersionedTextDocumentIdentifier>()
+          .having((td) => td.uri, 'uri', testFileUri)
+          .having((td) => td.version, 'version', 5),
+    );
+  }
+
+  test_type_bool() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    bool supplied = true,
+    bool suppliedAsDefault = true,
+    bool notSupplied = true,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    supplied: false,
+    suppliedAsDefault: true,
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('supplied', type: 'bool', value: false, isDefault: false),
+          isArg(
+            'suppliedAsDefault',
+            type: 'bool',
+            value: true,
+            isDefault: true,
+          ),
+          isArg('notSupplied', type: 'bool', value: true, isDefault: true),
+        ]),
+      ),
+    );
+  }
+
+  test_type_bool_nonLiterals() async {
+    var result = await getEditableArgumentsFor('''
+var myVar = true;
+const myConst = true;
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    bool? aVar,
+    bool? aConst,
+    bool? aExpr,
+    bool? aConstExpr,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    aVar: myVar,
+    aConst: myConst,
+    aExpr: DateTime.now().isBefore(DateTime.now()),
+    aConstExpr: 1 == 2,
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('aVar', type: 'bool', value: isNull, displayValue: 'myVar'),
+          isArg('aConst', type: 'bool', value: true, displayValue: 'myConst'),
+          isArg(
+            'aExpr',
+            type: 'bool',
+            value: isNull,
+            displayValue: 'DateTime.now().isBefore(DateTime.now())',
+          ),
+          isArg(
+            'aConstExpr',
+            type: 'bool',
+            value: false,
+            displayValue: '1 == 2',
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_type_double() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    double supplied = 1.0,
+    double suppliedAsDefault = 1.0,
+    double notSupplied = 1.0,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    supplied: 2.0,
+    suppliedAsDefault: 1.0,
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('supplied', type: 'double', value: 2.0, isDefault: false),
+          isArg(
+            'suppliedAsDefault',
+            type: 'double',
+            value: 1.0,
+            isDefault: true,
+          ),
+          isArg('notSupplied', type: 'double', value: 1.0, isDefault: true),
+        ]),
+      ),
+    );
+  }
+
+  test_type_double_intLiterals() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    double supplied = 1,
+    double suppliedAsDefault = 1,
+    double notSupplied = 1,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    supplied: 2,
+    suppliedAsDefault: 1,
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('supplied', type: 'double', value: 2, isDefault: false),
+          isArg('suppliedAsDefault', type: 'double', value: 1, isDefault: true),
+          isArg('notSupplied', type: 'double', value: 1, isDefault: true),
+        ]),
+      ),
+    );
+  }
+
+  test_type_double_nonLiterals() async {
+    var result = await getEditableArgumentsFor('''
+var myVar = 1.0;
+const myConst = 1.0;
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    double? aVar,
+    double? aConst,
+    double? aExpr,
+    double? aConstExpr,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    aVar: myVar,
+    aConst: myConst,
+    aExpr: DateTime.now().millisecondsSinceEpoch.toDouble(),
+    aConstExpr: 1.0 + myConst,
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('aVar', type: 'double', value: isNull, displayValue: 'myVar'),
+          isArg('aConst', type: 'double', value: 1.0, displayValue: 'myConst'),
+          isArg(
+            'aExpr',
+            type: 'double',
+            value: isNull,
+            displayValue: 'DateTime.now().millisecondsSinceEpoch.toDouble()',
+          ),
+          isArg(
+            'aConstExpr',
+            type: 'double',
+            value: 2.0,
+            displayValue: '1.0 + myConst',
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_type_enum() async {
+    var result = await getEditableArgumentsFor('''
+enum E { one, two }
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    E supplied = E.one,
+    E suppliedAsDefault = E.one,
+    E notSupplied = E.one,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    supplied: E.two,
+    suppliedAsDefault: E.one,
+  );
+}
+''');
+
+    var optionsMatcher = equals(['E.one', 'E.two']);
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg(
+            'supplied',
+            type: 'enum',
+            value: 'E.two',
+            isDefault: false,
+            options: optionsMatcher,
+          ),
+          isArg(
+            'suppliedAsDefault',
+            type: 'enum',
+            value: 'E.one',
+            isDefault: true,
+            options: optionsMatcher,
+          ),
+          isArg(
+            'notSupplied',
+            type: 'enum',
+            value: 'E.one',
+            isDefault: true,
+            options: optionsMatcher,
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_type_enum_nonLiterals() async {
+    var result = await getEditableArgumentsFor('''
+enum E { one, two }
+var myVar = E.one;
+const myConst = E.one;
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    E? aVar,
+    E? aConst,
+    E? aExpr,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    aVar: myVar,
+    aConst: myConst,
+    aExpr: E.values.first,
+  );
+}
+''');
+
+    var optionsMatcher = equals(['E.one', 'E.two']);
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg(
+            'aVar',
+            type: 'enum',
+            value: isNull,
+            displayValue: 'myVar',
+            options: optionsMatcher,
+          ),
+          isArg(
+            'aConst',
+            type: 'enum',
+            value: 'E.one',
+            displayValue: 'myConst',
+            options: optionsMatcher,
+          ),
+          isArg(
+            'aExpr',
+            type: 'enum',
+            value: isNull,
+            displayValue: 'E.values.first',
+            options: optionsMatcher,
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_type_int() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    int supplied = 1,
+    int suppliedAsDefault = 1,
+    int notSupplied = 1,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    supplied: 2,
+    suppliedAsDefault: 1,
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('supplied', type: 'int', value: 2, isDefault: false),
+          isArg('suppliedAsDefault', type: 'int', value: 1, isDefault: true),
+          isArg('notSupplied', type: 'int', value: 1, isDefault: true),
+        ]),
+      ),
+    );
+  }
+
+  test_type_int_nonLiterals() async {
+    var result = await getEditableArgumentsFor('''
+var myVar = 1;
+const myConst = 1;
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    int? aVar,
+    int? aConst,
+    int? aExpr,
+    int? aConstExpr,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    aVar: myVar,
+    aConst: myConst,
+    aExpr: DateTime.now().millisecondsSinceEpoch,
+    aConstExpr: 1 + myConst,
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('aVar', type: 'int', value: isNull, displayValue: 'myVar'),
+          isArg('aConst', type: 'int', value: 1, displayValue: 'myConst'),
+          isArg(
+            'aExpr',
+            type: 'int',
+            value: isNull,
+            displayValue: 'DateTime.now().millisecondsSinceEpoch',
+          ),
+          isArg(
+            'aConstExpr',
+            type: 'int',
+            value: 2,
+            displayValue: '1 + myConst',
+          ),
+        ]),
+      ),
+    );
+  }
+
+  test_type_string() async {
+    var result = await getEditableArgumentsFor('''
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    String supplied = 'a',
+    String suppliedAsDefault = 'a',
+    String notSupplied = 'a',
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    supplied: 'b',
+    suppliedAsDefault: 'a',
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('supplied', type: 'string', value: 'b', isDefault: false),
+          isArg(
+            'suppliedAsDefault',
+            type: 'string',
+            value: 'a',
+            isDefault: true,
+          ),
+          isArg('notSupplied', type: 'string', value: 'a', isDefault: true),
+        ]),
+      ),
+    );
+  }
+
+  test_type_string_nonLiterals() async {
+    var result = await getEditableArgumentsFor('''
+var myVar = 'a';
+const myConst = 'a';
+class MyWidget extends StatelessWidget {
+  const MyWidget({
+    String? aVar,
+    String? aConst,
+    String? aExpr,
+    String? aConstExpr,
+  });
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(
+    aVar: myVar,
+    aConst: myConst,
+    aExpr: DateTime.now().toString(),
+    aConstExpr: 'a' + 'b',
+  );
+}
+''');
+    expect(
+      result,
+      hasArgs(
+        orderedEquals([
+          isArg('aVar', type: 'string', value: isNull, displayValue: 'myVar'),
+          isArg('aConst', type: 'string', value: 'a', displayValue: 'myConst'),
+          isArg(
+            'aExpr',
+            type: 'string',
+            value: isNull,
+            displayValue: 'DateTime.now().toString()',
+          ),
+          isArg(
+            'aConstExpr',
+            type: 'string',
+            value: 'ab',
+            displayValue: "'a' + 'b'",
+          ),
+        ]),
+      ),
+    );
+  }
+}