[analysis_server] Use ElementLocation to properly resolve documentation in LSP completions

Fixes https://github.com/Dart-Code/Dart-Code/issues/3787.

Change-Id: Iffd83fbd96f2ff8dc136c2a3f5ec7824e7451ca2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/269960
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart b/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart
index 78f1364..6d690e4c 100644
--- a/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart
+++ b/pkg/analysis_server/lib/lsp_protocol/protocol_custom_generated.dart
@@ -307,6 +307,7 @@
   DartNotImportedCompletionResolutionInfo({
     required this.file,
     required this.libraryUri,
+    this.ref,
   });
   static DartNotImportedCompletionResolutionInfo fromJson(
       Map<String, Object?> json) {
@@ -314,9 +315,12 @@
     final file = fileJson as String;
     final libraryUriJson = json['libraryUri'];
     final libraryUri = libraryUriJson as String;
+    final refJson = json['ref'];
+    final ref = refJson as String?;
     return DartNotImportedCompletionResolutionInfo(
       file: file,
       libraryUri: libraryUri,
+      ref: ref,
     );
   }
 
@@ -328,11 +332,19 @@
   /// The URI to be imported if this completion is selected.
   final String libraryUri;
 
+  /// The ElementLocation of the item being completed.
+  ///
+  /// This is used to provide documentation in the resolved response.
+  final String? ref;
+
   @override
   Map<String, Object?> toJson() {
     var result = <String, Object?>{};
     result['file'] = file;
     result['libraryUri'] = libraryUri;
+    if (ref != null) {
+      result['ref'] = ref;
+    }
     return result;
   }
 
@@ -342,8 +354,12 @@
           allowsUndefined: false, allowsNull: false)) {
         return false;
       }
-      return _canParseString(obj, reporter, 'libraryUri',
-          allowsUndefined: false, allowsNull: false);
+      if (!_canParseString(obj, reporter, 'libraryUri',
+          allowsUndefined: false, allowsNull: false)) {
+        return false;
+      }
+      return _canParseString(obj, reporter, 'ref',
+          allowsUndefined: true, allowsNull: false);
     } else {
       reporter.reportError(
           'must be of type DartNotImportedCompletionResolutionInfo');
@@ -356,13 +372,15 @@
     return other is DartNotImportedCompletionResolutionInfo &&
         other.runtimeType == DartNotImportedCompletionResolutionInfo &&
         file == other.file &&
-        libraryUri == other.libraryUri;
+        libraryUri == other.libraryUri &&
+        ref == other.ref;
   }
 
   @override
   int get hashCode => Object.hash(
         file,
         libraryUri,
+        ref,
       );
 
   @override
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index 3cd41fb..0e7a233 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -13,6 +13,7 @@
 import 'package:analysis_server/src/provisional/completion/completion_core.dart';
 import 'package:analysis_server/src/services/completion/completion_performance.dart';
 import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
+import 'package:analysis_server/src/services/completion/dart/dart_completion_suggestion.dart';
 import 'package:analysis_server/src/services/completion/filtering/fuzzy_matcher.dart';
 import 'package:analysis_server/src/services/completion/yaml/analysis_options_generator.dart';
 import 'package:analysis_server/src/services/completion/yaml/fix_data_generator.dart';
@@ -455,12 +456,17 @@
         // call later.
         CompletionItemResolutionInfo? resolutionInfo;
         final libraryUri = item.libraryUri;
+
         if (useNotImportedCompletions &&
             libraryUri != null &&
             (item.isNotImported ?? false)) {
+          final dartElement =
+              item is DartCompletionSuggestion ? item.dartElement : null;
+
           resolutionInfo = DartNotImportedCompletionResolutionInfo(
             file: unit.path,
             libraryUri: libraryUri,
+            ref: dartElement?.location?.encoding,
           );
         }
 
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
index fa3d892..f33ce80 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion_resolve.dart
@@ -4,13 +4,13 @@
 
 import 'package:analysis_server/lsp_protocol/protocol.dart' hide Element;
 import 'package:analysis_server/src/computer/computer_hover.dart';
-import 'package:analysis_server/src/lsp/client_capabilities.dart';
 import 'package:analysis_server/src/lsp/constants.dart';
 import 'package:analysis_server/src/lsp/handlers/handlers.dart';
 import 'package:analysis_server/src/lsp/mapping.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/session.dart';
-import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/utilities/extensions/analysis_session.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
 
 class CompletionResolveHandler
@@ -47,13 +47,25 @@
     }
   }
 
