ruby-lsp 0.22.1 → 0.23.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +10 -9
  4. data/exe/ruby-lsp-check +5 -5
  5. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +88 -22
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +73 -55
  9. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
  10. data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
  11. data/lib/ruby_indexer/ruby_indexer.rb +1 -1
  12. data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
  13. data/lib/ruby_indexer/test/classes_and_modules_test.rb +11 -6
  14. data/lib/ruby_indexer/test/configuration_test.rb +116 -51
  15. data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
  16. data/lib/ruby_indexer/test/index_test.rb +72 -43
  17. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  18. data/lib/ruby_indexer/test/reference_finder_test.rb +1 -1
  19. data/lib/ruby_indexer/test/test_case.rb +2 -2
  20. data/lib/ruby_indexer/test/uri_test.rb +72 -0
  21. data/lib/ruby_lsp/addon.rb +9 -0
  22. data/lib/ruby_lsp/base_server.rb +15 -6
  23. data/lib/ruby_lsp/document.rb +10 -1
  24. data/lib/ruby_lsp/internal.rb +1 -1
  25. data/lib/ruby_lsp/listeners/code_lens.rb +8 -4
  26. data/lib/ruby_lsp/listeners/completion.rb +73 -4
  27. data/lib/ruby_lsp/listeners/definition.rb +73 -17
  28. data/lib/ruby_lsp/listeners/document_symbol.rb +12 -1
  29. data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
  30. data/lib/ruby_lsp/listeners/hover.rb +57 -0
  31. data/lib/ruby_lsp/requests/completion.rb +6 -0
  32. data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
  33. data/lib/ruby_lsp/requests/definition.rb +6 -0
  34. data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
  35. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
  36. data/lib/ruby_lsp/requests/rename.rb +14 -4
  37. data/lib/ruby_lsp/requests/support/common.rb +1 -5
  38. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
  39. data/lib/ruby_lsp/requests/workspace_symbol.rb +3 -2
  40. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  41. data/lib/ruby_lsp/server.rb +42 -7
  42. data/lib/ruby_lsp/setup_bundler.rb +31 -41
  43. data/lib/ruby_lsp/test_helper.rb +45 -11
  44. data/lib/ruby_lsp/type_inferrer.rb +22 -0
  45. data/lib/ruby_lsp/utils.rb +3 -0
  46. metadata +7 -8
  47. data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -0,0 +1,72 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "test_helper"
