ruby-lsp 0.17.4 → 0.17.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,5 +63,264 @@ module RubyIndexer
63
63
  assert_instance_of(Entry::SingletonClass, owner)
64
64
  assert_equal("File::<Class:File>", owner.name)
65
65
  end
66
+
67
+ def test_location_and_name_location_are_the_same
68
+ # NOTE: RBS does not store the name location for classes, modules or methods. This behaviour is not exactly what
69
+ # we would like, but for now we assign the same location to both
70
+
71
+ entries = @index["Array"]
72
+ refute_nil(entries)
73
+ entry = entries.find { |entry| entry.is_a?(Entry::Class) }
74
+
75
+ assert_same(entry.location, entry.name_location)
76
+ end
77
+
78
+ def test_rbs_method_with_required_positionals
79
+ entries = @index["crypt"]
80
+ assert_equal(1, entries.length)
81
+
82
+ entry = entries.first
83
+ signatures = entry.signatures
84
+ assert_equal(1, signatures.length)
85
+
86
+ first_signature = signatures.first
87
+
88
+ # (::string salt_str) -> ::String
89
+
90
+ assert_equal(1, first_signature.parameters.length)
91
+ assert_kind_of(Entry::RequiredParameter, first_signature.parameters[0])
92
+ assert_equal(:salt_str, first_signature.parameters[0].name)
93
+ end
94
+
95
+ def test_rbs_method_with_unnamed_required_positionals
96
+ entries = @index["try_convert"]
97
+ entry = entries.find { |entry| entry.owner.name == "Array::<Class:Array>" }
98
+
99
+ parameters = entry.signatures[0].parameters
100
+
101
+ assert_equal([:arg0], parameters.map(&:name))
102
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
103
+ end
104
+
105
+ def test_rbs_method_with_optional_positionals
106
+ entries = @index["polar"]
107
+ entry = entries.find { |entry| entry.owner.name == "Complex::<Class:Complex>" }
108
+
109
+ # def self.polar: (Numeric, ?Numeric) -> Complex
110
+
111
+ parameters = entry.signatures[0].parameters
112
+
113
+ assert_equal([:arg0, :arg1], parameters.map(&:name))
114
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
115
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
116
+ end
117
+
118
+ def test_rbs_method_with_optional_parameter
119
+ entries = @index["chomp"]
120
+ assert_equal(1, entries.length)
121
+
122
+ entry = entries.first
123
+ signatures = entry.signatures
124
+ assert_equal(1, signatures.length)
125
+
126
+ first_signature = signatures.first
127
+
128
+ # (?::string? separator) -> ::String
129
+
130
+ assert_equal(1, first_signature.parameters.length)
131
+ assert_kind_of(Entry::OptionalParameter, first_signature.parameters[0])
132
+ assert_equal(:separator, first_signature.parameters[0].name)
133
+ end
134
+
135
+ def test_rbs_method_with_required_and_optional_parameters
136
+ entries = @index["gsub"]
137
+ assert_equal(1, entries.length)
138
+
139
+ entry = entries.first
140
+
141
+ signatures = entry.signatures
142
+ assert_equal(3, signatures.length)
143
+
144
+ # (::Regexp | ::string pattern, ::string | ::hash[::String, ::_ToS] replacement) -> ::String
145
+ # | (::Regexp | ::string pattern) -> ::Enumerator[::String, ::String]
146
+ # | (::Regexp | ::string pattern) { (::String match) -> ::_ToS } -> ::String
147
+
148
+ parameters = signatures[0].parameters
149
+ assert_equal([:pattern, :replacement], parameters.map(&:name))
150
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
151
+ assert_kind_of(Entry::RequiredParameter, parameters[1])
152
+
153
+ parameters = signatures[1].parameters
154
+ assert_equal([:pattern], parameters.map(&:name))
155
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
156
+
157
+ parameters = signatures[2].parameters
158
+ assert_equal([:pattern, :"<anonymous block>"], parameters.map(&:name))
159
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
160
+ assert_kind_of(Entry::BlockParameter, parameters[1])
161
+ end
162
+
163
+ def test_rbs_anonymous_block_parameter
164
+ entries = @index["open"]
165
+ entry = entries.find { |entry| entry.owner.name == "File::<Class:File>" }
166
+
167
+ assert_equal(2, entry.signatures.length)
168
+
169
+ # (::String name, ?::String mode, ?::Integer perm) -> ::IO?
170
+ # | [T] (::String name, ?::String mode, ?::Integer perm) { (::IO) -> T } -> T
171
+
172
+ parameters = entry.signatures[0].parameters
173
+ assert_equal([:file_name, :mode, :perm], parameters.map(&:name))
174
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
175
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
176
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
177
+
178
+ parameters = entry.signatures[1].parameters
179
+ assert_equal([:file_name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
180
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
181
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
182
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
183
+ assert_kind_of(Entry::BlockParameter, parameters[3])
184
+ end
185
+
186
+ def test_rbs_method_with_rest_positionals
187
+ entries = @index["count"]
188
+ entry = entries.find { |entry| entry.owner.name == "String" }
189
+
190
+ parameters = entry.signatures.first.parameters
191
+ assert_equal(1, entry.signatures.length)
192
+
193
+ # (::String::selector selector_0, *::String::selector more_selectors) -> ::Integer
194
+
195
+ assert_equal([:selector_0, :more_selectors], parameters.map(&:name))
196
+ assert_kind_of(RubyIndexer::Entry::RequiredParameter, parameters[0])
197
+ assert_kind_of(RubyIndexer::Entry::RestParameter, parameters[1])
198
+ end
199
+
200
+ def test_rbs_method_with_trailing_positionals
201
+ entries = @index["select"] # https://ruby-doc.org/3.3.3/IO.html#method-c-select
202
+ entry = entries.find { |entry| entry.owner.name == "IO::<Class:IO>" }
203
+
204
+ signatures = entry.signatures
205
+ assert_equal(2, signatures.length)
206
+
207
+ # def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ] # rubocop:disable Layout/LineLength
208
+ # | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]? # rubocop:disable Layout/LineLength
209
+
210
+ parameters = signatures[0].parameters
211
+ assert_equal([:read_array, :write_array, :error_array], parameters.map(&:name))
212
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
213
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
214
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
215
+
216
+ parameters = signatures[1].parameters
217
+ assert_equal([:read_array, :write_array, :error_array, :timeout], parameters.map(&:name))
218
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
219
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
220
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
221
+ assert_kind_of(Entry::OptionalParameter, parameters[3])
222
+ end
223
+
224
+ def test_rbs_method_with_optional_keywords
225
+ entries = @index["step"]
226
+ entry = entries.find { |entry| entry.owner.name == "Numeric" }
227
+
228
+ signatures = entry.signatures
229
+ assert_equal(4, signatures.length)
230
+
231
+ # (?::Numeric limit, ?::Numeric step) { (::Numeric) -> void } -> self
232
+ # | (?::Numeric limit, ?::Numeric step) -> ::Enumerator[::Numeric, self]
233
+ # | (?by: ::Numeric, ?to: ::Numeric) { (::Numeric) -> void } -> self
234
+ # | (?by: ::Numeric, ?to: ::Numeric) -> ::Enumerator[::Numeric, self]
235
+
236
+ parameters = signatures[0].parameters
237
+ assert_equal([:limit, :step, :"<anonymous block>"], parameters.map(&:name))
238
+ assert_kind_of(Entry::OptionalParameter, parameters[0])
239
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
240
+ assert_kind_of(Entry::BlockParameter, parameters[2])
241
+
242
+ parameters = signatures[1].parameters
243
+ assert_equal([:limit, :step], parameters.map(&:name))
244
+ assert_kind_of(Entry::OptionalParameter, parameters[0])
245
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
246
+
247
+ parameters = signatures[2].parameters
248
+ assert_equal([:by, :to, :"<anonymous block>"], parameters.map(&:name))
249
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
250
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
251
+ assert_kind_of(Entry::BlockParameter, parameters[2])
252
+
253
+ parameters = signatures[3].parameters
254
+ assert_equal([:by, :to], parameters.map(&:name))
255
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[0])
256
+ assert_kind_of(Entry::OptionalKeywordParameter, parameters[1])
257
+ end
258
+
259
+ def test_rbs_method_with_required_keywords
260
+ # There are no methods in Core that have required keyword arguments,
261
+ # so we test against RBS directly
262
+
263
+ rbs = <<~RBS
264
+ class File
265
+ def foo: (a: ::Numeric sz, b: ::Numeric) -> void
266
+ end
267
+ RBS
268
+ signatures = parse_rbs_methods(rbs, "foo")
269
+ parameters = signatures[0].parameters
270
+ assert_equal([:a, :b], parameters.map(&:name))
271
+ assert_kind_of(Entry::KeywordParameter, parameters[0])
272
+ assert_kind_of(Entry::KeywordParameter, parameters[1])
273
+ end
274
+
275
+ def test_rbs_method_with_rest_keywords
276
+ entries = @index["method_missing"]
277
+ entry = entries.find { |entry| entry.owner.name == "BasicObject" }
278
+ signatures = entry.signatures
279
+ assert_equal(1, signatures.length)
280
+
281
+ # (Symbol, *untyped, **untyped) ?{ (*untyped, **untyped) -> untyped } -> untyped
282
+
283
+ parameters = signatures[0].parameters
284
+ assert_equal([:arg0, :"<anonymous splat>", :"<anonymous keyword splat>"], parameters.map(&:name))
285
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
286
+ assert_kind_of(Entry::RestParameter, parameters[1])
287
+ assert_kind_of(Entry::KeywordRestParameter, parameters[2])
288
+ end
289
+
290
+ def test_parse_simple_rbs
291
+ rbs = <<~RBS
292
+ class File
293
+ def self?.open: (String name, ?String mode, ?Integer perm) -> IO?
294
+ | [T] (String name, ?String mode, ?Integer perm) { (IO) -> T } -> T
295
+ end
296
+ RBS
297
+ signatures = parse_rbs_methods(rbs, "open")
298
+ assert_equal(2, signatures.length)
299
+ parameters = signatures[0].parameters
300
+ assert_equal([:name, :mode, :perm], parameters.map(&:name))
301
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
302
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
303
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
304
+
305
+ parameters = signatures[1].parameters
306
+ assert_equal([:name, :mode, :perm, :"<anonymous block>"], parameters.map(&:name))
307
+ assert_kind_of(Entry::RequiredParameter, parameters[0])
308
+ assert_kind_of(Entry::OptionalParameter, parameters[1])
309
+ assert_kind_of(Entry::OptionalParameter, parameters[2])
310
+ assert_kind_of(Entry::BlockParameter, parameters[3])
311
+ end
312
+
313
+ private
314
+
315
+ def parse_rbs_methods(rbs, method_name)
316
+ buffer = RBS::Buffer.new(content: rbs, name: "")
317
+ _, _, declarations = RBS::Parser.parse_signature(buffer)
318
+ index = RubyIndexer::Index.new
319
+ indexer = RubyIndexer::RBSIndexer.new(index)
320
+ pathname = Pathname.new("file.rbs")
321
+ indexer.process_signature(pathname, declarations)
322
+ entry = T.must(index[method_name]).first
323
+ T.cast(entry, Entry::Method).signatures
324
+ end
66
325
  end
67
326
  end
@@ -40,16 +40,12 @@ module RubyIndexer
40
40
  assert_nil(entries, "Expected #{expected_name} to not be indexed")
41
41
  end
42
42
 
43
- def assert_no_entries
44
- assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
45
- end
46
-
47
43
  def assert_no_indexed_entries
48
44
  assert_equal(@default_indexed_entries, @index.instance_variable_get(:@entries))
49
45
  end
50
46
 
51
47
  def assert_no_entry(entry)
52
- refute(@index.instance_variable_get(:@entries).key?(entry), "Expected '#{entry}' to not be indexed")
48
+ refute(@index.indexed?(entry), "Expected '#{entry}' to not be indexed")
53
49
  end
54
50
  end
55
51
  end
@@ -72,6 +72,15 @@ module RubyLsp
72
72
  addon.add_error(e)
73
73
  end
74
74
  end
75
+
76
+ # Intended for use by tests for addons
77
+ sig { params(addon_name: String).returns(Addon) }
78
+ def get(addon_name)
79
+ addon = addons.find { |addon| addon.name == addon_name }
80
+ raise "Could not find addon '#{addon_name}'" unless addon
81
+
82
+ addon
83
+ end
75
84
  end
76
85
 
77
86
  sig { void }
@@ -157,7 +166,10 @@ module RubyLsp
157
166
  # Creates a new Definition listener. This method is invoked on every Definition request
158
167
  sig do
159
168
  overridable.params(
160
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
169
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
170
+ Interface::Location,
171
+ Interface::LocationLink,
172
+ )],
161
173
  uri: URI::Generic,
