ruby-lsp 0.17.4 → 0.17.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,