ruby-lsp 0.22.1 → 0.23.10

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +12 -11
  5. data/exe/ruby-lsp-check +5 -5
  6. data/exe/ruby-lsp-launcher +41 -15
  7. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +26 -20
  8. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +191 -100
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +60 -30
  10. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +174 -61
  11. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +12 -0
  12. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +16 -14
  13. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +82 -61
  14. data/lib/{core_ext → ruby_indexer/lib/ruby_indexer}/uri.rb +29 -3
  15. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
  16. data/lib/ruby_indexer/ruby_indexer.rb +2 -1
  17. data/lib/ruby_indexer/test/class_variables_test.rb +140 -0
  18. data/lib/ruby_indexer/test/classes_and_modules_test.rb +30 -6
  19. data/lib/ruby_indexer/test/configuration_test.rb +116 -51
  20. data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
  21. data/lib/ruby_indexer/test/index_test.rb +143 -44
  22. data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
  23. data/lib/ruby_indexer/test/method_test.rb +86 -8
  24. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  25. data/lib/ruby_indexer/test/reference_finder_test.rb +90 -2
  26. data/lib/ruby_indexer/test/test_case.rb +2 -2
  27. data/lib/ruby_indexer/test/uri_test.rb +72 -0
  28. data/lib/ruby_lsp/addon.rb +9 -0
  29. data/lib/ruby_lsp/base_server.rb +17 -18
  30. data/lib/ruby_lsp/client_capabilities.rb +7 -1
  31. data/lib/ruby_lsp/document.rb +72 -10
  32. data/lib/ruby_lsp/erb_document.rb +5 -3
  33. data/lib/ruby_lsp/global_state.rb +42 -3
  34. data/lib/ruby_lsp/internal.rb +3 -1
  35. data/lib/ruby_lsp/listeners/code_lens.rb +9 -5
  36. data/lib/ruby_lsp/listeners/completion.rb +78 -6
  37. data/lib/ruby_lsp/listeners/definition.rb +80 -19
  38. data/lib/ruby_lsp/listeners/document_highlight.rb +3 -2
  39. data/lib/ruby_lsp/listeners/document_link.rb +21 -3
  40. data/lib/ruby_lsp/listeners/document_symbol.rb +12 -1
  41. data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
  42. data/lib/ruby_lsp/listeners/hover.rb +59 -2
  43. data/lib/ruby_lsp/load_sorbet.rb +3 -3
  44. data/lib/ruby_lsp/rbs_document.rb +2 -2
  45. data/lib/ruby_lsp/requests/code_action_resolve.rb +90 -6
  46. data/lib/ruby_lsp/requests/code_actions.rb +57 -1
  47. data/lib/ruby_lsp/requests/completion.rb +8 -1
  48. data/lib/ruby_lsp/requests/completion_resolve.rb +2 -1
  49. data/lib/ruby_lsp/requests/definition.rb +7 -1
  50. data/lib/ruby_lsp/requests/diagnostics.rb +1 -1
  51. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  52. data/lib/ruby_lsp/requests/folding_ranges.rb +2 -6
  53. data/lib/ruby_lsp/requests/formatting.rb +2 -6
  54. data/lib/ruby_lsp/requests/hover.rb +1 -1
  55. data/lib/ruby_lsp/requests/on_type_formatting.rb +2 -2
  56. data/lib/ruby_lsp/requests/prepare_rename.rb +51 -0
  57. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +1 -1
  58. data/lib/ruby_lsp/requests/references.rb +29 -2
  59. data/lib/ruby_lsp/requests/rename.rb +17 -7
  60. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  61. data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
  62. data/lib/ruby_lsp/requests/signature_help.rb +1 -1
  63. data/lib/ruby_lsp/requests/support/common.rb +2 -9
  64. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +3 -3
  65. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -13
  66. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +1 -1
  67. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -3
  68. data/lib/ruby_lsp/ruby_document.rb +80 -6
  69. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  70. data/lib/ruby_lsp/server.rb +205 -61
  71. data/lib/ruby_lsp/setup_bundler.rb +50 -43
  72. data/lib/ruby_lsp/store.rb +7 -7
  73. data/lib/ruby_lsp/test_helper.rb +45 -11
  74. data/lib/ruby_lsp/type_inferrer.rb +60 -31
  75. data/lib/ruby_lsp/utils.rb +63 -3
  76. metadata +8 -8
  77. data/lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb +0 -29
@@ -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
 
@@ -107,16 +113,17 @@ module RubyLsp
107
113
  # no sigil, Sorbet will still provide completion for constants