162
174
  node_context: NodeContext,
163
175
  dispatcher: Prism::Dispatcher,
@@ -3,6 +3,13 @@
3
3
 
4
4
  module RubyLsp
5
5
  class Document
6
+ class LanguageId < T::Enum
7
+ enums do
8
+ Ruby = new("ruby")
9
+ ERB = new("erb")
10
+ end
11
+ end
12
+
6
13
  extend T::Sig
7
14
  extend T::Helpers
8
15
 
@@ -34,21 +41,14 @@ module RubyLsp
34
41
  @parse_result = T.let(parse, Prism::ParseResult)
35
42
  end
36
43
 
37
- sig { returns(Prism::ProgramNode) }
38
- def tree
39
- @parse_result.value
40
- end
41
-
42
- sig { returns(T::Array[Prism::Comment]) }
43
- def comments
44
- @parse_result.comments
45
- end
46
-
47
44
  sig { params(other: Document).returns(T::Boolean) }
48
45
  def ==(other)
49
- @source == other.source
46
+ self.class == other.class && uri == other.uri && @source == other.source
50
47
  end
51
48
 
49
+ sig { abstract.returns(LanguageId) }
50
+ def language_id; end
51
+
52
52
  # TODO: remove this method once all nonpositional requests have been migrated to the listener pattern