5
+
6
+ module RubyIndexer
7
+ class URITest < Minitest::Test
8
+ def test_from_path_on_unix
9
+ uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb")
10
+ assert_equal("/some/unix/path/to/file.rb", uri.path)
11
+ end
12
+
13
+ def test_from_path_on_windows
14
+ uri = URI::Generic.from_path(path: "C:/some/windows/path/to/file.rb")
15
+ assert_equal("/C:/some/windows/path/to/file.rb", uri.path)
16
+ end
17
+
18
+ def test_from_path_on_windows_with_lowercase_drive
19
+ uri = URI::Generic.from_path(path: "c:/some/windows/path/to/file.rb")
20
+ assert_equal("/c:/some/windows/path/to/file.rb", uri.path)
21
+ end
22
+
23
+ def test_to_standardized_path_on_unix
24
+ uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb")
25
+ assert_equal(uri.path, uri.to_standardized_path)
26
+ end
27
+
28
+ def test_to_standardized_path_on_windows
29
+ uri = URI::Generic.from_path(path: "C:/some/windows/path/to/file.rb")
30
+ assert_equal("C:/some/windows/path/to/file.rb", uri.to_standardized_path)
31
+ end
32
+
33
+ def test_to_standardized_path_on_windows_with_lowercase_drive
34
+ uri = URI::Generic.from_path(path: "c:/some/windows/path/to/file.rb")
35
+ assert_equal("c:/some/windows/path/to/file.rb", uri.to_standardized_path)
36
+ end
37
+
38
+ def test_to_standardized_path_on_windows_with_received_uri
39
+ uri = URI("file:///c%3A/some/windows/path/to/file.rb")
40
+ assert_equal("c:/some/windows/path/to/file.rb", uri.to_standardized_path)
41
+ end
42
+
43
+ def test_plus_signs_are_properly_unescaped
44
+ path = "/opt/rubies/3.3.0/lib/ruby/3.3.0+0/pathname.rb"
45
+ uri = URI::Generic.from_path(path: path)
46
+ assert_equal(path, uri.to_standardized_path)
47
+ end
48
+
49
+ def test_from_path_with_fragment
50
+ uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb", fragment: "L1,3-2,9")
51
+ assert_equal("file:///some/unix/path/to/file.rb#L1,3-2,9", uri.to_s)
52
+ end
53
+
54
+ def test_from_path_windows_long_file_paths
55
+ uri = URI::Generic.from_path(path: "//?/C:/hostedtoolcache/windows/Ruby/3.3.1/x64/lib/ruby/3.3.0/open-uri.rb")
56
+ assert_equal("C:/hostedtoolcache/windows/Ruby/3.3.1/x64/lib/ruby/3.3.0/open-uri.rb", uri.to_standardized_path)
57
+ end
58
+
59
+ def test_from_path_computes_require_path_when_load_path_entry_is_given
60
+ uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb", load_path_entry: "/some/unix/path")
61
+ assert_equal("to/file", uri.require_path)
62
+ end
63
+
64
+ def test_allows_adding_require_path_with_load_path_entry
65
+ uri = URI::Generic.from_path(path: "/some/unix/path/to/file.rb")
66
+ assert_nil(uri.require_path)
67
+
68
+ uri.add_require_path_from_load_entry("/some/unix/path")
69
+ assert_equal("to/file", uri.require_path)
70
+ end
71
+ end
72
+ end
@@ -97,6 +97,15 @@ module RubyLsp
97
97
  errors
98
98
  end
99
99
 
100
+ # Unloads all add-ons. Only intended to be invoked once when shutting down the Ruby LSP server
101
+ sig { void }
102
+ def unload_addons
103
+ @addons.each(&:deactivate)
104
+ @addons.clear
105
+ @addon_classes.clear
106
+ @file_watcher_addons.clear
107
+ end
108
+
100
109
  # Get a reference to another add-on object by name and version. If an add-on exports an API that can be used by
101
110
  # other add-ons, this is the way to get access to that API.
102
111
  #
@@ -91,14 +91,13 @@ module RubyLsp
91
91
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
92
92
  # else is pushed into the incoming queue
93
93
  case method
94
- when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
94
+ when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange",
95
+ "$/cancelRequest"
95
96
  process_message(message)
96
97
  when "shutdown"
97
- send_log_message("Shutting down Ruby LSP...")
98
-
99
- shutdown
100
-
101
98
  @mutex.synchronize do
99
+ send_log_message("Shutting down Ruby LSP...")
100
+ shutdown
102
101
  run_shutdown
103
102
  @writer.write(Result.new(id: message[:id], response: nil).to_hash)
104
103
  end
@@ -133,6 +132,12 @@ module RubyLsp
133
132
  @outgoing_queue.pop
134
133
  end
135
134
 
135
+ # This method is only intended to be used in tests! Pushes a message to the incoming queue directly
136
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
137
+ def push_message(message)
138
+ @incoming_queue << message
139
+ end
140
+
136
141
  sig { abstract.params(message: T::Hash[Symbol, T.untyped]).void }
137
142
  def process_message(message); end
138
143
 
@@ -154,7 +159,11 @@ module RubyLsp
154
159
  # Check if the request was cancelled before trying to process it
155
160
  @mutex.synchronize do
156
161
  if id && @cancelled_requests.include?(id)