108
114
  return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
109
115
 
110
- name = constant_name(node)
116
+ name = RubyIndexer::Index.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
  )
@@ -155,7 +162,7 @@ module RubyLsp
155
162
  if (receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)) &&
156
163
  node.call_operator == "::"
157
164
 
158
- name = constant_name(receiver)
165
+ name = RubyIndexer::Index.constant_name(receiver)
159
166
 
160
167
  if name
161
168
  start_loc = node.location
@@ -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
@@ -402,6 +471,9 @@ module RubyLsp
402
471
  path_node_to_complete,
403
472
  )
404
473
  end
474
+ rescue Errno::EPERM
475
+ # If the user writes a relative require pointing to a path that the editor has no permissions to read, then glob
476
+ # might fail with EPERM
405
477
  end
406
478
 
407
479
  sig { params(node: Prism::CallNode, name: String).void }
@@ -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
 
@@ -112,7 +118,7 @@ module RubyLsp
112
118
 
113
119
  sig { params(node: Prism::ConstantPathNode).void }
114
120
  def on_constant_path_node_enter(node)
115
- name = constant_name(node)
121
+ name = RubyIndexer::Index.constant_name(node)
116
122
  return if name.nil?
117
123
 
118
124
  find_in_index(name)
@@ -120,7 +126,7 @@ module RubyLsp
120
126
 
121
127
  sig { params(node: Prism::ConstantReadNode).void }
122
128
  def on_constant_read_node_enter(node)
123
- name = constant_name(node)
129
+ name = RubyIndexer::Index.constant_name(node)
124
130
  return if name.nil?
125
131
 
126
132
  find_in_index(name)
@@ -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,12 @@ 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
+ full_path = uri.full_path
334
+ next if sorbet_level_true_or_higher?(@sorbet_level) && (!full_path || not_in_dependencies?(full_path))
280
335
 
281
336
  @response_builder << Interface::LocationLink.new(
282
- target_uri: URI::Generic.from_path(path: file_path).to_s,
337
+ target_uri: uri.to_s,
283
338
  target_range: range_from_location(target_method.location),
284
339
  target_selection_range: range_from_location(target_method.name_location),
285
340
  )
@@ -290,20 +345,22 @@ module RubyLsp
290
345
  def handle_require_definition(node, message)
291
346
  case message
292
347
  when :require
293
- entry = @index.search_require_paths(node.content).find do |indexable_path|
294
- indexable_path.require_path == node.content
348
+ entry = @index.search_require_paths(node.content).find do |uri|
349
+ uri.require_path == node.content
295
350
  end
296
351
 
297
352
  if entry
298
353
  candidate = entry.full_path
299
354
 
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
- )
355
+ if candidate
356
+ @response_builder << Interface::Location.new(
357
+ uri: URI::Generic.from_path(path: candidate).to_s,
358
+ range: Interface::Range.new(
359
+ start: Interface::Position.new(line: 0, character: 0),
360
+ end: Interface::Position.new(line: 0, character: 0),
361
+ ),
362
+ )
363
+ end
307
364
  end
308
365
  when :require_relative
309
366
  required_file = "#{node.content}.rb"
@@ -346,11 +403,15 @@ module RubyLsp
346
403
  # If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
347
404
  # additional behavior on top of jumping to RBIs. The only sigil where Sorbet cannot handle constants is typed
348
405
  # ignore
349
- file_path = entry.file_path
350
- next if @sorbet_level != RubyDocument::SorbetLevel::Ignore && not_in_dependencies?(file_path)
406
+ uri = entry.uri
407
+ full_path = uri.full_path
408
+
409
+ if @sorbet_level != RubyDocument::SorbetLevel::Ignore && (!full_path || not_in_dependencies?(full_path))
410
+ next
411
+ end
351
412
 
352
413
  @response_builder << Interface::LocationLink.new(
353
- target_uri: URI::Generic.from_path(path: file_path).to_s,
414
+ target_uri: uri.to_s,
354
415
  target_range: range_from_location(entry.location),
355
416
  target_selection_range: range_from_location(entry.name_location),
356
417
  )
@@ -120,7 +120,7 @@ module RubyLsp
120
120
  [target, node_value(target)]
121
121
  when Prism::ModuleNode, Prism::ClassNode, Prism::SingletonClassNode, Prism::DefNode, Prism::CaseNode,
122
122
  Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::IfNode, Prism::UnlessNode
123
- target
123
+ [target, nil]
124
124
  end
125
125
 
