ruby-lsp 0.12.2 → 0.12.4
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 -0
- data/VERSION +1 -1
- data/exe/ruby-lsp-doctor +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/{visitor.rb → collector.rb} +108 -59
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +8 -3
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +9 -3
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +22 -4
- data/lib/ruby_indexer/ruby_indexer.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +53 -0
- data/lib/ruby_indexer/test/method_test.rb +14 -0
- data/lib/ruby_lsp/document.rb +7 -9
- data/lib/ruby_lsp/executor.rb +29 -24
- data/lib/ruby_lsp/internal.rb +4 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +8 -4
- data/lib/ruby_lsp/requests/completion.rb +49 -3
- data/lib/ruby_lsp/requests/definition.rb +52 -25
- data/lib/ruby_lsp/requests/document_highlight.rb +18 -8
- data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +20 -0
- data/lib/ruby_lsp/requests/inlay_hints.rb +39 -1
- data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -1
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +13 -5
- data/lib/ruby_lsp/requests/support/common.rb +22 -2
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +0 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -11
- data/lib/ruby_lsp/ruby_document.rb +14 -0
- data/lib/ruby_lsp/setup_bundler.rb +2 -0
- data/lib/ruby_lsp/store.rb +1 -3
- data/lib/ruby_lsp/utils.rb +9 -0
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70a84356e392893c6f7b20e1701ef31621d9902af3a120c7013a13f80ec09568
|
4
|
+
data.tar.gz: bd360c2a5426631816f227ff115af967813ae7284d0a61845daf743b0a99d948
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 598494207add9412e909836e65f6288403189133711dbb17e39663eecc719089dba9f8e758069fda91666715d8c8fc5326055192ba5558f6d8151bbc21f40338
|
7
|
+
data.tar.gz: b86880d5b342e817d8cda46c4977a4229896d19b1fa495c6235f4d86ba74fa1a8ec737b7bad8a19af8859f315980f491b7bd8bcd8f25c1ad21d1ec3f119b9aaf
|
data/README.md
CHANGED
@@ -52,6 +52,7 @@ The Ruby LSP provides an addon system that allows other gems to enhance the base
|
|
52
52
|
features. This is the mechanism that powers addons like
|
53
53
|
|
54
54
|
- [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails)
|
55
|
+
- [Ruby LSP RSpec](https://github.com/st0012/ruby-lsp-rspec)
|
55
56
|
|
56
57
|
For instructions on how to create addons, see the [addons documentation](ADDONS.md).
|
57
58
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.12.
|
1
|
+
0.12.4
|
data/exe/ruby-lsp-doctor
CHANGED
@@ -10,6 +10,6 @@ RubyIndexer.configuration.indexables.each do |indexable|
|
|
10
10
|
puts "indexing: #{indexable.full_path}"
|
11
11
|
content = File.read(indexable.full_path)
|
12
12
|
result = Prism.parse(content)
|
13
|
-
|
14
|
-
result.value
|
13
|
+
collector = RubyIndexer::Collector.new(index, result, indexable.full_path)
|
14
|
+
collector.collect(result.value)
|
15
15
|
end
|
@@ -2,9 +2,11 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyIndexer
|
5
|
-
class
|
5
|
+
class Collector
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
+
LEAVE_EVENT = T.let(Object.new.freeze, Object)
|
9
|
+
|
8
10
|
sig { params(index: Index, parse_result: Prism::ParseResult, file_path: String).void }
|
9
11
|
def initialize(index, parse_result, file_path)
|
10
12
|
@index = index
|
@@ -16,26 +18,67 @@ module RubyIndexer
|
|
16
18
|
end,
|
17
19
|
T::Hash[Integer, Prism::Comment],
|
18
20
|
)
|
21
|
+
@queue = T.let([], T::Array[Object])
|
22
|
+
@current_owner = T.let(nil, T.nilable(Entry::Namespace))
|
19
23
|
|
20
24
|
super()
|
21
25
|
end
|
22
26
|
|
23
|
-
sig {
|
24
|
-
def
|
25
|
-
|
27
|
+
sig { params(node: Prism::Node).void }
|
28
|
+
def collect(node)
|
29
|
+
@queue = [node]
|
30
|
+
|
31
|
+
until @queue.empty?
|
32
|
+
node_or_event = @queue.shift
|
33
|
+
|
34
|
+
case node_or_event
|
35
|
+
when Prism::ProgramNode
|
36
|
+
@queue << node_or_event.statements
|
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
|
71
|
+
end
|
26
72
|
end
|
27
73
|
|
28
|
-
|
29
|
-
def visit_module_node(node)
|
30
|
-
add_module_entry(node)
|
31
|
-
end
|
74
|
+
private
|
32
75
|
|
33
|
-
sig {
|
34
|
-
def
|
76
|
+
sig { params(node: Prism::MultiWriteNode).void }
|
77
|
+
def handle_multi_write_node(node)
|
35
78
|
value = node.value
|
36
79
|
values = value.is_a?(Prism::ArrayNode) && value.opening_loc ? value.elements : []
|
37
80
|
|
38
|
-
node.
|
81
|
+
[*node.lefts, *node.rest, *node.rights].each_with_index do |target, i|
|
39
82
|
current_value = values[i]
|
40
83
|
# The moment we find a splat on the right hand side of the assignment, we can no longer figure out which value
|
41
84
|
# gets assigned to what
|
@@ -50,8 +93,8 @@ module RubyIndexer
|
|
50
93
|
end
|
51
94
|
end
|
52
95
|
|
53
|
-
sig {
|
54
|
-
def
|
96
|
+
sig { params(node: Prism::ConstantPathWriteNode).void }
|
97
|
+
def handle_constant_path_write_node(node)
|
55
98
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
56
99
|
target = node.target
|
57
100
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -60,8 +103,8 @@ module RubyIndexer
|
|
60
103
|
add_constant(node, name)
|
61
104
|
end
|
62
105
|
|
63
|
-
sig {
|
64
|
-
def
|
106
|
+
sig { params(node: Prism::ConstantPathOrWriteNode).void }
|
107
|
+
def handle_constant_path_or_write_node(node)
|
65
108
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
66
109
|
target = node.target
|
67
110
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -70,8 +113,8 @@ module RubyIndexer
|
|
70
113
|
add_constant(node, name)
|
71
114
|
end
|
72
115
|
|
73
|
-
sig {
|
74
|
-
def
|
116
|
+
sig { params(node: Prism::ConstantPathOperatorWriteNode).void }
|
117
|
+
def handle_constant_path_operator_write_node(node)
|
75
118
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
76
119
|
target = node.target
|
77
120
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -80,8 +123,8 @@ module RubyIndexer
|
|
80
123
|
add_constant(node, name)
|
81
124
|
end
|
82
125
|
|
83
|
-
sig {
|
84
|
-
def
|
126
|
+
sig { params(node: Prism::ConstantPathAndWriteNode).void }
|
127
|
+
def handle_constant_path_and_write_node(node)
|
85
128
|
# ignore variable constants like `var::FOO` or `self.class::FOO`
|
86
129
|
target = node.target
|
87
130
|
return unless target.parent.nil? || target.parent.is_a?(Prism::ConstantReadNode)
|
@@ -90,50 +133,44 @@ module RubyIndexer
|
|
90
133
|
add_constant(node, name)
|
91
134
|
end
|
92
135
|
|
93
|
-
sig {
|
94
|
-
def
|
95
|
-
name = fully_qualify_name(node.name.to_s)
|
96
|
-
add_constant(node, name)
|
97
|
-
end
|
98
|
-
|
99
|
-
sig { override.params(node: Prism::ConstantOrWriteNode).void }
|
100
|
-
def visit_constant_or_write_node(node)
|
101
|
-
name = fully_qualify_name(node.name.to_s)
|
102
|
-
add_constant(node, name)
|
103
|
-
end
|
104
|
-
|
105
|
-
sig { override.params(node: Prism::ConstantAndWriteNode).void }
|
106
|
-
def visit_constant_and_write_node(node)
|
136
|
+
sig { params(node: Prism::ConstantWriteNode).void }
|
137
|
+
def handle_constant_write_node(node)
|
107
138
|
name = fully_qualify_name(node.name.to_s)
|
108
139
|
add_constant(node, name)
|
109
140
|
end
|
110
141
|
|
111
|
-
sig {
|
112
|
-
def
|
113
|
-
name = fully_qualify_name(node.name.to_s)
|
114
|
-
add_constant(node, name)
|
115
|
-
end
|
116
|
-
|
117
|
-
sig { override.params(node: Prism::CallNode).void }
|
118
|
-
def visit_call_node(node)
|
142
|
+
sig { params(node: Prism::CallNode).void }
|
143
|
+
def handle_call_node(node)
|
119
144
|
message = node.message
|
120
145
|
handle_private_constant(node) if message == "private_constant"
|
121
146
|
end
|
122
147
|
|
123
|
-
sig {
|
124
|
-
def
|
148
|
+
sig { params(node: Prism::DefNode).void }
|
149
|
+
def handle_def_node(node)
|
125
150
|
method_name = node.name.to_s
|
126
151
|
comments = collect_comments(node)
|
127
152
|
case node.receiver
|
128
153
|
when nil
|
129
|
-
@index << Entry::InstanceMethod.new(
|
154
|
+
@index << Entry::InstanceMethod.new(
|
155
|
+
method_name,
|
156
|
+
@file_path,
|
157
|
+
node.location,
|
158
|
+
comments,
|
159
|
+
node.parameters,
|
160
|
+
@current_owner,
|
161
|
+
)
|
130
162
|
when Prism::SelfNode
|
131
|
-
@index << Entry::SingletonMethod.new(
|
163
|
+
@index << Entry::SingletonMethod.new(
|
164
|
+
method_name,
|
165
|
+
@file_path,
|
166
|
+
node.location,
|
167
|
+
comments,
|
168
|
+
node.parameters,
|
169
|
+
@current_owner,
|
170
|
+
)
|
132
171
|
end
|
133
172
|
end
|
134
173
|
|
135
|
-
private
|
136
|
-
|
137
174
|
sig { params(node: Prism::CallNode).void }
|
138
175
|
def handle_private_constant(node)
|
139
176
|
arguments = node.arguments&.arguments
|
@@ -189,12 +226,12 @@ module RubyIndexer
|
|
189
226
|
|
190
227
|
# If the right hand side is another constant assignment, we need to visit it because that constant has to be
|
191
228
|
# indexed too
|
192
|
-
|
229
|
+
@queue.prepend(value)
|
193
230
|
Entry::UnresolvedAlias.new(value.name.to_s, @stack.dup, name, @file_path, node.location, comments)
|
194
231
|
when Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
195
232
|
Prism::ConstantPathAndWriteNode
|
196
233
|
|
197
|
-
|
234
|
+
@queue.prepend(value)
|
198
235
|
Entry::UnresolvedAlias.new(value.target.slice, @stack.dup, name, @file_path, node.location, comments)
|
199
236
|
else
|
200
237
|
Entry::Constant.new(name, @file_path, node.location, comments)
|
@@ -204,20 +241,26 @@ module RubyIndexer
|
|
204
241
|
sig { params(node: Prism::ModuleNode).void }
|
205
242
|
def add_module_entry(node)
|
206
243
|
name = node.constant_path.location.slice
|
207
|
-
|
244
|
+
unless /^[A-Z:]/.match?(name)
|
245
|
+
@queue << node.body
|
246
|
+
return
|
247
|
+
end
|
208
248
|
|
209
249
|
comments = collect_comments(node)
|
210
|
-
|
211
|
-
@index <<
|
250
|
+
@current_owner = Entry::Module.new(fully_qualify_name(name), @file_path, node.location, comments)
|
251
|
+
@index << @current_owner
|
212
252
|
@stack << name
|
213
|
-
|
214
|
-
@stack.pop
|
253
|
+
@queue.prepend(node.body, LEAVE_EVENT)
|
215
254
|
end
|
216
255
|
|
217
256
|
sig { params(node: Prism::ClassNode).void }
|
218
257
|
def add_class_entry(node)
|
219
258
|
name = node.constant_path.location.slice
|
220
|
-
|
259
|
+
|
260
|
+
unless /^[A-Z:]/.match?(name)
|
261
|
+
@queue << node.body
|
262
|
+
return
|
263
|
+
end
|
221
264
|
|
222
265
|
comments = collect_comments(node)
|
223
266
|
|
@@ -227,10 +270,16 @@ module RubyIndexer
|
|
227
270
|
superclass.slice
|
228
271
|
end
|
229
272
|
|
230
|
-
@
|
273
|
+
@current_owner = Entry::Class.new(
|
274
|
+
fully_qualify_name(name),
|
275
|
+
@file_path,
|
276
|
+
node.location,
|
277
|
+
comments,
|
278
|
+
parent_class,
|
279
|
+
)
|
280
|
+
@index << @current_owner
|
231
281
|
@stack << name
|
232
|
-
|
233
|
-
@stack.pop
|
282
|
+
@queue.prepend(node.body, LEAVE_EVENT)
|
234
283
|
end
|
235
284
|
|
236
285
|
sig { params(node: Prism::Node).returns(T::Array[String]) }
|
@@ -249,7 +298,7 @@ module RubyIndexer
|
|
249
298
|
|
250
299
|
comment_content.delete_prefix!("#")
|
251
300
|
comment_content.delete_prefix!(" ")
|
252
|
-
comments.
|
301
|
+
comments.prepend(comment_content)
|
253
302
|
end
|
254
303
|
|
255
304
|
comments
|
@@ -71,8 +71,13 @@ module RubyIndexer
|
|
71
71
|
|
72
72
|
Dir.glob(pattern, File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
|
73
73
|
# All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
|
74
|
-
# entry is expensive, we memoize it
|
75
|
-
|
74
|
+
# entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
|
75
|
+
# on repositories that define multiple gems, like Rails. All frameworks are defined inside the Dir.pwd, but
|
76
|
+
# each one of them belongs to a different $LOAD_PATH entry
|
77
|
+
if load_path_entry.nil? || !path.start_with?(load_path_entry)
|
78
|
+
load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
|
79
|
+
end
|
80
|
+
|
76
81
|
IndexablePath.new(load_path_entry, path)
|
77
82
|
end
|
78
83
|
end
|
@@ -144,7 +149,7 @@ module RubyIndexer
|
|
144
149
|
# just ignore if they're missing
|
145
150
|
end
|
146
151
|
|
147
|
-
indexables.uniq!
|
152
|
+
indexables.uniq!(&:full_path)
|
148
153
|
indexables
|
149
154
|
end
|
150
155
|
|
@@ -101,6 +101,9 @@ module RubyIndexer
|
|
101
101
|
sig { returns(T::Array[Parameter]) }
|
102
102
|
attr_reader :parameters
|
103
103
|
|
104
|
+
sig { returns(T.nilable(Entry::Namespace)) }
|
105
|
+
attr_reader :owner
|
106
|
+
|
104
107
|
sig do
|
105
108
|
params(
|
106
109
|
name: String,
|
@@ -108,11 +111,13 @@ module RubyIndexer
|
|
108
111
|
location: Prism::Location,
|
109
112
|
comments: T::Array[String],
|
110
113
|
parameters_node: T.nilable(Prism::ParametersNode),
|
114
|
+
owner: T.nilable(Entry::Namespace),
|
111
115
|
).void
|
112
116
|
end
|
113
|
-
def initialize(name, file_path, location, comments, parameters_node)
|
117
|
+
def initialize(name, file_path, location, comments, parameters_node, owner) # rubocop:disable Metrics/ParameterLists
|
114
118
|
super(name, file_path, location, comments)
|
115
119
|
@parameters = T.let(list_params(parameters_node), T::Array[Parameter])
|
120
|
+
@owner = owner
|
116
121
|
end
|
117
122
|
|
118
123
|
private
|
@@ -136,8 +141,9 @@ module RubyIndexer
|
|
136
141
|
case node
|
137
142
|
when Prism::RequiredParameterNode
|
138
143
|
node.name
|
139
|
-
when Prism::
|
140
|
-
names = node.
|
144
|
+
when Prism::MultiTargetNode
|
145
|
+
names = [*node.lefts, *node.rest, *node.rights].map { |parameter_node| parameter_name(parameter_node) }
|
146
|
+
|
141
147
|
names_with_commas = names.join(", ")
|
142
148
|
:"(#{names_with_commas})"
|
143
149
|
end
|
@@ -93,8 +93,14 @@ module RubyIndexer
|
|
93
93
|
# [#<Entry::Class name="Foo::Baz">],
|
94
94
|
# ]
|
95
95
|
# ```
|
96
|
-
sig { params(query: String, nesting: T::Array[String]).returns(T::Array[T::Array[Entry]]) }
|
97
|
-
def prefix_search(query, nesting)
|
96
|
+
sig { params(query: String, nesting: T.nilable(T::Array[String])).returns(T::Array[T::Array[Entry]]) }
|
97
|
+
def prefix_search(query, nesting = nil)
|
98
|
+
unless nesting
|
99
|
+
results = @entries_tree.search(query)
|
100
|
+
results.uniq!
|
101
|
+
return results
|
102
|
+
end
|
103
|
+
|
98
104
|
results = nesting.length.downto(0).flat_map do |i|
|
99
105
|
prefix = T.must(nesting[0...i]).join("::")
|
100
106
|
namespaced_query = prefix.empty? ? query : "#{prefix}::#{query}"
|
@@ -180,8 +186,8 @@ module RubyIndexer
|
|
180
186
|
def index_single(indexable_path, source = nil)
|
181
187
|
content = source || File.read(indexable_path.full_path)
|
182
188
|
result = Prism.parse(content)
|
183
|
-
|
184
|
-
result.value
|
189
|
+
collector = Collector.new(self, result, indexable_path.full_path)
|
190
|
+
collector.collect(result.value)
|
185
191
|
|
186
192
|
require_path = indexable_path.require_path
|
187
193
|
@require_paths_tree.insert(require_path, indexable_path) if require_path
|
@@ -231,6 +237,18 @@ module RubyIndexer
|
|
231
237
|
real_parts.join("::")
|
232
238
|
end
|
233
239
|
|
240
|
+
# Attempts to find a given method for a resolved fully qualified receiver name. Returns `nil` if the method does not
|
241
|
+
# exist on that receiver
|
242
|
+
sig { params(method_name: String, receiver_name: String).returns(T.nilable(Entry::Method)) }
|
243
|
+
def resolve_method(method_name, receiver_name)
|
244
|
+
method_entries = T.cast(self[method_name], T.nilable(T::Array[Entry::Method]))
|
245
|
+
owner_entries = self[receiver_name]
|
246
|
+
return unless owner_entries && method_entries
|
247
|
+
|
248
|
+
owner_name = T.must(owner_entries.first).name
|
249
|
+
method_entries.find { |entry| entry.owner&.name == owner_name }
|
250
|
+
end
|
251
|
+
|
234
252
|
private
|
235
253
|
|
236
254
|
# Attempts to resolve an UnresolvedAlias into a resolved Alias. If the unresolved alias is pointing to a constant
|
@@ -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/collector"
|
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"
|
@@ -193,5 +193,58 @@ module RubyIndexer
|
|
193
193
|
|
194
194
|
assert_instance_of(Entry::UnresolvedAlias, entry)
|
195
195
|
end
|
196
|
+
|
197
|
+
def test_visitor_does_not_visit_unnecessary_nodes
|
198
|
+
concats = (0...10_000).map do |i|
|
199
|
+
<<~STRING
|
200
|
+
"string#{i}" \\
|
201
|
+
STRING
|
202
|
+
end.join
|
203
|
+
|
204
|
+
index(<<~RUBY)
|
205
|
+
module Foo
|
206
|
+
local_var = #{concats}
|
207
|
+
"final"
|
208
|
+
@class_instance_var = #{concats}
|
209
|
+
"final"
|
210
|
+
@@class_var = #{concats}
|
211
|
+
"final"
|
212
|
+
$global_var = #{concats}
|
213
|
+
"final"
|
214
|
+
CONST = #{concats}
|
215
|
+
"final"
|
216
|
+
end
|
217
|
+
RUBY
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_resolve_method_with_known_receiver
|
221
|
+
index(<<~RUBY)
|
222
|
+
module Foo
|
223
|
+
module Bar
|
224
|
+
def baz; end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
RUBY
|
228
|
+
|
229
|
+
entry = T.must(@index.resolve_method("baz", "Foo::Bar"))
|
230
|
+
assert_equal("baz", entry.name)
|
231
|
+
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_prefix_search_for_methods
|
235
|
+
index(<<~RUBY)
|
236
|
+
module Foo
|
237
|
+
module Bar
|
238
|
+
def baz; end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
RUBY
|
242
|
+
|
243
|
+
entries = @index.prefix_search("ba")
|
244
|
+
refute_empty(entries)
|
245
|
+
|
246
|
+
entry = T.must(entries.first).first
|
247
|
+
assert_equal("baz", entry.name)
|
248
|
+
end
|
196
249
|
end
|
197
250
|
end
|
@@ -69,5 +69,19 @@ module RubyIndexer
|
|
69
69
|
assert_equal(:"(a, (b, ))", parameter.name)
|
70
70
|
assert_instance_of(Entry::RequiredParameter, parameter)
|
71
71
|
end
|
72
|
+
|
73
|
+
def test_keeps_track_of_method_owner
|
74
|
+
index(<<~RUBY)
|
75
|
+
class Foo
|
76
|
+
def bar
|
77
|
+
end
|
78
|
+
end
|
79
|
+
RUBY
|
80
|
+
|
81
|
+
entry = T.must(@index["bar"].first)
|
82
|
+
owner_name = T.must(entry.owner).name
|
83
|
+
|
84
|
+
assert_equal("Foo", owner_name)
|
85
|
+
end
|
72
86
|
end
|
73
87
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
class Document
|
6
6
|
extend T::Sig
|
7
|
+
extend T::Helpers
|
8
|
+
|
9
|
+
abstract!
|
7
10
|
|
8
11
|
PositionShape = T.type_alias { { line: Integer, character: Integer } }
|
9
12
|
RangeShape = T.type_alias { { start: PositionShape, end: PositionShape } }
|
@@ -28,8 +31,8 @@ module RubyLsp
|
|
28
31
|
@source = T.let(source, String)
|
29
32
|
@version = T.let(version, Integer)
|
30
33
|
@uri = T.let(uri, URI::Generic)
|
31
|
-
@needs_parsing = T.let(
|
32
|
-
@parse_result = T.let(
|
34
|
+
@needs_parsing = T.let(true, T::Boolean)
|
35
|
+
@parse_result = T.let(parse, Prism::ParseResult)
|
33
36
|
end
|
34
37
|
|
35
38
|
sig { returns(Prism::ProgramNode) }
|
@@ -91,13 +94,8 @@ module RubyLsp
|
|
91
94
|
@cache.clear
|
92
95
|
end
|
93
96
|
|
94
|
-
sig {
|
95
|
-
def parse
|
96
|
-
return unless @needs_parsing
|
97
|
-
|
98
|
-
@needs_parsing = false
|
99
|
-
@parse_result = Prism.parse(@source)
|
100
|
-
end
|
97
|
+
sig { abstract.returns(Prism::ParseResult) }
|
98
|
+
def parse; end
|
101
99
|
|
102
100
|
sig { returns(T::Boolean) }
|
103
101
|
def syntax_error?
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -57,6 +57,8 @@ module RubyLsp
|
|
57
57
|
warn(errored_addons.map(&:backtraces).join("\n\n"))
|
58
58
|
end
|
59
59
|
|
60
|
+
RubyVM::YJIT.enable if defined? RubyVM::YJIT.enable
|
61
|
+
|
60
62
|
perform_initial_indexing
|
61
63
|
check_formatter_is_available
|
62
64
|
|
@@ -474,34 +476,32 @@ module RubyLsp
|
|
474
476
|
def completion(uri, position)
|
475
477
|
document = @store.get(uri)
|
476
478
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
else
|
486
|
-
[Prism::CallNode]
|
487
|
-
end
|
488
|
-
|
489
|
-
matched, parent, nesting = document.locate(document.tree, char_position, node_types: target_node_types)
|
479
|
+
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
480
|
+
# back by 1, so that we find the right node
|
481
|
+
char_position = document.create_scanner.find_char_position(position) - 1
|
482
|
+
matched, parent, nesting = document.locate(
|
483
|
+
document.tree,
|
484
|
+
char_position,
|
485
|
+
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
|
486
|
+
)
|
490
487
|
return unless matched && parent
|
491
488
|
|
492
489
|
target = case matched
|
493
490
|
when Prism::CallNode
|
494
491
|
message = matched.message
|
495
|
-
return unless message == "require"
|
496
492
|
|
497
|
-
|
498
|
-
|
493
|
+
if message == "require"
|
494
|
+
args = matched.arguments&.arguments
|
495
|
+
return if args.nil? || args.is_a?(Prism::ForwardingArgumentsNode)
|
499
496
|
|
500
|
-
|
501
|
-
|
502
|
-
|
497
|
+
argument = args.first
|
498
|
+
return unless argument.is_a?(Prism::StringNode)
|
499
|
+
return unless (argument.location.start_offset..argument.location.end_offset).cover?(char_position)
|
503
500
|
|
504
|
-
|
501
|
+
argument
|
502
|
+
else
|
503
|
+
matched
|
504
|
+
end
|
505
505
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
506
506
|
if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
|
507
507
|
parent
|
@@ -579,7 +579,7 @@ module RubyLsp
|
|
579
579
|
# notification
|
580
580
|
end
|
581
581
|
|
582
|
-
sig { params(options: T::Hash[Symbol, T.untyped]).returns(
|
582
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
583
583
|
def initialize_request(options)
|
584
584
|
@store.clear
|
585
585
|
|
@@ -680,7 +680,7 @@ module RubyLsp
|
|
680
680
|
completion_provider = if enabled_features["completion"]
|
681
681
|
Interface::CompletionOptions.new(
|
682
682
|
resolve_provider: false,
|
683
|
-
trigger_characters: ["/"
|
683
|
+
trigger_characters: ["/"],
|
684
684
|
completion_item: {
|
685
685
|
labelDetailsSupport: true,
|
686
686
|
},
|
@@ -716,7 +716,7 @@ module RubyLsp
|
|
716
716
|
|
717
717
|
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
718
718
|
|
719
|
-
|
719
|
+
{
|
720
720
|
capabilities: Interface::ServerCapabilities.new(
|
721
721
|
text_document_sync: Interface::TextDocumentSyncOptions.new(
|
722
722
|
change: Constant::TextDocumentSyncKind::INCREMENTAL,
|
@@ -740,7 +740,12 @@ module RubyLsp
|
|
740
740
|
definition_provider: enabled_features["definition"],
|
741
741
|
workspace_symbol_provider: enabled_features["workspaceSymbol"],
|
742
742
|
),
|
743
|
-
|
743
|
+
serverInfo: {
|
744
|
+
name: "Ruby LSP",
|
745
|
+
version: VERSION,
|
746
|
+
},
|
747
|
+
formatter: @store.formatter,
|
748
|
+
}
|
744
749
|
end
|
745
750
|
|
746
751
|
sig { void }
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -24,6 +24,10 @@ require "ruby_lsp/server"
|
|
24
24
|
require "ruby_lsp/executor"
|
25
25
|
require "ruby_lsp/requests"
|
26
26
|
require "ruby_lsp/listener"
|
27
|
+
require "ruby_lsp/document"
|
28
|
+
require "ruby_lsp/ruby_document"
|
27
29
|
require "ruby_lsp/store"
|
28
30
|
require "ruby_lsp/addon"
|
29
31
|
require "ruby_lsp/requests/support/rubocop_runner"
|
32
|
+
|
33
|
+
Bundler.ui.level = :silent
|