[analysis_server] Add "defaultValue" to EditableArguments API

This adds a new field "defaultValue" to the API response that contains the default value for a parameter.

Change-Id: Ief11da553d57871ad1e3fab54c731cbc98d674a4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406000
Reviewed-by: Elliott Brooks <elliottbrooks@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart
index a753a9b..29000e3 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/editable_arguments_mixin.dart
@@ -152,17 +152,17 @@
     return null;
   }
 
-  /// Returns the name of an enum constant prefixed with the enum name.
-  String? getQualifiedEnumConstantName(FieldElement2 enumConstant) {
-    var enumName = enumConstant.enclosingElement2.name3;
-    var name = enumConstant.name3;
-    return enumName != null && name != null ? '$enumName.$name' : null;
-  }
-
   /// Returns a list of the constants of an enum constant prefixed with the enum
   /// name.
   List<String> getQualifiedEnumConstantNames(EnumElement2 element3) =>
       element3.constants2.map(getQualifiedEnumConstantName).nonNulls.toList();
+
+  /// Returns the name of an enum constant prefixed with the enum name.
+  static String? getQualifiedEnumConstantName(FieldElement2 enumConstant) {
+    var enumName = enumConstant.enclosingElement2.name3;
+    var name = enumConstant.name3;
+    return enumName != null && name != null ? '$enumName.$name' : null;
+  }
 }
 
 extension on InvocationExpressionImpl {
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart
index e331b6c..5145f1b 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/custom/editable_arguments/handler_editable_arguments.dart
@@ -167,12 +167,14 @@
   }) {
     var valueExpression =
         argument is NamedExpression ? argument.expression : argument;
+    var hasArgument = valueExpression != null;
 
     // Lazily compute the values if we will use this parameter/argument.
     late var values = _getValues(parameter, valueExpression);
 
     String? type;
     Object? value;
+    Object? defaultValue;
     List<String>? options;
 
     // Determine whether a value for this parameter is editable.
@@ -186,40 +188,39 @@
     if (parameter.type.isDartCoreDouble) {
       type = 'double';
       value =
-          (values.argumentValue ?? values.parameterValue)?.toDoubleValue() ??
-          (values.argumentValue ?? values.parameterValue)?.toIntValue();
+          values.argumentValue?.toDoubleValue() ??
+          values.argumentValue?.toIntValue();
+      defaultValue =
+          values.parameterValue?.toDoubleValue() ??
+          values.parameterValue?.toIntValue();
     } else if (parameter.type.isDartCoreInt) {
       type = 'int';
-      value = (values.argumentValue ?? values.parameterValue)?.toIntValue();
+      value = values.argumentValue?.toIntValue();
+      defaultValue = values.parameterValue?.toIntValue();
     } else if (parameter.type.isDartCoreBool) {
       type = 'bool';
-      value = (values.argumentValue ?? values.parameterValue)?.toBoolValue();
+      value = values.argumentValue?.toBoolValue();
+      defaultValue = values.parameterValue?.toBoolValue();
     } else if (parameter.type.isDartCoreString) {
       type = 'string';
-      value = (values.argumentValue ?? values.parameterValue)?.toStringValue();
+      value = values.argumentValue?.toStringValue();
+      defaultValue = values.parameterValue?.toStringValue();
     } else if (parameter.type case InterfaceType(:EnumElement2 element3)) {
       type = 'enum';
       options = getQualifiedEnumConstantNames(element3);
-
-      // Try to match the argument value up with the enum.
-      var valueObject = values.argumentValue ?? values.parameterValue;
-      if (valueObject?.type case InterfaceType(
-        element3: EnumElement2 valueElement,
-      ) when element3 == valueElement) {
-        var index = valueObject?.getField('index')?.toIntValue();
-        if (index != null) {
-          var enumConstant = element3.constants2.elementAtOrNull(index);
-          if (enumConstant != null) {
-            value = getQualifiedEnumConstantName(enumConstant);
-          }
-        }
-      }
+      value = values.argumentValue?.toEnumStringValue(element3);
+      defaultValue = values.parameterValue?.toEnumStringValue(element3);
     } else {
       // TODO(dantup): Determine which parameters we don't include (such as
       //  Widgets) and which we include just without values.
       return null;
     }
 
+    // If no argument is present, we always populate "value" with the default.
+    if (!hasArgument) {
+      value = defaultValue;
+    }
+
     var isEditable = notEditableReason == null;
 
     // Compute a displayValue.
@@ -246,6 +247,7 @@
       displayValue: displayValue,
       options: options,
       isDefault: values.isDefault,
+      defaultValue: defaultValue,
       hasArgument: valueExpression != null,
       isRequired: parameter.isRequired,
       isNullable:
@@ -255,3 +257,23 @@
     );
   }
 }