157
- send_message(Result.new(id: id, response: nil))
162
+ send_message(Error.new(
163
+ id: id,
164
+ code: Constant::ErrorCodes::REQUEST_CANCELLED,
165
+ message: "Request #{id} was cancelled",
166
+ ))
158
167
  @cancelled_requests.delete(id)
159
168
  next
160
169
  end
@@ -15,6 +15,7 @@ module RubyLsp
15
15
  extend T::Helpers
16
16
  extend T::Generic
17
17
 
18
+ class LocationNotFoundError < StandardError; end
18
19
  ParseResultType = type_member
19
20
 
20
21
  # This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
@@ -144,7 +145,15 @@ module RubyLsp
144
145
  def find_char_position(position)
145
146
  # Find the character index for the beginning of the requested line
146
147
  until @current_line == position[:line]
147
- @pos += 1 until LINE_BREAK == @source[@pos]
148
+ until LINE_BREAK == @source[@pos]
149
+ @pos += 1
150
+
151
+ if @pos >= @source.length
152
+ # Pack the code points back into the original string to provide context in the error message
153
+ raise LocationNotFoundError, "Requested position: #{position}\nSource:\n\n#{@source.pack("U*")}"
154
+ end
155
+ end
156
+
148
157
  @pos += 1
149
158
  @current_line += 1
150
159
  end
@@ -26,7 +26,6 @@ require "fileutils"
26
26
  require "ruby-lsp"
27
27
  require "ruby_lsp/base_server"
28
28
  require "ruby_indexer/ruby_indexer"
29
- require "core_ext/uri"
30
29
  require "ruby_lsp/utils"
31
30
  require "ruby_lsp/static_docs"
32
31
  require "ruby_lsp/scope"
@@ -78,6 +77,7 @@ require "ruby_lsp/requests/hover"
78
77
  require "ruby_lsp/requests/inlay_hints"
79
78
  require "ruby_lsp/requests/on_type_formatting"
80
79
  require "ruby_lsp/requests/prepare_type_hierarchy"
80
+ require "ruby_lsp/requests/prepare_rename"
81
81
  require "ruby_lsp/requests/range_formatting"
82
82
  require "ruby_lsp/requests/references"
83
83
  require "ruby_lsp/requests/rename"
@@ -15,7 +15,7 @@ module RubyLsp
15
15
  "bundle exec ruby"
16
16
  rescue Bundler::GemfileNotFound
17
17
  "ruby"
18
- end + " -Itest ",
18
+ end,
19
19
  String,
20
20
  )
21
21
  ACCESS_MODIFIERS = T.let([:public, :private, :protected], T::Array[Symbol])
@@ -198,7 +198,7 @@ module RubyLsp
198
198
 
199
199
  @response_builder << create_code_lens(
200
200
  node,
201
- title: "Run",
201
+ title: "Run",
202
202
  command_name: "rubyLsp.runTest",
203
203
  arguments: arguments,
204
204
  data: { type: "test", **grouping_data },
@@ -206,7 +206,7 @@ module RubyLsp
206
206
 
207
207
  @response_builder << create_code_lens(
208
208
  node,
209
- title: "Run In Terminal",
209
+ title: "Run In Terminal",
210
210
  command_name: "rubyLsp.runTestInTerminal",
211
211
  arguments: arguments,
212
212
  data: { type: "test_in_terminal", **grouping_data },
@@ -229,7 +229,11 @@ module RubyLsp
229
229
  ).returns(String)
230
230
  end
231
231
  def generate_test_command(group_stack: [], spec_name: nil, method_name: nil)
232
- command = BASE_COMMAND + T.must(@path)
232
+ path = T.must(@path)
233
+ command = BASE_COMMAND
234
+ command += " -Itest" if File.fnmatch?("**/test/**/*", path, File::FNM_PATHNAME)
235
+ command += " -Ispec" if File.fnmatch?("**/spec/**/*", path, File::FNM_PATHNAME)
236
+ command += " #{path}"
233
237
 
234
238
  case @global_state.test_library
235
239
  when "minitest"
@@ -97,6 +97,12 @@ module RubyLsp
97
97
  :on_instance_variable_operator_write_node_enter,
98
98
  :on_instance_variable_or_write_node_enter,
99
99
  :on_instance_variable_target_node_enter,
100
+ :on_class_variable_and_write_node_enter,
101
+ :on_class_variable_operator_write_node_enter,
102
+ :on_class_variable_or_write_node_enter,
103
+ :on_class_variable_read_node_enter,
104
+ :on_class_variable_target_node_enter,
105
+ :on_class_variable_write_node_enter,
100
106
  )