53
53
  sig do
54
54
  type_parameters(:T)
@@ -96,10 +96,8 @@ module RubyLsp
96
96
  sig { abstract.returns(Prism::ParseResult) }
97
97
  def parse; end
98
98
 
99
- sig { returns(T::Boolean) }
100
- def syntax_error?
101
- @parse_result.failure?
102
- end
99
+ sig { abstract.returns(T::Boolean) }
100
+ def syntax_error?; end
103
101
 
104
102
  sig { returns(Scanner) }
105
103
  def create_scanner
@@ -0,0 +1,125 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ class ERBDocument < Document
6
+ extend T::Sig
7
+
8
+ sig { override.returns(Prism::ParseResult) }
9
+ def parse
10
+ return @parse_result unless @needs_parsing
11
+
12
+ @needs_parsing = false
13
+ scanner = ERBScanner.new(@source)
14
+ scanner.scan
15
+ @parse_result = Prism.parse(scanner.ruby)
16
+ end
17
+
18
+ sig { override.returns(T::Boolean) }
19
+ def syntax_error?
20
+ @parse_result.failure?
21
+ end
22
+
23
+ sig { override.returns(LanguageId) }
24
+ def language_id
25
+ LanguageId::ERB
26
+ end
27
+
28
+ class ERBScanner
29
+ extend T::Sig
30
+
31
+ sig { returns(String) }
32
+ attr_reader :ruby, :html
33
+
34
+ sig { params(source: String).void }
35
+ def initialize(source)
36
+ @source = source
37
+ @html = T.let(+"", String)
38
+ @ruby = T.let(+"", String)
39
+ @current_pos = T.let(0, Integer)
40
+ @inside_ruby = T.let(false, T::Boolean)
41
+ end
42
+
43
+ sig { void }
44
+ def scan
45
+ while @current_pos < @source.length
46
+ scan_char
47
+ @current_pos += 1
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ sig { void }
54
+ def scan_char
55
+ char = @source[@current_pos]
56
+
57
+ case char
58
+ when "<"
59
+ if next_char == "%"
60
+ @inside_ruby = true
61
+ @current_pos += 1
62
+ push_char(" ")
63
+
64
+ if next_char == "=" && @source[@current_pos + 2] == "="
65
+ @current_pos += 2
66
+ push_char(" ")
67
+ elsif next_char == "=" || next_char == "-"
68
+ @current_pos += 1
69
+ push_char(" ")
70
+ end
71
+ else
72
+ push_char(T.must(char))
73
+ end
74
+ when "-"
75
+ if @inside_ruby && next_char == "%" &&
76
+ @source[@current_pos + 2] == ">"
77
+ @current_pos += 2
78
+ push_char(" ")
79
+ @inside_ruby = false
80
+ else
81
+ push_char(T.must(char))
82
+ end
83
+ when "%"
84
+ if @inside_ruby && next_char == ">"
85
+ @inside_ruby = false
86
+ @current_pos += 1
87
+ push_char(" ")
88
+ else
89
+ push_char(T.must(char))
90
+ end
91
+ when "\r"
92
+ @ruby << char
93
+ @html << char
94
+
95
+ if next_char == "\n"
96
+ @ruby << next_char
97
+ @html << next_char
98
+ @current_pos += 1
99
+ end
100
+ when "\n"
101
+ @ruby << char
102
+ @html << char
103
+ else
104
+ push_char(T.must(char))
105
+ end
106
+ end
107
+
108
+ sig { params(char: String).void }
109
+ def push_char(char)
110
+ if @inside_ruby
111
+ @ruby << char
112
+ @html << " " * char.length
113
+ else
114
+ @ruby << " " * char.length
115
+ @html << char
116
+ end
117
+ end
118
+
119
+ sig { returns(String) }
120
+ def next_char
121
+ @source[@current_pos + 1] || ""
122
+ end
123
+ end
124
+ end
125
+ end
@@ -21,7 +21,7 @@ module RubyLsp
21
21
  attr_reader :encoding
