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