ruby-lsp 0.16.7 → 0.17.1

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +139 -18
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +101 -12
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +183 -10
  7. data/lib/ruby_indexer/test/classes_and_modules_test.rb +55 -9
  8. data/lib/ruby_indexer/test/configuration_test.rb +4 -5
  9. data/lib/ruby_indexer/test/constant_test.rb +8 -8
  10. data/lib/ruby_indexer/test/index_test.rb +528 -0
  11. data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
  12. data/lib/ruby_indexer/test/method_test.rb +37 -0
  13. data/lib/ruby_indexer/test/test_case.rb +3 -1
  14. data/lib/ruby_lsp/addon.rb +8 -8
  15. data/lib/ruby_lsp/document.rb +3 -3
  16. data/lib/ruby_lsp/internal.rb +1 -0
  17. data/lib/ruby_lsp/listeners/completion.rb +74 -17
  18. data/lib/ruby_lsp/listeners/definition.rb +62 -6
  19. data/lib/ruby_lsp/listeners/hover.rb +60 -6
  20. data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
  21. data/lib/ruby_lsp/node_context.rb +28 -0
  22. data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
  23. data/lib/ruby_lsp/requests/code_actions.rb +16 -15
  24. data/lib/ruby_lsp/requests/completion.rb +21 -13
  25. data/lib/ruby_lsp/requests/completion_resolve.rb +9 -0
  26. data/lib/ruby_lsp/requests/definition.rb +20 -5
  27. data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
  28. data/lib/ruby_lsp/requests/hover.rb +5 -6
  29. data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
  30. data/lib/ruby_lsp/requests/signature_help.rb +3 -3
  31. data/lib/ruby_lsp/requests/support/common.rb +4 -3
  32. data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -1
  33. data/lib/ruby_lsp/server.rb +10 -4
  34. data/lib/ruby_lsp/test_helper.rb +1 -0
  35. metadata +4 -2
@@ -15,7 +15,7 @@ module RubyIndexer
15
15
  @index.index_single(IndexablePath.new(nil, "/fake/path/foo.rb"), source)
16
16
  end
17
17
 
18
- def assert_entry(expected_name, type, expected_location)
18
+ def assert_entry(expected_name, type, expected_location, visibility: nil)
19
19
  entries = @index[expected_name]
20
20
  refute_empty(entries, "Expected #{expected_name} to be indexed")
21
21
 
@@ -28,6 +28,8 @@ module RubyIndexer
28
28
  ":#{location.end_line - 1}-#{location.end_column}"
29
29
 
30
30
  assert_equal(expected_location, location_string)
31
+
32
+ assert_equal(visibility, entry.visibility) if visibility
31
33
  end
32
34
 
33
35
  def refute_entry(expected_name)
@@ -99,8 +99,8 @@ module RubyLsp
99
99
  end
100
100
 
101
101
  sig { returns(String) }
102
- def backtraces
103
- @errors.filter_map(&:backtrace).join("\n\n")
102
+ def errors_details
103
+ @errors.map(&:full_message).join("\n\n")
104
104
  end
105
105
 
106
106
  # Each addon should implement `MyAddon#activate` and use to perform any sort of initialization, such as
@@ -131,11 +131,11 @@ module RubyLsp
131
131
  sig do
132
132
  overridable.params(
133
133
  response_builder: ResponseBuilders::Hover,
134
- nesting: T::Array[String],
134
+ node_context: NodeContext,
135
135
  dispatcher: Prism::Dispatcher,
136
136
  ).void
137
137
  end
138
- def create_hover_listener(response_builder, nesting, dispatcher); end
138
+ def create_hover_listener(response_builder, node_context, dispatcher); end
139
139
 
140
140
  # Creates a new DocumentSymbol listener. This method is invoked on every DocumentSymbol request
141
141
  sig do
@@ -159,21 +159,21 @@ module RubyLsp
159
159
  overridable.params(
160
160
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
161
161
  uri: URI::Generic,
162
- nesting: T::Array[String],
162
+ node_context: NodeContext,
163
163
  dispatcher: Prism::Dispatcher,
164
164
  ).void
165
165
  end
166
- def create_definition_listener(response_builder, uri, nesting, dispatcher); end
166
+ def create_definition_listener(response_builder, uri, node_context, dispatcher); end
167
167
 
