ruby-lsp 0.17.3 → 0.17.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/VERSION +1 -1
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +251 -100
  5. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +173 -114
  6. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +337 -77
  7. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +43 -14
  8. data/lib/ruby_indexer/test/classes_and_modules_test.rb +79 -3
  9. data/lib/ruby_indexer/test/index_test.rb +563 -29
  10. data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
  11. data/lib/ruby_indexer/test/method_test.rb +75 -25
  12. data/lib/ruby_indexer/test/rbs_indexer_test.rb +38 -2
  13. data/lib/ruby_indexer/test/test_case.rb +1 -5
  14. data/lib/ruby_lsp/addon.rb +13 -1
  15. data/lib/ruby_lsp/document.rb +50 -23
  16. data/lib/ruby_lsp/erb_document.rb +125 -0
  17. data/lib/ruby_lsp/global_state.rb +11 -4
  18. data/lib/ruby_lsp/internal.rb +3 -0
  19. data/lib/ruby_lsp/listeners/completion.rb +69 -34
  20. data/lib/ruby_lsp/listeners/definition.rb +34 -23
  21. data/lib/ruby_lsp/listeners/hover.rb +14 -7
  22. data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
  23. data/lib/ruby_lsp/node_context.rb +6 -1
  24. data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -2
  25. data/lib/ruby_lsp/requests/completion.rb +6 -5
  26. data/lib/ruby_lsp/requests/completion_resolve.rb +7 -4
  27. data/lib/ruby_lsp/requests/definition.rb +4 -3
  28. data/lib/ruby_lsp/requests/formatting.rb +2 -0
  29. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
  30. data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
  31. data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
  32. data/lib/ruby_lsp/requests/support/common.rb +19 -1
  33. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
  34. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +87 -0
  35. data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
  36. data/lib/ruby_lsp/requests.rb +2 -0
  37. data/lib/ruby_lsp/ruby_document.rb +10 -0
  38. data/lib/ruby_lsp/server.rb +95 -26
  39. data/lib/ruby_lsp/store.rb +23 -8
  40. data/lib/ruby_lsp/test_helper.rb +3 -1
  41. data/lib/ruby_lsp/type_inferrer.rb +86 -0
  42. metadata +10 -6