-  Future<ErrorOr<CompletionItem>> resolveDartCompletion(
+  Future<ErrorOr<CompletionItem>> resolveDartNotImportedCompletion(
     CompletionItem item,
-    LspClientCapabilities clientCapabilities,
-    CancellationToken token, {
-    required String file,
-    required Uri libraryUri,
-  }) async {
+    DartNotImportedCompletionResolutionInfo data,
+    CancellationToken token,
+  ) async {
+    final clientCapabilities = server.clientCapabilities;
+    if (clientCapabilities == null) {
+      // This should not happen unless a client misbehaves.
+      return error(ErrorCodes.ServerNotInitialized,
+          'Requests not before server is initilized');
+    }
+
+    final file = data.file;
+    final libraryUri = Uri.parse(data.libraryUri);
+    final elementLocationReference = data.ref;
+    final elementLocation = elementLocationReference != null
+        ? ElementLocationImpl.con2(elementLocationReference)
+        : null;
+
     const timeout = Duration(milliseconds: 1000);
     var timer = Stopwatch()..start();
     _latestCompletionItem = item;
@@ -108,7 +120,9 @@
 
         // Look up documentation if we can get an element for this item.
         Either2<MarkupContent, String>? documentation;
-        final element = await _getElement(session, libraryUri, item);
+        final element = elementLocation != null
+            ? await session.locateElement(elementLocation)
+            : null;
         if (element != null) {
           final formats = clientCapabilities.completionDocumentationFormats;
           final dartDocInfo = server.getDartdocDirectiveInfoForSession(session);
@@ -172,27 +186,6 @@
     );
   }
 
-  Future<ErrorOr<CompletionItem>> resolveDartNotImportedCompletion(
-    CompletionItem item,
-    DartNotImportedCompletionResolutionInfo data,
-    CancellationToken token,
-  ) async {
-    final clientCapabilities = server.clientCapabilities;
-    if (clientCapabilities == null) {
-      // This should not happen unless a client misbehaves.
-      return error(ErrorCodes.ServerNotInitialized,
-          'Requests not before server is initilized');
-    }
-
-    return resolveDartCompletion(
-      item,
-      clientCapabilities,
-      token,
-      file: data.file,
-      libraryUri: Uri.parse(data.libraryUri),
-    );
-  }
-
   Future<ErrorOr<CompletionItem>> resolvePubPackageCompletion(
     CompletionItem item,
     PubPackageCompletionItemResolutionInfo data,
@@ -228,32 +221,4 @@
       data: item.data,
     ));
   }
-
-  /// Gets the [Element] for the completion item [item] in [libraryUri].
-  Future<Element?> _getElement(
-    AnalysisSession session,
-    Uri libraryUri,
-    CompletionItem item,
-  ) async {
-    // If filterText is different to the label, it's because label has
-    // parens/args appended so we should take the filterText to get the
-    // elements name without. We cannot use insertText as it may include
-    // snippets, whereas filterText is always just the pure string.
-    var name = item.filterText ?? item.label;
-
-    // The label might be `MyEnum.myValue`, but we need to find `MyEnum`.
-    if (name.contains('.')) {
-      name = name.substring(0, name.indexOf('.'));
-    }
-
-    // TODO(dantup): This is not handling default constructors or enums
-    //  correctly, so they will both show dart docs from the class/enum and not
-    //  the constructor/enum member. Extension members are not found at all and
-    //  will provide no docs.
-
-    final result = await session.getLibraryByUri(libraryUri.toString());
-    return result is LibraryElementResult
-        ? result.element.exportNamespace.get(name)
-        : null;
-  }
 }
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/dart_completion_suggestion.dart b/pkg/analysis_server/lib/src/services/completion/dart/dart_completion_suggestion.dart
new file mode 100644
index 0000000..cff4b25
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/dart/dart_completion_suggestion.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, 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/src/protocol_server.dart'
+    show CompletionSuggestion;
+import 'package:analyzer/dart/element/element.dart';
+
+/// An extension of [CompletionSuggestion] that includes additional
+/// Dart-specific fields that are not part of the JSON protocol.
+class DartCompletionSuggestion extends CompletionSuggestion {
+  final Element? dartElement;
+
+  DartCompletionSuggestion(
+    super.kind,
+    super.relevance,
+    super.completion,
+    super.selectionOffset,
+    super.selectionLength,
+    super.isDeprecated,
+    super.isPotential, {
+    super.displayText,
+    super.replacementOffset,
+    super.replacementLength,
+    super.docSummary,
+    super.docComplete,
+    super.declaringType,
+    super.defaultArgumentListString,
+    super.defaultArgumentListTextRanges,
+    super.element,
+    super.returnType,
+    super.parameterNames,
+    super.parameterTypes,
+    super.requiredParameterCount,
+    super.hasNamedParameters,
+    super.parameterName,
+    super.parameterType,
+    super.libraryUri,
+    super.isNotImported,
+    required this.dartElement,
+  });
+}
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index 6b34277..b20f618 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -11,6 +11,7 @@
     hide Element, ElementKind;
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
 import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
