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