22
22
 
23
23
  sig { returns(T::Boolean) }
24
- attr_reader :supports_watching_files
24
+ attr_reader :supports_watching_files, :experimental_features
25
25
 
26
26
  sig { returns(TypeInferrer) }
27
27
  attr_reader :type_inferrer
@@ -39,6 +39,7 @@ module RubyLsp
39
39
  @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
40
40
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
41
41
  @supports_watching_files = T.let(false, T::Boolean)
42
+ @experimental_features = T.let(false, T::Boolean)
42
43
  end
43
44
 
44
45
  sig { params(identifier: String, instance: Requests::Support::Formatter).void }
@@ -87,6 +88,8 @@ module RubyLsp
87
88
  if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
88
89
  @supports_watching_files = true
89
90
  end
91
+
92
+ @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
90
93
  end
91
94
 
92
95
  sig { returns(String) }
@@ -15,6 +15,7 @@ Bundler.ui.level = :silent
15
15
  require "uri"
16
16
  require "cgi"
17
17
  require "set"
18
+ require "strscan"
18
19
  require "prism"
19
20
  require "prism/visitor"
20
21
  require "language_server-protocol"
@@ -34,6 +35,7 @@ require "ruby_lsp/response_builders"
34
35
  require "ruby_lsp/node_context"