101
107
  end
102
108
 
@@ -110,13 +116,14 @@ module RubyLsp
110
116
  name = constant_name(node)
111
117
  return if name.nil?
112
118
 
119
+ range = range_from_location(node.location)
113
120
  candidates = @index.constant_completion_candidates(name, @node_context.nesting)
114
121
  candidates.each do |entries|
115
122
  complete_name = T.must(entries.first).name
116
123
  @response_builder << build_entry_completion(
117
124
  complete_name,
118
125
  name,
119
- range_from_location(node.location),
126
+ range,
120
127
  entries,
121
128
  top_level?(complete_name),
122
129
  )
@@ -246,6 +253,36 @@ module RubyLsp
246
253
  handle_instance_variable_completion(node.name.to_s, node.location)
247
254
  end
248
255
 
256
+ sig { params(node: Prism::ClassVariableAndWriteNode).void }
257
+ def on_class_variable_and_write_node_enter(node)
258
+ handle_class_variable_completion(node.name.to_s, node.name_loc)
259
+ end
260
+
261
+ sig { params(node: Prism::ClassVariableOperatorWriteNode).void }
262
+ def on_class_variable_operator_write_node_enter(node)
263
+ handle_class_variable_completion(node.name.to_s, node.name_loc)
264
+ end
265
+
266
+ sig { params(node: Prism::ClassVariableOrWriteNode).void }
267
+ def on_class_variable_or_write_node_enter(node)
268
+ handle_class_variable_completion(node.name.to_s, node.name_loc)
269
+ end
270
+
271
+ sig { params(node: Prism::ClassVariableTargetNode).void }
272
+ def on_class_variable_target_node_enter(node)
273
+ handle_class_variable_completion(node.name.to_s, node.location)
274
+ end
275
+
276
+ sig { params(node: Prism::ClassVariableReadNode).void }
277
+ def on_class_variable_read_node_enter(node)
278
+ handle_class_variable_completion(node.name.to_s, node.location)
279
+ end
280
+
281
+ sig { params(node: Prism::ClassVariableWriteNode).void }
282
+ def on_class_variable_write_node_enter(node)
283
+ handle_class_variable_completion(node.name.to_s, node.name_loc)
284
+ end
285
+
249
286
  private
250
287
 
251
288
  sig { params(name: String, range: Interface::Range).void }
@@ -326,6 +363,37 @@ module RubyLsp
326
363
  end
327
364
  end
328
365
 
366
+ sig { params(name: String, location: Prism::Location).void }
367
+ def handle_class_variable_completion(name, location)
368
+ type = @type_inferrer.infer_receiver_type(@node_context)
369
+ return unless type
370
+
371
+ range = range_from_location(location)
372
+
373
+ @index.class_variable_completion_candidates(name, type.name).each do |entry|
374
+ variable_name = entry.name
375
+
376
+ label_details = Interface::CompletionItemLabelDetails.new(
377
+ description: entry.file_name,
378
+ )
379
+
380
+ @response_builder << Interface::CompletionItem.new(
381
+ label: variable_name,
382
+ label_details: label_details,
383
+ text_edit: Interface::TextEdit.new(
384
+ range: range,
385
+ new_text: variable_name,
386
+ ),
387
+ kind: Constant::CompletionItemKind::FIELD,
388
+ data: {
389
+ owner_name: entry.owner&.name,
390
+ },
391
+ )
392
+ end
393
+ rescue RubyIndexer::Index::NonExistingNamespaceError
394
+ # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
395
+ end
396
+
329
397
  sig { params(name: String, location: Prism::Location).void }
