ruby-lsp 0.16.6 → 0.16.7
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/exe/ruby-lsp +21 -4
- data/exe/ruby-lsp-check +1 -3
- data/exe/ruby-lsp-doctor +1 -4
- data/lib/core_ext/uri.rb +3 -0
- data/lib/ruby_indexer/lib/ruby_indexer/{collector.rb → declaration_listener.rb} +123 -126
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +4 -2
- data/lib/ruby_indexer/ruby_indexer.rb +1 -1
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +51 -1
- data/lib/ruby_indexer/test/constant_test.rb +3 -0
- data/lib/ruby_indexer/test/method_test.rb +56 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +11 -0
- data/lib/ruby_lsp/listeners/completion.rb +76 -40
- data/lib/ruby_lsp/listeners/definition.rb +16 -7
- data/lib/ruby_lsp/listeners/document_highlight.rb +1 -1
- data/lib/ruby_lsp/listeners/document_link.rb +1 -1
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +59 -3
- data/lib/ruby_lsp/requests/completion.rb +2 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +17 -10
- data/lib/ruby_lsp/requests/definition.rb +2 -1
- data/lib/ruby_lsp/requests/support/common.rb +20 -2
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebc166637de670ea02144e74ccb2e99ae1a9824c9f99995380d81bed99ff6a29
|
4
|
+
data.tar.gz: 48b7ef9364f4d1a8044042c8476414e87da5b38ca4871683c16d06636fc8e411
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80c4f791e7dfef66623f5190eaba4dc76c745a27f74238d912c34711c408803ec952d5ec3e8041ebb6410b99c74d2f3d8274db5dffa8f187e48a7032a3960086
|
7
|
+
data.tar.gz: 941209321fb263b9a22cbe27102e8761c6ef127902e2b74f760fec1a8701d96e5bcef5f01c704e7215e061437cac2577c38e100464cccfe03909aa42ee1e318e
|
data/README.md
CHANGED
@@ -33,7 +33,7 @@ The Ruby LSP features include
|
|
33
33
|
- Completion for classes, modules, constants and require paths
|
34
34
|
- Fuzzy search classes, modules and constants anywhere in the project and its dependencies (workspace symbol)
|
35
35
|
|
36
|
-
Adding method support for definition, completion, hover and workspace symbol is
|
36
|
+
Adding method support for definition, completion, hover and workspace symbol is partially supported, but not yet complete. Follow progress in https://github.com/Shopify/ruby-lsp/issues/899
|
37
37
|
|
38
38
|
See complete information about features [here](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
|
39
39
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.16.
|
1
|
+
0.16.7
|
data/exe/ruby-lsp
CHANGED
@@ -18,6 +18,10 @@ parser = OptionParser.new do |opts|
|
|
18
18
|
options[:debug] = true
|
19
19
|
end
|
20
20
|
|
21
|
+
opts.on("--time-index", "Measure the time it takes to index the project") do
|
22
|
+
options[:time_index] = true
|
23
|
+
end
|
24
|
+
|
21
25
|
opts.on(
|
22
26
|
"--branch [BRANCH]",
|
23
27
|
"Launch the Ruby LSP using the specified branch rather than the release version",
|
@@ -80,14 +84,27 @@ if options[:debug]
|
|
80
84
|
end
|
81
85
|
|
82
86
|
begin
|
83
|
-
|
84
|
-
$stdout = $stderr
|
87
|
+
ENV.delete("RUBY_DEBUG_IRB_CONSOLE")
|
85
88
|
require "debug/open_nonstop"
|
86
89
|
rescue LoadError
|
87
90
|
$stderr.puts("You need to install the debug gem to use the --debug flag")
|
88
|
-
ensure
|
89
|
-
$stdout = original_stdout
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
94
|
+
if options[:time_index]
|
95
|
+
require "benchmark"
|
96
|
+
|
97
|
+
index = RubyIndexer::Index.new
|
98
|
+
|
99
|
+
result = Benchmark.realtime { index.index_all }
|
100
|
+
entries = index.instance_variable_get(:@entries)
|
101
|
+
entries_by_entry_type = entries.values.flatten.group_by(&:class)
|
102
|
+
|
103
|
+
puts <<~MSG
|
104
|
+
Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{result.round(5)} seconds and generated:
|
105
|
+
- #{entries_by_entry_type.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
|
106
|
+
MSG
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
93
110
|
RubyLsp::Server.new.start
|
data/exe/ruby-lsp-check
CHANGED
@@ -47,9 +47,7 @@ index = RubyIndexer::Index.new
|
|
47
47
|
indexables = RubyIndexer.configuration.indexables
|
48
48
|
|
49
49
|
indexables.each_with_index do |indexable, i|
|
50
|
-
|
51
|
-
collector = RubyIndexer::Collector.new(index, result, indexable.full_path)
|
52
|
-
collector.collect(result.value)
|
50
|
+
index.index_single(indexable)
|
53
51
|
rescue => e
|
54
52
|
errors[indexable.full_path] = e
|
55
53
|
ensure
|
data/exe/ruby-lsp-doctor
CHANGED
@@ -19,8 +19,5 @@ puts "Globbing for indexable files"
|
|
19
19
|
|
20
20
|
RubyIndexer.configuration.indexables.each do |indexable|
|
21
21
|
puts "indexing: #{indexable.full_path}"
|
22
|
-
|
23
|
-
result = Prism.parse(content)
|
24
|
-
collector = RubyIndexer::Collector.new(index, result, indexable.full_path)
|
25
|
-
collector.collect(result.value)
|
22
|
+
index.index_single(indexable)
|
26
23
|
end
|
data/lib/core_ext/uri.rb
CHANGED
@@ -11,6 +11,9 @@ module URI
|
|
11
11
|
# On Windows, if the path begins with the disk name, we need to add a leading slash to make it a valid URI
|
12
12
|
escaped_path = if /^[A-Z]:/i.match?(path)
|
13
13
|
DEFAULT_PARSER.escape("/#{path}")
|
14
|
+
elsif path.start_with?("//?/")
|
15
|
+
# Some paths on Windows start with "//?/". This is a special prefix that allows for long file paths
|
16
|
+
DEFAULT_PARSER.escape(path.delete_prefix("//?"))
|
14
17
|
else
|
15
18
|
DEFAULT_PARSER.escape(path)
|
16
19
|
end
|
@@ -2,79 +2,103 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyIndexer
|
5
|
-
class
|
5
|
+
class DeclarationListener
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def initialize(index, parse_result, file_path)
|
8
|
+
sig do
|
9
|
+
params(index: Index, dispatcher: Prism::Dispatcher, parse_result: Prism::ParseResult, file_path: String).void
|
10
|
+
end
|
11
|
+
def initialize(index, dispatcher, parse_result, file_path)
|
12
12
|
@index = index
|
13
13
|
@file_path = file_path
|
14
|
-
@stack = T.let([], T::Array[String])
|
15
14
|
@comments_by_line = T.let(
|
16
15
|
parse_result.comments.to_h do |c|
|
17
16
|
[c.location.start_line, c]
|
18
17
|
end,
|
19
18
|
T::Hash[Integer, Prism::Comment],
|
20
19
|
)
|
21
|
-
@
|
22
|
-
|
20
|
+
@inside_def = T.let(false, T::Boolean)
|
21
|
+
|
22
|
+
# The nesting stack we're currently inside. Used to determine the fully qualified name of constants, but only
|
23
|
+
# stored by unresolved aliases which need the original nesting to be lazily resolved
|
24
|
+
@stack = T.let([], T::Array[String])
|
23
25
|
|
24
|
-
|
26
|
+
# A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
|
27
|
+
@owner_stack = T.let([], T::Array[Entry::Namespace])
|
28
|
+
|
29
|
+
dispatcher.register(
|
30
|
+
self,
|
31
|
+
:on_class_node_enter,
|
32
|
+
:on_class_node_leave,
|
33
|
+
:on_module_node_enter,
|
34
|
+
:on_module_node_leave,
|
35
|
+
:on_def_node_enter,
|
36
|
+
:on_def_node_leave,
|
37
|
+
:on_call_node_enter,
|
38
|
+
:on_multi_write_node_enter,
|
39
|
+
:on_constant_path_write_node_enter,
|
40
|
+
:on_constant_path_or_write_node_enter,
|
41
|
+
:on_constant_path_operator_write_node_enter,
|
42
|
+
:on_constant_path_and_write_node_enter,
|
43
|
+
:on_constant_or_write_node_enter,
|
44
|
+
:on_constant_write_node_enter,
|
45
|
+
:on_constant_or_write_node_enter,
|
46
|
+
:on_constant_and_write_node_enter,
|
47
|
+
:on_constant_operator_write_node_enter,
|
48
|
+
)
|
25
49
|
end
|
26
50
|
|
27
|
-
sig { params(node: Prism::
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
when Prism::StatementsNode
|
38
|
-
T.unsafe(@queue).prepend(*node_or_event.body)
|
39
|
-
when Prism::ClassNode
|
40
|
-
add_class_entry(node_or_event)
|
41
|
-
when Prism::ModuleNode
|
42
|
-
add_module_entry(node_or_event)
|
43
|
-
when Prism::MultiWriteNode
|
44
|
-
handle_multi_write_node(node_or_event)
|
45
|
-
when Prism::ConstantPathWriteNode
|
46
|
-
handle_constant_path_write_node(node_or_event)
|
47
|
-
when Prism::ConstantPathOrWriteNode
|
48
|
-
handle_constant_path_or_write_node(node_or_event)
|
49
|
-
when Prism::ConstantPathOperatorWriteNode
|
50
|
-
handle_constant_path_operator_write_node(node_or_event)
|
51
|
-
when Prism::ConstantPathAndWriteNode
|
52
|
-
handle_constant_path_and_write_node(node_or_event)
|
53
|
-
when Prism::ConstantWriteNode
|
54
|
-
handle_constant_write_node(node_or_event)
|
55
|
-
when Prism::ConstantOrWriteNode
|
56
|
-
name = fully_qualify_name(node_or_event.name.to_s)
|
57
|
-
add_constant(node_or_event, name)
|
58
|
-
when Prism::ConstantAndWriteNode
|
59
|
-
name = fully_qualify_name(node_or_event.name.to_s)
|
60
|
-
add_constant(node_or_event, name)
|
61
|
-
when Prism::ConstantOperatorWriteNode
|
62
|
-
name = fully_qualify_name(node_or_event.name.to_s)
|
63
|
-
add_constant(node_or_event, name)
|
64
|
-
when Prism::CallNode
|
65
|
-
handle_call_node(node_or_event)
|
66
|
-
when Prism::DefNode
|
67
|
-
handle_def_node(node_or_event)
|
68
|
-
when LEAVE_EVENT
|
69
|
-
@stack.pop
|
70
|
-
end
|
51
|
+
sig { params(node: Prism::ClassNode).void }
|
52
|
+
def on_class_node_enter(node)
|
53
|
+
name = node.constant_path.location.slice
|
54
|
+
|
55
|
+
comments = collect_comments(node)
|
56
|
+
|
57
|
+
superclass = node.superclass
|
58
|
+
parent_class = case superclass
|
59
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
60
|
+
superclass.slice
|
71
61
|
end
|
62
|
+
|
63
|
+
entry = Entry::Class.new(
|
64
|
+
fully_qualify_name(name),
|
65
|
+
@file_path,
|
66
|
+
node.location,
|
67
|
+
comments,
|
68
|
+
parent_class,
|
69
|
+
)
|
70
|
+
|
71
|
+
@owner_stack << entry
|
72
|
+
@index << entry
|
73
|
+
@stack << name
|
72
74
|
end
|
73
75
|
|
74
|
-
|
76
|
+
sig { params(node: Prism::ClassNode).void }
|
77
|
+
def on_class_node_leave(node)
|
78
|
+
@stack.pop
|
79
|
+
@owner_stack.pop
|
80
|
+
end
|
81
|
+
|
82
|
+
sig { params(node: Prism::ModuleNode).void }
|
83
|
+
def on_module_node_enter(node)
|
84
|
+
name = node.constant_path.location.slice
|
85
|
+
|
86
|
+
comments = collect_comments(node)
|
87
|
+
entry = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
|
88
|
+
|
89
|
+
@owner_stack << entry
|
90
|
+
@index << entry
|
91
|
+
@stack << name
|
92
|
+
end
|
93
|
+
|
94
|
+
sig { params(node: Prism::ModuleNode).void }
|
95
|
+
def on_module_node_leave(node)
|
96
|
+
@stack.pop
|
97
|
+
@owner_stack.pop
|
98
|
+
end
|
75
99
|
|
76
100
|
sig { params(node: Prism::MultiWriteNode).void }
|
77
|
-
def
|
101
|
+
def on_multi_write_node_enter(node)
|
78
102
|
value = node.value
|
79
103
|
values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []
|
80
104
|
|
@@ -94,7 +118,7 @@ module RubyIndexer
|
|
94
118
|
end
|
95
119
|
|
96
120
|
sig { params(node: Prism::ConstantPathWriteNode).void }
|
97
|
-
def
|
121
|
+
def on_constant_path_write_node_enter(node)
|
98
122
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
99
123
|
target = node.target
|
100
124
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -104,7 +128,7 @@ module RubyIndexer
|
|
104
128
|
end
|
105
129
|
|
106
130
|
sig { params(node: Prism::ConstantPathOrWriteNode).void }
|
107
|
-
def
|
131
|
+
def on_constant_path_or_write_node_enter(node)
|
108
132
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
109
133
|
target = node.target
|
110
134
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -114,7 +138,7 @@ module RubyIndexer
|
|
114
138
|
end
|
115
139
|
|
116
140
|
sig { params(node: Prism::ConstantPathOperatorWriteNode).void }
|
117
|
-
def
|
141
|
+
def on_constant_path_operator_write_node_enter(node)
|
118
142
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
119
143
|
target = node.target
|
120
144
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -124,7 +148,7 @@ module RubyIndexer
|
|
124
148
|
end
|
125
149
|
|
126
150
|
sig { params(node: Prism::ConstantPathAndWriteNode).void }
|
127
|
-
def
|
151
|
+
def on_constant_path_and_write_node_enter(node)
|
128
152
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
129
153
|
target = node.target
|
130
154
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -134,13 +158,31 @@ module RubyIndexer
|
|
134
158
|
end
|
135
159
|
|
136
160
|
sig { params(node: Prism::ConstantWriteNode).void }
|
137
|
-
def
|
161
|
+
def on_constant_write_node_enter(node)
|
162
|
+
name = fully_qualify_name(node.name.to_s)
|
163
|
+
add_constant(node, name)
|
164
|
+
end
|
165
|
+
|
166
|
+
sig { params(node: Prism::ConstantOrWriteNode).void }
|
167
|
+
def on_constant_or_write_node_enter(node)
|
168
|
+
name = fully_qualify_name(node.name.to_s)
|
169
|
+
add_constant(node, name)
|
170
|
+
end
|
171
|
+
|
172
|
+
sig { params(node: Prism::ConstantAndWriteNode).void }
|
173
|
+
def on_constant_and_write_node_enter(node)
|
174
|
+
name = fully_qualify_name(node.name.to_s)
|
175
|
+
add_constant(node, name)
|
176
|
+
end
|
177
|
+
|
178
|
+
sig { params(node: Prism::ConstantOperatorWriteNode).void }
|
179
|
+
def on_constant_operator_write_node_enter(node)
|
138
180
|
name = fully_qualify_name(node.name.to_s)
|
139
181
|
add_constant(node, name)
|
140
182
|
end
|
141
183
|
|
142
184
|
sig { params(node: Prism::CallNode).void }
|
143
|
-
def
|
185
|
+
def on_call_node_enter(node)
|
144
186
|
message = node.name
|
145
187
|
|
146
188
|
case message
|
@@ -153,14 +195,15 @@ module RubyIndexer
|
|
153
195
|
when :attr_accessor
|
154
196
|
handle_attribute(node, reader: true, writer: true)
|
155
197
|
when :include
|
156
|
-
|
198
|
+
handle_module_operation(node, :included_modules)
|
157
199
|
when :prepend
|
158
|
-
|
200
|
+
handle_module_operation(node, :prepended_modules)
|
159
201
|
end
|
160
202
|
end
|
161
203
|
|
162
204
|
sig { params(node: Prism::DefNode).void }
|
163
|
-
def
|
205
|
+
def on_def_node_enter(node)
|
206
|
+
@inside_def = true
|
164
207
|
method_name = node.name.to_s
|
165
208
|
comments = collect_comments(node)
|
166
209
|
case node.receiver
|
@@ -171,7 +214,7 @@ module RubyIndexer
|
|
171
214
|
node.location,
|
172
215
|
comments,
|
173
216
|
node.parameters,
|
174
|
-
@
|
217
|
+
@owner_stack.last,
|
175
218
|
)
|
176
219
|
when Prism::SelfNode
|
177
220
|
@index << Entry::SingletonMethod.new(
|
@@ -180,11 +223,18 @@ module RubyIndexer
|
|
180
223
|
node.location,
|
181
224
|
comments,
|
182
225
|
node.parameters,
|
183
|
-
@
|
226
|
+
@owner_stack.last,
|
184
227
|
)
|
185
228
|
end
|
186
229
|
end
|
187
230
|
|
231
|
+
sig { params(node: Prism::DefNode).void }
|
232
|
+
def on_def_node_leave(node)
|
233
|
+
@inside_def = false
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
188
238
|
sig { params(node: Prism::CallNode).void }
|
189
239
|
def handle_private_constant(node)
|
190
240
|
arguments = node.arguments&.arguments
|
@@ -240,62 +290,16 @@ module RubyIndexer
|
|
240
290
|
|
241
291
|
# If the right hand side is another constant assignment, we need to visit it because that constant has to be
|
242
292
|
# indexed too
|
243
|
-
@queue.prepend(value)
|
244
293
|
Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
|
245
294
|
when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
246
295
|
Prism::ConstantPathAndWriteNode
|
247
296
|
|
248
|
-
@queue.prepend(value)
|
249
297
|
Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
|
250
298
|
else
|
251
299
|
Entry::Constant.new(name, @file_path, node.location, comments)
|
252
300
|
end
|
253
301
|
end
|
254
302
|
|
255
|
-
sig { params(node: Prism::ModuleNode).void }
|
256
|
-
def add_module_entry(node)
|
257
|
-
name = node.constant_path.location.slice
|
258
|
-
unless /^[A-Z:]/.match?(name)
|
259
|
-
@queue << node.body
|
260
|
-
return
|
261
|
-
end
|
262
|
-
|
263
|
-
comments = collect_comments(node)
|
264
|
-
@current_owner = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
|
265
|
-
@index << @current_owner
|
266
|
-
@stack << name
|
267
|
-
@queue.prepend(node.body, LEAVE_EVENT)
|
268
|
-
end
|
269
|
-
|
270
|
-
sig { params(node: Prism::ClassNode).void }
|
271
|
-
def add_class_entry(node)
|
272
|
-
name = node.constant_path.location.slice
|
273
|
-
|
274
|
-
unless /^[A-Z:]/.match?(name)
|
275
|
-
@queue << node.body
|
276
|
-
return
|
277
|
-
end
|
278
|
-
|
279
|
-
comments = collect_comments(node)
|
280
|
-
|
281
|
-
superclass = node.superclass
|
282
|
-
parent_class = case superclass
|
283
|
-
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
284
|
-
superclass.slice
|
285
|
-
end
|
286
|
-
|
287
|
-
@current_owner = Entry::Class.new(
|
288
|
-
fully_qualify_name(name),
|
289
|
-
@file_path,
|
290
|
-
node.location,
|
291
|
-
comments,
|
292
|
-
parent_class,
|
293
|
-
)
|
294
|
-
@index << @current_owner
|
295
|
-
@stack << name
|
296
|
-
@queue.prepend(node.body, LEAVE_EVENT)
|
297
|
-
end
|
298
|
-
|
299
303
|
sig { params(node: Prism::Node).returns(T::Array[String]) }
|
300
304
|
def collect_comments(node)
|
301
305
|
comments = []
|
@@ -350,24 +354,17 @@ module RubyIndexer
|
|
350
354
|
|
351
355
|
next unless name && loc
|
352
356
|
|
353
|
-
@index << Entry::Accessor.new(name, @file_path, loc, comments, @
|
354
|
-
@index << Entry::Accessor.new("#{name}=", @file_path, loc, comments, @
|
357
|
+
@index << Entry::Accessor.new(name, @file_path, loc, comments, @owner_stack.last) if reader
|
358
|
+
@index << Entry::Accessor.new("#{name}=", @file_path, loc, comments, @owner_stack.last) if writer
|
355
359
|
end
|
356
360
|
end
|
357
361
|
|
358
|
-
sig { params(node: Prism::CallNode).void }
|
359
|
-
def handle_include(node)
|
360
|
-
handle_module_operation(node, :included_modules)
|
361
|
-
end
|
362
|
-
|
363
|
-
sig { params(node: Prism::CallNode).void }
|
364
|
-
def handle_prepend(node)
|
365
|
-
handle_module_operation(node, :prepended_modules)
|
366
|
-
end
|
367
|
-
|
368
362
|
sig { params(node: Prism::CallNode, operation: Symbol).void }
|
369
363
|
def handle_module_operation(node, operation)
|
370
|
-
return
|
364
|
+
return if @inside_def
|
365
|
+
|
366
|
+
owner = @owner_stack.last
|
367
|
+
return unless owner
|
371
368
|
|
372
369
|
arguments = node.arguments&.arguments
|
373
370
|
return unless arguments
|
@@ -381,7 +378,7 @@ module RubyIndexer
|
|
381
378
|
# If a constant path reference is dynamic or missing parts, we can't
|
382
379
|
# index it
|
383
380
|
end
|
384
|
-
collection = operation == :included_modules ?
|
381
|
+
collection = operation == :included_modules ? owner.included_modules : owner.prepended_modules
|
385
382
|
collection.concat(names)
|
386
383
|
end
|
387
384
|
end
|
@@ -185,9 +185,11 @@ module RubyIndexer
|
|
185
185
|
sig { params(indexable_path: IndexablePath, source: T.nilable(String)).void }
|
186
186
|
def index_single(indexable_path, source = nil)
|
187
187
|
content = source || File.read(indexable_path.full_path)
|
188
|
+
dispatcher = Prism::Dispatcher.new
|
189
|
+
|
188
190
|
result = Prism.parse(content)
|
189
|
-
|
190
|
-
|
191
|
+
DeclarationListener.new(self, dispatcher, result, indexable_path.full_path)
|
192
|
+
dispatcher.dispatch(result.value)
|
191
193
|
|
192
194
|
require_path = indexable_path.require_path
|
193
195
|
@require_paths_tree.insert(require_path, indexable_path) if require_path
|
@@ -5,7 +5,7 @@ require "yaml"
|
|
5
5
|
require "did_you_mean"
|
6
6
|
|
7
7
|
require "ruby_indexer/lib/ruby_indexer/indexable_path"
|
8
|
-
require "ruby_indexer/lib/ruby_indexer/
|
8
|
+
require "ruby_indexer/lib/ruby_indexer/declaration_listener"
|
9
9
|
require "ruby_indexer/lib/ruby_indexer/index"
|
10
10
|
require "ruby_indexer/lib/ruby_indexer/entry"
|
11
11
|
require "ruby_indexer/lib/ruby_indexer/configuration"
|
@@ -14,6 +14,15 @@ module RubyIndexer
|
|
14
14
|
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
15
15
|
end
|
16
16
|
|
17
|
+
def test_conditional_class
|
18
|
+
index(<<~RUBY)
|
19
|
+
class Foo
|
20
|
+
end if condition
|
21
|
+
RUBY
|
22
|
+
|
23
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
24
|
+
end
|
25
|
+
|
17
26
|
def test_class_with_statements
|
18
27
|
index(<<~RUBY)
|
19
28
|
class Foo
|
@@ -60,7 +69,23 @@ module RubyIndexer
|
|
60
69
|
end
|
61
70
|
RUBY
|
62
71
|
|
72
|
+
assert_entry("self::Bar", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_dynamically_namespaced_class_doesnt_affect_other_classes
|
76
|
+
index(<<~RUBY)
|
77
|
+
class Foo
|
78
|
+
class self::Bar
|
79
|
+
end
|
80
|
+
|
81
|
+
class Bar
|
82
|
+
end
|
83
|
+
end
|
84
|
+
RUBY
|
85
|
+
|
63
86
|
refute_entry("self::Bar")
|
87
|
+
assert_entry("Foo", Entry::Class, "/fake/path/foo.rb:0-0:6-3")
|
88
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:4-2:5-5")
|
64
89
|
end
|
65
90
|
|
66
91
|
def test_empty_statements_module
|
@@ -72,6 +97,15 @@ module RubyIndexer
|
|
72
97
|
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
73
98
|
end
|
74
99
|
|
100
|
+
def test_conditional_module
|
101
|
+
index(<<~RUBY)
|
102
|
+
module Foo
|
103
|
+
end if condition
|
104
|
+
RUBY
|
105
|
+
|
106
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
107
|
+
end
|
108
|
+
|
75
109
|
def test_module_with_statements
|
76
110
|
index(<<~RUBY)
|
77
111
|
module Foo
|
@@ -106,7 +140,23 @@ module RubyIndexer
|
|
106
140
|
end
|
107
141
|
RUBY
|
108
142
|
|
109
|
-
|
143
|
+
assert_entry("self::Bar", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_dynamically_namespaced_module_doesnt_affect_other_modules
|
147
|
+
index(<<~RUBY)
|
148
|
+
module Foo
|
149
|
+
class self::Bar
|
150
|
+
end
|
151
|
+
|
152
|
+
module Bar
|
153
|
+
end
|
154
|
+
end
|
155
|
+
RUBY
|
156
|
+
|
157
|
+
assert_entry("Foo::self::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
158
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:6-3")
|
159
|
+
assert_entry("Foo::Bar", Entry::Module, "/fake/path/foo.rb:4-2:5-5")
|
110
160
|
end
|
111
161
|
|
112
162
|
def test_nested_modules_and_classes
|
@@ -12,10 +12,13 @@ module RubyIndexer
|
|
12
12
|
class ::Bar
|
13
13
|
FOO = 2
|
14
14
|
end
|
15
|
+
|
16
|
+
BAR = 3 if condition
|
15
17
|
RUBY
|
16
18
|
|
17
19
|
assert_entry("FOO", Entry::Constant, "/fake/path/foo.rb:0-0:0-7")
|
18
20
|
assert_entry("Bar::FOO", Entry::Constant, "/fake/path/foo.rb:3-2:3-9")
|
21
|
+
assert_entry("BAR", Entry::Constant, "/fake/path/foo.rb:6-0:6-7")
|
19
22
|
end
|
20
23
|
|
21
24
|
def test_constant_or_writes
|
@@ -16,6 +16,17 @@ module RubyIndexer
|
|
16
16
|
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
17
17
|
end
|
18
18
|
|
19
|
+
def test_conditional_method
|
20
|
+
index(<<~RUBY)
|
21
|
+
class Foo
|
22
|
+
def bar
|
23
|
+
end if condition
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
|
27
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
28
|
+
end
|
29
|
+
|
19
30
|
def test_singleton_method_using_self_receiver
|
20
31
|
index(<<~RUBY)
|
21
32
|
class Foo
|
@@ -38,6 +49,28 @@ module RubyIndexer
|
|
38
49
|
assert_no_entry("bar")
|
39
50
|
end
|
40
51
|
|
52
|
+
def test_method_under_dynamic_class_or_module
|
53
|
+
index(<<~RUBY)
|
54
|
+
module Foo
|
55
|
+
class self::Bar
|
56
|
+
def bar
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module Bar
|
62
|
+
def bar
|
63
|
+
end
|
64
|
+
end
|
65
|
+
RUBY
|
66
|
+
|
67
|
+
assert_equal(2, @index["bar"].length)
|
68
|
+
first_entry = T.must(@index["bar"].first)
|
69
|
+
assert_equal("Foo::self::Bar", first_entry.owner.name)
|
70
|
+
second_entry = T.must(@index["bar"].last)
|
71
|
+
assert_equal("Bar", second_entry.owner.name)
|
72
|
+
end
|
73
|
+
|
41
74
|
def test_method_with_parameters
|
42
75
|
index(<<~RUBY)
|
43
76
|
class Foo
|
@@ -285,5 +318,28 @@ module RubyIndexer
|
|
285
318
|
|
286
319
|
assert_no_entry("bar")
|
287
320
|
end
|
321
|
+
|
322
|
+
def test_properly_tracks_multiple_levels_of_nesting
|
323
|
+
index(<<~RUBY)
|
324
|
+
module Foo
|
325
|
+
def first; end
|
326
|
+
|
327
|
+
module Bar
|
328
|
+
def second; end
|
329
|
+
end
|
330
|
+
|
331
|
+
def third; end
|
332
|
+
end
|
333
|
+
RUBY
|
334
|
+
|
335
|
+
entry = T.must(@index["first"]&.first)
|
336
|
+
assert_equal("Foo", T.must(entry.owner).name)
|
337
|
+
|
338
|
+
entry = T.must(@index["second"]&.first)
|
339
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
340
|
+
|
341
|
+
entry = T.must(@index["third"]&.first)
|
342
|
+
assert_equal("Foo", T.must(entry.owner).name)
|
343
|
+
end
|
288
344
|
end
|
289
345
|
end
|
@@ -42,6 +42,8 @@ module RubyLsp
|
|
42
42
|
@group_stack = T.let([], T::Array[String])
|
43
43
|
@group_id = T.let(1, Integer)
|
44
44
|
@group_id_stack = T.let([], T::Array[Integer])
|
45
|
+
# We want to avoid adding code lenses for nested definitions
|
46
|
+
@def_depth = T.let(0, Integer)
|
45
47
|
|
46
48
|
dispatcher.register(
|
47
49
|
self,
|
@@ -50,6 +52,7 @@ module RubyLsp
|
|
50
52
|
:on_module_node_enter,
|
51
53
|
:on_module_node_leave,
|
52
54
|
:on_def_node_enter,
|
55
|
+
:on_def_node_leave,
|
53
56
|
:on_call_node_enter,
|
54
57
|
:on_call_node_leave,
|
55
58
|
)
|
@@ -88,6 +91,9 @@ module RubyLsp
|
|
88
91
|
|
89
92
|
sig { params(node: Prism::DefNode).void }
|
90
93
|
def on_def_node_enter(node)
|
94
|
+
@def_depth += 1
|
95
|
+
return if @def_depth > 1
|
96
|
+
|
91
97
|
class_name = @group_stack.last
|
92
98
|
return unless class_name&.end_with?("Test")
|
93
99
|
|
@@ -105,6 +111,11 @@ module RubyLsp
|
|
105
111
|
end
|
106
112
|
end
|
107
113
|
|
114
|
+
sig { params(node: Prism::DefNode).void }
|
115
|
+
def on_def_node_leave(node)
|
116
|
+
@def_depth -= 1
|
117
|
+
end
|
118
|
+
|
108
119
|
sig { params(node: Prism::ModuleNode).void }
|
109
120
|
def on_module_node_enter(node)
|
110
121
|
if (path = namespace_constant_name(node))
|
@@ -47,7 +47,7 @@ module RubyLsp
|
|
47
47
|
@response_builder << build_entry_completion(
|
48
48
|
complete_name,
|
49
49
|
name,
|
50
|
-
node,
|
50
|
+
range_from_location(node.location),
|
51
51
|
entries,
|
52
52
|
top_level?(complete_name),
|
53
53
|
)
|
@@ -62,6 +62,53 @@ module RubyLsp
|
|
62
62
|
name = constant_name(node)
|
63
63
|
return if name.nil?
|
64
64
|
|
65
|
+
constant_path_completion(name, range_from_location(node.location))
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(node: Prism::CallNode).void }
|
69
|
+
def on_call_node_enter(node)
|
70
|
+
receiver = node.receiver
|
71
|
+
|
72
|
+
# When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke singleton
|
73
|
+
# methods). However, in addition to providing method completion, we also need to show possible constant
|
74
|
+
# completions
|
75
|
+
if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
|
76
|
+
node.call_operator == "::"
|
77
|
+
|
78
|
+
name = constant_name(receiver)
|
79
|
+
|
80
|
+
if name
|
81
|
+
start_loc = node.location
|
82
|
+
end_loc = T.must(node.call_operator_loc)
|
83
|
+
|
84
|
+
constant_path_completion(
|
85
|
+
"#{name}::",
|
86
|
+
Interface::Range.new(
|
87
|
+
start: Interface::Position.new(line: start_loc.start_line - 1, character: start_loc.start_column),
|
88
|
+
end: Interface::Position.new(line: end_loc.end_line - 1, character: end_loc.end_column),
|
89
|
+
),
|
90
|
+
)
|
91
|
+
return
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
name = node.message
|
96
|
+
return unless name
|
97
|
+
|
98
|
+
case name
|
99
|
+
when "require"
|
100
|
+
complete_require(node)
|
101
|
+
when "require_relative"
|
102
|
+
complete_require_relative(node)
|
103
|
+
else
|
104
|
+
complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
sig { params(name: String, range: Interface::Range).void }
|
111
|
+
def constant_path_completion(name, range)
|
65
112
|
top_level_reference = if name.start_with?("::")
|
66
113
|
name = name.delete_prefix("::")
|
67
114
|
true
|
@@ -71,8 +118,13 @@ module RubyLsp
|
|
71
118
|
|
72
119
|
# If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
|
73
120
|
# order to find which possible constants match the desired search
|
74
|
-
|
75
|
-
|
121
|
+
aliased_namespace = if name.end_with?("::")
|
122
|
+
name.delete_suffix("::")
|
123
|
+
else
|
124
|
+
*namespace, incomplete_name = name.split("::")
|
125
|
+
T.must(namespace).join("::")
|
126
|
+
end
|
127
|
+
|
76
128
|
namespace_entries = @index.resolve(aliased_namespace, @nesting)
|
77
129
|
return unless namespace_entries
|
78
130
|
|
@@ -88,37 +140,19 @@ module RubyLsp
|
|
88
140
|
first_entry = T.must(entries.first)
|
89
141
|
next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
|
90
142
|
|
91
|
-
constant_name =
|
92
|
-
|
143
|
+
constant_name = first_entry.name.delete_prefix("#{real_namespace}::")
|
93
144
|
full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
|
94
145
|
|
95
146
|
@response_builder << build_entry_completion(
|
96
147
|
full_name,
|
97
148
|
name,
|
98
|
-
|
149
|
+
range,
|
99
150
|
entries,
|
100
151
|
top_level_reference || top_level?(T.must(entries.first).name),
|
101
152
|
)
|
102
153
|
end
|
103
154
|
end
|
104
155
|
|
105
|
-
sig { params(node: Prism::CallNode).void }
|
106
|
-
def on_call_node_enter(node)
|
107
|
-
name = node.message
|
108
|
-
return unless name
|
109
|
-
|
110
|
-
case name
|
111
|
-
when "require"
|
112
|
-
complete_require(node)
|
113
|
-
when "require_relative"
|
114
|
-
complete_require_relative(node)
|
115
|
-
else
|
116
|
-
complete_self_receiver_method(node, name) if !@typechecker_enabled && self_receiver?(node)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
private
|
121
|
-
|
122
156
|
sig { params(node: Prism::CallNode).void }
|
123
157
|
def complete_require(node)
|
124
158
|
arguments_node = node.arguments
|
@@ -223,12 +257,12 @@ module RubyLsp
|
|
223
257
|
params(
|
224
258
|
real_name: String,
|
225
259
|
incomplete_name: String,
|
226
|
-
|
260
|
+
range: Interface::Range,
|
227
261
|
entries: T::Array[RubyIndexer::Entry],
|
228
262
|
top_level: T::Boolean,
|
229
263
|
).returns(Interface::CompletionItem)
|
230
264
|
end
|
231
|
-
def build_entry_completion(real_name, incomplete_name,
|
265
|
+
def build_entry_completion(real_name, incomplete_name, range, entries, top_level)
|
232
266
|
first_entry = T.must(entries.first)
|
233
267
|
kind = case first_entry
|
234
268
|
when RubyIndexer::Entry::Class
|
@@ -263,20 +297,22 @@ module RubyLsp
|
|
263
297
|
#
|
264
298
|
# Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
|
265
299
|
# end
|
266
|
-
@nesting.
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
300
|
+
unless @nesting.join("::").start_with?(incomplete_name)
|
301
|
+
@nesting.each do |namespace|
|
302
|
+
prefix = "#{namespace}::"
|
303
|
+
shortened_name = insertion_text.delete_prefix(prefix)
|
304
|
+
|
305
|
+
# 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}"
|
307
|
+
break if real_name != conflict_name && @index[conflict_name]
|
308
|
+
|
309
|
+
insertion_text = shortened_name
|
310
|
+
|
311
|
+
# If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
|
312
|
+
# `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in
|
313
|
+
# their typing
|
314
|
+
filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
|
315
|
+
end
|
280
316
|
end
|
281
317
|
|
282
318
|
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
@@ -286,7 +322,7 @@ module RubyLsp
|
|
286
322
|
label: real_name,
|
287
323
|
filter_text: filter_text,
|
288
324
|
text_edit: Interface::TextEdit.new(
|
289
|
-
range:
|
325
|
+
range: range,
|
290
326
|
new_text: insertion_text,
|
291
327
|
),
|
292
328
|
kind: kind,
|
@@ -30,6 +30,7 @@ module RubyLsp
|
|
30
30
|
dispatcher.register(
|
31
31
|
self,
|
32
32
|
:on_call_node_enter,
|
33
|
+
:on_block_argument_node_enter,
|
33
34
|
:on_constant_read_node_enter,
|
34
35
|
:on_constant_path_node_enter,
|
35
36
|
)
|
@@ -42,10 +43,21 @@ module RubyLsp
|
|
42
43
|
if message == :require || message == :require_relative
|
43
44
|
handle_require_definition(node)
|
44
45
|
else
|
45
|
-
handle_method_definition(node)
|
46
|
+
handle_method_definition(message.to_s, self_receiver?(node))
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
50
|
+
sig { params(node: Prism::BlockArgumentNode).void }
|
51
|
+
def on_block_argument_node_enter(node)
|
52
|
+
expression = node.expression
|
53
|
+
return unless expression.is_a?(Prism::SymbolNode)
|
54
|
+
|
55
|
+
value = expression.value
|
56
|
+
return unless value
|
57
|
+
|
58
|
+
handle_method_definition(value, false)
|
59
|
+
end
|
60
|
+
|
49
61
|
sig { params(node: Prism::ConstantPathNode).void }
|
50
62
|
def on_constant_path_node_enter(node)
|
51
63
|
name = constant_name(node)
|
@@ -64,12 +76,9 @@ module RubyLsp
|
|
64
76
|
|
65
77
|
private
|
66
78
|
|
67
|
-
sig { params(
|
68
|
-
def handle_method_definition(
|
69
|
-
|
70
|
-
return unless message
|
71
|
-
|
72
|
-
methods = if self_receiver?(node)
|
79
|
+
sig { params(message: String, self_receiver: T::Boolean).void }
|
80
|
+
def handle_method_definition(message, self_receiver)
|
81
|
+
methods = if self_receiver
|
73
82
|
@index.resolve_method(message, @nesting.join("::"))
|
74
83
|
else
|
75
84
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
@@ -271,7 +271,7 @@ module RubyLsp
|
|
271
271
|
def on_constant_path_node_enter(node)
|
272
272
|
return unless matches?(node, CONSTANT_PATH_NODES)
|
273
273
|
|
274
|
-
add_highlight(Constant::DocumentHighlightKind::READ, node.
|
274
|
+
add_highlight(Constant::DocumentHighlightKind::READ, node.name_loc)
|
275
275
|
end
|
276
276
|
|
277
277
|
sig { params(node: Prism::ConstantReadNode).void }
|
@@ -30,7 +30,7 @@ module RubyLsp
|
|
30
30
|
lookup[spec.name] = {}
|
31
31
|
lookup[spec.name][spec.version.to_s] = {}
|
32
32
|
|
33
|
-
Dir.glob("**/*.rb", base: "#{spec.full_gem_path}/").each do |path|
|
33
|
+
Dir.glob("**/*.rb", base: "#{spec.full_gem_path.delete_prefix("//?/")}/").each do |path|
|
34
34
|
lookup[spec.name][spec.version.to_s][path] = "#{spec.full_gem_path}/#{path}"
|
35
35
|
end
|
36
36
|
end
|
@@ -58,6 +58,7 @@ module RubyLsp
|
|
58
58
|
:on_constant_operator_write_node_enter,
|
59
59
|
:on_constant_or_write_node_enter,
|
60
60
|
:on_constant_target_node_enter,
|
61
|
+
:on_constant_path_node_enter,
|
61
62
|
:on_local_variable_and_write_node_enter,
|
62
63
|
:on_local_variable_operator_write_node_enter,
|
63
64
|
:on_local_variable_or_write_node_enter,
|
@@ -302,17 +303,64 @@ module RubyLsp
|
|
302
303
|
def on_class_node_enter(node)
|
303
304
|
return unless visible?(node, @range)
|
304
305
|
|
305
|
-
|
306
|
+
constant_path = node.constant_path
|
307
|
+
|
308
|
+
if constant_path.is_a?(Prism::ConstantReadNode)
|
309
|
+
@response_builder.add_token(constant_path.location, :class, [:declaration])
|
310
|
+
else
|
311
|
+
each_constant_path_part(constant_path) do |part|
|
312
|
+
loc = case part
|
313
|
+
when Prism::ConstantPathNode
|
314
|
+
part.name_loc
|
315
|
+
when Prism::ConstantReadNode
|
316
|
+
part.location
|
317
|
+
end
|
318
|
+
next unless loc
|
319
|
+
|
320
|
+
@response_builder.add_token(loc, :class, [:declaration])
|
321
|
+
end
|
322
|
+
end
|
306
323
|
|
307
324
|
superclass = node.superclass
|
308
|
-
|
325
|
+
|
326
|
+
if superclass.is_a?(Prism::ConstantReadNode)
|
327
|
+
@response_builder.add_token(superclass.location, :class)
|
328
|
+
elsif superclass
|
329
|
+
each_constant_path_part(superclass) do |part|
|
330
|
+
loc = case part
|
331
|
+
when Prism::ConstantPathNode
|
332
|
+
part.name_loc
|
333
|
+
when Prism::ConstantReadNode
|
334
|
+
part.location
|
335
|
+
end
|
336
|
+
next unless loc
|
337
|
+
|
338
|
+
@response_builder.add_token(loc, :class)
|
339
|
+
end
|
340
|
+
end
|
309
341
|
end
|
310
342
|
|
311
343
|
sig { params(node: Prism::ModuleNode).void }
|
312
344
|
def on_module_node_enter(node)
|
313
345
|
return unless visible?(node, @range)
|
314
346
|
|
315
|
-
|
347
|
+
constant_path = node.constant_path
|
348
|
+
|
349
|
+
if constant_path.is_a?(Prism::ConstantReadNode)
|
350
|
+
@response_builder.add_token(constant_path.location, :namespace, [:declaration])
|
351
|
+
else
|
352
|
+
each_constant_path_part(constant_path) do |part|
|
353
|
+
loc = case part
|
354
|
+
when Prism::ConstantPathNode
|
355
|
+
part.name_loc
|
356
|
+
when Prism::ConstantReadNode
|
357
|
+
part.location
|
358
|
+
end
|
359
|
+
next unless loc
|
360
|
+
|
361
|
+
@response_builder.add_token(loc, :namespace, [:declaration])
|
362
|
+
end
|
363
|
+
end
|
316
364
|
end
|
317
365
|
|
318
366
|
sig { params(node: Prism::ImplicitNode).void }
|
@@ -327,6 +375,14 @@ module RubyLsp
|
|
327
375
|
@inside_implicit_node = false
|
328
376
|
end
|
329
377
|
|
378
|
+
sig { params(node: Prism::ConstantPathNode).void }
|
379
|
+
def on_constant_path_node_enter(node)
|
380
|
+
return if @inside_implicit_node
|
381
|
+
return unless visible?(node, @range)
|
382
|
+
|
383
|
+
@response_builder.add_token(node.name_loc, :namespace)
|
384
|
+
end
|
385
|
+
|
330
386
|
private
|
331
387
|
|
332
388
|
# Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that
|
@@ -11,6 +11,7 @@ module RubyLsp
|
|
11
11
|
# suggests possible completions according to what the developer is typing.
|
12
12
|
#
|
13
13
|
# Currently supported targets:
|
14
|
+
#
|
14
15
|
# - Classes
|
15
16
|
# - Modules
|
16
17
|
# - Constants
|
@@ -34,7 +35,7 @@ module RubyLsp
|
|
34
35
|
def provider
|
35
36
|
Interface::CompletionOptions.new(
|
36
37
|
resolve_provider: true,
|
37
|
-
trigger_characters: ["/", "\"", "'"],
|
38
|
+
trigger_characters: ["/", "\"", "'", ":"],
|
38
39
|
completion_item: {
|
39
40
|
labelDetailsSupport: true,
|
40
41
|
},
|
@@ -36,20 +36,27 @@ module RubyLsp
|
|
36
36
|
@item = item
|
37
37
|
end
|
38
38
|
|
39
|
-
sig { override.returns(
|
39
|
+
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
40
40
|
def perform
|
41
|
+
# Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
|
42
|
+
# a completion resolve request must always return the original completion item without modifying ANY fields
|
43
|
+
# other than label details and documentation. If we modify anything, the completion behaviour might be broken.
|
44
|
+
#
|
45
|
+
# For example, forgetting to return the `insertText` included in the original item will make the editor use the
|
46
|
+
# `label` for the text edit instead
|
41
47
|
label = @item[:label]
|
42
48
|
entries = @index[label] || []
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
),
|
49
|
+
|
50
|
+
@item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
|
51
|
+
description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
|
52
|
+
)
|
53
|
+
|
54
|
+
@item[:documentation] = Interface::MarkupContent.new(
|
55
|
+
kind: "markdown",
|
56
|
+
value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
|
52
57
|
)
|
58
|
+
|
59
|
+
@item
|
53
60
|
end
|
54
61
|
end
|
55
62
|
end
|
@@ -12,6 +12,7 @@ module RubyLsp
|
|
12
12
|
# definition of the symbol under the cursor.
|
13
13
|
#
|
14
14
|
# Currently supported targets:
|
15
|
+
#
|
15
16
|
# - Classes
|
16
17
|
# - Modules
|
17
18
|
# - Constants
|
@@ -47,7 +48,7 @@ module RubyLsp
|
|
47
48
|
|
48
49
|
target, parent, nesting = document.locate_node(
|
49
50
|
position,
|
50
|
-
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
|
51
|
+
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::BlockArgumentNode],
|
51
52
|
)
|
52
53
|
|
53
54
|
if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
|
@@ -50,12 +50,12 @@ module RubyLsp
|
|
50
50
|
params(
|
51
51
|
node: Prism::Node,
|
52
52
|
title: String,
|
53
|
-
command_name: String,
|
53
|
+
command_name: T.nilable(String),
|
54
54
|
arguments: T.nilable(T::Array[T.untyped]),
|
55
55
|
data: T.nilable(T::Hash[T.untyped, T.untyped]),
|
56
56
|
).returns(Interface::CodeLens)
|
57
57
|
end
|
58
|
-
def create_code_lens(node, title:, command_name
|
58
|
+
def create_code_lens(node, title:, command_name: nil, arguments: nil, data: nil)
|
59
59
|
range = range_from_node(node)
|
60
60
|
|
61
61
|
Interface::CodeLens.new(
|
@@ -167,6 +167,24 @@ module RubyLsp
|
|
167
167
|
constant_name(path)
|
168
168
|
end
|
169
169
|
end
|
170
|
+
|
171
|
+
# Iterates over each part of a constant path, so that we can easily push response items for each section of the
|
172
|
+
# name. For example, for `Foo::Bar::Baz`, this method will invoke the block with `Foo`, then `Bar` and finally
|
173
|
+
# `Baz`.
|
174
|
+
sig do
|
175
|
+
params(
|
176
|
+
node: Prism::Node,
|
177
|
+
block: T.proc.params(part: Prism::Node).void,
|
178
|
+
).void
|
179
|
+
end
|
180
|
+
def each_constant_path_part(node, &block)
|
181
|
+
current = T.let(node, T.nilable(Prism::Node))
|
182
|
+
|
183
|
+
while current.is_a?(Prism::ConstantPathNode)
|
184
|
+
block.call(current)
|
185
|
+
current = current.parent
|
186
|
+
end
|
187
|
+
end
|
170
188
|
end
|
171
189
|
end
|
172
190
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -30,20 +30,20 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.29.0
|
34
34
|
- - "<"
|
35
35
|
- !ruby/object:Gem::Version
|
36
|
-
version: '0.
|
36
|
+
version: '0.30'
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.
|
43
|
+
version: 0.29.0
|
44
44
|
- - "<"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0.
|
46
|
+
version: '0.30'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sorbet-runtime
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,8 +78,8 @@ files:
|
|
78
78
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
79
79
|
- lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
|
80
80
|
- lib/ruby-lsp.rb
|
81
|
-
- lib/ruby_indexer/lib/ruby_indexer/collector.rb
|
82
81
|
- lib/ruby_indexer/lib/ruby_indexer/configuration.rb
|
82
|
+
- lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
|
83
83
|
- lib/ruby_indexer/lib/ruby_indexer/entry.rb
|
84
84
|
- lib/ruby_indexer/lib/ruby_indexer/index.rb
|
85
85
|
- lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
|
@@ -178,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
178
|
- !ruby/object:Gem::Version
|
179
179
|
version: '0'
|
180
180
|
requirements: []
|
181
|
-
rubygems_version: 3.5.
|
181
|
+
rubygems_version: 3.5.10
|
182
182
|
signing_key:
|
183
183
|
specification_version: 4
|
184
184
|
summary: An opinionated language server for Ruby
|