ruby-lsp 0.17.2 → 0.17.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 +2 -0
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +280 -74
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +102 -102
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +234 -56
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +147 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -2
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/constant_test.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +702 -71
- data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
- data/lib/ruby_indexer/test/method_test.rb +74 -24
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +67 -0
- data/lib/ruby_indexer/test/test_case.rb +7 -0
- data/lib/ruby_lsp/document.rb +37 -8
- data/lib/ruby_lsp/global_state.rb +43 -18
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
- data/lib/ruby_lsp/listeners/completion.rb +53 -14
- data/lib/ruby_lsp/listeners/definition.rb +11 -7
- data/lib/ruby_lsp/listeners/hover.rb +14 -7
- data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
- data/lib/ruby_lsp/node_context.rb +6 -1
- data/lib/ruby_lsp/requests/completion.rb +5 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
- data/lib/ruby_lsp/requests/support/common.rb +19 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/server.rb +54 -4
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +86 -0
- metadata +29 -4
@@ -20,7 +20,9 @@ module RubyIndexer
|
|
20
20
|
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
21
21
|
|
22
22
|
entry = T.must(@index["@a"]&.first)
|
23
|
-
|
23
|
+
owner = T.must(entry.owner)
|
24
|
+
assert_instance_of(Entry::Class, owner)
|
25
|
+
assert_equal("Foo::Bar", owner.name)
|
24
26
|
end
|
25
27
|
|
26
28
|
def test_instance_variable_and_write
|
@@ -38,7 +40,9 @@ module RubyIndexer
|
|
38
40
|
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
39
41
|
|
40
42
|
entry = T.must(@index["@a"]&.first)
|
41
|
-
|
43
|
+
owner = T.must(entry.owner)
|
44
|
+
assert_instance_of(Entry::Class, owner)
|
45
|
+
assert_equal("Foo::Bar", owner.name)
|
42
46
|
end
|
43
47
|
|
44
48
|
def test_instance_variable_operator_write
|
@@ -56,7 +60,9 @@ module RubyIndexer
|
|
56
60
|
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
57
61
|
|
58
62
|
entry = T.must(@index["@a"]&.first)
|
59
|
-
|
63
|
+
owner = T.must(entry.owner)
|
64
|
+
assert_instance_of(Entry::Class, owner)
|
65
|
+
assert_equal("Foo::Bar", owner.name)
|
60
66
|
end
|
61
67
|
|
62
68
|
def test_instance_variable_or_write
|
@@ -74,7 +80,9 @@ module RubyIndexer
|
|
74
80
|
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:4-6:4-8")
|
75
81
|
|
76
82
|
entry = T.must(@index["@a"]&.first)
|
77
|
-
|
83
|
+
owner = T.must(entry.owner)
|
84
|
+
assert_instance_of(Entry::Class, owner)
|
85
|
+
assert_equal("Foo::Bar", owner.name)
|
78
86
|
end
|
79
87
|
|
80
88
|
def test_instance_variable_target
|
@@ -93,10 +101,14 @@ module RubyIndexer
|
|
93
101
|
assert_entry("@b", Entry::InstanceVariable, "/fake/path/foo.rb:4-10:4-12")
|
94
102
|
|
95
103
|
entry = T.must(@index["@a"]&.first)
|
96
|
-
|
104
|
+
owner = T.must(entry.owner)
|
105
|
+
assert_instance_of(Entry::Class, owner)
|
106
|
+
assert_equal("Foo::Bar", owner.name)
|
97
107
|
|
98
108
|
entry = T.must(@index["@b"]&.first)
|
99
|
-
|
109
|
+
owner = T.must(entry.owner)
|
110
|
+
assert_instance_of(Entry::Class, owner)
|
111
|
+
assert_equal("Foo::Bar", owner.name)
|
100
112
|
end
|
101
113
|
|
102
114
|
def test_empty_name_instance_variables
|
@@ -118,6 +130,14 @@ module RubyIndexer
|
|
118
130
|
module Foo
|
119
131
|
class Bar
|
120
132
|
@a = 123
|
133
|
+
|
134
|
+
class << self
|
135
|
+
def hello
|
136
|
+
@b = 123
|
137
|
+
end
|
138
|
+
|
139
|
+
@c = 123
|
140
|
+
end
|
121
141
|
end
|
122
142
|
end
|
123
143
|
RUBY
|
@@ -125,7 +145,64 @@ module RubyIndexer
|
|
125
145
|
assert_entry("@a", Entry::InstanceVariable, "/fake/path/foo.rb:2-4:2-6")
|
126
146
|
|
127
147
|
entry = T.must(@index["@a"]&.first)
|
128
|
-
|
148
|
+
owner = T.must(entry.owner)
|
149
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
150
|
+
assert_equal("Foo::Bar::<Class:Bar>", owner.name)
|
151
|
+
|
152
|
+
assert_entry("@b", Entry::InstanceVariable, "/fake/path/foo.rb:6-8:6-10")
|
153
|
+
|
154
|
+
entry = T.must(@index["@b"]&.first)
|
155
|
+
owner = T.must(entry.owner)
|
156
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
157
|
+
assert_equal("Foo::Bar::<Class:Bar>", owner.name)
|
158
|
+
|
159
|
+
assert_entry("@c", Entry::InstanceVariable, "/fake/path/foo.rb:9-6:9-8")
|
160
|
+
|
161
|
+
entry = T.must(@index["@c"]&.first)
|
162
|
+
owner = T.must(entry.owner)
|
163
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
164
|
+
assert_equal("Foo::Bar::<Class:Bar>::<Class:<Class:Bar>>", owner.name)
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_top_level_instance_variables
|
168
|
+
index(<<~RUBY)
|
169
|
+
@a = 123
|
170
|
+
RUBY
|
171
|
+
|
172
|
+
entry = T.must(@index["@a"]&.first)
|
173
|
+
assert_nil(entry.owner)
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_class_instance_variables_inside_self_method
|
177
|
+
index(<<~RUBY)
|
178
|
+
class Foo
|
179
|
+
def self.bar
|
180
|
+
@a = 123
|
181
|
+
end
|
182
|
+
end
|
183
|
+
RUBY
|
184
|
+
|
185
|
+
entry = T.must(@index["@a"]&.first)
|
186
|
+
owner = T.must(entry.owner)
|
187
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
188
|
+
assert_equal("Foo::<Class:Foo>", owner.name)
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_instance_variable_inside_dynamic_method_declaration
|
192
|
+
index(<<~RUBY)
|
193
|
+
class Foo
|
194
|
+
def something.bar
|
195
|
+
@a = 123
|
196
|
+
end
|
197
|
+
end
|
198
|
+
RUBY
|
199
|
+
|
200
|
+
# If the surrounding method is beind defined on any dynamic value that isn't `self`, then we attribute the
|
201
|
+
# instance variable to the wrong owner since there's no way to understand that statically
|
202
|
+
entry = T.must(@index["@a"]&.first)
|
203
|
+
owner = T.must(entry.owner)
|
204
|
+
assert_instance_of(Entry::Class, owner)
|
205
|
+
assert_equal("Foo", owner.name)
|
129
206
|
end
|
130
207
|
end
|
131
208
|
end
|
@@ -13,7 +13,7 @@ module RubyIndexer
|
|
13
13
|
end
|
14
14
|
RUBY
|
15
15
|
|
16
|
-
assert_entry("bar", Entry::
|
16
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_conditional_method
|
@@ -24,7 +24,7 @@ module RubyIndexer
|
|
24
24
|
end
|
25
25
|
RUBY
|
26
26
|
|
27
|
-
assert_entry("bar", Entry::
|
27
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
28
28
|
end
|
29
29
|
|
30
30
|
def test_singleton_method_using_self_receiver
|
@@ -35,7 +35,12 @@ module RubyIndexer
|
|
35
35
|
end
|
36
36
|
RUBY
|
37
37
|
|
38
|
-
assert_entry("bar", Entry::
|
38
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
39
|
+
|
40
|
+
entry = T.must(@index["bar"].first)
|
41
|
+
owner = T.must(entry.owner)
|
42
|
+
assert_equal("Foo::<Class:Foo>", owner.name)
|
43
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
39
44
|
end
|
40
45
|
|
41
46
|
def test_singleton_method_using_other_receiver_is_not_indexed
|
@@ -83,9 +88,9 @@ module RubyIndexer
|
|
83
88
|
def baz; end
|
84
89
|
RUBY
|
85
90
|
|
86
|
-
assert_entry("foo", Entry::
|
87
|
-
assert_entry("bar", Entry::
|
88
|
-
assert_entry("baz", Entry::
|
91
|
+
assert_entry("foo", Entry::Method, "/fake/path/foo.rb:0-8:1-3", visibility: Entry::Visibility::PRIVATE)
|
92
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:3-0:3-12", visibility: Entry::Visibility::PUBLIC)
|
93
|
+
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:7-0:7-12", visibility: Entry::Visibility::PROTECTED)
|
89
94
|
end
|
90
95
|
|
91
96
|
def test_visibility_tracking_with_nested_class_or_modules
|
@@ -103,9 +108,9 @@ module RubyIndexer
|
|
103
108
|
end
|
104
109
|
RUBY
|
105
110
|
|
106
|
-
assert_entry("foo", Entry::
|
107
|
-
assert_entry("bar", Entry::
|
108
|
-
assert_entry("baz", Entry::
|
111
|
+
assert_entry("foo", Entry::Method, "/fake/path/foo.rb:3-2:3-14", visibility: Entry::Visibility::PRIVATE)
|
112
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:6-4:6-16", visibility: Entry::Visibility::PUBLIC)
|
113
|
+
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
|
109
114
|
end
|
110
115
|
|
111
116
|
def test_method_with_parameters
|
@@ -116,7 +121,7 @@ module RubyIndexer
|
|
116
121
|
end
|
117
122
|
RUBY
|
118
123
|
|
119
|
-
assert_entry("bar", Entry::
|
124
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
120
125
|
entry = T.must(@index["bar"].first)
|
121
126
|
assert_equal(1, entry.parameters.length)
|
122
127
|
parameter = entry.parameters.first
|
@@ -132,7 +137,7 @@ module RubyIndexer
|
|
132
137
|
end
|
133
138
|
RUBY
|
134
139
|
|
135
|
-
assert_entry("bar", Entry::
|
140
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
136
141
|
entry = T.must(@index["bar"].first)
|
137
142
|
assert_equal(1, entry.parameters.length)
|
138
143
|
parameter = entry.parameters.first
|
@@ -148,7 +153,7 @@ module RubyIndexer
|
|
148
153
|
end
|
149
154
|
RUBY
|
150
155
|
|
151
|
-
assert_entry("bar", Entry::
|
156
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
152
157
|
entry = T.must(@index["bar"].first)
|
153
158
|
assert_equal(1, entry.parameters.length)
|
154
159
|
parameter = entry.parameters.first
|
@@ -164,7 +169,7 @@ module RubyIndexer
|
|
164
169
|
end
|
165
170
|
RUBY
|
166
171
|
|
167
|
-
assert_entry("bar", Entry::
|
172
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
168
173
|
entry = T.must(@index["bar"].first)
|
169
174
|
assert_equal(2, entry.parameters.length)
|
170
175
|
a, b = entry.parameters
|
@@ -184,7 +189,7 @@ module RubyIndexer
|
|
184
189
|
end
|
185
190
|
RUBY
|
186
191
|
|
187
|
-
assert_entry("bar", Entry::
|
192
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
188
193
|
entry = T.must(@index["bar"].first)
|
189
194
|
assert_equal(2, entry.parameters.length)
|
190
195
|
a, b = entry.parameters
|
@@ -209,7 +214,7 @@ module RubyIndexer
|
|
209
214
|
end
|
210
215
|
RUBY
|
211
216
|
|
212
|
-
assert_entry("bar", Entry::
|
217
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
213
218
|
entry = T.must(@index["bar"].first)
|
214
219
|
assert_equal(2, entry.parameters.length)
|
215
220
|
a, b = entry.parameters
|
@@ -246,7 +251,7 @@ module RubyIndexer
|
|
246
251
|
end
|
247
252
|
RUBY
|
248
253
|
|
249
|
-
assert_entry("bar", Entry::
|
254
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
250
255
|
entry = T.must(@index["bar"].first)
|
251
256
|
assert_equal(1, entry.parameters.length)
|
252
257
|
param = entry.parameters.first
|
@@ -287,7 +292,7 @@ module RubyIndexer
|
|
287
292
|
end
|
288
293
|
RUBY
|
289
294
|
|
290
|
-
assert_entry("bar", Entry::
|
295
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
291
296
|
entry = T.must(@index["bar"].first)
|
292
297
|
assert_equal(2, entry.parameters.length)
|
293
298
|
first, second = entry.parameters
|
@@ -307,7 +312,7 @@ module RubyIndexer
|
|
307
312
|
end
|
308
313
|
RUBY
|
309
314
|
|
310
|
-
assert_entry("bar", Entry::
|
315
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:2-5")
|
311
316
|
entry = T.must(@index["bar"].first)
|
312
317
|
assert_empty(entry.parameters)
|
313
318
|
end
|
@@ -359,24 +364,69 @@ module RubyIndexer
|
|
359
364
|
def test_properly_tracks_multiple_levels_of_nesting
|
360
365
|
index(<<~RUBY)
|
361
366
|
module Foo
|
362
|
-
def
|
367
|
+
def first_method; end
|
363
368
|
|
364
369
|
module Bar
|
365
|
-
def
|
370
|
+
def second_method; end
|
366
371
|
end
|
367
372
|
|
368
|
-
def
|
373
|
+
def third_method; end
|
369
374
|
end
|
370
375
|
RUBY
|
371
376
|
|
372
|
-
entry = T.
|
377
|
+
entry = T.cast(@index["first_method"]&.first, Entry::Method)
|
373
378
|
assert_equal("Foo", T.must(entry.owner).name)
|
374
379
|
|
375
|
-
entry = T.
|
380
|
+
entry = T.cast(@index["second_method"]&.first, Entry::Method)
|
376
381
|
assert_equal("Foo::Bar", T.must(entry.owner).name)
|
377
382
|
|
378
|
-
entry = T.
|
383
|
+
entry = T.cast(@index["third_method"]&.first, Entry::Method)
|
379
384
|
assert_equal("Foo", T.must(entry.owner).name)
|
380
385
|
end
|
386
|
+
|
387
|
+
def test_keeps_track_of_aliases
|
388
|
+
index(<<~RUBY)
|
389
|
+
class Foo
|
390
|
+
alias whatever to_s
|
391
|
+
alias_method :foo, :to_a
|
392
|
+
alias_method "bar", "to_a"
|
393
|
+
|
394
|
+
# These two are not indexed because they are dynamic or incomplete
|
395
|
+
alias_method baz, :to_a
|
396
|
+
alias_method :baz
|
397
|
+
end
|
398
|
+
RUBY
|
399
|
+
|
400
|
+
assert_entry("whatever", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:1-8:1-16")
|
401
|
+
assert_entry("foo", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:2-15:2-19")
|
402
|
+
assert_entry("bar", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:3-15:3-20")
|
403
|
+
# Foo plus 3 valid aliases
|
404
|
+
assert_equal(4, @index.instance_variable_get(:@entries).length - @default_indexed_entries.length)
|
405
|
+
end
|
406
|
+
|
407
|
+
def test_singleton_methods
|
408
|
+
index(<<~RUBY)
|
409
|
+
class Foo
|
410
|
+
def self.bar; end
|
411
|
+
|
412
|
+
class << self
|
413
|
+
def baz; end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
RUBY
|
417
|
+
|
418
|
+
assert_entry("bar", Entry::Method, "/fake/path/foo.rb:1-2:1-19")
|
419
|
+
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:4-4:4-16")
|
420
|
+
|
421
|
+
bar_owner = T.must(T.must(@index["bar"].first).owner)
|
422
|
+
baz_owner = T.must(T.must(@index["baz"].first).owner)
|
423
|
+
|
424
|
+
assert_instance_of(Entry::SingletonClass, bar_owner)
|
425
|
+
assert_instance_of(Entry::SingletonClass, baz_owner)
|
426
|
+
|
427
|
+
# Regardless of whether the method was added through `self.something` or `class << self`, the owner object must be
|
428
|
+
# the exact same
|
429
|
+
assert_same(bar_owner, baz_owner)
|
430
|
+
end
|
381
431
|
end
|
382
432
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "test_case"
|
5
|
+
|
6
|
+
module RubyIndexer
|
7
|
+
class RBSIndexerTest < TestCase
|
8
|
+
def test_index_core_classes
|
9
|
+
entries = @index["Array"]
|
10
|
+
refute_nil(entries)
|
11
|
+
# Array is a class but also an instance method on Kernel
|
12
|
+
assert_equal(2, entries.length)
|
13
|
+
entry = entries.find { |entry| entry.is_a?(RubyIndexer::Entry::Class) }
|
14
|
+
assert_match(%r{/gems/rbs-.*/core/array.rbs}, entry.file_path)
|
15
|
+
assert_equal("array.rbs", entry.file_name)
|
16
|
+
assert_equal("Object", entry.parent_class)
|
17
|
+
assert_equal(1, entry.mixin_operations.length)
|
18
|
+
enumerable_include = entry.mixin_operations.first
|
19
|
+
assert_equal("Enumerable", enumerable_include.module_name)
|
20
|
+
|
21
|
+
# Using fixed positions would be fragile, so let's just check some basics.
|
22
|
+
assert_operator(entry.location.start_line, :>, 0)
|
23
|
+
assert_operator(entry.location.end_line, :>, entry.location.start_line)
|
24
|
+
assert_equal(0, entry.location.start_column)
|
25
|
+
assert_operator(entry.location.end_column, :>, 0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_index_core_modules
|
29
|
+
entries = @index["Kernel"]
|
30
|
+
refute_nil(entries)
|
31
|
+
assert_equal(1, entries.length)
|
32
|
+
entry = entries.first
|
33
|
+
assert_match(%r{/gems/rbs-.*/core/kernel.rbs}, entry.file_path)
|
34
|
+
assert_equal("kernel.rbs", entry.file_name)
|
35
|
+
|
36
|
+
# Using fixed positions would be fragile, so let's just check some basics.
|
37
|
+
assert_operator(entry.location.start_line, :>, 0)
|
38
|
+
assert_operator(entry.location.end_line, :>, entry.location.start_line)
|
39
|
+
assert_equal(0, entry.location.start_column)
|
40
|
+
assert_operator(entry.location.end_column, :>, 0)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_index_methods
|
44
|
+
entries = @index["initialize"]
|
45
|
+
refute_nil(entries)
|
46
|
+
entry = entries.find { |entry| entry.owner.name == "Array" }
|
47
|
+
assert_match(%r{/gems/rbs-.*/core/array.rbs}, entry.file_path)
|
48
|
+
assert_equal("array.rbs", entry.file_name)
|
49
|
+
assert_equal(Entry::Visibility::PUBLIC, entry.visibility)
|
50
|
+
|
51
|
+
# Using fixed positions would be fragile, so let's just check some basics.
|
52
|
+
assert_operator(entry.location.start_line, :>, 0)
|
53
|
+
assert_operator(entry.location.end_line, :>, entry.location.start_line)
|
54
|
+
assert_equal(2, entry.location.start_column)
|
55
|
+
assert_operator(entry.location.end_column, :>, 0)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_attaches_correct_owner_to_singleton_methods
|
59
|
+
entries = @index["basename"]
|
60
|
+
refute_nil(entries)
|
61
|
+
|
62
|
+
owner = entries.first.owner
|
63
|
+
assert_instance_of(Entry::SingletonClass, owner)
|
64
|
+
assert_equal("File::<Class:File>", owner.name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -7,6 +7,8 @@ module RubyIndexer
|
|
7
7
|
class TestCase < Minitest::Test
|
8
8
|
def setup
|
9
9
|
@index = Index.new
|
10
|
+
RBSIndexer.new(@index).index_ruby_core
|
11
|
+
@default_indexed_entries = @index.instance_variable_get(:@entries).dup
|
10
12
|
end
|
11
13
|
|
12
14
|
private
|
@@ -17,6 +19,7 @@ module RubyIndexer
|
|
17
19
|
|
18
20
|
def assert_entry(expected_name, type, expected_location, visibility: nil)
|
19
21
|
entries = @index[expected_name]
|
22
|
+
refute_nil(entries, "Expected #{expected_name} to be indexed")
|
20
23
|
refute_empty(entries, "Expected #{expected_name} to be indexed")
|
21
24
|
|
22
25
|
entry = entries.first
|
@@ -41,6 +44,10 @@ module RubyIndexer
|
|
41
44
|
assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
|
42
45
|
end
|
43
46
|
|
47
|
+
def assert_no_indexed_entries
|
48
|
+
assert_equal(@default_indexed_entries, @index.instance_variable_get(:@entries))
|
49
|
+
end
|
50
|
+
|
44
51
|
def assert_no_entry(entry)
|
45
52
|
refute(@index.instance_variable_get(:@entries).key?(entry), "Expected '#{entry}' to not be indexed")
|
46
53
|
end
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -127,7 +127,10 @@ module RubyLsp
|
|
127
127
|
queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
128
128
|
closest = node
|
129
129
|
parent = T.let(nil, T.nilable(Prism::Node))
|
130
|
-
|
130
|
+
nesting_nodes = T.let(
|
131
|
+
[],
|
132
|
+
T::Array[T.any(Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode)],
|
133
|
+
)
|
131
134
|
call_node = T.let(nil, T.nilable(Prism::CallNode))
|
132
135
|
|
133
136
|
until queue.empty?
|
@@ -151,13 +154,18 @@ module RubyLsp
|
|
151
154
|
|
152
155
|
# If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
|
153
156
|
# need to pop the stack
|
154
|
-
previous_level =
|
155
|
-
|
157
|
+
previous_level = nesting_nodes.last
|
158
|
+
nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
|
156
159
|
|
157
160
|
# Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
|
158
161
|
# target when it is a constant
|
159
|
-
|
160
|
-
|
162
|
+
case candidate
|
163
|
+
when Prism::ClassNode, Prism::ModuleNode
|
164
|
+
nesting_nodes << candidate
|
165
|
+
when Prism::SingletonClassNode
|
166
|
+
nesting_nodes << candidate
|
167
|
+
when Prism::DefNode
|
168
|
+
nesting_nodes << candidate
|
161
169
|
end
|
162
170
|
|
163
171
|
if candidate.is_a?(Prism::CallNode)
|
@@ -189,11 +197,32 @@ module RubyLsp
|
|
189
197
|
# The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
|
190
198
|
# though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
|
191
199
|
if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
|
192
|
-
last_level =
|
193
|
-
|
200
|
+
last_level = nesting_nodes.last
|
201
|
+
|
202
|
+
if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
|
203
|
+
last_level.constant_path == closest
|
204
|
+
nesting_nodes.pop
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
nesting = []
|
209
|
+
surrounding_method = T.let(nil, T.nilable(String))
|
210
|
+
|
211
|
+
nesting_nodes.each do |node|
|
212
|
+
case node
|
213
|
+
when Prism::ClassNode, Prism::ModuleNode
|
214
|
+
nesting << node.constant_path.slice
|
215
|
+
when Prism::SingletonClassNode
|
216
|
+
nesting << "<Class:#{nesting.last}>"
|
217
|
+
when Prism::DefNode
|
218
|
+
surrounding_method = node.name.to_s
|
219
|
+
next unless node.receiver.is_a?(Prism::SelfNode)
|
220
|
+
|
221
|
+
nesting << "<Class:#{nesting.last}>"
|
222
|
+
end
|
194
223
|
end
|
195
224
|
|
196
|
-
NodeContext.new(closest, parent, nesting
|
225
|
+
NodeContext.new(closest, parent, nesting, call_node, surrounding_method)
|
197
226
|
end
|
198
227
|
|
199
228
|
sig { returns(T::Boolean) }
|
@@ -12,7 +12,7 @@ module RubyLsp
|
|
12
12
|
attr_accessor :formatter
|
13
13
|
|
14
14
|
sig { returns(T::Boolean) }
|
15
|
-
attr_reader :
|
15
|
+
attr_reader :has_type_checker
|
16
16
|
|
17
17
|
sig { returns(RubyIndexer::Index) }
|
18
18
|
attr_reader :index
|
@@ -23,6 +23,9 @@ module RubyLsp
|
|
23
23
|
sig { returns(T::Boolean) }
|
24
24
|
attr_reader :supports_watching_files
|
25
25
|
|
26
|
+
sig { returns(TypeInferrer) }
|
27
|
+
attr_reader :type_inferrer
|
28
|
+
|
26
29
|
sig { void }
|
27
30
|
def initialize
|
28
31
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
@@ -31,8 +34,9 @@ module RubyLsp
|
|
31
34
|
@formatter = T.let("auto", String)
|
32
35
|
@linters = T.let([], T::Array[String])
|
33
36
|
@test_library = T.let("minitest", String)
|
34
|
-
@
|
37
|
+
@has_type_checker = T.let(true, T::Boolean)
|
35
38
|
@index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
|
39
|
+
@type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
|
36
40
|
@supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
|
37
41
|
@supports_watching_files = T.let(false, T::Boolean)
|
38
42
|
end
|
@@ -54,18 +58,19 @@ module RubyLsp
|
|
54
58
|
|
55
59
|
sig { params(options: T::Hash[Symbol, T.untyped]).void }
|
56
60
|
def apply_options(options)
|
57
|
-
|
61
|
+
direct_dependencies = gather_direct_dependencies
|
62
|
+
all_dependencies = gather_direct_and_indirect_dependencies
|
58
63
|
workspace_uri = options.dig(:workspaceFolders, 0, :uri)
|
59
64
|
@workspace_uri = URI(workspace_uri) if workspace_uri
|
60
65
|
|
61
66
|
specified_formatter = options.dig(:initializationOptions, :formatter)
|
62
67
|
@formatter = specified_formatter if specified_formatter
|
63
|
-
@formatter = detect_formatter(
|
68
|
+
@formatter = detect_formatter(direct_dependencies, all_dependencies) if @formatter == "auto"
|
64
69
|
|
65
70
|
specified_linters = options.dig(:initializationOptions, :linters)
|
66
|
-
@linters = specified_linters || detect_linters(
|
67
|
-
@test_library = detect_test_library(
|
68
|
-
@
|
71
|
+
@linters = specified_linters || detect_linters(direct_dependencies)
|
72
|
+
@test_library = detect_test_library(direct_dependencies)
|
73
|
+
@has_type_checker = detect_typechecker(direct_dependencies)
|
69
74
|
|
70
75
|
encodings = options.dig(:capabilities, :general, :positionEncodings)
|
71
76
|
@encoding = if !encodings || encodings.empty?
|
@@ -103,16 +108,18 @@ module RubyLsp
|
|
103
108
|
|
104
109
|
private
|
105
110
|
|
106
|
-
sig { params(
|
107
|
-
def detect_formatter(
|
111
|
+
sig { params(direct_dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(String) }
|
112
|
+
def detect_formatter(direct_dependencies, all_dependencies)
|
108
113
|
# NOTE: Intentionally no $ at end, since we want to match rubocop-shopify, etc.
|
109
|
-
if
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
114
|
+
return "rubocop" if direct_dependencies.any?(/^rubocop/)
|
115
|
+
|
116
|
+
syntax_tree_is_direct_dependency = direct_dependencies.include?("syntax_tree")
|
117
|
+
return "syntax_tree" if syntax_tree_is_direct_dependency
|
118
|
+
|
119
|
+
rubocop_is_transitive_dependency = all_dependencies.include?("rubocop")
|
120
|
+
return "rubocop" if dot_rubocop_yml_present && rubocop_is_transitive_dependency
|
121
|
+
|
122
|
+
"none"
|
116
123
|
end
|
117
124
|
|
118
125
|
# Try to detect if there are linters in the project's dependencies. For auto-detection, we always only consider a
|
@@ -132,7 +139,7 @@ module RubyLsp
|
|
132
139
|
# by ruby-lsp-rails. A Rails app doesn't need to depend on the rails gem itself, individual components like
|
133
140
|
# activestorage may be added to the gemfile so that other components aren't downloaded. Check for the presence
|
134
141
|
# of bin/rails to support these cases.
|
135
|
-
elsif
|
142
|
+
elsif bin_rails_present
|
136
143
|
"rails"
|
137
144
|
# NOTE: Intentionally ends with $ to avoid mis-matching minitest-reporters, etc. in a Rails app.
|
138
145
|
elsif dependencies.any?(/^minitest$/)
|
@@ -162,8 +169,18 @@ module RubyLsp
|
|
162
169
|
false
|
163
170
|
end
|
164
171
|
|
172
|
+
sig { returns(T::Boolean) }
|
173
|
+
def bin_rails_present
|
174
|
+
File.exist?(File.join(workspace_path, "bin/rails"))
|
175
|
+
end
|
176
|
+
|
177
|
+
sig { returns(T::Boolean) }
|
178
|
+
def dot_rubocop_yml_present
|
179
|
+
File.exist?(File.join(workspace_path, ".rubocop.yml"))
|
180
|
+
end
|
181
|
+
|
165
182
|
sig { returns(T::Array[String]) }
|
166
|
-
def
|
183
|
+
def gather_direct_dependencies
|
167
184
|
Bundler.with_original_env { Bundler.default_gemfile }
|
168
185
|
Bundler.locked_gems.dependencies.keys + gemspec_dependencies
|
169
186
|
rescue Bundler::GemfileNotFound
|
@@ -176,5 +193,13 @@ module RubyLsp
|
|
176
193
|
.grep(Bundler::Source::Gemspec)
|
177
194
|
.flat_map { _1.gemspec&.dependencies&.map(&:name) }
|
178
195
|
end
|
196
|
+
|
197
|
+
sig { returns(T::Array[String]) }
|
198
|
+
def gather_direct_and_indirect_dependencies
|
199
|
+
Bundler.with_original_env { Bundler.default_gemfile }
|
200
|
+
Bundler.locked_gems.specs.map(&:name)
|
201
|
+
rescue Bundler::GemfileNotFound
|
202
|
+
[]
|
203
|
+
end
|
179
204
|
end
|
180
205
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -18,6 +18,7 @@ require "set"
|
|
18
18
|
require "prism"
|
19
19
|
require "prism/visitor"
|
20
20
|
require "language_server-protocol"
|
21
|
+
require "rbs"
|
21
22
|
|
22
23
|
require "ruby-lsp"
|
23
24
|
require "ruby_lsp/base_server"
|
@@ -27,6 +28,7 @@ require "ruby_lsp/utils"
|
|
27
28
|
require "ruby_lsp/parameter_scope"
|
28
29
|
require "ruby_lsp/global_state"
|
29
30
|
require "ruby_lsp/server"
|
31
|
+
require "ruby_lsp/type_inferrer"
|
30
32
|
require "ruby_lsp/requests"
|
31
33
|
require "ruby_lsp/response_builders"
|
32
34
|
require "ruby_lsp/node_context"
|
@@ -236,7 +236,7 @@ module RubyLsp
|
|
236
236
|
# so there must be something to the left of the available path.
|
237
237
|
group_stack = T.must(group_stack[last_dynamic_reference_index + 1..])
|
238
238
|
if method_name
|
239
|
-
" --name " + "/::#{Shellwords.escape(group_stack.join("::") + "#" + method_name)}$/"
|
239
|
+
" --name " + "/::#{Shellwords.escape(group_stack.join("::")) + "#" + Shellwords.escape(method_name)}$/"
|
240
240
|
else
|
241
241
|
# When clicking on a CodeLens for `Test`, `(#|::)` will match all tests
|
242
242
|
# that are registered on the class itself (matches after `#`) and all tests
|
@@ -245,7 +245,7 @@ module RubyLsp
|
|
245
245
|
end
|
246
246
|
elsif method_name
|
247
247
|
# We know the entire path, do an exact match
|
248
|
-
" --name " + Shellwords.escape(group_stack.join("::") + "#" + method_name)
|
248
|
+
" --name " + Shellwords.escape(group_stack.join("::")) + "#" + Shellwords.escape(method_name)
|
249
249
|
elsif spec_name
|
250
250
|
" --name " + "/#{Shellwords.escape(spec_name)}/"
|
251
251
|
else
|