330
398
  def handle_instance_variable_completion(name, location)
331
399
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
@@ -335,6 +403,7 @@ module RubyLsp
335
403
  type = @type_inferrer.infer_receiver_type(@node_context)
336
404
  return unless type
337
405
 
406
+ range = range_from_location(location)
338
407
  @index.instance_variable_completion_candidates(name, type.name).each do |entry|
339
408
  variable_name = entry.name
340
409
 
@@ -346,7 +415,7 @@ module RubyLsp
346
415
  label: variable_name,
347
416
  label_details: label_details,
348
417
  text_edit: Interface::TextEdit.new(
349
- range: range_from_location(location),
418
+ range: range,
350
419
  new_text: variable_name,
351
420
  ),
352
421
  kind: Constant::CompletionItemKind::FIELD,
@@ -368,9 +437,9 @@ module RubyLsp
368
437
 
369
438
  return unless path_node_to_complete.is_a?(Prism::StringNode)
370
439
 
371
- matched_indexable_paths = @index.search_require_paths(path_node_to_complete.content)
440
+ matched_uris = @index.search_require_paths(path_node_to_complete.content)
372
441
 
373
- matched_indexable_paths.map!(&:require_path).sort!.each do |path|
442
+ matched_uris.map!(&:require_path).sort!.each do |path|
374
443
  @response_builder << build_completion(T.must(path), path_node_to_complete)
375
444
  end
376
445
  end
@@ -55,6 +55,12 @@ module RubyLsp
55
55
  :on_symbol_node_enter,
56
56
  :on_super_node_enter,
57
57
  :on_forwarding_super_node_enter,
58
+ :on_class_variable_and_write_node_enter,
59
+ :on_class_variable_operator_write_node_enter,
60
+ :on_class_variable_or_write_node_enter,
61
+ :on_class_variable_read_node_enter,
62
+ :on_class_variable_target_node_enter,
63
+ :on_class_variable_write_node_enter,
58
64
  )
59
65
  end
60
66
 
@@ -196,6 +202,36 @@ module RubyLsp
196
202
  handle_super_node_definition
197
203
  end
198
204
 
205
+ sig { params(node: Prism::ClassVariableAndWriteNode).void }
206
+ def on_class_variable_and_write_node_enter(node)
207
+ handle_class_variable_definition(node.name.to_s)
208
+ end
209
+
210
+ sig { params(node: Prism::ClassVariableOperatorWriteNode).void }
211
+ def on_class_variable_operator_write_node_enter(node)
212
+ handle_class_variable_definition(node.name.to_s)
213
+ end
214
+
215
+ sig { params(node: Prism::ClassVariableOrWriteNode).void }
216
+ def on_class_variable_or_write_node_enter(node)
217
+ handle_class_variable_definition(node.name.to_s)
218
+ end
219
+
220
+ sig { params(node: Prism::ClassVariableTargetNode).void }
221
+ def on_class_variable_target_node_enter(node)
222
+ handle_class_variable_definition(node.name.to_s)
223
+ end
224
+
225
+ sig { params(node: Prism::ClassVariableReadNode).void }
226
+ def on_class_variable_read_node_enter(node)
227
+ handle_class_variable_definition(node.name.to_s)
228
+ end
229
+
230
+ sig { params(node: Prism::ClassVariableWriteNode).void }
231
+ def on_class_variable_write_node_enter(node)
232
+ handle_class_variable_definition(node.name.to_s)
233
+ end
234
+
199
235
  private
200
236
 
201
237
  sig { void }
@@ -223,7 +259,7 @@ module RubyLsp
223
259
  location = entry.location
224
260
 
