ruby-lsp 0.16.7 → 0.17.1

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