@@ -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
- assert_equal("Foo::Bar", T.must(entry.owner).name)
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
- assert_equal("Foo::Bar", T.must(entry.owner).name)
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
- assert_equal("Foo::Bar", T.must(entry.owner).name)
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
- assert_equal("Foo::Bar", T.must(entry.owner).name)
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
- assert_equal("Foo::Bar", T.must(entry.owner).name)
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
- assert_equal("Foo::Bar", T.must(entry.owner).name)
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
- assert_equal("Foo::Bar", T.must(entry.owner).name)
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::SingletonMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:0-8:1-3", visibility: Entry::Visibility::PRIVATE)
87
- assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:3-0:3-12", visibility: Entry::Visibility::PUBLIC)
88
- assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:7-0:7-12", visibility: Entry::Visibility::PROTECTED)
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::InstanceMethod, "/fake/path/foo.rb:3-2:3-14", visibility: Entry::Visibility::PRIVATE)
107
- assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:6-4:6-16", visibility: Entry::Visibility::PUBLIC)
108
- assert_entry("baz", Entry::InstanceMethod, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
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,23 +364,23 @@ module RubyIndexer
359
364
  def test_properly_tracks_multiple_levels_of_nesting
360
365
  index(<<~RUBY)
361
366
  module Foo
362
- def first; end
367
+ def first_method; end
363
368
 
364
369
  module Bar
365
- def second; end
370
+ def second_method; end
366
371
  end
367
372
 
368
- def third; end
373
+ def third_method; end
369
374
  end
370
375
  RUBY
371
376
 
372
- entry = T.must(@index["first"]&.first)
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.must(@index["second"]&.first)
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.must(@index["third"]&.first)
383
+ entry = T.cast(@index["third_method"]&.first, Entry::Method)
379
384
  assert_equal("Foo", T.must(entry.owner).name)
380
385
  end
381
386
 
@@ -396,7 +401,52 @@ module RubyIndexer
396
401
  assert_entry("foo", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:2-15:2-19")
397
402
  assert_entry("bar", Entry::UnresolvedMethodAlias, "/fake/path/foo.rb:3-15:3-20")
398
403
  # Foo plus 3 valid aliases
399
- assert_equal(4, @index.instance_variable_get(:@entries).length - @default_indexed_entries.length)
404
+ assert_equal(4, @index.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
431
+
432
+ def test_name_location_points_to_method_identifier_location
433
+ index(<<~RUBY)
434
+ class Foo
435
+ def bar
436
+ a = 123
437
+ a + 456
438
+ end
439
+ end
440
+ RUBY
441
+
442
+ entry = T.must(@index["bar"].first)
443
+ refute_equal(entry.location, entry.name_location)
444
+
445
+ name_location = entry.name_location
446
+ assert_equal(2, name_location.start_line)
447
+ assert_equal(2, name_location.end_line)
448
+ assert_equal(6, name_location.start_column)
449
+ assert_equal(9, name_location.end_column)
400
450
  end
401
451
  end
402
452
  end
@@ -8,8 +8,9 @@ module RubyIndexer
8
8
  def test_index_core_classes
9
9
  entries = @index["Array"]
10
10
  refute_nil(entries)
11
- assert_equal(1, entries.length)
12
- entry = entries.first
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) }
13
14
  assert_match(%r{/gems/rbs-.*/core/array.rbs}, entry.file_path)
14
15
  assert_equal("array.rbs", entry.file_name)
15
16
  assert_equal("Object", entry.parent_class)
@@ -38,5 +39,40 @@ module RubyIndexer
38
39
  assert_equal(0, entry.location.start_column)
39
40
  assert_operator(entry.location.end_column, :>, 0)
40
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
+
67
+ def test_location_and_name_location_are_the_same
68
+ # NOTE: RBS does not store the name location for classes, modules or methods. This behaviour is not exactly what
69
+ # we would like, but for now we assign the same location to both
70
+
71
+ entries = @index["Array"]
72
+ refute_nil(entries)
73
+ entry = entries.find { |entry| entry.is_a?(Entry::Class) }
74
+
75
+ assert_same(entry.location, entry.name_location)
76
+ end
41
77
  end
42
78
  end
@@ -40,16 +40,12 @@ module RubyIndexer
40
40
  assert_nil(entries, "Expected #{expected_name} to not be indexed")
41
41
  end
42
42
 
43
- def assert_no_entries
44
- assert_empty(@index.instance_variable_get(:@entries), "Expected nothing to be indexed")
45
- end
46
-
47
43
  def assert_no_indexed_entries
48
44
  assert_equal(@default_indexed_entries, @index.instance_variable_get(:@entries))
49
45
  end
50
46
 
51
47
  def assert_no_entry(entry)
52
- refute(@index.instance_variable_get(:@entries).key?(entry), "Expected '#{entry}' to not be indexed")
48
+ refute(@index.indexed?(entry), "Expected '#{entry}' to not be indexed")
53
49
  end
54
50
  end
55
51
  end
@@ -72,6 +72,15 @@ module RubyLsp
72
72
  addon.add_error(e)
73
73
  end
74
74
  end
75
+
76
+ # Intended for use by tests for addons
77
+ sig { params(addon_name: String).returns(Addon) }
78
+ def get(addon_name)
79
+ addon = addons.find { |addon| addon.name == addon_name }
80
+ raise "Could not find addon '#{addon_name}'" unless addon
81
+
82
+ addon
83
+ end
75
84
  end
76
85
 
77
86
  sig { void }
@@ -157,7 +166,10 @@ module RubyLsp
157
166
  # Creates a new Definition listener. This method is invoked on every Definition request
158
167
  sig do
159
168
  overridable.params(
160
- response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
169
+ response_builder: ResponseBuilders::CollectionResponseBuilder[T.any(
170
+ Interface::Location,
171
+ Interface::LocationLink,
172
+ )],
161
173
  uri: URI::Generic,
162
174
  node_context: NodeContext,
163
175
  dispatcher: Prism::Dispatcher,
@@ -3,6 +3,13 @@
3
3
 
4
4
  module RubyLsp
5
5
  class Document
6
+ class LanguageId < T::Enum
7
+ enums do
8
+ Ruby = new("ruby")
9
+ ERB = new("erb")
10
+ end
11
+ end
12
+
6
13
  extend T::Sig
7
14
  extend T::Helpers
8
15
 
@@ -34,21 +41,14 @@ module RubyLsp
34
41
  @parse_result = T.let(parse, Prism::ParseResult)
35
42
  end
36
43
 
37
- sig { returns(Prism::ProgramNode) }
38
- def tree
39
- @parse_result.value
40
- end
41
-
42
- sig { returns(T::Array[Prism::Comment]) }
43
- def comments
44
- @parse_result.comments
45
- end
46
-
47
44
  sig { params(other: Document).returns(T::Boolean) }
48
45
  def ==(other)
49
- @source == other.source
46
+ self.class == other.class && uri == other.uri && @source == other.source
50
47
  end
51
48
 
49
+ sig { abstract.returns(LanguageId) }
50
+ def language_id; end
51
+
52
52
  # TODO: remove this method once all nonpositional requests have been migrated to the listener pattern
53
53
  sig do
54
54
  type_parameters(:T)
@@ -96,10 +96,8 @@ module RubyLsp
96
96
  sig { abstract.returns(Prism::ParseResult) }
97
97
  def parse; end
98
98
 
99
- sig { returns(T::Boolean) }
100
- def syntax_error?
101
- @parse_result.failure?
102
- end
99
+ sig { abstract.returns(T::Boolean) }
100
+ def syntax_error?; end
103
101
 
104
102
  sig { returns(Scanner) }
105
103
  def create_scanner
@@ -127,7 +125,10 @@ module RubyLsp
127
125
  queue = T.let(node.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
128
126
  closest = node
129
127
  parent = T.let(nil, T.nilable(Prism::Node))
130
- nesting = T.let([], T::Array[T.any(Prism::ClassNode, Prism::ModuleNode)])
128
+ nesting_nodes = T.let(
129
+ [],
130
+ T::Array[T.any(Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode)],
131
+ )
131
132
  call_node = T.let(nil, T.nilable(Prism::CallNode))
132
133
 
133
134
  until queue.empty?
@@ -151,13 +152,18 @@ module RubyLsp
151
152
 
152
153
  # If the candidate starts after the end of the previous nesting level, then we've exited that nesting level and
153
154
  # need to pop the stack
154
- previous_level = nesting.last
155
- nesting.pop if previous_level && loc.start_offset > previous_level.location.end_offset
155
+ previous_level = nesting_nodes.last
156
+ nesting_nodes.pop if previous_level && loc.start_offset > previous_level.location.end_offset
156
157
 
157
158
  # Keep track of the nesting where we found the target. This is used to determine the fully qualified name of the
158
159
  # target when it is a constant
159
- if candidate.is_a?(Prism::ClassNode) || candidate.is_a?(Prism::ModuleNode)
160
- nesting << candidate
160
+ case candidate
161
+ when Prism::ClassNode, Prism::ModuleNode
162
+ nesting_nodes << candidate
163
+ when Prism::SingletonClassNode
164
+ nesting_nodes << candidate
165
+ when Prism::DefNode
166
+ nesting_nodes << candidate
161
167
  end
162
168
 
163
169
  if candidate.is_a?(Prism::CallNode)
@@ -189,11 +195,32 @@ module RubyLsp
189
195
  # The correct target is `Foo::Bar` with an empty nesting. `Foo::Bar` should not appear in the nesting stack, even
190
196
  # though the class/module node does indeed enclose the target, because it would lead to incorrect behavior
191
197
  if closest.is_a?(Prism::ConstantReadNode) || closest.is_a?(Prism::ConstantPathNode)
192
- last_level = nesting.last
193
- nesting.pop if last_level && last_level.constant_path == closest
198
+ last_level = nesting_nodes.last
199
+
200
+ if (last_level.is_a?(Prism::ModuleNode) || last_level.is_a?(Prism::ClassNode)) &&
201
+ last_level.constant_path == closest
202
+ nesting_nodes.pop
203
+ end
204
+ end
205
+
206
+ nesting = []
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
194
221
  end
195
222
 
196
- NodeContext.new(closest, parent, nesting.map { |n| n.constant_path.location.slice }, call_node)
223
+ NodeContext.new(closest, parent, nesting, call_node, surrounding_method)
197
224
  end
198
225
 
199
226
  sig { returns(T::Boolean) }