ruby-lsp 0.17.3 → 0.17.5

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +251 -100
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +173 -114
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +337 -77
  7. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +43 -14
  8. data/lib/ruby_indexer/test/classes_and_modules_test.rb +79 -3
  9. data/lib/ruby_indexer/test/index_test.rb +563 -29
  10. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  11. data/lib/ruby_indexer/test/method_test.rb +75 -25
  12. data/lib/ruby_indexer/test/rbs_indexer_test.rb +38 -2
  13. data/lib/ruby_indexer/test/test_case.rb +1 -5
  14. data/lib/ruby_lsp/addon.rb +13 -1
  15. data/lib/ruby_lsp/document.rb +50 -23
  16. data/lib/ruby_lsp/erb_document.rb +125 -0
  17. data/lib/ruby_lsp/global_state.rb +11 -4
  18. data/lib/ruby_lsp/internal.rb +3 -0
  19. data/lib/ruby_lsp/listeners/completion.rb +69 -34
  20. data/lib/ruby_lsp/listeners/definition.rb +34 -23
  21. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  22. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  23. data/lib/ruby_lsp/node_context.rb +6 -1
  24. data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
  25. data/lib/ruby_lsp/requests/completion.rb +6 -5
  26. data/lib/ruby_lsp/requests/completion_resolve.rb +7 -4
  27. data/lib/ruby_lsp/requests/definition.rb +4 -3
  28. data/lib/ruby_lsp/requests/formatting.rb +2 -0
  29. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  30. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  31. data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
  32. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  33. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  34. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +87 -0
  35. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  36. data/lib/ruby_lsp/requests.rb +2 -0
  37. data/lib/ruby_lsp/ruby_document.rb +10 -0
  38. data/lib/ruby_lsp/server.rb +95 -26
  39. data/lib/ruby_lsp/store.rb +23 -8
  40. data/lib/ruby_lsp/test_helper.rb +3 -1
  41. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  42. metadata +10 -6
@@ -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
@@ -12,7 +12,7 @@ module RubyLsp
12
12
  attr_accessor :formatter
13
13
 
14
14
  sig { returns(T::Boolean) }
15
- attr_reader :typechecker
15
+ attr_reader :has_type_checker
16
16
 
17
17
  sig { returns(RubyIndexer::Index) }
18
18
  attr_reader :index
@@ -21,7 +21,10 @@ 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
+
26
+ sig { returns(TypeInferrer) }
27
+ attr_reader :type_inferrer
25
28
 
26
29
  sig { void }
27
30
  def initialize
@@ -31,10 +34,12 @@ module RubyLsp
31
34
  @formatter = T.let("auto", String)
32
35
  @linters = T.let([], T::Array[String])
33
36
  @test_library = T.let("minitest", String)
34
- @typechecker = T.let(true, T::Boolean)
37
+ @has_type_checker = T.let(true, T::Boolean)
35
38
  @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
39
+ @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
36
40
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
37
41
  @supports_watching_files = T.let(false, T::Boolean)
42
+ @experimental_features = T.let(false, T::Boolean)
38
43
  end
39
44
 
40
45
  sig { params(identifier: String, instance: Requests::Support::Formatter).void }
@@ -66,7 +71,7 @@ module RubyLsp
66
71
  specified_linters = options.dig(:initializationOptions, :linters)
67
72
  @linters = specified_linters || detect_linters(direct_dependencies)
68
73
  @test_library = detect_test_library(direct_dependencies)
69
- @typechecker = detect_typechecker(direct_dependencies)
74
+ @has_type_checker = detect_typechecker(direct_dependencies)
70
75
 
71
76
  encodings = options.dig(:capabilities, :general, :positionEncodings)
72
77
  @encoding = if !encodings || encodings.empty?
@@ -83,6 +88,8 @@ module RubyLsp
83
88
  if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
84
89
  @supports_watching_files = true
85
90
  end
91
+
92
+ @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
86
93
  end