168
168
  # Creates a new Completion listener. This method is invoked on every Completion request
169
169
  sig do
170
170
  overridable.params(
171
171
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
172
- nesting: T::Array[String],
172
+ node_context: NodeContext,
173
173
  dispatcher: Prism::Dispatcher,
174
174
  uri: URI::Generic,
175
175
  ).void
176
176
  end
177
- def create_completion_listener(response_builder, nesting, dispatcher, uri); end
177
+ def create_completion_listener(response_builder, node_context, dispatcher, uri); end
178
178
  end
179
179
  end
@@ -110,7 +110,7 @@ module RubyLsp
110
110
  params(
111
111
  position: T::Hash[Symbol, T.untyped],
112
112
  node_types: T::Array[T.class_of(Prism::Node)],
113
- ).returns([T.nilable(Prism::Node), T.nilable(Prism::Node), T::Array[String]])
113
+ ).returns(NodeContext)
114
114
  end
115
115
  def locate_node(position, node_types: [])
116
116
  locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
@@ -121,7 +121,7 @@ module RubyLsp
121
121
  node: Prism::Node,
122
122
  char_position: Integer,
123
123
  node_types: T::Array[T.class_of(Prism::Node)],
124
- ).returns([T.nilable(Prism::Node), T.nilable(Prism::Node), T::Array[String]])
124
+ ).returns(NodeContext)
125
125
  end
126
126
  def locate(node, char_position, node_types: [])
127
127
  queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
@@ -170,7 +170,7 @@ module RubyLsp
170
170
  end
171
171
  end
172
172
 
173
- [closest, parent, nesting.map { |n| n.constant_path.location.slice }]
173
+ NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice })
174
174
  end
175
175
 
176
176
  sig { returns(T::Boolean) }
@@ -29,6 +29,7 @@ require "ruby_lsp/global_state"
29
29
  require "ruby_lsp/server"
30
30
  require "ruby_lsp/requests"
31
31
  require "ruby_lsp/response_builders"
32
+ require "ruby_lsp/node_context"
32
33
  require "ruby_lsp/document"
33
34
  require "ruby_lsp/ruby_document"
34
35
  require "ruby_lsp/store"
@@ -11,17 +11,17 @@ module RubyLsp
11
11
  params(
12
12
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
13
13
  global_state: GlobalState,
14
- nesting: T::Array[String],
14
+ node_context: NodeContext,
15
15
  typechecker_enabled: T::Boolean,
16
16
  dispatcher: Prism::Dispatcher,
17
17
  uri: URI::Generic,
18
18
  ).void
19
19
  end