+import 'package:analysis_server/src/services/completion/dart/dart_completion_suggestion.dart';
 import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
 import 'package:analysis_server/src/services/completion/dart/utilities.dart';
 import 'package:analysis_server/src/utilities/extensions/ast.dart';
@@ -350,7 +351,7 @@
       required String displayText,
       required int selectionOffset,
     }) {
-      return CompletionSuggestion(
+      return DartCompletionSuggestion(
         CompletionSuggestionKind.INVOCATION,
         Relevance.closure,
         completion,
@@ -359,6 +360,7 @@
         false,
         false,
         displayText: displayText,
+        dartElement: type.element,
       );
     }
 
@@ -724,7 +726,7 @@
       buffer.write('$indent});');
 
       _addSuggestion(
-        CompletionSuggestion(
+        DartCompletionSuggestion(
           kind,
           relevance,
           buffer.toString(),
@@ -734,6 +736,7 @@
           false,
           // Let the user know that we are going to insert a complete statement.
           displayText: 'setState(() {});',
+          dartElement: method,
         ),
         textToMatchOverride: 'setState',
       );
@@ -812,7 +815,7 @@
       relevance = Relevance.namedArgument;
     }
 
-    var suggestion = CompletionSuggestion(
+    var suggestion = DartCompletionSuggestion(
         CompletionSuggestionKind.NAMED_ARGUMENT,
         relevance,
         completion,
@@ -822,7 +825,8 @@
         false,
         parameterName: name,
         parameterType: type,
-        replacementLength: replacementLength);
+        replacementLength: replacementLength,
+        dartElement: parameter);
     if (parameter is FieldFormalParameterElement) {
       _setDocumentation(suggestion, parameter);
       suggestion.element =
@@ -916,7 +920,7 @@
     var offsetDelta = targetId.offset + replacement.indexOf(completion);
     var displayText =
         displayTextBuffer.isNotEmpty ? displayTextBuffer.toString() : null;
-    var suggestion = CompletionSuggestion(
+    var suggestion = DartCompletionSuggestion(
         CompletionSuggestionKind.OVERRIDE,
         Relevance.override,
         completion,
@@ -924,7 +928,8 @@
         selectionRange.length,
         element.hasDeprecated,
         false,
-        displayText: displayText);
+        displayText: displayText,
+        dartElement: element);
     suggestion.element = protocol.convertElement(element,
         withNullability: _isNonNullableByDefault);
     _addSuggestion(
@@ -1418,6 +1423,7 @@
       documentation: documentation,
       defaultArgumentList: defaultArgumentList,
       element: suggestedElement,
+      dartElement: element,
     );
   }
 
@@ -1651,7 +1657,7 @@
 
   @override
   CompletionSuggestion build() {
-    return CompletionSuggestion(
+    return DartCompletionSuggestion(
       kind,
       relevance,
       completion,
@@ -1672,6 +1678,7 @@
       defaultArgumentListTextRanges: element.defaultArgumentList?.ranges,
       libraryUri: libraryUriStr,
       isNotImported: isNotImported ? true : null,
+      dartElement: element.dartElement,
     );
   }
 }
@@ -1691,6 +1698,7 @@
   CompletionDefaultArgumentList? defaultArgumentList;
   final _ElementDocumentation? documentation;
   final protocol.Element element;
+  final Element dartElement;
 
   _ElementCompletionData({
     required this.completion,
@@ -1704,6 +1712,7 @@
     required this.defaultArgumentList,
     required this.documentation,
     required this.element,
+    required this.dartElement,
   });
 }
 
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 138d951..1e54c63 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -38,16 +38,16 @@
 void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(CompletionTest);
+    defineReflectiveTests(CompletionDocumentationResolutionTest);
     defineReflectiveTests(DartSnippetCompletionTest);
     defineReflectiveTests(FlutterSnippetCompletionTest);
     defineReflectiveTests(FlutterSnippetCompletionWithoutNullSafetyTest);
   });
 }
 