87
94
 
88
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"
@@ -28,11 +29,13 @@ require "ruby_lsp/utils"
28
29
  require "ruby_lsp/parameter_scope"
29
30
  require "ruby_lsp/global_state"
30
31
  require "ruby_lsp/server"
32
+ require "ruby_lsp/type_inferrer"
31
33
  require "ruby_lsp/requests"
32
34
  require "ruby_lsp/response_builders"
33
35
  require "ruby_lsp/node_context"
34
36
  require "ruby_lsp/document"
35
37
  require "ruby_lsp/ruby_document"
38
+ require "ruby_lsp/erb_document"
36
39
  require "ruby_lsp/store"
37
40
  require "ruby_lsp/addon"
38
41
  require "ruby_lsp/requests/support/rubocop_runner"
@@ -15,15 +15,26 @@ module RubyLsp
15
15
  typechecker_enabled: T::Boolean,
16
16
  dispatcher: Prism::Dispatcher,
17
17
  uri: URI::Generic,
18
+ trigger_character: T.nilable(String),
18
19
  ).void
19
20
  end
20
- def initialize(response_builder, global_state, node_context, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
21
+ def initialize( # rubocop:disable Metrics/ParameterLists
22
+ response_builder,
23
+ global_state,
24
+ node_context,
25
+ typechecker_enabled,
26
+ dispatcher,
27
+ uri,
28
+ trigger_character
29
+ )
21
30
  @response_builder = response_builder
22
31
  @global_state = global_state
23
32
  @index = T.let(global_state.index, RubyIndexer::Index)
33
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
24
34
  @node_context = node_context
25
35
  @typechecker_enabled = typechecker_enabled
26
36
  @uri = uri
37
+ @trigger_character = trigger_character
27
38
 
28
39
  dispatcher.register(
29
40
  self,
@@ -42,7 +53,7 @@ module RubyLsp
42
53
  # Handle completion on regular constant references (e.g. `Bar`)
43
54
  sig { params(node: Prism::ConstantReadNode).void }
44
55
  def on_constant_read_node_enter(node)
45
- return if @global_state.typechecker
56
+ return if @global_state.has_type_checker
46
57
 
47
58
  name = constant_name(node)
48
59
  return if name.nil?
@@ -63,7 +74,7 @@ module RubyLsp
63
74
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
64
75
  sig { params(node: Prism::ConstantPathNode).void }
65
76
  def on_constant_path_node_enter(node)
66
- return if @global_state.typechecker
77
+ return if @global_state.has_type_checker
67
78
 
68
79
  name = constant_name(node)
69
80
  return if name.nil?
@@ -107,7 +118,7 @@ module RubyLsp
107
118
  when "require_relative"
108
119
  complete_require_relative(node)
109
120
  else
110
- complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
121
+ complete_methods(node, name) unless @typechecker_enabled
111
122
  end
112
123
  end
113
124
 
@@ -158,7 +169,7 @@ module RubyLsp
158
169
  name.delete_suffix("::")
159
170
  else
160
171
  *namespace, incomplete_name = name.split("::")
161
- T.must(namespace).join("::")
172
+ namespace.join("::")
162
173
  end
163
174
 
164
175
  nesting = @node_context.nesting
@@ -192,11 +203,19 @@ module RubyLsp
192
203
 
193
204
  sig { params(name: String, location: Prism::Location).void }
194
205
  def handle_instance_variable_completion(name, location)
195
- @index.instance_variable_completion_candidates(name, @node_context.fully_qualified_name).each do |entry|
206
+ type = @type_inferrer.infer_receiver_type(@node_context)
207
+ return unless type
208
+
209
+ @index.instance_variable_completion_candidates(name, type).each do |entry|
196
210
  variable_name = entry.name
197
211
 
212
+ label_details = Interface::CompletionItemLabelDetails.new(
213
+ description: entry.file_name,
214
+ )
215
+
198
216
  @response_builder << Interface::CompletionItem.new(
199
217
  label: variable_name,
218
+ label_details: label_details,
200
219
  text_edit: Interface::TextEdit.new(
201
220
  range: range_from_location(location),
202
221
  new_text: variable_name,
@@ -257,40 +276,50 @@ module RubyLsp
257
276
  end
258
277
 
259
278
  sig { params(node: Prism::CallNode, name: String).void }
260
- def complete_self_receiver_method(node, name)
261
- receiver_entries = @index[@node_context.fully_qualified_name]
262
- return unless receiver_entries
279
+ def complete_methods(node, name)
280
+ type = @type_inferrer.infer_receiver_type(@node_context)
281
+ return unless type
263
282
 
264
- receiver = T.must(receiver_entries.first)
283
+ # When the trigger character is a dot, Prism matches the name of the call node to whatever is next in the source
284
+ # code, leading to us searching for the wrong name. What we want to do instead is show every available method
285
+ # when dot is pressed
286
+ method_name = @trigger_character == "." ? nil : name
265
287
 
266
- @index.method_completion_candidates(name, receiver.name).each do |entry|
267
- @response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
288
+ range = if method_name
289
+ range_from_location(T.must(node.message_loc))
290
+ else
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
268
299
  end
269
- end
270
300
 
271
- sig do
272
- params(
273
- entry: RubyIndexer::Entry::Member,
274
- node: Prism::CallNode,
275
- ).returns(Interface::CompletionItem)
276
- end
277
- def build_method_completion(entry, node)
278
- name = entry.name
301
+ return unless range
279
302
 
280
- Interface::CompletionItem.new(
281
- label: name,
282
- filter_text: name,
283
- text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
284
- kind: Constant::CompletionItemKind::METHOD,
285
- label_details: Interface::CompletionItemLabelDetails.new(
286
- detail: "(#{entry.parameters.map(&:decorated_name).join(", ")})",
303
+ @index.method_completion_candidates(method_name, type).each do |entry|
304
+ entry_name = entry.name
305
+
306
+ label_details = Interface::CompletionItemLabelDetails.new(
287
307
  description: entry.file_name,
288
- ),
289
- documentation: Interface::MarkupContent.new(
290
- kind: "markdown",
291
- value: markdown_from_index_entries(name, entry),
292
- ),
293
- )
308
+ detail: entry.decorated_parameters,
309
+ )
310
+ @response_builder << Interface::CompletionItem.new(
311
+ label: entry_name,
312
+ filter_text: entry_name,
313
+ label_details: label_details,
314
+ text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
315
+ kind: Constant::CompletionItemKind::METHOD,
316
+ data: {
317
+ owner_name: entry.owner&.name,
318
+ },
319
+ )
320
+ end
321
+ rescue RubyIndexer::Index::NonExistingNamespaceError
322
+ # We have not indexed this namespace, so we can't provide any completions
294
323
  end
295
324
 
296
325
  sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
@@ -374,8 +403,14 @@ module RubyLsp
374
403
  # When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
375
404
  # For these top level references, we need to include the `::` as part of the filter text or else it won't match
376
405
  # the right entries in the index
406
+
407
+ label_details = Interface::CompletionItemLabelDetails.new(
408
+ description: entries.map(&:file_name).join(","),
409
+ )
410
+
377
411
  Interface::CompletionItem.new(
378
412
  label: real_name,
413
+ label_details: label_details,
379
414
  filter_text: filter_text,
380
415
  text_edit: Interface::TextEdit.new(
381
416
  range: range,
@@ -11,18 +11,24 @@ module RubyLsp
11
11
 
12
12
  sig do
13
13
  params(
14
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
14
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
15
+ Interface::Location,
16
+ Interface::LocationLink,
17
+ )],
15
18
  global_state: GlobalState,
19
+ language_id: Document::LanguageId,
16
20
  uri: URI::Generic,
17
21
  node_context: NodeContext,
18
22
  dispatcher: Prism::Dispatcher,
19
23
  typechecker_enabled: T::Boolean,
20
24
  ).void
21
25
  end
22
- def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
26
+ def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
23
27
  @response_builder = response_builder
24
28
  @global_state = global_state
25
29
  @index = T.let(global_state.index, RubyIndexer::Index)
30
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
31
+ @language_id = language_id
26
32
  @uri = uri
27
33
  @node_context = node_context
28
34
  @typechecker_enabled = typechecker_enabled
@@ -48,7 +54,15 @@ module RubyLsp
48
54
  message = node.message
49
55
  return unless message
50
56
 
51
- handle_method_definition(message, self_receiver?(node))
57
+ inferrer_receiver_type = @type_inferrer.infer_receiver_type(@node_context)
58
+
59
+ # Until we can properly infer the receiver type in erb files (maybe with ruby-lsp-rails),
60
+ # treating method calls' type as `nil` will allow users to get some completion support first
61
+ if @language_id == Document::LanguageId::ERB && inferrer_receiver_type == "Object"
62
+ inferrer_receiver_type = nil
63
+ end
64
+
65
+ handle_method_definition(message, inferrer_receiver_type)
52
66
  end
53
67
 
54
68
  sig { params(node: Prism::StringNode).void }
@@ -70,7 +84,7 @@ module RubyLsp
70
84
  value = expression.value
71
85
  return unless value
72
86
 
73
- handle_method_definition(value, false)
87
+ handle_method_definition(value, nil)
74
88
  end
75
89
 
76
90
  sig { params(node: Prism::ConstantPathNode).void }
@@ -123,7 +137,10 @@ module RubyLsp
123
137
 
124
138
  sig { params(name: String).void }
125
139
  def handle_instance_variable_definition(name)
126
- entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
140
+ type = @type_inferrer.infer_receiver_type(@node_context)
141
+ return unless type
142
+
143
+ entries = @index.resolve_instance_variable(name, type)
127
144
  return unless entries
128
145
 
129
146
  entries.each do |entry|
@@ -141,10 +158,10 @@ module RubyLsp
141
158
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
142
159
  end
143
160
 
144
- sig { params(message: String, self_receiver: T::Boolean).void }
145
- def handle_method_definition(message, self_receiver)
146
- methods = if self_receiver
147
- @index.resolve_method(message, @node_context.fully_qualified_name)
161
+ sig { params(message: String, receiver_type: T.nilable(String)).void }
162
+ def handle_method_definition(message, receiver_type)
163
+ methods = if receiver_type
164
+ @index.resolve_method(message, receiver_type)
148
165
  else
149
166
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
150
167
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -154,16 +171,13 @@ module RubyLsp
154
171
  return unless methods
155
172
 
156
173
  methods.each do |target_method|
157
- location = target_method.location
158
174
  file_path = target_method.file_path
159
175
  next if @typechecker_enabled && not_in_dependencies?(file_path)
160
176
 
161
- @response_builder << Interface::Location.new(
162
- uri: URI::Generic.from_path(path: file_path).to_s,
163
- range: Interface::Range.new(
164
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
165
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
166
- ),
177
+ @response_builder << Interface::LocationLink.new(
178
+ target_uri: URI::Generic.from_path(path: file_path).to_s,
179
+ target_range: range_from_location(target_method.location),
180
+ target_selection_range: range_from_location(target_method.name_location),
167
181
  )
168
182
  end
169
183
  end
@@ -214,19 +228,16 @@ module RubyLsp
214
228
  return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
215
229
 
216
230
  entries.each do |entry|
217
- location = entry.location
218
231
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
219
232
  # additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
220
233
  # in the project, even if the files are typed false
221
234
  file_path = entry.file_path
222
235
  next if @typechecker_enabled && not_in_dependencies?(file_path)
223
236
 
224
- @response_builder << Interface::Location.new(
225
- uri: URI::Generic.from_path(path: file_path).to_s,
226
- range: Interface::Range.new(
227
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
228
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
229
- ),
237
+ @response_builder << Interface::LocationLink.new(
238
+ target_uri: URI::Generic.from_path(path: file_path).to_s,
239
+ target_range: range_from_location(entry.location),
240
+ target_selection_range: range_from_location(entry.name_location),
230
241
  )
231
242
  end
232
243
  end
@@ -47,6 +47,7 @@ module RubyLsp
47
47
  @response_builder = response_builder
48
48
  @global_state = global_state
49
49
  @index = T.let(global_state.index, RubyIndexer::Index)
50
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
50
51
  @path = T.let(uri.to_standardized_path, T.nilable(String))
51
52
  @node_context = node_context
52
53
  @typechecker_enabled = typechecker_enabled
@@ -78,14 +79,14 @@ module RubyLsp
78
79
 
79
80
  sig { params(node: Prism::ConstantWriteNode).void }
80
81
  def on_constant_write_node_enter(node)
81
- return if @global_state.typechecker
82
+ return if @global_state.has_type_checker
82
83
 
83
84
  generate_hover(node.name.to_s, node.name_loc)
84
85
  end
85
86
 
86
87
  sig { params(node: Prism::ConstantPathNode).void }
87
88
  def on_constant_path_node_enter(node)
88
- return if @global_state.typechecker
89
+ return if @global_state.has_type_checker
89
90
 
90
91
  name = constant_name(node)
91
92
  return if name.nil?
@@ -95,8 +96,6 @@ module RubyLsp
95
96
 
96
97
  sig { params(node: Prism::CallNode).void }
97
98
  def on_call_node_enter(node)
98
- return unless self_receiver?(node)
99
-
100
99
  if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem
101
100
  generate_gem_hover(node)
102
101
  return
@@ -107,10 +106,15 @@ module RubyLsp
107
106
  message = node.message
108
107
  return unless message
109
108
 
110
- methods = @index.resolve_method(message, @node_context.fully_qualified_name)
109
+ type = @type_inferrer.infer_receiver_type(@node_context)
110
+ return unless type
111
+
112
+ methods = @index.resolve_method(message, type)
111
113
  return unless methods
112
114
 
113
- categorized_markdown_from_index_entries(message, methods).each do |category, content|
115
+ title = "#{message}#{T.must(methods.first).decorated_parameters}"
116
+
117
+ categorized_markdown_from_index_entries(title, methods).each do |category, content|
114
118
  @response_builder.push(content, category: category)
115
119
  end
116
120
  end
@@ -149,7 +153,10 @@ module RubyLsp
149
153
 
150
154
  sig { params(name: String).void }
151
155
  def handle_instance_variable_hover(name)
152
- entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
156
+ type = @type_inferrer.infer_receiver_type(@node_context)
157
+ return unless type
158
+
159
+ entries = @index.resolve_instance_variable(name, type)
153
160
  return unless entries
154
161
 
155
162
  categorized_markdown_from_index_entries(name, entries).each do |category, content|
@@ -21,6 +21,7 @@ module RubyLsp
21
21
  @response_builder = response_builder
22
22
  @global_state = global_state
23
23
  @index = T.let(global_state.index, RubyIndexer::Index)
24
+ @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
24
25
  @node_context = node_context
25
26
  dispatcher.register(self, :on_call_node_enter)
26
27
  end
@@ -28,12 +29,14 @@ module RubyLsp
28
29
  sig { params(node: Prism::CallNode).void }
29
30
  def on_call_node_enter(node)
30
31
  return if @typechecker_enabled
31
- return unless self_receiver?(node)
32
32
 
33
33
  message = node.message
34
34
  return unless message
35
35
 
36
- methods = @index.resolve_method(message, @node_context.fully_qualified_name)
36
+ type = @type_inferrer.infer_receiver_type(@node_context)
37
+ return unless type
38
+
39
+ methods = @index.resolve_method(message, type)
37
40
  return unless methods
38
41
 
39
42
  target_method = methods.first
@@ -16,19 +16,24 @@ module RubyLsp
16
16
  sig { returns(T.nilable(Prism::CallNode)) }
17
17
  attr_reader :call_node
18
18
 
19
+ sig { returns(T.nilable(String)) }
20
+ attr_reader :surrounding_method
21
+
19
22
  sig do
20
23
  params(
21
24
  node: T.nilable(Prism::Node),
22
25
  parent: T.nilable(Prism::Node),
23
26
  nesting: T::Array[String],
24
27
  call_node: T.nilable(Prism::CallNode),
28
+ surrounding_method: T.nilable(String),
25
29
  ).void
26
30
  end
27
- def initialize(node, parent, nesting, call_node)
31
+ def initialize(node, parent, nesting, call_node, surrounding_method)
28
32
  @node = node
29
33
  @parent = parent
30
34
  @nesting = nesting
31
35
  @call_node = call_node
36
+ @surrounding_method = surrounding_method
32
37
  end
33
38
 
34
39
  sig { returns(String) }
@@ -69,7 +69,7 @@ module RubyLsp
69
69
 
70
70
  # Find the closest statements node, so that we place the refactor in a valid position
71
71
  node_context = @document
72
- .locate(@document.tree, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
72
+ .locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
73
73
 
74
74
  closest_statements = node_context.node
75
75
  parent_statements = node_context.parent
@@ -164,7 +164,7 @@ module RubyLsp
164
164
  extracted_source = T.must(@document.source[start_index...end_index])
165
165
 
166
166
  # Find the closest method declaration node, so that we place the refactor in a valid position
167
- node_context = @document.locate(@document.tree, start_index, node_types: [Prism::DefNode])
167
+ node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
168
168
  closest_def = T.cast(node_context.node, Prism::DefNode)
169
169
  return Error::InvalidTargetRange if closest_def.nil?
170
170
 
@@ -36,7 +36,7 @@ module RubyLsp
36
36
  def provider
37
37
  Interface::CompletionOptions.new(
38
38
  resolve_provider: true,
39
- trigger_characters: ["/", "\"", "'", ":", "@"],
39
+ trigger_characters: ["/", "\"", "'", ":", "@", "."],
40
40
  completion_item: {
41
41
  labelDetailsSupport: true,
42
42
  },
@@ -48,20 +48,20 @@ module RubyLsp
48
48
  params(
49
49
  document: Document,
50
50
  global_state: GlobalState,
51
- position: T::Hash[Symbol, T.untyped],
51
+ params: T::Hash[Symbol, T.untyped],
52
52
  typechecker_enabled: T::Boolean,
53
53
  dispatcher: Prism::Dispatcher,
54
54
  ).void
55
55
  end
56
- def initialize(document, global_state, position, typechecker_enabled, dispatcher)
56
+ def initialize(document, global_state, params, typechecker_enabled, dispatcher)
57
57
  super()
58
58
  @target = T.let(nil, T.nilable(Prism::Node))
59
59
  @dispatcher = dispatcher
60
60
  # Completion always receives the position immediately after the character that was just typed. Here we adjust it
61
61
  # back by 1, so that we find the right node
62
- char_position = document.create_scanner.find_char_position(position) - 1
62
+ char_position = document.create_scanner.find_char_position(params[:position]) - 1
63
63
  node_context = document.locate(
64
- document.tree,
64
+ document.parse_result.value,
65
65
  char_position,
66
66
  node_types: [
67
67
  Prism::CallNode,
@@ -87,6 +87,7 @@ module RubyLsp
87
87
  typechecker_enabled,
88
88
  dispatcher,
89
89
  document.uri,
90
+ params.dig(:context, :triggerCharacter),
90
91
  )
91
92
 
92
93
  Addon.addons.each do |addon|