20
- def initialize(response_builder, global_state, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
20
+ def initialize(response_builder, global_state, node_context, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
21
21
  @response_builder = response_builder
22
22
  @global_state = global_state
23
23
  @index = T.let(global_state.index, RubyIndexer::Index)
24
- @nesting = nesting
24
+ @node_context = node_context
25
25
  @typechecker_enabled = typechecker_enabled
26
26
  @uri = uri
27
27
 
@@ -30,6 +30,12 @@ module RubyLsp
30
30
  :on_constant_path_node_enter,
31
31
  :on_constant_read_node_enter,
32
32
  :on_call_node_enter,
33
+ :on_instance_variable_read_node_enter,
34
+ :on_instance_variable_write_node_enter,
35
+ :on_instance_variable_and_write_node_enter,
36
+ :on_instance_variable_operator_write_node_enter,
37
+ :on_instance_variable_or_write_node_enter,
38
+ :on_instance_variable_target_node_enter,
33
39
  )
34
40
  end
35
41
 
@@ -41,7 +47,7 @@ module RubyLsp
41
47
  name = constant_name(node)
42
48
  return if name.nil?
43
49
 
44
- candidates = @index.prefix_search(name, @nesting)
50
+ candidates = @index.prefix_search(name, @node_context.nesting)
45
51
  candidates.each do |entries|
46
52
  complete_name = T.must(entries.first).name
47
53
  @response_builder << build_entry_completion(
@@ -105,6 +111,36 @@ module RubyLsp
105
111
  end
106
112
  end
107
113
 
114
+ sig { params(node: Prism::InstanceVariableReadNode).void }
115
+ def on_instance_variable_read_node_enter(node)
116
+ handle_instance_variable_completion(node.name.to_s, node.location)
117
+ end
118
+
119
+ sig { params(node: Prism::InstanceVariableWriteNode).void }
120
+ def on_instance_variable_write_node_enter(node)
121
+ handle_instance_variable_completion(node.name.to_s, node.name_loc)
122
+ end
123
+
124
+ sig { params(node: Prism::InstanceVariableAndWriteNode).void }
125
+ def on_instance_variable_and_write_node_enter(node)
126
+ handle_instance_variable_completion(node.name.to_s, node.name_loc)
127
+ end
128
+
129
+ sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
130
+ def on_instance_variable_operator_write_node_enter(node)
131
+ handle_instance_variable_completion(node.name.to_s, node.name_loc)
132
+ end
133
+
134
+ sig { params(node: Prism::InstanceVariableOrWriteNode).void }
135
+ def on_instance_variable_or_write_node_enter(node)
136
+ handle_instance_variable_completion(node.name.to_s, node.name_loc)
137
+ end
138
+
139
+ sig { params(node: Prism::InstanceVariableTargetNode).void }
140
+ def on_instance_variable_target_node_enter(node)
141
+ handle_instance_variable_completion(node.name.to_s, node.location)
142
+ end
143
+
108
144
  private
109
145
 
110
146
  sig { params(name: String, range: Interface::Range).void }
@@ -125,20 +161,21 @@ module RubyLsp
125
161
  T.must(namespace).join("::")
126
162
  end
127
163
 
128
- namespace_entries = @index.resolve(aliased_namespace, @nesting)
164
+ nesting = @node_context.nesting
165
+ namespace_entries = @index.resolve(aliased_namespace, nesting)
129
166
  return unless namespace_entries
130
167
 
131
168
  real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
132
169
 
133
170
  candidates = @index.prefix_search(
134
171
  "#{real_namespace}::#{incomplete_name}",
135
- top_level_reference ? [] : @nesting,
172
+ top_level_reference ? [] : nesting,
136
173
  )
137
174
  candidates.each do |entries|
138
175
  # The only time we may have a private constant reference from outside of the namespace is if we're dealing
139
176
  # with ConstantPath and the entry name doesn't start with the current nesting
140
177
  first_entry = T.must(entries.first)
141
- next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
178
+ next if first_entry.private? && !first_entry.name.start_with?("#{nesting}::")
142
179
 
143
180
  constant_name = first_entry.name.delete_prefix("#{real_namespace}::")
144
181
  full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
@@ -153,6 +190,27 @@ module RubyLsp
153
190
  end
154
191
  end
155
192
 
193
+ sig { params(name: String, location: Prism::Location).void }
194
+ def handle_instance_variable_completion(name, location)
195
+ @index.instance_variable_completion_candidates(name, @node_context.fully_qualified_name).each do |entry|
196
+ variable_name = entry.name
197
+
198
+ @response_builder << Interface::CompletionItem.new(
199
+ label: variable_name,
200
+ text_edit: Interface::TextEdit.new(
201
+ range: range_from_location(location),
202
+ new_text: variable_name,
203
+ ),
204
+ kind: Constant::CompletionItemKind::FIELD,
205
+ data: {
206
+ owner_name: entry.owner&.name,
207
+ },
208
+ )
209
+ end
210
+ rescue RubyIndexer::Index::NonExistingNamespaceError
211
+ # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
212
+ end
213
+
156
214
  sig { params(node: Prism::CallNode).void }
157
215
  def complete_require(node)
158
216
  arguments_node = node.arguments
@@ -200,15 +258,12 @@ module RubyLsp
200
258
 
201
259
  sig { params(node: Prism::CallNode, name: String).void }
202
260
  def complete_self_receiver_method(node, name)
203
- receiver_entries = @index[@nesting.join("::")]
261
+ receiver_entries = @index[@node_context.fully_qualified_name]
204
262
  return unless receiver_entries
205
263
 
206
264
  receiver = T.must(receiver_entries.first)
207
265
 
208
- @index.prefix_search(name).each do |entries|
209
- entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
210
- next unless entry
211
-
266
+ @index.method_completion_candidates(name, receiver.name).each do |entry|
212
267
  @response_builder << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
213
268
  end
214
269
  end
@@ -297,13 +352,14 @@ module RubyLsp
297
352
  #
298
353
  # Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
299
354
  # end
300
- unless @nesting.join("::").start_with?(incomplete_name)
301
- @nesting.each do |namespace|
355
+ nesting = @node_context.nesting
356
+ unless @node_context.fully_qualified_name.start_with?(incomplete_name)
357
+ nesting.each do |namespace|
302
358
  prefix = "#{namespace}::"
303
359
  shortened_name = insertion_text.delete_prefix(prefix)
304
360
 
305
361
  # If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
306
- conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
362
+ conflict_name = "#{@node_context.fully_qualified_name}::#{shortened_name}"
307
363
  break if real_name != conflict_name && @index[conflict_name]
308
364
 
309
365
  insertion_text = shortened_name
@@ -345,8 +401,9 @@ module RubyLsp
345
401
  # ```
346
402
  sig { params(entry_name: String).returns(T::Boolean) }
347
403
  def top_level?(entry_name)
348
- @nesting.length.downto(0).each do |i|
349
- prefix = T.must(@nesting[0...i]).join("::")
404
+ nesting = @node_context.nesting
405
+ nesting.length.downto(0).each do |i|
406
+ prefix = T.must(nesting[0...i]).join("::")
350
407
  full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
351
408
  next if full_name == entry_name
352
409
 
@@ -14,17 +14,17 @@ module RubyLsp
14
14
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
15
15
  global_state: GlobalState,
16
16
  uri: URI::Generic,
17
- nesting: T::Array[String],
17
+ node_context: NodeContext,
18
18
  dispatcher: Prism::Dispatcher,
19
19
  typechecker_enabled: T::Boolean,
20
20
  ).void
21
21
  end
22
- def initialize(response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
22
+ def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
23
23
  @response_builder = response_builder
24
24
  @global_state = global_state
25
25
  @index = T.let(global_state.index, RubyIndexer::Index)
26
26
  @uri = uri
27
- @nesting = nesting
27
+ @node_context = node_context
28
28
  @typechecker_enabled = typechecker_enabled
29
29
 
30
30
  dispatcher.register(
@@ -33,6 +33,12 @@ module RubyLsp
33
33
  :on_block_argument_node_enter,
34
34
  :on_constant_read_node_enter,
35
35
  :on_constant_path_node_enter,
36
+ :on_instance_variable_read_node_enter,
37
+ :on_instance_variable_write_node_enter,
38
+ :on_instance_variable_and_write_node_enter,
39
+ :on_instance_variable_operator_write_node_enter,
40
+ :on_instance_variable_or_write_node_enter,
41
+ :on_instance_variable_target_node_enter,
36
42
  )
37
43
  end
38
44
 
@@ -74,12 +80,62 @@ module RubyLsp
74
80
  find_in_index(name)
75
81
  end
76
82
 
83
+ sig { params(node: Prism::InstanceVariableReadNode).void }
84
+ def on_instance_variable_read_node_enter(node)
85
+ handle_instance_variable_definition(node.name.to_s)
86
+ end
87
+
88
+ sig { params(node: Prism::InstanceVariableWriteNode).void }
89
+ def on_instance_variable_write_node_enter(node)
90
+ handle_instance_variable_definition(node.name.to_s)
91
+ end
92
+
93
+ sig { params(node: Prism::InstanceVariableAndWriteNode).void }
94
+ def on_instance_variable_and_write_node_enter(node)
95
+ handle_instance_variable_definition(node.name.to_s)
96
+ end
97
+
98
+ sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
99
+ def on_instance_variable_operator_write_node_enter(node)
100
+ handle_instance_variable_definition(node.name.to_s)
101
+ end
102
+
103
+ sig { params(node: Prism::InstanceVariableOrWriteNode).void }
104
+ def on_instance_variable_or_write_node_enter(node)
105
+ handle_instance_variable_definition(node.name.to_s)
106
+ end
107
+
108
+ sig { params(node: Prism::InstanceVariableTargetNode).void }
109
+ def on_instance_variable_target_node_enter(node)
110
+ handle_instance_variable_definition(node.name.to_s)
111
+ end
112
+
77
113
  private
78
114
 
115
+ sig { params(name: String).void }
116
+ def handle_instance_variable_definition(name)
117
+ entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
118
+ return unless entries
119
+
120
+ entries.each do |entry|
121
+ location = entry.location
122
+
123
+ @response_builder << Interface::Location.new(
124
+ uri: URI::Generic.from_path(path: entry.file_path).to_s,
125
+ range: Interface::Range.new(
126
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
127
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
128
+ ),
129
+ )
130
+ end
131
+ rescue RubyIndexer::Index::NonExistingNamespaceError
132
+ # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
133
+ end
134
+
79
135
  sig { params(message: String, self_receiver: T::Boolean).void }
80
136
  def handle_method_definition(message, self_receiver)
81
137
  methods = if self_receiver
82
- @index.resolve_method(message, @nesting.join("::"))
138
+ @index.resolve_method(message, @node_context.fully_qualified_name)
83
139
  else
84
140
  # If the method doesn't have a receiver, then we provide a few candidates to jump to
85
141
  # But we don't want to provide too many candidates, as it can be overwhelming
@@ -147,13 +203,13 @@ module RubyLsp
147
203
 
148
204
  sig { params(value: String).void }
149
205
  def find_in_index(value)
150
- entries = @index.resolve(value, @nesting)
206
+ entries = @index.resolve(value, @node_context.nesting)
151
207
  return unless entries
152
208
 
153
209
  # We should only allow jumping to the definition of private constants if the constant is defined in the same
154
210
  # namespace as the reference
155
211
  first_entry = T.must(entries.first)
156
- return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
212
+ return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
157
213
 
158
214
  entries.each do |entry|
159
215
  location = entry.location
@@ -13,6 +13,12 @@ module RubyLsp
13
13
  Prism::ConstantReadNode,
14
14
  Prism::ConstantWriteNode,
15
15
  Prism::ConstantPathNode,
16
+ Prism::InstanceVariableReadNode,
17
+ Prism::InstanceVariableAndWriteNode,
18
+ Prism::InstanceVariableOperatorWriteNode,
19
+ Prism::InstanceVariableOrWriteNode,
20
+ Prism::InstanceVariableTargetNode,
21
+ Prism::InstanceVariableWriteNode,
16
22
  ],
17
23
  T::Array[T.class_of(Prism::Node)],
18
24
  )