-@reflectiveTest
-class CompletionTest extends AbstractLspAnalysisServerTest
+abstract class AbstractCompletionTest extends AbstractLspAnalysisServerTest
     with CompletionTestMixin {
-  CompletionTest() {
+  AbstractCompletionTest() {
     defaultInitializationOptions = {
       // Default to a high budget for tests because everything is cold and
       // may take longer to return.
@@ -55,6 +55,174 @@
     };
   }
 
+  void expectDocumentation(CompletionItem completion, Matcher matcher) {
+    final docs = completion.documentation?.map(
+      (markup) => markup.value,
+      (string) => string,
+    );
+    expect(docs, matcher);
+  }
+}
+
+@reflectiveTest
+class CompletionDocumentationResolutionTest extends AbstractCompletionTest {
+  late String content;
+
+  Future<CompletionItem> getCompletionItem(String label) async {
+    final completions =
+        await getCompletion(mainFileUri, positionFromMarker(content));
+    return completions.singleWhere((c) => c.label == label);
+  }
+
+  Future<void> initializeServer() async {
+    final initialAnalysis = waitForAnalysisComplete();
+    await initialize(
+        workspaceCapabilities:
+            withApplyEditSupport(emptyWorkspaceClientCapabilities));
+    await openFile(mainFileUri, withoutMarkers(content));
+    await initialAnalysis;
+  }
+
+  Future<void> test_class() async {
+    newFile(
+      join(projectFolderPath, 'my_class.dart'),
+      '''
+/// Class.
+class MyClass {}
+      ''',
+    );
+
+    content = '''
+void f() {
+  MyClass^
+}
+    ''';
+
+    await initializeServer();
+
+    final completion = await getCompletionItem('MyClass');
+    expectDocumentation(completion, isNull);
+
+    final resolved = await resolveCompletion(completion);
+    expectDocumentation(resolved, contains('Class.'));
+  }
+
+  Future<void> test_class_constructor() async {
+    newFile(
+      join(projectFolderPath, 'my_class.dart'),
+      '''
+class MyClass {
+  /// Constructor.
+  MyClass();
+}
+      ''',
+    );
+
+    content = '''
+void f() {
+  MyClass^
+}
+    ''';
+
+    await initializeServer();
+
+    final completion = await getCompletionItem('MyClass()');
+    expectDocumentation(completion, isNull);
+
+    final resolved = await resolveCompletion(completion);
+    expectDocumentation(resolved, contains('Constructor.'));
+  }
+
+  Future<void> test_class_constructorNamed() async {
+    newFile(
+      join(projectFolderPath, 'my_class.dart'),
+      '''
+class MyClass {
+  /// Named Constructor.
+  MyClass.named();
+}
+      ''',
+    );
+
+    content = '''
+void f() {
+  MyClass^
+}
+    ''';
+
+    await initializeServer();
+
+    final completion = await getCompletionItem('MyClass.named()');
+    expectDocumentation(completion, isNull);
+
+    final resolved = await resolveCompletion(completion);
+    expectDocumentation(resolved, contains('Named Constructor.'));
+  }
+
+  Future<void> test_enum() async {
+    newFile(
+      join(projectFolderPath, 'my_enum.dart'),
+      '''
+/// Enum.
+enum MyEnum {}
+      ''',
+    );
+
+    content = '''
+void f() {
+  MyEnum^
+}
+    ''';
+
+    await initializeServer();
+
+    final completion = await getCompletionItem('MyEnum');
+    expectDocumentation(completion, isNull);
+
+    final resolved = await resolveCompletion(completion);
+    expectDocumentation(resolved, contains('Enum.'));
+  }
+
+  Future<void> test_enum_member() async {
+    // Function used to provide type context in main file without importing
+    // the enum.
+    newFile(
+      join(projectFolderPath, 'lib', 'func.dart'),
+      '''
+import 'my_enum.dart';
+void enumFunc(MyEnum e) {}
+      ''',
+    );
+
+    newFile(
+      join(projectFolderPath, 'lib', 'my_enum.dart'),
+      '''
+enum MyEnum {
+  /// Enum Member.
+  one,
+}
+      ''',
+    );
+
+    content = '''
+import 'func.dart';
+void f() {
+  enumFunc(MyEnum^)
+}
+    ''';
+
+    await initializeServer();
+
+    final completion = await getCompletionItem('MyEnum.one');
+    expectDocumentation(completion, isNull);
+
+    final resolved = await resolveCompletion(completion);
+    expectDocumentation(resolved, contains('Enum Member.'));
+  }
+}
+
+@reflectiveTest
+class CompletionTest extends AbstractCompletionTest {
   /// Checks whether the correct types of documentation are returned for
   /// completions based on [preference].
   Future<void> assertDocumentation(
@@ -85,21 +253,17 @@
 
     final res = await getCompletion(mainFileUri, positionFromMarker(content));
     final completion = res.singleWhere((c) => c.label == 'A');
-    final docs = completion.documentation?.map(
-      (markup) => markup.value,
-      (string) => string,
-    );
 
     if (includesSummary) {
-      expect(docs, contains('Summary.'));
+      expectDocumentation(completion, contains('Summary.'));
     } else {
-      expect(docs, isNot(contains('Summary.')));
+      expectDocumentation(completion, isNot(contains('Summary.')));
     }
 
     if (includesFull) {
-      expect(docs, contains('Full.'));
+      expectDocumentation(completion, contains('Full.'));
     } else {
-      expect(docs, isNot(contains('Full.')));
+      expectDocumentation(completion, isNot(contains('Full.')));
     }
   }
 
@@ -143,23 +307,19 @@
 
     // Expect no docs in original response and correct type of docs added
     // during resolve.
-    expect(completion.documentation, isNull);
+    expectDocumentation(completion, isNull);
     final resolved = await resolveCompletion(completion);
-    final docs = resolved.documentation?.map(
-      (markup) => markup.value,
-      (string) => string,
-    );
 
     if (includesSummary) {
-      expect(docs, contains('Summary.'));
+      expectDocumentation(resolved, contains('Summary.'));
     } else {
-      expect(docs, isNot(contains('Summary.')));
+      expectDocumentation(resolved, isNot(contains('Summary.')));
     }
 
     if (includesFull) {
-      expect(docs, contains('Full.'));
+      expectDocumentation(resolved, contains('Full.'));
     } else {
-      expect(docs, isNot(contains('Full.')));
+      expectDocumentation(resolved, isNot(contains('Full.')));
     }
   }
 
@@ -1982,7 +2142,7 @@
     final originalTextEdit = completion.textEdit;
 
     // Expect no docs, this is added during resolve.
-    expect(completion.documentation, isNull);
+    expectDocumentation(completion, isNull);
 
     // Resolve the completion item (via server) to get any additional edits.
     // This is LSP's equiv of getSuggestionDetails() and is invoked by LSP
@@ -1998,10 +2158,7 @@
     );
 
     // Ensure the doc comment was added.
