ruby-lsp 0.17.6 → 0.17.8
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 +6 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +25 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +16 -3
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +4 -1
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +38 -0
- data/lib/ruby_indexer/test/index_test.rb +37 -0
- data/lib/ruby_lsp/base_server.rb +1 -1
- data/lib/ruby_lsp/document.rb +14 -24
- data/lib/ruby_lsp/global_state.rb +8 -4
- data/lib/ruby_lsp/listeners/completion.rb +88 -0
- data/lib/ruby_lsp/listeners/definition.rb +27 -3
- data/lib/ruby_lsp/listeners/hover.rb +38 -11
- data/lib/ruby_lsp/node_context.rb +65 -5
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -0
- data/lib/ruby_lsp/requests/definition.rb +2 -0
- data/lib/ruby_lsp/server.rb +67 -37
- data/lib/ruby_lsp/store.rb +4 -0
- data/lib/ruby_lsp/type_inferrer.rb +17 -19
- metadata +3 -5
- data/exe/ruby-lsp-doctor +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3a8a120b4cf3045cdeec98d2fb417ffdd300947b6426788d827346c43812ffc
|
4
|
+
data.tar.gz: b43a823e71fca99a9cf79b3a6ce19e7a0ec86e499b943aecae1666788859d055
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9662d88d3f37b4fd7b744a69f38f2a20cd4bf00291f63968578e50bef3400916870efdcf58b6e81dd78a6829c4f8a7dd174e6d212ddc3a265420b29eaa2b6d8a
|
7
|
+
data.tar.gz: b788ef9185e02a732cd2dccef6273d8dd3fb471ebb8c2300b3ac78cc48d3952eee378a777cc4cf4ceb927590fff7bcdfc57c95f0924b21e589eb850ec9128641
|
data/README.md
CHANGED
@@ -78,7 +78,12 @@ default gems, except for
|
|
78
78
|
- Gems that only appear under the `:development` group
|
79
79
|
- All Ruby files under `test/**/*.rb`
|
80
80
|
|
81
|
-
|
81
|
+
This behaviour can be overridden and tuned. Learn how to configure it [for VS Code](vscode/README.md#Indexing-Configuration) or [for other editors](EDITORS.md#Indexing-Configuration).
|
82
|
+
|
83
|
+
Note that indexing-dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by
|
84
|
+
the configuration changes.
|
85
|
+
|
86
|
+
The older approach of using a `.index.yml` file has been deprecated and will be removed in a future release.
|
82
87
|
|
83
88
|
```yaml
|
84
89
|
# Exclude files based on a given pattern. Often used to exclude test files or fixtures
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.17.
|
1
|
+
0.17.8
|
data/exe/ruby-lsp
CHANGED
@@ -36,6 +36,10 @@ parser = OptionParser.new do |opts|
|
|
36
36
|
options[:experimental] = true
|
37
37
|
end
|
38
38
|
|
39
|
+
opts.on("--doctor", "Run troubleshooting steps") do
|
40
|
+
options[:doctor] = true
|
41
|
+
end
|
42
|
+
|
39
43
|
opts.on("-h", "--help", "Print this help") do
|
40
44
|
puts opts.help
|
41
45
|
puts
|
@@ -107,4 +111,25 @@ if options[:time_index]
|
|
107
111
|
return
|
108
112
|
end
|
109
113
|
|
114
|
+
if options[:doctor]
|
115
|
+
if File.exist?(".index.yml")
|
116
|
+
begin
|
117
|
+
config = YAML.parse_file(".index.yml").to_ruby
|
118
|
+
rescue => e
|
119
|
+
abort("Error parsing config: #{e.message}")
|
120
|
+
end
|
121
|
+
RubyIndexer.configuration.apply_config(config)
|
122
|
+
end
|
123
|
+
|
124
|
+
index = RubyIndexer::Index.new
|
125
|
+
|
126
|
+
puts "Globbing for indexable files"
|
127
|
+
|
128
|
+
RubyIndexer.configuration.indexables.each do |indexable|
|
129
|
+
puts "indexing: #{indexable.full_path}"
|
130
|
+
index.index_single(indexable)
|
131
|
+
end
|
132
|
+
return
|
133
|
+
end
|
134
|
+
|
110
135
|
RubyLsp::Server.new.start
|
@@ -71,7 +71,7 @@ module RubyIndexer
|
|
71
71
|
|
72
72
|
superclass = node.superclass
|
73
73
|
|
74
|
-
nesting =
|
74
|
+
nesting = actual_nesting(name)
|
75
75
|
|
76
76
|
parent_class = case superclass
|
77
77
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
@@ -119,8 +119,7 @@ module RubyIndexer
|
|
119
119
|
|
120
120
|
comments = collect_comments(node)
|
121
121
|
|
122
|
-
|
123
|
-
entry = Entry::Module.new(nesting, @file_path, node.location, constant_path.location, comments)
|
122
|
+
entry = Entry::Module.new(actual_nesting(name), @file_path, node.location, constant_path.location, comments)
|
124
123
|
|
125
124
|
@owner_stack << entry
|
126
125
|
@index.add(entry)
|
@@ -709,5 +708,19 @@ module RubyIndexer
|
|
709
708
|
:"(#{names_with_commas})"
|
710
709
|
end
|
711
710
|
end
|
711
|
+
|
712
|
+
sig { params(name: String).returns(T::Array[String]) }
|
713
|
+
def actual_nesting(name)
|
714
|
+
nesting = @stack + [name]
|
715
|
+
corrected_nesting = []
|
716
|
+
|
717
|
+
nesting.reverse_each do |name|
|
718
|
+
corrected_nesting.prepend(name.delete_prefix("::"))
|
719
|
+
|
720
|
+
break if name.start_with?("::")
|
721
|
+
end
|
722
|
+
|
723
|
+
corrected_nesting
|
724
|
+
end
|
712
725
|
end
|
713
726
|
end
|
@@ -331,14 +331,17 @@ module RubyIndexer
|
|
331
331
|
params(
|
332
332
|
method_name: String,
|
333
333
|
receiver_name: String,
|
334
|
+
inherited_only: T::Boolean,
|
334
335
|
).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
|
335
336
|
end
|
336
|
-
def resolve_method(method_name, receiver_name)
|
337
|
+
def resolve_method(method_name, receiver_name, inherited_only: false)
|
337
338
|
method_entries = self[method_name]
|
338
339
|
return unless method_entries
|
339
340
|
|
340
341
|
ancestors = linearized_ancestors_of(receiver_name.delete_prefix("::"))
|
341
342
|
ancestors.each do |ancestor|
|
343
|
+
next if inherited_only && ancestor == receiver_name
|
344
|
+
|
342
345
|
found = method_entries.filter_map do |entry|
|
343
346
|
case entry
|
344
347
|
when Entry::Member, Entry::MethodAlias
|
@@ -546,5 +546,43 @@ module RubyIndexer
|
|
546
546
|
assert_equal(7, name_location.start_column)
|
547
547
|
assert_equal(10, name_location.end_column)
|
548
548
|
end
|
549
|
+
|
550
|
+
def test_indexing_namespaces_inside_top_level_references
|
551
|
+
index(<<~RUBY)
|
552
|
+
module ::Foo
|
553
|
+
class Bar
|
554
|
+
end
|
555
|
+
end
|
556
|
+
RUBY
|
557
|
+
|
558
|
+
# We want to explicitly verify that we didn't introduce the leading `::` by accident, but `Index#[]` deletes the
|
559
|
+
# prefix when we use `refute_entry`
|
560
|
+
entries = @index.instance_variable_get(:@entries)
|
561
|
+
refute(entries.key?("::Foo"))
|
562
|
+
refute(entries.key?("::Foo::Bar"))
|
563
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:0-0:3-3")
|
564
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:1-2:2-5")
|
565
|
+
end
|
566
|
+
|
567
|
+
def test_indexing_namespaces_inside_nested_top_level_references
|
568
|
+
index(<<~RUBY)
|
569
|
+
class Baz
|
570
|
+
module ::Foo
|
571
|
+
class Bar
|
572
|
+
end
|
573
|
+
|
574
|
+
class ::Qux
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|
578
|
+
RUBY
|
579
|
+
|
580
|
+
refute_entry("Baz::Foo")
|
581
|
+
refute_entry("Baz::Foo::Bar")
|
582
|
+
assert_entry("Baz", Entry::Class, "/fake/path/foo.rb:0-0:8-3")
|
583
|
+
assert_entry("Foo", Entry::Module, "/fake/path/foo.rb:1-2:7-5")
|
584
|
+
assert_entry("Foo::Bar", Entry::Class, "/fake/path/foo.rb:2-4:3-7")
|
585
|
+
assert_entry("Qux", Entry::Class, "/fake/path/foo.rb:5-4:6-7")
|
586
|
+
end
|
549
587
|
end
|
550
588
|
end
|
@@ -285,6 +285,43 @@ module RubyIndexer
|
|
285
285
|
assert_includes(second_entry.comments, "Hello from second `bar`")
|
286
286
|
end
|
287
287
|
|
288
|
+
def test_resolve_method_inherited_only
|
289
|
+
index(<<~RUBY)
|
290
|
+
class Bar
|
291
|
+
def baz; end
|
292
|
+
end
|
293
|
+
|
294
|
+
class Foo < Bar
|
295
|
+
def baz; end
|
296
|
+
end
|
297
|
+
RUBY
|
298
|
+
|
299
|
+
entry = T.must(@index.resolve_method("baz", "Foo", inherited_only: true).first)
|
300
|
+
|
301
|
+
assert_equal("Bar", T.must(entry.owner).name)
|
302
|
+
end
|
303
|
+
|
304
|
+
def test_resolve_method_inherited_only_for_prepended_module
|
305
|
+
index(<<~RUBY)
|
306
|
+
module Bar
|
307
|
+
def baz
|
308
|
+
super
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class Foo
|
313
|
+
prepend Bar
|
314
|
+
|
315
|
+
def baz; end
|
316
|
+
end
|
317
|
+
RUBY
|
318
|
+
|
319
|
+
# This test is just to document the fact that we don't yet support resolving inherited methods for modules that
|
320
|
+
# are prepended. The only way to support this is to find all namespaces that have the module a subtype, so that we
|
321
|
+
# can show the results for everywhere the module has been prepended.
|
322
|
+
assert_nil(@index.resolve_method("baz", "Bar", inherited_only: true))
|
323
|
+
end
|
324
|
+
|
288
325
|
def test_prefix_search_for_methods
|
289
326
|
index(<<~RUBY)
|
290
327
|
module Foo
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -53,7 +53,7 @@ module RubyLsp
|
|
53
53
|
|
54
54
|
# We don't want to try to parse documents on text synchronization notifications
|
55
55
|
@store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
|
56
|
-
rescue
|
56
|
+
rescue Store::NonExistingDocumentError
|
57
57
|
# If we receive a request for a file that no longer exists, we don't want to fail
|
58
58
|
end
|
59
59
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -127,8 +127,18 @@ module RubyLsp
|
|
127
127
|
parent = T.let(nil, T.nilable(Prism::Node))
|
128
128
|
nesting_nodes = T.let(
|
129
129
|
[],
|
130
|
-
T::Array[T.any(
|
130
|
+
T::Array[T.any(
|
131
|
+
Prism::ClassNode,
|
132
|
+
Prism::ModuleNode,
|
133
|
+
Prism::SingletonClassNode,
|
134
|
+
Prism::DefNode,
|
135
|
+
Prism::BlockNode,
|
136
|
+
Prism::LambdaNode,
|
137
|
+
Prism::ProgramNode,
|
138
|
+
)],
|
131
139
|
)
|
140
|
+
|
141
|
+
nesting_nodes << node if node.is_a?(Prism::ProgramNode)
|
132
142
|
call_node = T.let(nil, T.nilable(Prism::CallNode))
|
133
143
|
|
134
144
|
until queue.empty?
|
@@ -158,11 +168,8 @@ module RubyLsp
|
|
158
168
|
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
|
159
169
|
# target when it is a constant
|
160
170
|
case candidate
|
161
|
-
when Prism::ClassNode, Prism::ModuleNode
|
162
|
-
|
163
|
-
when Prism::SingletonClassNode
|
164
|
-
nesting_nodes << candidate
|
165
|
-
when Prism::DefNode
|
171
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode, Prism::BlockNode,
|
172
|
+
Prism::LambdaNode
|
166
173
|
nesting_nodes << candidate
|
167
174
|
end
|
168
175
|
|
@@ -203,24 +210,7 @@ module RubyLsp
|
|
203
210
|
end
|
204
211
|
end
|
205
212
|
|
206
|
-
|
207
|
-
surrounding_method = T.let(nil, T.nilable(String))
|
208
|
-
|
209
|
-
nesting_nodes.each do |node|
|
210
|
-
case node
|
211
|
-
when Prism::ClassNode, Prism::ModuleNode
|
212
|
-
nesting << node.constant_path.slice
|
213
|
-
when Prism::SingletonClassNode
|
214
|
-
nesting << "<Class:#{nesting.last}>"
|
215
|
-
when Prism::DefNode
|
216
|
-
surrounding_method = node.name.to_s
|
217
|
-
next unless node.receiver.is_a?(Prism::SelfNode)
|
218
|
-
|
219
|
-
nesting << "<Class:#{nesting.last}>"
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
NodeContext.new(closest, parent, nesting, call_node, surrounding_method)
|
213
|
+
NodeContext.new(closest, parent, nesting_nodes, call_node)
|
224
214
|
end
|
225
215
|
|
226
216
|
sig { returns(T::Boolean) }
|
@@ -69,7 +69,7 @@ module RubyLsp
|
|
69
69
|
@formatter = detect_formatter(direct_dependencies, all_dependencies) if @formatter == "auto"
|
70
70
|
|
71
71
|
specified_linters = options.dig(:initializationOptions, :linters)
|
72
|
-
@linters = specified_linters || detect_linters(direct_dependencies)
|
72
|
+
@linters = specified_linters || detect_linters(direct_dependencies, all_dependencies)
|
73
73
|
@test_library = detect_test_library(direct_dependencies)
|
74
74
|
@has_type_checker = detect_typechecker(direct_dependencies)
|
75
75
|
|
@@ -127,10 +127,14 @@ module RubyLsp
|
|
127
127
|
|
128
128
|
# Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
|
129
129
|
# single linter. To have multiple linters running, the user must configure them manually
|
130
|
-
sig { params(dependencies: T::Array[String]).returns(T::Array[String]) }
|
131
|
-
def detect_linters(dependencies)
|
130
|
+
sig { params(dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(T::Array[String]) }
|
131
|
+
def detect_linters(dependencies, all_dependencies)
|
132
132
|
linters = []
|
133
|
-
|
133
|
+
|
134
|
+
if dependencies.any?(/^rubocop/) || (all_dependencies.include?("rubocop") && dot_rubocop_yml_present)
|
135
|
+
linters << "rubocop"
|
136
|
+
end
|
137
|
+
|
134
138
|
linters
|
135
139
|
end
|
136
140
|
|
@@ -7,6 +7,50 @@ module RubyLsp
|
|
7
7
|
extend T::Sig
|
8
8
|
include Requests::Support::Common
|
9
9
|
|
10
|
+
KEYWORDS = [
|
11
|
+
"alias",
|
12
|
+
"and",
|
13
|
+
"begin",
|
14
|
+
"BEGIN",
|
15
|
+
"break",
|
16
|
+
"case",
|
17
|
+
"class",
|
18
|
+
"def",
|
19
|
+
"defined?",
|
20
|
+
"do",
|
21
|
+
"else",
|
22
|
+
"elsif",
|
23
|
+
"end",
|
24
|
+
"END",
|
25
|
+
"ensure",
|
26
|
+
"false",
|
27
|
+
"for",
|
28
|
+
"if",
|
29
|
+
"in",
|
30
|
+
"module",
|
31
|
+
"next",
|
32
|
+
"nil",
|
33
|
+
"not",
|
34
|
+
"or",
|
35
|
+
"redo",
|
36
|
+
"rescue",
|
37
|
+
"retry",
|
38
|
+
"return",
|
39
|
+
"self",
|
40
|
+
"super",
|
41
|
+
"then",
|
42
|
+
"true",
|
43
|
+
"undef",
|
44
|
+
"unless",
|
45
|
+
"until",
|
46
|
+
"when",
|
47
|
+
"while",
|
48
|
+
"yield",
|
49
|
+
"__ENCODING__",
|
50
|
+
"__FILE__",
|
51
|
+
"__LINE__",
|
52
|
+
].freeze
|
53
|
+
|
10
54
|
sig do
|
11
55
|
params(
|
12
56
|
response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
|
@@ -277,6 +321,12 @@ module RubyLsp
|
|
277
321
|
|
278
322
|
sig { params(node: Prism::CallNode, name: String).void }
|
279
323
|
def complete_methods(node, name)
|
324
|
+
# If the node has a receiver, then we don't need to provide local nor keyword completions
|
325
|
+
if !@global_state.has_type_checker && !node.receiver
|
326
|
+
add_local_completions(node, name)
|
327
|
+
add_keyword_completions(node, name)
|
328
|
+
end
|
329
|
+
|
280
330
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
281
331
|
return unless type
|
282
332
|
|
@@ -322,6 +372,44 @@ module RubyLsp
|
|
322
372
|
# We have not indexed this namespace, so we can't provide any completions
|
323
373
|
end
|
324
374
|
|
375
|
+
sig { params(node: Prism::CallNode, name: String).void }
|
376
|
+
def add_local_completions(node, name)
|
377
|
+
range = range_from_location(T.must(node.message_loc))
|
378
|
+
|
379
|
+
@node_context.locals_for_scope.each do |local|
|
380
|
+
local_name = local.to_s
|
381
|
+
next unless local_name.start_with?(name)
|
382
|
+
|
383
|
+
@response_builder << Interface::CompletionItem.new(
|
384
|
+
label: local_name,
|
385
|
+
filter_text: local_name,
|
386
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: local_name),
|
387
|
+
kind: Constant::CompletionItemKind::VARIABLE,
|
388
|
+
data: {
|
389
|
+
skip_resolve: true,
|
390
|
+
},
|
391
|
+
)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
sig { params(node: Prism::CallNode, name: String).void }
|
396
|
+
def add_keyword_completions(node, name)
|
397
|
+
range = range_from_location(T.must(node.message_loc))
|
398
|
+
|
399
|
+
KEYWORDS.each do |keyword|
|
400
|
+
next unless keyword.start_with?(name)
|
401
|
+
|
402
|
+
@response_builder << Interface::CompletionItem.new(
|
403
|
+
label: keyword,
|
404
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: keyword),
|
405
|
+
kind: Constant::CompletionItemKind::KEYWORD,
|
406
|
+
data: {
|
407
|
+
skip_resolve: true,
|
408
|
+
},
|
409
|
+
)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
325
413
|
sig { params(label: String, node: Prism::StringNode).returns(Interface::CompletionItem) }
|
326
414
|
def build_completion(label, node)
|
327
415
|
# We should use the content location as we only replace the content and not the delimiters of the string
|
@@ -46,6 +46,8 @@ module RubyLsp
|
|
46
46
|
:on_instance_variable_or_write_node_enter,
|
47
47
|
:on_instance_variable_target_node_enter,
|
48
48
|
:on_string_node_enter,
|
49
|
+
:on_super_node_enter,
|
50
|
+
:on_forwarding_super_node_enter,
|
49
51
|
)
|
50
52
|
end
|
51
53
|
|
@@ -133,8 +135,30 @@ module RubyLsp
|
|
133
135
|
handle_instance_variable_definition(node.name.to_s)
|
134
136
|
end
|
135
137
|
|
138
|
+
sig { params(node: Prism::SuperNode).void }
|
139
|
+
def on_super_node_enter(node)
|
140
|
+
handle_super_node_definition
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { params(node: Prism::ForwardingSuperNode).void }
|
144
|
+
def on_forwarding_super_node_enter(node)
|
145
|
+
handle_super_node_definition
|
146
|
+
end
|
147
|
+
|
136
148
|
private
|
137
149
|
|
150
|
+
sig { void }
|
151
|
+
def handle_super_node_definition
|
152
|
+
surrounding_method = @node_context.surrounding_method
|
153
|
+
return unless surrounding_method
|
154
|
+
|
155
|
+
handle_method_definition(
|
156
|
+
surrounding_method,
|
157
|
+
@type_inferrer.infer_receiver_type(@node_context),
|
158
|
+
inherited_only: true,
|
159
|
+
)
|
160
|
+
end
|
161
|
+
|
138
162
|
sig { params(name: String).void }
|
139
163
|
def handle_instance_variable_definition(name)
|
140
164
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
@@ -158,10 +182,10 @@ module RubyLsp
|
|
158
182
|
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
159
183
|
end
|
160
184
|
|
161
|
-
sig { params(message: String, receiver_type: T.nilable(String)).void }
|
162
|
-
def handle_method_definition(message, receiver_type)
|
185
|
+
sig { params(message: String, receiver_type: T.nilable(String), inherited_only: T::Boolean).void }
|
186
|
+
def handle_method_definition(message, receiver_type, inherited_only: false)
|
163
187
|
methods = if receiver_type
|
164
|
-
@index.resolve_method(message, receiver_type)
|
188
|
+
@index.resolve_method(message, receiver_type, inherited_only: inherited_only)
|
165
189
|
else
|
166
190
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
167
191
|
# But we don't want to provide too many candidates, as it can be overwhelming
|
@@ -21,6 +21,8 @@ module RubyLsp
|
|
21
21
|
Prism::InstanceVariableWriteNode,
|
22
22
|
Prism::SymbolNode,
|
23
23
|
Prism::StringNode,
|
24
|
+
Prism::SuperNode,
|
25
|
+
Prism::ForwardingSuperNode,
|
24
26
|
],
|
25
27
|
T::Array[T.class_of(Prism::Node)],
|
26
28
|
)
|
@@ -64,6 +66,8 @@ module RubyLsp
|
|
64
66
|
:on_instance_variable_operator_write_node_enter,
|
65
67
|
:on_instance_variable_or_write_node_enter,
|
66
68
|
:on_instance_variable_target_node_enter,
|
69
|
+
:on_super_node_enter,
|
70
|
+
:on_forwarding_super_node_enter,
|
67
71
|
)
|
68
72
|
end
|
69
73
|
|
@@ -106,17 +110,7 @@ module RubyLsp
|
|
106
110
|
message = node.message
|
107
111
|
return unless message
|
108
112
|
|
109
|
-
|
110
|
-
return unless type
|
111
|
-
|
112
|
-
methods = @index.resolve_method(message, type)
|
113
|
-
return unless methods
|
114
|
-
|
115
|
-
title = "#{message}#{T.must(methods.first).decorated_parameters}"
|
116
|
-
|
117
|
-
categorized_markdown_from_index_entries(title, methods).each do |category, content|
|
118
|
-
@response_builder.push(content, category: category)
|
119
|
-
end
|
113
|
+
handle_method_hover(message)
|
120
114
|
end
|
121
115
|
|
122
116
|
sig { params(node: Prism::InstanceVariableReadNode).void }
|
@@ -149,8 +143,41 @@ module RubyLsp
|
|
149
143
|
handle_instance_variable_hover(node.name.to_s)
|
150
144
|
end
|
151
145
|
|
146
|
+
sig { params(node: Prism::SuperNode).void }
|
147
|
+
def on_super_node_enter(node)
|
148
|
+
handle_super_node_hover
|
149
|
+
end
|
150
|
+
|
151
|
+
sig { params(node: Prism::ForwardingSuperNode).void }
|
152
|
+
def on_forwarding_super_node_enter(node)
|
153
|
+
handle_super_node_hover
|
154
|
+
end
|
155
|
+
|
152
156
|
private
|
153
157
|
|
158
|
+
sig { void }
|
159
|
+
def handle_super_node_hover
|
160
|
+
surrounding_method = @node_context.surrounding_method
|
161
|
+
return unless surrounding_method
|
162
|
+
|
163
|
+
handle_method_hover(surrounding_method, inherited_only: true)
|
164
|
+
end
|
165
|
+
|
166
|
+
sig { params(message: String, inherited_only: T::Boolean).void }
|
167
|
+
def handle_method_hover(message, inherited_only: false)
|
168
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
169
|
+
return unless type
|
170
|
+
|
171
|
+
methods = @index.resolve_method(message, type, inherited_only: inherited_only)
|
172
|
+
return unless methods
|
173
|
+
|
174
|
+
title = "#{message}#{T.must(methods.first).decorated_parameters}"
|
175
|
+
|
176
|
+
categorized_markdown_from_index_entries(title, methods).each do |category, content|
|
177
|
+
@response_builder.push(content, category: category)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
154
181
|
sig { params(name: String).void }
|
155
182
|
def handle_instance_variable_hover(name)
|
156
183
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
@@ -23,22 +23,82 @@ module RubyLsp
|
|
23
23
|
params(
|
24
24
|
node: T.nilable(Prism::Node),
|
25
25
|
parent: T.nilable(Prism::Node),
|
26
|
-
|
26
|
+
nesting_nodes: T::Array[T.any(
|
27
|
+
Prism::ClassNode,
|
28
|
+
Prism::ModuleNode,
|
29
|
+
Prism::SingletonClassNode,
|
30
|
+
Prism::DefNode,
|
31
|
+
Prism::BlockNode,
|
32
|
+
Prism::LambdaNode,
|
33
|
+
Prism::ProgramNode,
|
34
|
+
)],
|
27
35
|
call_node: T.nilable(Prism::CallNode),
|
28
|
-
surrounding_method: T.nilable(String),
|
29
36
|
).void
|
30
37
|
end
|
31
|
-
def initialize(node, parent,
|
38
|
+
def initialize(node, parent, nesting_nodes, call_node)
|
32
39
|
@node = node
|
33
40
|
@parent = parent
|
34
|
-
@
|
41
|
+
@nesting_nodes = nesting_nodes
|
35
42
|
@call_node = call_node
|
36
|
-
|
43
|
+
|
44
|
+
nesting, surrounding_method = handle_nesting_nodes(nesting_nodes)
|
45
|
+
@nesting = T.let(nesting, T::Array[String])
|
46
|
+
@surrounding_method = T.let(surrounding_method, T.nilable(String))
|
37
47
|
end
|
38
48
|
|
39
49
|
sig { returns(String) }
|
40
50
|
def fully_qualified_name
|
41
51
|
@fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
|
42
52
|
end
|
53
|
+
|
54
|
+
sig { returns(T::Array[Symbol]) }
|
55
|
+
def locals_for_scope
|
56
|
+
locals = []
|
57
|
+
|
58
|
+
@nesting_nodes.each do |node|
|
59
|
+
if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::SingletonClassNode) ||
|
60
|
+
node.is_a?(Prism::DefNode)
|
61
|
+
locals.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
locals.concat(node.locals)
|
65
|
+
end
|
66
|
+
|
67
|
+
locals
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
sig do
|
73
|
+
params(nodes: T::Array[T.any(
|
74
|
+
Prism::ClassNode,
|
75
|
+
Prism::ModuleNode,
|
76
|
+
Prism::SingletonClassNode,
|
77
|
+
Prism::DefNode,
|
78
|
+
Prism::BlockNode,
|
79
|
+
Prism::LambdaNode,
|
80
|
+
Prism::ProgramNode,
|
81
|
+
)]).returns([T::Array[String], T.nilable(String)])
|
82
|
+
end
|
83
|
+
def handle_nesting_nodes(nodes)
|
84
|
+
nesting = []
|
85
|
+
surrounding_method = T.let(nil, T.nilable(String))
|
86
|
+
|
87
|
+
@nesting_nodes.each do |node|
|
88
|
+
case node
|
89
|
+
when Prism::ClassNode, Prism::ModuleNode
|
90
|
+
nesting << node.constant_path.slice
|
91
|
+
when Prism::SingletonClassNode
|
92
|
+
nesting << "<Class:#{nesting.last}>"
|
93
|
+
when Prism::DefNode
|
94
|
+
surrounding_method = node.name.to_s
|
95
|
+
next unless node.receiver.is_a?(Prism::SelfNode)
|
96
|
+
|
97
|
+
nesting << "<Class:#{nesting.last}>"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
[nesting, surrounding_method]
|
102
|
+
end
|
43
103
|
end
|
44
104
|
end
|
@@ -38,6 +38,8 @@ module RubyLsp
|
|
38
38
|
|
39
39
|
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
40
40
|
def perform
|
41
|
+
return @item if @item.dig(:data, :skip_resolve)
|
42
|
+
|
41
43
|
# Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
|
42
44
|
# a completion resolve request must always return the original completion item without modifying ANY fields
|
43
45
|
# other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -98,16 +98,27 @@ module RubyLsp
|
|
98
98
|
rescue StandardError, LoadError => e
|
99
99
|
# If an error occurred in a request, we have to return an error response or else the editor will hang
|
100
100
|
if message[:id]
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
101
|
+
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
|
102
|
+
# from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
|
103
|
+
# reporting these to our telemetry
|
104
|
+
if e.is_a?(Store::NonExistingDocumentError)
|
105
|
+
send_message(Error.new(
|
106
|
+
id: message[:id],
|
107
|
+
code: Constant::ErrorCodes::INVALID_PARAMS,
|
108
|
+
message: e.full_message,
|
109
|
+
))
|
110
|
+
else
|
111
|
+
send_message(Error.new(
|
112
|
+
id: message[:id],
|
113
|
+
code: Constant::ErrorCodes::INTERNAL_ERROR,
|
114
|
+
message: e.full_message,
|
115
|
+
data: {
|
116
|
+
errorClass: e.class.name,
|
117
|
+
errorMessage: e.message,
|
118
|
+
backtrace: e.backtrace&.join("\n"),
|
119
|
+
},
|
120
|
+
))
|
121
|
+
end
|
111
122
|
end
|
112
123
|
|
113
124
|
$stderr.puts("Error processing #{message[:method]}: #{e.full_message}")
|
@@ -243,6 +254,8 @@ module RubyLsp
|
|
243
254
|
)
|
244
255
|
end
|
245
256
|
|
257
|
+
process_indexing_configuration(options.dig(:initializationOptions, :indexing))
|
258
|
+
|
246
259
|
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
247
260
|
end
|
248
261
|
|
@@ -251,28 +264,6 @@ module RubyLsp
|
|
251
264
|
load_addons
|
252
265
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
253
266
|
|
254
|
-
indexing_config = {}
|
255
|
-
|
256
|
-
# Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
|
257
|
-
index_path = File.join(@global_state.workspace_path, ".index.yml")
|
258
|
-
|
259
|
-
if File.exist?(index_path)
|
260
|
-
begin
|
261
|
-
indexing_config = YAML.parse_file(index_path).to_ruby
|
262
|
-
rescue Psych::SyntaxError => e
|
263
|
-
message = "Syntax error while loading configuration: #{e.message}"
|
264
|
-
send_message(
|
265
|
-
Notification.new(
|
266
|
-
method: "window/showMessage",
|
267
|
-
params: Interface::ShowMessageParams.new(
|
268
|
-
type: Constant::MessageType::WARNING,
|
269
|
-
message: message,
|
270
|
-
),
|
271
|
-
),
|
272
|
-
)
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
267
|
if defined?(Requests::Support::RuboCopFormatter)
|
277
268
|
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
278
269
|
end
|
@@ -280,7 +271,7 @@ module RubyLsp
|
|
280
271
|
@global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
|
281
272
|
end
|
282
273
|
|
283
|
-
perform_initial_indexing
|
274
|
+
perform_initial_indexing
|
284
275
|
check_formatter_is_available
|
285
276
|
end
|
286
277
|
|
@@ -766,12 +757,10 @@ module RubyLsp
|
|
766
757
|
Addon.addons.each(&:deactivate)
|
767
758
|
end
|
768
759
|
|
769
|
-
sig {
|
770
|
-
def perform_initial_indexing
|
760
|
+
sig { void }
|
761
|
+
def perform_initial_indexing
|
771
762
|
# The begin progress invocation happens during `initialize`, so that the notification is sent before we are
|
772
763
|
# stuck indexing files
|
773
|
-
RubyIndexer.configuration.apply_config(config_hash)
|
774
|
-
|
775
764
|
Thread.new do
|
776
765
|
begin
|
777
766
|
RubyIndexer::RBSIndexer.new(@global_state.index).index_ruby_core
|
@@ -872,5 +861,46 @@ module RubyLsp
|
|
872
861
|
)
|
873
862
|
end
|
874
863
|
end
|
864
|
+
|
865
|
+
sig { params(indexing_options: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
866
|
+
def process_indexing_configuration(indexing_options)
|
867
|
+
# Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
|
868
|
+
index_path = File.join(@global_state.workspace_path, ".index.yml")
|
869
|
+
|
870
|
+
if File.exist?(index_path)
|
871
|
+
begin
|
872
|
+
RubyIndexer.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
|
873
|
+
send_message(
|
874
|
+
Notification.new(
|
875
|
+
method: "window/showMessage",
|
876
|
+
params: Interface::ShowMessageParams.new(
|
877
|
+
type: Constant::MessageType::WARNING,
|
878
|
+
message: "The .index.yml configuration file is deprecated. " \
|
879
|
+
"Please use editor settings to configure the index",
|
880
|
+
),
|
881
|
+
),
|
882
|
+
)
|
883
|
+
rescue Psych::SyntaxError => e
|
884
|
+
message = "Syntax error while loading configuration: #{e.message}"
|
885
|
+
send_message(
|
886
|
+
Notification.new(
|
887
|
+
method: "window/showMessage",
|
888
|
+
params: Interface::ShowMessageParams.new(
|
889
|
+
type: Constant::MessageType::WARNING,
|
890
|
+
message: message,
|
891
|
+
),
|
892
|
+
),
|
893
|
+
)
|
894
|
+
end
|
895
|
+
return
|
896
|
+
end
|
897
|
+
|
898
|
+
return unless indexing_options
|
899
|
+
|
900
|
+
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
901
|
+
RubyIndexer.configuration.apply_config(
|
902
|
+
indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
|
903
|
+
)
|
904
|
+
end
|
875
905
|
end
|
876
906
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -5,6 +5,8 @@ module RubyLsp
|
|
5
5
|
class Store
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
+
class NonExistingDocumentError < StandardError; end
|
9
|
+
|
8
10
|
sig { returns(T::Boolean) }
|
9
11
|
attr_accessor :supports_progress
|
10
12
|
|
@@ -45,6 +47,8 @@ module RubyLsp
|
|
45
47
|
end
|
46
48
|
set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
|
47
49
|
T.must(@state[uri.to_s])
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
raise NonExistingDocumentError, uri.to_s
|
48
52
|
end
|
49
53
|
|
50
54
|
sig do
|
@@ -20,16 +20,9 @@ module RubyLsp
|
|
20
20
|
when Prism::CallNode
|
21
21
|
infer_receiver_for_call_node(node, node_context)
|
22
22
|
when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
|
23
|
-
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode
|
24
|
-
|
25
|
-
|
26
|
-
# inherits from Object
|
27
|
-
return "Object" if nesting.empty?
|
28
|
-
|
29
|
-
fully_qualified_name = node_context.fully_qualified_name
|
30
|
-
return fully_qualified_name if node_context.surrounding_method
|
31
|
-
|
32
|
-
"#{fully_qualified_name}::<Class:#{nesting.last}>"
|
23
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
|
24
|
+
Prism::SuperNode, Prism::ForwardingSuperNode
|
25
|
+
self_receiver_handling(node_context)
|
33
26
|
end
|
34
27
|
end
|
35
28
|
|
@@ -41,15 +34,7 @@ module RubyLsp
|
|
41
34
|
|
42
35
|
case receiver
|
43
36
|
when Prism::SelfNode, nil
|
44
|
-
|
45
|
-
# If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
|
46
|
-
# inherits from Object
|
47
|
-
return "Object" if nesting.empty?
|
48
|
-
return node_context.fully_qualified_name if node_context.surrounding_method
|
49
|
-
|
50
|
-
# If we're not inside a method, then we're inside the body of a class or module, which is a singleton
|
51
|
-
# context
|
52
|
-
"#{nesting.join("::")}::<Class:#{nesting.last}>"
|
37
|
+
self_receiver_handling(node_context)
|
53
38
|
when Prism::ConstantPathNode, Prism::ConstantReadNode
|
54
39
|
# When the receiver is a constant reference, we have to try to resolve it to figure out the right
|
55
40
|
# receiver. But since the invocation is directly on the constant, that's the singleton context of that
|
@@ -68,6 +53,19 @@ module RubyLsp
|
|
68
53
|
end
|
69
54
|
end
|
70
55
|
|
56
|
+
sig { params(node_context: NodeContext).returns(String) }
|
57
|
+
def self_receiver_handling(node_context)
|
58
|
+
nesting = node_context.nesting
|
59
|
+
# If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
|
60
|
+
# inherits from Object
|
61
|
+
return "Object" if nesting.empty?
|
62
|
+
return node_context.fully_qualified_name if node_context.surrounding_method
|
63
|
+
|
64
|
+
# If we're not inside a method, then we're inside the body of a class or module, which is a singleton
|
65
|
+
# context
|
66
|
+
"#{nesting.join("::")}::<Class:#{nesting.last}>"
|
67
|
+
end
|
68
|
+
|
71
69
|
sig do
|
72
70
|
params(
|
73
71
|
node: T.any(
|
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.17.
|
4
|
+
version: 0.17.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -84,7 +84,6 @@ email:
|
|
84
84
|
executables:
|
85
85
|
- ruby-lsp
|
86
86
|
- ruby-lsp-check
|
87
|
-
- ruby-lsp-doctor
|
88
87
|
extensions: []
|
89
88
|
extra_rdoc_files: []
|
90
89
|
files:
|
@@ -93,7 +92,6 @@ files:
|
|
93
92
|
- VERSION
|
94
93
|
- exe/ruby-lsp
|
95
94
|
- exe/ruby-lsp-check
|
96
|
-
- exe/ruby-lsp-doctor
|
97
95
|
- lib/core_ext/uri.rb
|
98
96
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
99
97
|
- lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
|
@@ -206,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
204
|
- !ruby/object:Gem::Version
|
207
205
|
version: '0'
|
208
206
|
requirements: []
|
209
|
-
rubygems_version: 3.5.
|
207
|
+
rubygems_version: 3.5.15
|
210
208
|
signing_key:
|
211
209
|
specification_version: 4
|
212
210
|
summary: An opinionated language server for Ruby
|
data/exe/ruby-lsp-doctor
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
5
|
-
require "ruby_lsp/internal"
|
6
|
-
|
7
|
-
if File.exist?(".index.yml")
|
8
|
-
begin
|
9
|
-
config = YAML.parse_file(".index.yml").to_ruby
|
10
|
-
rescue => e
|
11
|
-
abort("Error parsing config: #{e.message}")
|
12
|
-
end
|
13
|
-
RubyIndexer.configuration.apply_config(config)
|
14
|
-
end
|
15
|
-
|
16
|
-
index = RubyIndexer::Index.new
|
17
|
-
|
18
|
-
puts "Globbing for indexable files"
|
19
|
-
|
20
|
-
RubyIndexer.configuration.indexables.each do |indexable|
|
21
|
-
puts "indexing: #{indexable.full_path}"
|
22
|
-
index.index_single(indexable)
|
23
|
-
end
|