[analysis_server] Add 'range' to EditableArguments response

See https://github.com/flutter/devtools/issues/9035

Change-Id: Ia2cadea05f10f16e00d1dbf0240b16761c0c6b0f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419600
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Elliott Brooks <elliottbrooks@google.com>
Commit-Queue: 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 2889807..ad855c5 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
@@ -15,6 +15,7 @@
 /// Information about the arguments and parameters for an invocation.
 typedef EditableInvocationInfo =
     ({
+      AstNode invocation,
       String? widgetName,
       String? widgetDocumentation,
       List<FormalParameterElement> parameters,
@@ -51,6 +52,10 @@
       };
     });
 
+    if (invocation == null) {
+      return null;
+    }
+
     String? widgetName, widgetDocumentation;
     if (invocation is InstanceCreationExpression) {
       widgetName = invocation.constructorName.type.name2.lexeme;
@@ -106,6 +111,7 @@
     };
 
     return (
+      invocation: invocation,
       widgetName: widgetName,
       widgetDocumentation: widgetDocumentation,
       parameters: parameters,
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 7c205d4..0c15c1c 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
@@ -83,8 +83,8 @@
       }
 
       // Locate the invocation we're editing.
-      var invocation = getInvocationInfo(result, offset);
-      if (invocation == null) {
+      var invocationInfo = getInvocationInfo(result, offset);
+      if (invocationInfo == null) {
         return error(
           ServerErrorCodes.EditArgumentInvalidPosition,
           'No invocation was found at the provided position',
@@ -92,6 +92,7 @@
       }
 
       var (
+        invocation: _,
         :widgetName,
         :widgetDocumentation,
         :parameters,
@@ -100,7 +101,7 @@
         :argumentList,
         :numPositionals,
         :numSuppliedPositionals,
-      ) = invocation;
+      ) = invocationInfo;
 
       // Find the parameter we're editing the argument for.
       var parameterName = params.edit.name;
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 5603076..741a9ad 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
@@ -90,14 +90,15 @@
     TextDocumentIdentifier textDocument,
     int offset,
   ) {
-    var invocation = getInvocationInfo(result, offset);
-    if (invocation == null) {
+    var invocationInfo = getInvocationInfo(result, offset);
+    if (invocationInfo == null) {
       return null;
     }
 
     var textDocument = server.getVersionedDocumentIdentifier(result.path);
 
     var (
+      :invocation,
       :widgetName,
       :widgetDocumentation,
       :parameters,
@@ -106,7 +107,7 @@
       :argumentList,
       :numPositionals,
       :numSuppliedPositionals,
-    ) = invocation;
+    ) = invocationInfo;
 
     // Build the complete list of editable arguments.
     //
@@ -135,6 +136,7 @@
       name: widgetName,
       documentation: widgetDocumentation,
       arguments: editableArguments.nonNulls.toList(),
+      range: toRange(result.lineInfo, invocation.offset, invocation.length),
     );
   }
 
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 256956b..f7808c9 100644
--- a/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
+++ b/pkg/analysis_server/test/shared/shared_editable_arguments_tests.dart
@@ -1058,6 +1058,18 @@
     );
   }
 
+  Future<void> test_range() 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!.range, code.range.range);
+  }
+
   Future<void> test_textDocument_unopenedFile() async {
     var result = await getEditableArgumentsFor('''
 class MyWidget extends StatelessWidget {
diff --git a/pkg/analysis_server/tool/lsp_spec/generate_all.dart b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
index 82278c2..68b711a 100644
--- a/pkg/analysis_server/tool/lsp_spec/generate_all.dart
+++ b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
@@ -425,6 +425,7 @@
       field('documentation', type: 'string', canBeUndefined: true),
       // TODO(dantup): field('refactors', ...),
       field('arguments', type: 'EditableArgument', array: true),
+      field('range', type: 'Range', comment: 'The range of the invocation.'),
     ]),
     interface('EditableArgument', [
       field(
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 4c5658b..30bdfcc 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
@@ -1693,11 +1693,15 @@
   final String? documentation;
 
   final String? name;
+
+  /// The range of the invocation.
+  final Range range;
   final TextDocumentIdentifier textDocument;
   EditableArguments({
     required this.arguments,
     this.documentation,
     this.name,
+    required this.range,
     required this.textDocument,
   });
   @override
@@ -1705,6 +1709,7 @@
         lspHashCode(arguments),
         documentation,
         name,
+        range,
         textDocument,
       );
 
@@ -1715,6 +1720,7 @@
         const DeepCollectionEquality().equals(arguments, other.arguments) &&
         documentation == other.documentation &&
         name == other.name &&
+        range == other.range &&
         textDocument == other.textDocument;
   }
 
@@ -1728,6 +1734,7 @@
     if (name != null) {
       result['name'] = name;
     }
+    result['range'] = range.toJson();
     result['textDocument'] = textDocument.toJson();
     return result;
   }
@@ -1749,6 +1756,10 @@
           allowsUndefined: true, allowsNull: false)) {
         return false;
       }
+      if (!_canParseRange(obj, reporter, 'range',
+          allowsUndefined: false, allowsNull: false)) {
+        return false;
+      }
       return _canParseTextDocumentIdentifier(obj, reporter, 'textDocument',
           allowsUndefined: false, allowsNull: false);
     } else {
@@ -1766,6 +1777,8 @@
     final documentation = documentationJson as String?;
     final nameJson = json['name'];
     final name = nameJson as String?;
+    final rangeJson = json['range'];
+    final range = Range.fromJson(rangeJson as Map<String, Object?>);
     final textDocumentJson = json['textDocument'];
     final textDocument = TextDocumentIdentifier.fromJson(
         textDocumentJson as Map<String, Object?>);
@@ -1773,6 +1786,7 @@
       arguments: arguments,
       documentation: documentation,
       name: name,
+      range: range,
       textDocument: textDocument,
     );
   }