-    expect(
-      resolved.documentation!.valueEquals('This class is in another file.'),
-      isTrue,
-    );
+    expectDocumentation(resolved, equals('This class is in another file.'));
 
     // Ensure the edit did not change.
     expect(resolved.textEdit, originalTextEdit);
@@ -2485,7 +2642,7 @@
     final originalTextEdit = completion.textEdit;
 
     // Expect no docs, this is added during resolve.
-    expect(completion.documentation, isNull);
+    expectDocumentation(completion, isNull);
 
     // Resolve the completion item (via server) to get any additional edits.
     // This is LSP's equiv of getSuggestionDetails() and is invoked by LSP
@@ -2501,10 +2658,7 @@
     );
 
     // Ensure the doc comment was added.
-    expect(
-      resolved.documentation!.valueEquals('This class is in another file.'),
-      isTrue,
-    );
+    expectDocumentation(resolved, equals('This class is in another file.'));
 
     // Ensure the edit did not change.
     expect(resolved.textEdit, originalTextEdit);
@@ -2762,7 +2916,7 @@
     expect(completion.textEdit, isNotNull);
 
     // Expect no docs, this is added during resolve.
-    expect(completion.documentation, isNull);
+    expectDocumentation(completion, isNull);
 
     // Resolve the completion item (via server) to get any additional edits.
     // This is LSP's equiv of getSuggestionDetails() and is invoked by LSP
@@ -2887,6 +3041,7 @@
     );
     await openFile(mainFileUri, withoutMarkers(content));
     await initialAnalysis;
+
     final res = await getCompletion(mainFileUri, positionFromMarker(content));
 
     // Ensure the item doesn't appear in the results (because we might not
diff --git a/pkg/analysis_server/tool/lsp_spec/generate_all.dart b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
index 1f4720c..111adb9 100644
--- a/pkg/analysis_server/tool/lsp_spec/generate_all.dart
+++ b/pkg/analysis_server/tool/lsp_spec/generate_all.dart
@@ -319,6 +319,13 @@
           type: 'string',
           comment: 'The URI to be imported if this completion is selected.',
         ),
+        field(
+          'ref',
+          type: 'string',
+          canBeUndefined: true,
+          comment: 'The ElementLocation of the item being completed.\n\n'
+              'This is used to provide documentation in the resolved response.',
+        ),
       ],
       baseType: 'CompletionItemResolutionInfo',
     ),