ruby-lsp 0.17.3 → 0.17.5

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