225
261
  @response_builder << Interface::Location.new(
226
- uri: URI::Generic.from_path(path: entry.file_path).to_s,
262
+ uri: entry.uri.to_s,
227
263
  range: Interface::Range.new(
228
264
  start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
229
265
  end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
@@ -232,6 +268,24 @@ module RubyLsp
232
268
  end
233
269
  end
234
270
 
271
+ sig { params(name: String).void }
272
+ def handle_class_variable_definition(name)
273
+ type = @type_inferrer.infer_receiver_type(@node_context)
274
+ return unless type
275
+
276
+ entries = @index.resolve_class_variable(name, type.name)
277
+ return unless entries
278
+
279
+ entries.each do |entry|
280
+ @response_builder << Interface::Location.new(
281
+ uri: entry.uri.to_s,
282
+ range: range_from_location(entry.location),
283
+ )
284
+ end
285
+ rescue RubyIndexer::Index::NonExistingNamespaceError
286
+ # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
287
+ end
288
+
235
289
  sig { params(name: String).void }
236
290
  def handle_instance_variable_definition(name)
237
291
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
@@ -248,7 +302,7 @@ module RubyLsp
248
302
  location = entry.location
249
303
 
250
304
  @response_builder << Interface::Location.new(
251
- uri: URI::Generic.from_path(path: entry.file_path).to_s,
305
+ uri: entry.uri.to_s,
252
306
  range: Interface::Range.new(
253
307
  start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
254
308
  end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
@@ -275,11 +329,11 @@ module RubyLsp
275
329
  return unless methods
276
330
 
277
331
  methods.each do |target_method|
278
- file_path = target_method.file_path
279
- next if sorbet_level_true_or_higher?(@sorbet_level) && not_in_dependencies?(file_path)
332
+ uri = target_method.uri
333
+ next if sorbet_level_true_or_higher?(@sorbet_level) && not_in_dependencies?(T.must(uri.full_path))
280
334
 
281
335
  @response_builder << Interface::LocationLink.new(
282
- target_uri: URI::Generic.from_path(path: file_path).to_s,
336
+ target_uri: uri.to_s,
283
337
  target_range: range_from_location(target_method.location),
284
338
  target_selection_range: range_from_location(target_method.name_location),
285
339
  )
@@ -290,20 +344,22 @@ module RubyLsp
290
344
  def handle_require_definition(node, message)
291
345
  case message
292
346
  when :require
293
- entry = @index.search_require_paths(node.content).find do |indexable_path|
294
- indexable_path.require_path == node.content
347
+ entry = @index.search_require_paths(node.content).find do |uri|
348
+ uri.require_path == node.content
295
349
  end
296
350
 
297
351
  if entry
298
352
  candidate = entry.full_path
299
353
 
300
- @response_builder << Interface::Location.new(
301
- uri: URI::Generic.from_path(path: candidate).to_s,
302
- range: Interface::Range.new(
303
- start: Interface::Position.new(line: 0, character: 0),
304
- end: Interface::Position.new(line: 0, character: 0),
305
- ),
306
- )
354
+ if candidate
355
+ @response_builder << Interface::Location.new(
356
+ uri: URI::Generic.from_path(path: candidate).to_s,
357
+ range: Interface::Range.new(
358
+ start: Interface::Position.new(line: 0, character: 0),
359
+ end: Interface::Position.new(line: 0, character: 0),
360
+ ),
361
+ )
362
+ end
307
363
  end
308
364
  when :require_relative
309
365
  required_file = "#{node.content}.rb"
@@ -346,11 +402,11 @@ module RubyLsp
346
402
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
347
403
  # additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
348
404
  # ignore
349
- file_path = entry.file_path
350
- next if @sorbet_level != RubyDocument::SorbetLevel::Ignore && not_in_dependencies?(file_path)
405
+ uri = entry.uri
406
+ next if @sorbet_level != RubyDocument::SorbetLevel::Ignore && not_in_dependencies?(T.must(uri.full_path))
351
407
 
352
408
  @response_builder << Interface::LocationLink.new(
353
- target_uri: URI::Generic.from_path(path: file_path).to_s,
409
+ target_uri: uri.to_s,
354
410
  target_range: range_from_location(entry.location),
355
411
  target_selection_range: range_from_location(entry.name_location),
356
412
  )
@@ -41,6 +41,7 @@ module RubyLsp
41
41
  :on_module_node_enter,
42
42
  :on_module_node_leave,
43
43
  :on_instance_variable_write_node_enter,
44
+ :on_instance_variable_target_node_enter,
44
45
  :on_instance_variable_operator_write_node_enter,
45
46
  :on_instance_variable_or_write_node_enter,
46
47
  :on_instance_variable_and_write_node_enter,
@@ -272,6 +273,16 @@ module RubyLsp
272
273
  )
273
274
  end
274
275
 
276
+ sig { params(node: Prism::InstanceVariableTargetNode).void }
277
+ def on_instance_variable_target_node_enter(node)
278
+ create_document_symbol(
279
+ name: node.name.to_s,
280
+ kind: Constant::SymbolKind::FIELD,
281
+ range_location: node.location,
282
+ selection_range_location: node.location,
283
+ )
284
+ end
285
+
275
286
  sig { params(node: Prism::InstanceVariableOperatorWriteNode).void }
276
287
  def on_instance_variable_operator_write_node_enter(node)
277
288
  create_document_symbol(
@@ -329,7 +340,7 @@ module RubyLsp
329
340
  ).returns(Interface::DocumentSymbol)
330
341
  end
331
342
  def create_document_symbol(name:, kind:, range_location:, selection_range_location:)
332
- name = "<blank>" if name.empty?
343
+ name = "<blank>" if name.strip.empty?
333
344
  symbol = Interface::DocumentSymbol.new(
334
345
  name: name,
335
346
  kind: kind,
@@ -195,7 +195,7 @@ module RubyLsp
195
195
  def push_comment_ranges
196
196
  # Group comments that are on consecutive lines and then push ranges for each group that has at least 2 comments
197
197
  @comments.chunk_while do |this, other|
198
- this.location.end_line + 1 == other.location.start_line
198
+ this.location.end_line + 1 == other.location.start_line && !this.trailing? && !other.trailing?
199
199
  end.each do |chunk|
200
200
  next if chunk.length == 1
201
201
 
@@ -31,6 +31,12 @@ module RubyLsp
31
31
  Prism::SuperNode,
32
32
  Prism::ForwardingSuperNode,
33
33
  Prism::YieldNode,
34
+ Prism::ClassVariableAndWriteNode,
35
+ Prism::ClassVariableOperatorWriteNode,
36
+ Prism::ClassVariableOrWriteNode,
37
+ Prism::ClassVariableReadNode,
38
+ Prism::ClassVariableTargetNode,
39
+ Prism::ClassVariableWriteNode,
34
40
  ],
35
41
  T::Array[T.class_of(Prism::Node)],
36
42
  )
@@ -85,6 +91,12 @@ module RubyLsp
85
91
  :on_string_node_enter,
86
92
  :on_interpolated_string_node_enter,
87
93
  :on_yield_node_enter,
94
+ :on_class_variable_and_write_node_enter,
95
+ :on_class_variable_operator_write_node_enter,
96
+ :on_class_variable_or_write_node_enter,
97
+ :on_class_variable_read_node_enter,
98
+ :on_class_variable_target_node_enter,
99
+ :on_class_variable_write_node_enter,
88
100
  )
89
101
  end
90
102
 
@@ -215,6 +227,36 @@ module RubyLsp
215
227
  handle_keyword_documentation(node.keyword)
216
228
  end
217
229
 
230
+ sig { params(node: Prism::ClassVariableAndWriteNode).void }
231
+ def on_class_variable_and_write_node_enter(node)
232
+ handle_class_variable_hover(node.name.to_s)
233
+ end
234
+
235
+ sig { params(node: Prism::ClassVariableOperatorWriteNode).void }
236
+ def on_class_variable_operator_write_node_enter(node)
237
+ handle_class_variable_hover(node.name.to_s)
238
+ end
239
+
240
+ sig { params(node: Prism::ClassVariableOrWriteNode).void }
241
+ def on_class_variable_or_write_node_enter(node)
242
+ handle_class_variable_hover(node.name.to_s)
243
+ end
244
+
245
+ sig { params(node: Prism::ClassVariableTargetNode).void }
246
+ def on_class_variable_target_node_enter(node)
247
+ handle_class_variable_hover(node.name.to_s)
248
+ end
249
+
250
+ sig { params(node: Prism::ClassVariableReadNode).void }
251
+ def on_class_variable_read_node_enter(node)
252
+ handle_class_variable_hover(node.name.to_s)
253
+ end
254
+
255
+ sig { params(node: Prism::ClassVariableWriteNode).void }
256
+ def on_class_variable_write_node_enter(node)
257
+ handle_class_variable_hover(node.name.to_s)
258
+ end
259
+
218
260
  private
219
261
 
220
262
  sig { params(node: T.any(Prism::InterpolatedStringNode, Prism::StringNode)).void }
@@ -317,6 +359,21 @@ module RubyLsp
317
359
  end
318
360
  end
319
361
 
362
+ sig { params(name: String).void }
363
+ def handle_class_variable_hover(name)
364
+ type = @type_inferrer.infer_receiver_type(@node_context)
365
+ return unless type
366
+
367
+ entries = @index.resolve_class_variable(name, type.name)
368
+ return unless entries
369
+
370
+ categorized_markdown_from_index_entries(name, entries).each do |category, content|
371
+ @response_builder.push(content, category: category)
372
+ end
373
+ rescue RubyIndexer::Index::NonExistingNamespaceError
374
+ # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
375
+ end
376
+
320
377
  sig { params(name: String, location: Prism::Location).void }
321
378
  def generate_hover(name, location)
322
379
  entries = @index.resolve(name, @node_context.nesting)
@@ -62,6 +62,12 @@ module RubyLsp
62
62
  Prism::InstanceVariableOrWriteNode,
63
63
  Prism::InstanceVariableTargetNode,
64
64
  Prism::InstanceVariableWriteNode,
65
+ Prism::ClassVariableAndWriteNode,
66
+ Prism::ClassVariableOperatorWriteNode,
67
+ Prism::ClassVariableOrWriteNode,
68
+ Prism::ClassVariableReadNode,
69
+ Prism::ClassVariableTargetNode,
70
+ Prism::ClassVariableWriteNode,
65
71
  ],
