ruby-lsp 0.16.7 → 0.17.2
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +139 -18
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +101 -12
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +183 -10
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +55 -9
- data/lib/ruby_indexer/test/configuration_test.rb +4 -5
- data/lib/ruby_indexer/test/constant_test.rb +8 -8
- data/lib/ruby_indexer/test/index_test.rb +528 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +131 -0
- data/lib/ruby_indexer/test/method_test.rb +37 -0
- data/lib/ruby_indexer/test/test_case.rb +3 -1
- data/lib/ruby_lsp/addon.rb +8 -8
- data/lib/ruby_lsp/document.rb +26 -3
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/listeners/completion.rb +74 -17
- data/lib/ruby_lsp/listeners/definition.rb +82 -24
- data/lib/ruby_lsp/listeners/hover.rb +62 -6
- data/lib/ruby_lsp/listeners/signature_help.rb +4 -4
- data/lib/ruby_lsp/node_context.rb +39 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +73 -2
- data/lib/ruby_lsp/requests/code_actions.rb +16 -15
- data/lib/ruby_lsp/requests/completion.rb +21 -13
- data/lib/ruby_lsp/requests/completion_resolve.rb +9 -0
- data/lib/ruby_lsp/requests/definition.rb +25 -5
- data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
- data/lib/ruby_lsp/requests/hover.rb +5 -6
- data/lib/ruby_lsp/requests/on_type_formatting.rb +8 -4
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +4 -3
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +23 -6
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +5 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +7 -4
- data/lib/ruby_lsp/server.rb +11 -5
- data/lib/ruby_lsp/test_helper.rb +1 -0
- metadata +5 -3
|
@@ -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)
|
data/lib/ruby_lsp/addon.rb
CHANGED
|
@@ -99,8 +99,8 @@ module RubyLsp
|
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
sig { returns(String) }
|
|
102
|
-
def
|
|
103
|
-
@errors.
|
|
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
|
-
|
|
134
|
+
node_context: NodeContext,
|
|
135
135
|
dispatcher: Prism::Dispatcher,
|
|
136
136
|
).void
|
|
137
137
|
end
|
|
138
|
-
def create_hover_listener(response_builder,
|
|
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
|
-
|
|
162
|
+
node_context: NodeContext,
|
|
163
163
|
dispatcher: Prism::Dispatcher,
|
|
164
164
|
).void
|
|
165
165
|
end
|
|
166
|
-
def create_definition_listener(response_builder, uri,
|
|
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
|
-
|
|
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,
|
|
177
|
+
def create_completion_listener(response_builder, node_context, dispatcher, uri); end
|
|
178
178
|
end
|
|
179
179
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
|
@@ -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(
|
|
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,13 +121,14 @@ 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(
|
|
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)])
|
|
128
128
|
closest = node
|
|
129
129
|
parent = T.let(nil, T.nilable(Prism::Node))
|
|
130
130
|
nesting = T.let([], T::Array[T.any(Prism::ClassNode, Prism::ModuleNode)])
|
|
131
|
+
call_node = T.let(nil, T.nilable(Prism::CallNode))
|
|
131
132
|
|
|
132
133
|
until queue.empty?
|
|
133
134
|
candidate = queue.shift
|
|
@@ -159,6 +160,15 @@ module RubyLsp
|
|
|
159
160
|
nesting << candidate
|
|
160
161
|
end
|
|
161
162
|
|
|
163
|
+
if candidate.is_a?(Prism::CallNode)
|
|
164
|
+
arg_loc = candidate.arguments&.location
|
|
165
|
+
blk_loc = candidate.block&.location
|
|
166
|
+
if (arg_loc && (arg_loc.start_offset...arg_loc.end_offset).cover?(char_position)) ||
|
|
167
|
+
(blk_loc && (blk_loc.start_offset...blk_loc.end_offset).cover?(char_position))
|
|
168
|
+
call_node = candidate
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
162
172
|
# If there are node types to filter by, and the current node is not one of those types, then skip it
|
|
163
173
|
next if node_types.any? && node_types.none? { |type| candidate.class == type }
|
|
164
174
|
|
|
@@ -170,7 +180,20 @@ module RubyLsp
|
|
|
170
180
|
end
|
|
171
181
|
end
|
|
172
182
|
|
|
173
|
-
|
|
183
|
+
# When targeting the constant part of a class/module definition, we do not want the nesting to be duplicated. That
|
|
184
|
+
# is, when targeting Bar in the following example:
|
|
185
|
+
#
|
|
186
|
+
# ```ruby
|
|
187
|
+
# class Foo::Bar; end
|
|
188
|
+
# ```
|
|
189
|
+
# The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
|
|
190
|
+
# though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
|
|
191
|
+
if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
|
|
192
|
+
last_level = nesting.last
|
|
193
|
+
nesting.pop if last_level && last_level.constant_path == closest
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice }, call_node)
|
|
174
197
|
end
|
|
175
198
|
|
|
176
199
|
sig { returns(T::Boolean) }
|
data/lib/ruby_lsp/internal.rb
CHANGED
|
@@ -11,17 +11,17 @@ module RubyLsp
|
|
|
11
11
|
params(
|
|
12
12
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
|
13
13
|
global_state: GlobalState,
|
|
14
|
-
|
|
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,
|
|
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
|
-
@
|
|
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
|
-
|
|
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 ? [] :
|
|
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.
|
|
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[@
|
|
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.
|
|
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
|
-
|
|
301
|
-
|
|
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 = "#{@
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
@
|
|
27
|
+
@node_context = node_context
|
|
28
28
|
@typechecker_enabled = typechecker_enabled
|
|
29
29
|
|
|
30
30
|
dispatcher.register(
|
|
@@ -33,18 +33,33 @@ 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,
|
|
42
|
+
:on_string_node_enter,
|
|
36
43
|
)
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
sig { params(node: Prism::CallNode).void }
|
|
40
47
|
def on_call_node_enter(node)
|
|
41
|
-
message = node.
|
|
48
|
+
message = node.message
|
|
49
|
+
return unless message
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
handle_method_definition(message, self_receiver?(node))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
sig { params(node: Prism::StringNode).void }
|
|
55
|
+
def on_string_node_enter(node)
|
|
56
|
+
enclosing_call = @node_context.call_node
|
|
57
|
+
return unless enclosing_call
|
|
58
|
+
|
|
59
|
+
name = enclosing_call.name
|
|
60
|
+
return unless name == :require || name == :require_relative
|
|
61
|
+
|
|
62
|
+
handle_require_definition(node, name)
|
|
48
63
|
end
|
|
49
64
|
|
|
50
65
|
sig { params(node: Prism::BlockArgumentNode).void }
|
|
@@ -74,12 +89,62 @@ module RubyLsp
|
|
|
74
89
|
find_in_index(name)
|
|
75
90
|
end
|
|
76
91
|
|
|
92
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
|
93
|
+
def on_instance_variable_read_node_enter(node)
|
|
94
|
+
handle_instance_variable_definition(node.name.to_s)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
|
98
|
+
def on_instance_variable_write_node_enter(node)
|
|
99
|
+
handle_instance_variable_definition(node.name.to_s)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
|
103
|
+
def on_instance_variable_and_write_node_enter(node)
|
|
104
|
+
handle_instance_variable_definition(node.name.to_s)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
|
108
|
+
def on_instance_variable_operator_write_node_enter(node)
|
|
109
|
+
handle_instance_variable_definition(node.name.to_s)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
|
113
|
+
def on_instance_variable_or_write_node_enter(node)
|
|
114
|
+
handle_instance_variable_definition(node.name.to_s)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
|
118
|
+
def on_instance_variable_target_node_enter(node)
|
|
119
|
+
handle_instance_variable_definition(node.name.to_s)
|
|
120
|
+
end
|
|
121
|
+
|
|
77
122
|
private
|
|
78
123
|
|
|
124
|
+
sig { params(name: String).void }
|
|
125
|
+
def handle_instance_variable_definition(name)
|
|
126
|
+
entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
|
|
127
|
+
return unless entries
|
|
128
|
+
|
|
129
|
+
entries.each do |entry|
|
|
130
|
+
location = entry.location
|
|
131
|
+
|
|
132
|
+
@response_builder << Interface::Location.new(
|
|
133
|
+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
|
|
134
|
+
range: Interface::Range.new(
|
|
135
|
+
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
|
136
|
+
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
|
141
|
+
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
|
142
|
+
end
|
|
143
|
+
|
|
79
144
|
sig { params(message: String, self_receiver: T::Boolean).void }
|
|
80
145
|
def handle_method_definition(message, self_receiver)
|
|
81
146
|
methods = if self_receiver
|
|
82
|
-
@index.resolve_method(message, @
|
|
147
|
+
@index.resolve_method(message, @node_context.fully_qualified_name)
|
|
83
148
|
else
|
|
84
149
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
|
85
150
|
# But we don't want to provide too many candidates, as it can be overwhelming
|
|
@@ -103,19 +168,12 @@ module RubyLsp
|
|
|
103
168
|
end
|
|
104
169
|
end
|
|
105
170
|
|
|
106
|
-
sig { params(node: Prism::
|
|
107
|
-
def handle_require_definition(node)
|
|
108
|
-
message = node.name
|
|
109
|
-
arguments = node.arguments
|
|
110
|
-
return unless arguments
|
|
111
|
-
|
|
112
|
-
argument = arguments.arguments.first
|
|
113
|
-
return unless argument.is_a?(Prism::StringNode)
|
|
114
|
-
|
|
171
|
+
sig { params(node: Prism::StringNode, message: Symbol).void }
|
|
172
|
+
def handle_require_definition(node, message)
|
|
115
173
|
case message
|
|
116
174
|
when :require
|
|
117
|
-
entry = @index.search_require_paths(
|
|
118
|
-
indexable_path.require_path ==
|
|
175
|
+
entry = @index.search_require_paths(node.content).find do |indexable_path|
|
|
176
|
+
indexable_path.require_path == node.content
|
|
119
177
|
end
|
|
120
178
|
|
|
121
179
|
if entry
|
|
@@ -130,7 +188,7 @@ module RubyLsp
|
|
|
130
188
|
)
|
|
131
189
|
end
|
|
132
190
|
when :require_relative
|
|
133
|
-
required_file = "#{
|
|
191
|
+
required_file = "#{node.content}.rb"
|
|
134
192
|
path = @uri.to_standardized_path
|
|
135
193
|
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
|
|
136
194
|
candidate = File.expand_path(File.join(current_folder, required_file))
|
|
@@ -147,13 +205,13 @@ module RubyLsp
|
|
|
147
205
|
|
|
148
206
|
sig { params(value: String).void }
|
|
149
207
|
def find_in_index(value)
|
|
150
|
-
entries = @index.resolve(value, @nesting)
|
|
208
|
+
entries = @index.resolve(value, @node_context.nesting)
|
|
151
209
|
return unless entries
|
|
152
210
|
|
|
153
211
|
# We should only allow jumping to the definition of private constants if the constant is defined in the same
|
|
154
212
|
# namespace as the reference
|
|
155
213
|
first_entry = T.must(entries.first)
|
|
156
|
-
return if first_entry.
|
|
214
|
+
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{value}"
|
|
157
215
|
|
|
158
216
|
entries.each do |entry|
|
|
159
217
|
location = entry.location
|
|
@@ -13,6 +13,14 @@ 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,
|
|
22
|
+
Prism::SymbolNode,
|
|
23
|
+
Prism::StringNode,
|
|
16
24
|
],
|
|
17
25
|
T::Array[T.class_of(Prism::Node)],
|
|
18
26
|
)
|
|
@@ -30,17 +38,17 @@ module RubyLsp
|
|
|
30
38
|
response_builder: ResponseBuilders::Hover,
|
|
31
39
|
global_state: GlobalState,
|
|
32
40
|
uri: URI::Generic,
|
|
33
|
-
|
|
41
|
+
node_context: NodeContext,
|
|
34
42
|
dispatcher: Prism::Dispatcher,
|
|
35
43
|
typechecker_enabled: T::Boolean,
|
|
36
44
|
).void
|
|
37
45
|
end
|
|
38
|
-
def initialize(response_builder, global_state, uri,
|
|
46
|
+
def initialize(response_builder, global_state, uri, node_context, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
|
|
39
47
|
@response_builder = response_builder
|
|
40
48
|
@global_state = global_state
|
|
41
49
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
|
42
50
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
|
43
|
-
@
|
|
51
|
+
@node_context = node_context
|
|
44
52
|
@typechecker_enabled = typechecker_enabled
|
|
45
53
|
|
|
46
54
|
dispatcher.register(
|
|
@@ -49,6 +57,12 @@ module RubyLsp
|
|
|
49
57
|
:on_constant_write_node_enter,
|
|
50
58
|
:on_constant_path_node_enter,
|
|
51
59
|
:on_call_node_enter,
|
|
60
|
+
:on_instance_variable_read_node_enter,
|
|
61
|
+
:on_instance_variable_write_node_enter,
|
|
62
|
+
:on_instance_variable_and_write_node_enter,
|
|
63
|
+
:on_instance_variable_operator_write_node_enter,
|
|
64
|
+
:on_instance_variable_or_write_node_enter,
|
|
65
|
+
:on_instance_variable_target_node_enter,
|
|
52
66
|
)
|
|
53
67
|
end
|
|
54
68
|
|
|
@@ -93,7 +107,7 @@ module RubyLsp
|
|
|
93
107
|
message = node.message
|
|
94
108
|
return unless message
|
|
95
109
|
|
|
96
|
-
methods = @index.resolve_method(message, @
|
|
110
|
+
methods = @index.resolve_method(message, @node_context.fully_qualified_name)
|
|
97
111
|
return unless methods
|
|
98
112
|
|
|
99
113
|
categorized_markdown_from_index_entries(message, methods).each do |category, content|
|
|
@@ -101,17 +115,59 @@ module RubyLsp
|
|
|
101
115
|
end
|
|
102
116
|
end
|
|
103
117
|
|
|
118
|
+
sig { params(node: Prism::InstanceVariableReadNode).void }
|
|
119
|
+
def on_instance_variable_read_node_enter(node)
|
|
120
|
+
handle_instance_variable_hover(node.name.to_s)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
sig { params(node: Prism::InstanceVariableWriteNode).void }
|
|
124
|
+
def on_instance_variable_write_node_enter(node)
|
|
125
|
+
handle_instance_variable_hover(node.name.to_s)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
sig { params(node: Prism::InstanceVariableAndWriteNode).void }
|
|
129
|
+
def on_instance_variable_and_write_node_enter(node)
|
|
130
|
+
handle_instance_variable_hover(node.name.to_s)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
|
|
134
|
+
def on_instance_variable_operator_write_node_enter(node)
|
|
135
|
+
handle_instance_variable_hover(node.name.to_s)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig { params(node: Prism::InstanceVariableOrWriteNode).void }
|
|
139
|
+
def on_instance_variable_or_write_node_enter(node)
|
|
140
|
+
handle_instance_variable_hover(node.name.to_s)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
sig { params(node: Prism::InstanceVariableTargetNode).void }
|
|
144
|
+
def on_instance_variable_target_node_enter(node)
|
|
145
|
+
handle_instance_variable_hover(node.name.to_s)
|
|
146
|
+
end
|
|
147
|
+
|
|
104
148
|
private
|
|
105
149
|
|
|
150
|
+
sig { params(name: String).void }
|
|
151
|
+
def handle_instance_variable_hover(name)
|
|
152
|
+
entries = @index.resolve_instance_variable(name, @node_context.fully_qualified_name)
|
|
153
|
+
return unless entries
|
|
154
|
+
|
|
155
|
+
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
|
156
|
+
@response_builder.push(content, category: category)
|
|
157
|
+
end
|
|
158
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
|
159
|
+
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
|
160
|
+
end
|
|
161
|
+
|
|
106
162
|
sig { params(name: String, location: Prism::Location).void }
|
|
107
163
|
def generate_hover(name, location)
|
|
108
|
-
entries = @index.resolve(name, @nesting)
|
|
164
|
+
entries = @index.resolve(name, @node_context.nesting)
|
|
109
165
|
return unless entries
|
|
110
166
|
|
|
111
167
|
# We should only show hover for private constants if the constant is defined in the same namespace as the
|
|
112
168
|
# reference
|
|
113
169
|
first_entry = T.must(entries.first)
|
|
114
|
-
return if first_entry.
|
|
170
|
+
return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{name}"
|
|
115
171
|
|
|
116
172
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
|
117
173
|
@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
|
-
|
|
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,
|
|
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
|
-
@
|
|
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, @
|
|
36
|
+
methods = @index.resolve_method(message, @node_context.fully_qualified_name)
|
|
37
37
|
return unless methods
|
|
38
38
|
|
|
39
39
|
target_method = methods.first
|