+
+extension on DartObject? {
+  Object? toEnumStringValue(EnumElement2 element3) {
+    var valueObject = this;
+    if (valueObject?.type case InterfaceType(
+      element3: EnumElement2 valueElement,
+    ) when element3 == valueElement) {
+      var index = valueObject?.getField('index')?.toIntValue();
+      if (index != null) {
+        var enumConstant = element3.constants2.elementAtOrNull(index);
+        if (enumConstant != null) {
+          return EditableArgumentsMixin.getQualifiedEnumConstantName(
+            enumConstant,
+          );
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart b/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
index e98d02b..44fa05c 100644
--- a/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
+++ b/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
@@ -75,6 +75,7 @@
     Object? displayValue = anything,
     Object? hasArgument = anything,
     Object? isDefault = anything,
+    Object? defaultValue = anything,
     Object? isRequired = anything,
     Object? isNullable = anything,
     Object? isEditable = anything,
@@ -88,6 +89,7 @@
         .having((arg) => arg.displayValue, 'displayValue', displayValue)
         .having((arg) => arg.hasArgument, 'hasArgument', hasArgument)
         .having((arg) => arg.isDefault, 'isDefault', isDefault)
+        .having((arg) => arg.defaultValue, 'defaultValue', defaultValue)
         .having((arg) => arg.isRequired, 'isRequired', isRequired)
         .having((arg) => arg.isNullable, 'isNullable', isNullable)
         .having((arg) => arg.isEditable, 'isEditable', isEditable)
@@ -125,6 +127,104 @@
         );
   }
 
+  test_defaultValue_named_default() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget({int? a = 1});
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(a: 1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: 1)));
+  }
+
+  test_defaultValue_named_default_constantVariable() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  static const constantOne = 1;
+
+  const MyWidget({int? a = constantOne});
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(a: 1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: 1)));
+  }
+
+  test_defaultValue_named_default_null() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget({int? a = null});
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(a: 1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: null)));
+  }
+
+  test_defaultValue_named_noDefault() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget({int? a});
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(a: 1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: null)));
+  }
+
+  test_defaultValue_named_required_noDefault() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget({required int? a});
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(a: 1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: null)));
+  }
+
+  test_defaultValue_positional() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget(int a);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: null)));
+  }
+
+  test_defaultValue_positional_optional_default() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget([int? a = 1]);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: 1)));
+  }
+
+  test_defaultValue_positional_optional_noDefault() async {
+    var result = await getEditableArgumentsFor(r'''
+class MyWidget extends StatelessWidget {
+  const MyWidget([int? a]);
+
+  @override
+  Widget build(BuildContext context) => MyW^idget(1);
+}
+''');
+    expect(result, hasArg(isArg('a', defaultValue: null)));
+  }
+
   test_documentation_literal() async {
     var result = await getEditableArgumentsFor('''
 class MyWidget extends StatelessWidget {
@@ -977,14 +1077,27 @@
       result,
       hasArgs(
         orderedEquals([
-          isArg('supplied', type: 'bool', value: false, isDefault: false),
+          isArg(
+            'supplied',
+            type: 'bool',
+            value: false,
+            isDefault: false,
+            defaultValue: true,
+          ),
           isArg(
             'suppliedAsDefault',
             type: 'bool',
             value: true,
             isDefault: true,
+            defaultValue: true,
           ),
-          isArg('notSupplied', type: 'bool', value: true, isDefault: true),
+          isArg(
+            'notSupplied',
+            type: 'bool',
+            value: true,
+            isDefault: true,
+            defaultValue: true,
+          ),
         ]),
       ),
     );
@@ -1054,14 +1167,27 @@
       result,
       hasArgs(
         orderedEquals([
-          isArg('supplied', type: 'double', value: 2.0, isDefault: false),
+          isArg(
+            'supplied',
+            type: 'double',
+            value: 2.0,
+            isDefault: false,
+            defaultValue: 1.0,
+          ),
           isArg(
             'suppliedAsDefault',
             type: 'double',
             value: 1.0,
             isDefault: true,
+            defaultValue: 1.0,
           ),
-          isArg('notSupplied', type: 'double', value: 1.0, isDefault: true),
+          isArg(
+            'notSupplied',
+            type: 'double',
+            value: 1.0,
+            isDefault: true,
+            defaultValue: 1.0,
+          ),
         ]),
       ),
     );
@@ -1167,6 +1293,7 @@
             type: 'enum',
             value: 'E.two',
             isDefault: false,
+            defaultValue: 'E.one',
             options: optionsMatcher,
           ),
           isArg(
@@ -1174,6 +1301,7 @@
             type: 'enum',
             value: 'E.one',
             isDefault: true,
+            defaultValue: 'E.one',
             options: optionsMatcher,
           ),
           isArg(
@@ -1181,6 +1309,7 @@
             type: 'enum',
             value: 'E.one',
             isDefault: true,
+            defaultValue: 'E.one',
             options: optionsMatcher,
           ),
         ]),
@@ -1260,9 +1389,27 @@
       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),
+          isArg(
+            'supplied',
+            type: 'int',
+            value: 2,
+            isDefault: false,
+            defaultValue: 1,
+          ),
+          isArg(
+            'suppliedAsDefault',
+            type: 'int',
+            value: 1,
+            isDefault: true,
+            defaultValue: 1,
+          ),
+          isArg(
+            'notSupplied',
+            type: 'int',
+            value: 1,
+            isDefault: true,
+            defaultValue: 1,
+          ),
         ]),
       ),
     );
@@ -1332,14 +1479,27 @@
       result,
       hasArgs(
         orderedEquals([
-          isArg('supplied', type: 'string', value: 'b', isDefault: false),
+          isArg(
+            'supplied',
+            type: 'string',
+            value: 'b',
+            isDefault: false,
+            defaultValue: 'a',
+          ),
           isArg(
             'suppliedAsDefault',
             type: 'string',
             value: 'a',
             isDefault: true,
+            defaultValue: 'a',
           ),
-          isArg('notSupplied', type: 'string', value: 'a', isDefault: true),
+          isArg(
+            'notSupplied',
+            type: 'string',
+            value: 'a',
+            isDefault: true,
+            defaultValue: 'a',
+          ),
         ]),
       ),
     );
diff --git a/pkg/analysis_server/tool/lsp_spec/generate_all.dart b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
index 6134d6a..efc6401 100644
--- a/pkg/analysis_server/tool/lsp_spec/generate_all.dart
+++ b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
@@ -468,6 +468,16 @@
             'because there is no argument or because it is explicitly provided '
             'as the same value.',
       ),
+      Field(
+        name: 'defaultValue',
+        type: TypeReference.LspAny,
+        allowsNull: false,
+        allowsUndefined: true,
+        comment:
+            'The default value for this parameter if no argument is supplied. '
+            'Setting the argument to this value does not remove it from the '
+            'argument list.',
+      ),
       field(
         'displayValue',
         type: 'string',
diff --git a/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart b/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart
index 39db11a..78a8b9f 100644
--- a/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart
+++ b/third_party/pkg/language_server_protocol/lib/protocol_custom_generated.dart
@@ -1437,6 +1437,10 @@
     EditableArgument.fromJson,
   );
 
+  /// The default value for this parameter if no argument is supplied. Setting
+  /// the argument to this value does not remove it from the argument list.
+  final Object? defaultValue;
+
   /// A string that can be displayed to indicate the value for this argument.
   /// This will be populated in cases where the source code is not literally the
   /// same as the value field, for example an expression or named constant.
@@ -1482,6 +1486,7 @@
   /// and displayValue can be shown as the current value instead.
   final Object? value;
   EditableArgument({
+    this.defaultValue,
     this.displayValue,
     required this.hasArgument,
     required this.isDefault,
@@ -1496,6 +1501,7 @@
   });
   @override
   int get hashCode => Object.hash(
+        defaultValue,
         displayValue,
         hasArgument,
         isDefault,
@@ -1513,6 +1519,7 @@
   bool operator ==(Object other) {
     return other is EditableArgument &&
         other.runtimeType == EditableArgument &&
+        defaultValue == other.defaultValue &&
         displayValue == other.displayValue &&
         hasArgument == other.hasArgument &&
         isDefault == other.isDefault &&
@@ -1529,6 +1536,9 @@
   @override
   Map<String, Object?> toJson() {
     var result = <String, Object?>{};
+    if (defaultValue != null) {
+      result['defaultValue'] = defaultValue;
+    }
     if (displayValue != null) {
       result['displayValue'] = displayValue;
     }
@@ -1601,6 +1611,8 @@
   }
 
   static EditableArgument fromJson(Map<String, Object?> json) {
+    final defaultValueJson = json['defaultValue'];
+    final defaultValue = defaultValueJson;
     final displayValueJson = json['displayValue'];
     final displayValue = displayValueJson as String?;
     final hasArgumentJson = json['hasArgument'];
@@ -1625,6 +1637,7 @@
     final valueJson = json['value'];
     final value = valueJson;
     return EditableArgument(
+      defaultValue: defaultValue,
       displayValue: displayValue,
       hasArgument: hasArgument,
       isDefault: isDefault,