66
72
  code_units_cache: document.code_units_cache,
67
73
  )
@@ -49,7 +49,8 @@ module RubyLsp
49
49
  if owner_name
50
50
  entries = entries.select do |entry|
51
51
  (entry.is_a?(RubyIndexer::Entry::Member) || entry.is_a?(RubyIndexer::Entry::InstanceVariable) ||
52
- entry.is_a?(RubyIndexer::Entry::MethodAlias)) && entry.owner&.name == owner_name
52
+ entry.is_a?(RubyIndexer::Entry::MethodAlias) || entry.is_a?(RubyIndexer::Entry::ClassVariable)) &&
53
+ entry.owner&.name == owner_name
53
54
  end
54
55
  end
55
56
 
@@ -56,6 +56,12 @@ module RubyLsp
56
56
  Prism::StringNode,
57
57
  Prism::SuperNode,
58
58
  Prism::ForwardingSuperNode,
59
+ Prism::ClassVariableAndWriteNode,
60
+ Prism::ClassVariableOperatorWriteNode,
61
+ Prism::ClassVariableOrWriteNode,
62
+ Prism::ClassVariableReadNode,
63
+ Prism::ClassVariableTargetNode,
64
+ Prism::ClassVariableWriteNode,
59
65
  ],
60
66
  code_units_cache: document.code_units_cache,
61
67
  )