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