35
36
  require "ruby_lsp/document"
36
37
  require "ruby_lsp/ruby_document"
38
+ require "ruby_lsp/erb_document"
37
39
  require "ruby_lsp/store"
38
40
  require "ruby_lsp/addon"
39
41
  require "ruby_lsp/requests/support/rubocop_runner"
@@ -209,8 +209,13 @@ module RubyLsp
209
209
  @index.instance_variable_completion_candidates(name, type).each do |entry|
210
210
  variable_name = entry.name
211
211
 
212
+ label_details = Interface::CompletionItemLabelDetails.new(
213
+ description: entry.file_name,
214
+ )
215
+
212
216
  @response_builder << Interface::CompletionItem.new(
213
217
  label: variable_name,
218
+ label_details: label_details,
214
219
  text_edit: Interface::TextEdit.new(
215
220
  range: range_from_location(location),
216
221
  new_text: variable_name,
@@ -283,19 +288,29 @@ module RubyLsp
283
288
  range = if method_name
284
289
  range_from_location(T.must(node.message_loc))
285
290
  else
286
- loc = T.must(node.call_operator_loc)
287
- Interface::Range.new(
288
- start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
289
- end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
290
- )
291
+ loc = node.call_operator_loc
292
+
293
+ if loc
294
+ Interface::Range.new(
295
+ start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
296
+ end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
297
+ )
298
+ end
291
299
  end
292
300
 
301
+ return unless range
302
+
293
303
  @index.method_completion_candidates(method_name, type).each do |entry|
294
304
  entry_name = entry.name
295
305
 
306
+ label_details = Interface::CompletionItemLabelDetails.new(
307
+ description: entry.file_name,
308
+ detail: entry.decorated_parameters,
309
+ )
296
310
  @response_builder << Interface::CompletionItem.new(
297
311
  label: entry_name,
298
312
  filter_text: entry_name,
313
+ label_details: label_details,
299
314
  text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
300
315
  kind: Constant::CompletionItemKind::METHOD,
301
316
  data: {
@@ -307,31 +322,6 @@ module RubyLsp
307
322
  # We have not indexed this namespace, so we can't provide any completions
308
323
  end
309
324
 
310
- sig do
311
- params(
312
- entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
313
- node: Prism::CallNode,
314
- ).returns(Interface::CompletionItem)
315
- end
316
- def build_method_completion(entry, node)
317
- name = entry.name
318
-
319
- Interface::CompletionItem.new(
320
- label: name,
321
- filter_text: name,
322
- text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
323
- kind: Constant::CompletionItemKind::METHOD,
324
- label_details: Interface::CompletionItemLabelDetails.new(
325
- detail: entry.decorated_parameters,
326
- description: entry.file_name,
327
- ),
328
- documentation: Interface::MarkupContent.new(
329
- kind: "markdown",
330
- value: markdown_from_index_entries(name, entry),
331
- ),
332
- )
333
- end
334
-
335
325
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
336
326
  def build_completion(label, node)
337
327
  # We should use the content location as we only replace the content and not the delimiters of the string
@@ -413,8 +403,14 @@ module RubyLsp
413
403
  # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
414
404
  # For these top level references, we need to include the `::` as part of the filter text or else it won't match
415
405
  # the right entries in the index
406
+
407
+ label_details = Interface::CompletionItemLabelDetails.new(
408
+ description: entries.map(&:file_name).join(","),
409
+ )
410
+
416
411
  Interface::CompletionItem.new(
417
412
  label: real_name,
413
+ label_details: label_details,
418
414
  filter_text: filter_text,
419
415
  text_edit: Interface::TextEdit.new(
420
416
  range: range,