126
126
  @target = T.let(highlight_target, T.nilable(Prism::Node))
@@ -620,7 +620,8 @@ module RubyLsp
620
620
 
621
621
  sig { params(keyword_loc: T.nilable(Prism::Location), end_loc: T.nilable(Prism::Location)).void }
622
622
  def add_matching_end_highlights(keyword_loc, end_loc)
623
- return unless keyword_loc && end_loc && end_loc.length.positive?
623
+ return unless keyword_loc && end_loc
624
+ return unless end_loc.length.positive?
624
625
  return unless covers_target_position?(keyword_loc) || covers_target_position?(end_loc)
625
626
 
626
627
  add_highlight(Constant::DocumentHighlightKind::TEXT, keyword_loc)
@@ -124,11 +124,26 @@ module RubyLsp
124
124
  match = comment.location.slice.match(%r{source://.*#\d+$})
125
125
  return unless match
126
126
 
127
- uri = T.cast(URI(T.must(match[0])), URI::Source)
127
+ uri = T.cast(
128
+ begin
129
+ URI(T.must(match[0]))
130
+ rescue URI::Error
131
+ nil
132
+ end,
133
+ T.nilable(URI::Source),
134
+ )
135
+ return unless uri
136
+
128
137
  gem_version = resolve_version(uri)
129
138
  return if gem_version.nil?
130
139
 
131
- file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(uri.path))
140
+ path = uri.path
141
+ return unless path
142
+
143
+ gem_name = uri.gem_name
144
+ return unless gem_name
145
+
146
+ file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
132
147
  return if file_path.nil?
133
148
 
134
149
  @response_builder << Interface::DocumentLink.new(
@@ -149,7 +164,10 @@ module RubyLsp
149
164
 
150
165
  return @gem_version unless @gem_version.nil? || @gem_version.empty?
151
166
 
152
- GEM_TO_VERSION_MAP[uri.gem_name]
167
+ gem_name = uri.gem_name
168
+ return unless gem_name
169
+
170
+ GEM_TO_VERSION_MAP[gem_name]
153
171
  end
154
172
  end
155
173
  end
@@ -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
 
@@ -102,7 +114,7 @@ module RubyLsp
102
114
  def on_constant_read_node_enter(node)
103
115
  return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
104
116
 
105
- name = constant_name(node)
117
+ name = RubyIndexer::Index.constant_name(node)
106
118
  return if name.nil?
107
119
 
108
120
  generate_hover(name, node.location)
@@ -119,7 +131,7 @@ module RubyLsp
119
131
  def on_constant_path_node_enter(node)
120
132
  return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
121
133
 
122
- name = constant_name(node)
134
+ name = RubyIndexer::Index.constant_name(node)
123
135
  return if name.nil?
124
136
 
125
137
  generate_hover(name, node.location)
@@ -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)
@@ -6,11 +6,11 @@ require "sorbet-runtime"
6
6
  begin
7
7
  T::Configuration.default_checked_level = :never
8
8
  # Suppresses call validation errors
9
- T::Configuration.call_validation_error_handler = ->(*) {}
9
+ T::Configuration.call_validation_error_handler = ->(*arg) {}
10
10
  # Suppresses errors caused by T.cast, T.let, T.must, etc.
11
- T::Configuration.inline_type_error_handler = ->(*) {}
11
+ T::Configuration.inline_type_error_handler = ->(*arg) {}
12
12
  # Suppresses errors caused by incorrect parameter ordering
13
- T::Configuration.sig_validation_error_handler = ->(*) {}
13
+ T::Configuration.sig_validation_error_handler = ->(*arg) {}
14
14
  rescue
15
15
  # Need this rescue so that if another gem has
16
16
  # already set the checked level by the time we
@@ -8,8 +8,8 @@ module RubyLsp
8
8
 
9
9
  ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
10
10
 
11
- sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
12
- def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
11
+ sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
12
+ def initialize(source:, version:, uri:, global_state:)
13
13
  @syntax_error = T.let(false, T::Boolean)
14
14
  super
15
15
  end
@@ -42,6 +42,10 @@ module RubyLsp
42
42
  refactor_method
43
43
  when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
44
44
  switch_block_style
45
+ when CodeActions::CREATE_ATTRIBUTE_READER,
46
+ CodeActions::CREATE_ATTRIBUTE_WRITER,
47
+ CodeActions::CREATE_ATTRIBUTE_ACCESSOR
48
+ create_attribute_accessor
45
49
  else
46
50
  Error::UnknownCodeAction
47
51
  end
@@ -92,9 +96,7 @@ module RubyLsp
92
96
  source_range = @code_action.dig(:data, :range)
93
97
  return Error::EmptySelection if source_range[:start] == source_range[:end]
94
98
 
95
- scanner = @document.create_scanner
96
- start_index = scanner.find_char_position(source_range[:start])
97
- end_index = scanner.find_char_position(source_range[:end])
99
+ start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
98
100
  extracted_source = T.must(@document.source[start_index...end_index])
99
101
 
100
102
  # Find the closest statements node, so that we place the refactor in a valid position
@@ -192,9 +194,7 @@ module RubyLsp
192
194
  source_range = @code_action.dig(:data, :range)
193
195
  return Error::EmptySelection if source_range[:start] == source_range[:end]
194
196
 
195
- scanner = @document.create_scanner
196
- start_index = scanner.find_char_position(source_range[:start])
197
- end_index = scanner.find_char_position(source_range[:end])
197
+ start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
198
198
  extracted_source = T.must(@document.source[start_index...end_index])
199
199
 
200
200
  # Find the closest method declaration node, so that we place the refactor in a valid position
@@ -329,6 +329,90 @@ module RubyLsp
329
329
 
330
330
  indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
331
331
  end
332
+
333
+ sig { returns(T.any(Interface::CodeAction, Error)) }
334
+ def create_attribute_accessor
335
+ source_range = @code_action.dig(:data, :range)
336
+
337
+ node = if source_range[:start] != source_range[:end]
338
+ @document.locate_first_within_range(
339
+ @code_action.dig(:data, :range),
340
+ node_types: CodeActions::INSTANCE_VARIABLE_NODES,
341
+ )
342
+ end
343
+
344
+ if node.nil?
345
+ node_context = @document.locate_node(
346
+ source_range[:start],
347
+ node_types: CodeActions::INSTANCE_VARIABLE_NODES,
348
+ )
349
+ node = node_context.node
350
+
351
+ return Error::EmptySelection unless CodeActions::INSTANCE_VARIABLE_NODES.include?(node.class)
352
+ end
353
+
354
+ node = T.cast(
355
+ node,
356
+ T.any(
357
+ Prism::InstanceVariableAndWriteNode,
358
+ Prism::InstanceVariableOperatorWriteNode,
359
+ Prism::InstanceVariableOrWriteNode,
360
+ Prism::InstanceVariableReadNode,
361
+ Prism::InstanceVariableTargetNode,
362
+ Prism::InstanceVariableWriteNode,
363
+ ),
364
+ )
365
+
366
+ node_context = @document.locate_node(
367
+ {
368
+ line: node.location.start_line,
369
+ character: node.location.start_character_column,
370
+ },
371
+ node_types: [
372
+ Prism::ClassNode,
373
+ Prism::ModuleNode,
374
+ Prism::SingletonClassNode,
375
+ ],
376
+ )
377
+ closest_node = node_context.node
378
+ return Error::InvalidTargetRange if closest_node.nil?
379
+
380
+ attribute_name = node.name[1..]
381
+ indentation = " " * (closest_node.location.start_column + 2)
382
+ attribute_accessor_source = T.must(
383
+ case @code_action[:title]
384
+ when CodeActions::CREATE_ATTRIBUTE_READER
385
+ "#{indentation}attr_reader :#{attribute_name}\n\n"
386
+ when CodeActions::CREATE_ATTRIBUTE_WRITER
387
+ "#{indentation}attr_writer :#{attribute_name}\n\n"
388
+ when CodeActions::CREATE_ATTRIBUTE_ACCESSOR
389
+ "#{indentation}attr_accessor :#{attribute_name}\n\n"
390
+ end,
391
+ )
392
+
393
+ target_start_line = closest_node.location.start_line
394
+ target_range = {
395
+ start: { line: target_start_line, character: 0 },
396
+ end: { line: target_start_line, character: 0 },
397
+ }
398
+
399
+ Interface::CodeAction.new(
400
+ title: @code_action[:title],
401
+ edit: Interface::WorkspaceEdit.new(
402
+ document_changes: [
403
+ Interface::TextDocumentEdit.new(
404
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
405
+ uri: @code_action.dig(:data, :uri),
406
+ version: nil,
407
+ ),
408
+ edits: [
409
+ create_text_edit(target_range, attribute_accessor_source),
410
+ ],
411
+ ),
412
+ ],
413
+ ),
414
+ )
415
+ end
332
416
  end
333
417
  end
334
418
  end