@@ -30,17 +36,17 @@ module RubyLsp
30
36
  response_builder: ResponseBuilders::Hover,
31
37
  global_state: GlobalState,
32
38
  uri: URI::Generic,
33
- nesting: T::Array[String],
39
+ node_context: NodeContext,
34
40
  dispatcher: Prism::Dispatcher,
35
41
  typechecker_enabled: T::Boolean,
36
42
  ).void
37
43
  end
38
- def initialize(response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
44
+ def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
39
45
  @response_builder = response_builder
40
46
  @global_state = global_state
41
47
  @index = T.let(global_state.index, RubyIndexer::Index)
42
48
  @path = T.let(uri.to_standardized_path, T.nilable(String))
43
- @nesting = nesting
49
+ @node_context = node_context
44
50
  @typechecker_enabled = typechecker_enabled
45
51
 
46
52
  dispatcher.register(
@@ -49,6 +55,12 @@ module RubyLsp
49
55
  :on_constant_write_node_enter,
50
56
  :on_constant_path_node_enter,
51
57
  :on_call_node_enter,
58
+ :on_instance_variable_read_node_enter,
59
+ :on_instance_variable_write_node_enter,
60
+ :on_instance_variable_and_write_node_enter,
61
+ :on_instance_variable_operator_write_node_enter,
62
+ :on_instance_variable_or_write_node_enter,
63
+ :on_instance_variable_target_node_enter,
52
64
  )
53
65
  end
54
66
 
@@ -93,7 +105,7 @@ module RubyLsp
93
105
  message = node.message
94
106
  return unless message
95
107
 
96
- methods = @index.resolve_method(message, @nesting.join("::"))
108
+ methods = @index.resolve_method(message, @node_context.fully_qualified_name)
97
109
  return unless methods
98
110
 
99
111
  categorized_markdown_from_index_entries(message, methods).each do |category, content|
@@ -101,17 +113,59 @@ module RubyLsp
101
113
  end
102
114
  end
103
115
 
116
+ sig { params(node: Prism::InstanceVariableReadNode).void }
117
+ def on_instance_variable_read_node_enter(node)
118
+ handle_instance_variable_hover(node.name.to_s)
119
+ end
120
+
121
+ sig { params(node: Prism::InstanceVariableWriteNode).void }
122
+ def on_instance_variable_write_node_enter(node)
123
+ handle_instance_variable_hover(node.name.to_s)
124
+ end
125
+
126
+ sig { params(node: Prism::InstanceVariableAndWriteNode).void }
127
+ def on_instance_variable_and_write_node_enter(node)
128
+ handle_instance_variable_hover(node.name.to_s)
129
+ end
130
+
131
+ sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
132
+ def on_instance_variable_operator_write_node_enter(node)
133
+ handle_instance_variable_hover(node.name.to_s)
134
+ end
135
+
136
+ sig { params(node: Prism::InstanceVariableOrWriteNode).void }
137
+ def on_instance_variable_or_write_node_enter(node)
138
+ handle_instance_variable_hover(node.name.to_s)
139
+ end
140
+
141
+ sig { params(node: Prism::InstanceVariableTargetNode).void }
142
+ def on_instance_variable_target_node_enter(node)
143
+ handle_instance_variable_hover(node.name.to_s)
144
+ end
145
+
104
146
  private
105
147
 
148
+ sig { params(name: String).void }
149
+ def handle_instance_variable_hover(name)
150
+ entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
151
+ return unless entries
152
+
153
+ categorized_markdown_from_index_entries(name, entries).each do |category, content|
154
+ @response_builder.push(content, category: category)
155
+ end
156
+ rescue RubyIndexer::Index::NonExistingNamespaceError
157
+ # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
158
+ end
159
+
106
160
  sig { params(name: String, location: Prism::Location).void }
107
161
  def generate_hover(name, location)
108
- entries = @index.resolve(name, @nesting)
162
+ entries = @index.resolve(name, @node_context.nesting)
109
163
  return unless entries
110
164
 
111
165
  # We should only show hover for private constants if the constant is defined in the same namespace as the
112
166
  # reference
113
167
  first_entry = T.must(entries.first)
114
- return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{name}"
168
+ return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{name}"
115
169
 
116
170
  categorized_markdown_from_index_entries(name, entries).each do |category, content|
117
171
  @response_builder.push(content, category: category)
@@ -11,17 +11,17 @@ module RubyLsp
11
11
  params(
12
12
  response_builder: ResponseBuilders::SignatureHelp,
13
13
  global_state: GlobalState,
14
- nesting: T::Array[String],
14
+ node_context: NodeContext,
15
15
  dispatcher: Prism::Dispatcher,
16
16
  typechecker_enabled: T::Boolean,
17
17
  ).void
18
18
  end
19
- def initialize(response_builder, global_state, nesting, dispatcher, typechecker_enabled)
19
+ def initialize(response_builder, global_state, node_context, dispatcher, typechecker_enabled)
20
20
  @typechecker_enabled = typechecker_enabled
21
21
  @response_builder = response_builder
22
22
  @global_state = global_state
23
23
  @index = T.let(global_state.index, RubyIndexer::Index)
24
- @nesting = nesting
24
+ @node_context = node_context
25
25
  dispatcher.register(self, :on_call_node_enter)
26
26
  end
27
27
 
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  message = node.message
34
34
  return unless message
35
35
 
36
- methods = @index.resolve_method(message, @nesting.join("::"))
36
+ methods = @index.resolve_method(message, @node_context.fully_qualified_name)
37
37
  return unless methods
38
38
 
39
39
  target_method = methods.first
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # This class allows listeners to access contextual information about a node in the AST, such as its parent
6
+ # and its namespace nesting.
7
+ class NodeContext
8
+ extend T::Sig
9
+
10
+ sig { returns(T.nilable(Prism::Node)) }
11
+ attr_reader :node, :parent
12
+
13
+ sig { returns(T::Array[String]) }
14
+ attr_reader :nesting
15
+
16
+ sig { params(node: T.nilable(Prism::Node), parent: T.nilable(Prism::Node), nesting: T::Array[String]).void }
17
+ def initialize(node, parent, nesting)
18
+ @node = node
19
+ @parent = parent
20
+ @nesting = nesting
21
+ end
22
+
23
+ sig { returns(String) }
24
+ def fully_qualified_name
25
+ @fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
26
+ end
27
+ end
28
+ end