ruby-lsp 0.22.1 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
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,51 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # The
7
+ # [prepare_rename](https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename)
8
+ # # request checks the validity of a rename operation at a given location.
9
+ class PrepareRename < Request
10
+ extend T::Sig
11
+ include Support::Common
12
+
13
+ sig do
14
+ params(
15
+ document: RubyDocument,
16
+ position: T::Hash[Symbol, T.untyped],
17
+ ).void
18
+ end
19
+ def initialize(document, position)
20
+ super()
21
+ @document = document
22
+ @position = T.let(position, T::Hash[Symbol, Integer])
23
+ end
24
+
25
+ sig { override.returns(T.nilable(Interface::Range)) }
26
+ def perform
27
+ char_position = @document.create_scanner.find_char_position(@position)
28
+
29
+ node_context = RubyDocument.locate(
30
+ @document.parse_result.value,
31
+ char_position,
32
+ node_types: [Prism::ConstantReadNode, Prism::ConstantPathNode, Prism::ConstantPathTargetNode],
33
+ code_units_cache: @document.code_units_cache,
34
+ )
35
+ target = node_context.node
36
+ parent = node_context.parent
37
+ return if !target || target.is_a?(Prism::ProgramNode)
38
+
39
+ if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
40
+ target = determine_target(
41
+ target,
42
+ parent,
43
+ @position,
44
+ )
45
+ end
46
+
47
+ range_from_location(target.location)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -66,7 +66,7 @@ module RubyLsp
66
66
  Interface::TypeHierarchyItem.new(
67
67
  name: first_entry.name,
68
68
  kind: kind_for_entry(first_entry),
69
- uri: URI::Generic.from_path(path: first_entry.file_path).to_s,
69
+ uri: first_entry.uri.to_s,
70
70
  range: range,
71
71
  selection_range: range,
72
72
  ),
@@ -12,6 +12,15 @@ module RubyLsp
12
12
 
13
13
  class InvalidNameError < StandardError; end
14
14
 
15
+ class << self
16
+ extend T::Sig
17
+
18
+ sig { returns(Interface::RenameOptions) }
19
+ def provider
20
+ Interface::RenameOptions.new(prepare_provider: true)
21
+ end
22
+ end
23
+
15
24
  sig do
16
25
  params(
17
26
  global_state: GlobalState,
@@ -106,7 +115,9 @@ module RubyLsp
106
115
 
107
116
  T.must(@global_state.index[fully_qualified_name]).each do |entry|
108
117
  # Do not rename files that are not part of the workspace
109
- next unless entry.file_path.start_with?(@global_state.workspace_path)
118
+ uri = entry.uri
119
+ file_path = T.must(uri.full_path)
120
+ next unless file_path.start_with?(@global_state.workspace_path)
110
121
 
111
122
  case entry
112
123
  when RubyIndexer::Entry::Class, RubyIndexer::Entry::Module, RubyIndexer::Entry::Constant,
@@ -117,13 +128,12 @@ module RubyLsp
117
128
  if "#{file_name}.rb" == entry.file_name
118
129
  new_file_name = file_from_constant_name(T.must(@new_name.split("::").last))
119
130
 
120
- old_uri = URI::Generic.from_path(path: entry.file_path).to_s
121
131
  new_uri = URI::Generic.from_path(path: File.join(
122
- File.dirname(entry.file_path),
132
+ File.dirname(file_path),
123
133
  "#{new_file_name}.rb",
124
134
  )).to_s
125
135
 
126
- document_changes << Interface::RenameFile.new(kind: "rename", old_uri: old_uri, new_uri: new_uri)
136
+ document_changes << Interface::RenameFile.new(kind: "rename", old_uri: uri.to_s, new_uri: new_uri)
127
137
  end
128
138
  end
129
139
  end
@@ -93,11 +93,7 @@ module RubyLsp
93
93
  # based, which is why instead of the usual subtraction of 1 to line numbers, we are actually adding 1 to
94
94
  # columns. The format for VS Code file URIs is
95
95
  # `file:///path/to/file.rb#Lstart_line,start_column-end_line,end_column`
96
- uri = URI::Generic.from_path(
97
- path: entry.file_path,
98
- fragment: "L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}",
99
- )
100
-
96
+ uri = "#{entry.uri}#L#{loc.start_line},#{loc.start_column + 1}-#{loc.end_line},#{loc.end_column + 1}"
101
97
  definitions << "[#{entry.file_name}](#{uri})"
102
98
  content << "\n\n#{entry.comments}" unless entry.comments.empty?
103
99
  end
@@ -65,7 +65,7 @@ module RubyLsp
65
65
  Interface::TypeHierarchyItem.new(
66
66
  name: entry.name,
67
67
  kind: kind_for_entry(entry),
68
- uri: URI::Generic.from_path(path: entry.file_path).to_s,
68
+ uri: entry.uri.to_s,
69
69
  range: range_from_location(entry.location),
70
70
  selection_range: range_from_location(entry.name_location),
71
71
  detail: entry.file_name,
@@ -21,7 +21,8 @@ module RubyLsp
21
21
  sig { override.returns(T::Array[Interface::WorkspaceSymbol]) }
22
22
  def perform
23
23
  @index.fuzzy_search(@query).filter_map do |entry|
24
- file_path = entry.file_path
24
+ uri = entry.uri
25
+ file_path = T.must(uri.full_path)
25
26
 
26
27
  # We only show symbols declared in the workspace
27
28
  in_dependencies = !not_in_dependencies?(file_path)
@@ -43,7 +44,7 @@ module RubyLsp
43
44
  container_name: container.join("::"),
44
45
  kind: kind,
45
46
  location: Interface::Location.new(
46
- uri: URI::Generic.from_path(path: file_path).to_s,
47
+ uri: uri.to_s,
47
48
  range: Interface::Range.new(
48
49
  start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column),
49
50
  end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
@@ -5,7 +5,7 @@ def compose(raw_initialize)
5
5
  require_relative "../setup_bundler"
6
6
  require "json"
7
7
  require "uri"
8
- require_relative "../../core_ext/uri"
8
+ require "ruby_indexer/lib/ruby_indexer/uri"
9
9
 
10
10
  initialize_request = JSON.parse(raw_initialize, symbolize_names: true)
11
11
  workspace_uri = initialize_request.dig(:params, :workspaceFolders, 0, :uri)
@@ -71,6 +71,8 @@ module RubyLsp
71
71
  text_document_prepare_type_hierarchy(message)
72
72
  when "textDocument/rename"
73
73
  text_document_rename(message)
74
+ when "textDocument/prepareRename"
75
+ text_document_prepare_rename(message)
74
76
  when "textDocument/references"
75
77
  text_document_references(message)
76
78
  when "typeHierarchy/supertypes"
@@ -117,12 +119,24 @@ module RubyLsp
117
119
  # If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
118
120
  # from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
119
121
  # reporting these to our telemetry
120
- if e.is_a?(Store::NonExistingDocumentError)
122
+ case e
123
+ when Store::NonExistingDocumentError
121
124
  send_message(Error.new(
122
125
  id: message[:id],
123
126
  code: Constant::ErrorCodes::INVALID_PARAMS,
124
127
  message: e.full_message,
125
128
  ))
129
+ when Document::LocationNotFoundError
130
+ send_message(Error.new(
131
+ id: message[:id],
132
+ code: Constant::ErrorCodes::REQUEST_FAILED,
133
+ message: <<~MESSAGE,
134
+ Request #{message[:method]} failed to find the target position.
135
+ The file might have been modified while the server was in the middle of searching for the target.
136
+ If you experience this regularly, please report any findings and extra information on
137
+ https://github.com/Shopify/ruby-lsp/issues/2446
138
+ MESSAGE
139
+ ))
126
140
  else
127
141
  send_message(Error.new(
128
142
  id: message[:id],
@@ -237,6 +251,7 @@ module RubyLsp
237
251
  completion_provider = Requests::Completion.provider if enabled_features["completion"]
238
252
  signature_help_provider = Requests::SignatureHelp.provider if enabled_features["signatureHelp"]
239
253
  type_hierarchy_provider = Requests::PrepareTypeHierarchy.provider if enabled_features["typeHierarchy"]
254
+ rename_provider = Requests::Rename.provider unless @global_state.has_type_checker
240
255
 
241
256
  response = {
242
257
  capabilities: Interface::ServerCapabilities.new(
@@ -263,7 +278,7 @@ module RubyLsp
263
278
  workspace_symbol_provider: enabled_features["workspaceSymbol"] && !@global_state.has_type_checker,
264
279
  signature_help_provider: signature_help_provider,
265
280
  type_hierarchy_provider: type_hierarchy_provider,
266
- rename_provider: !@global_state.has_type_checker,
281
+ rename_provider: rename_provider,
267
282
  references_provider: !@global_state.has_type_checker,
268
283
  document_range_formatting_provider: true,
269
284
  experimental: {
@@ -727,6 +742,24 @@ module RubyLsp
727
742
  send_message(Error.new(id: message[:id], code: Constant::ErrorCodes::REQUEST_FAILED, message: e.message))
728
743
  end
729
744
 
745
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
746
+ def text_document_prepare_rename(message)
747
+ params = message[:params]
748
+ document = @store.get(params.dig(:textDocument, :uri))
749
+
750
+ unless document.is_a?(RubyDocument)
751
+ send_empty_response(message[:id])
752
+ return
753
+ end
754
+
755
+ send_message(
756
+ Result.new(
757
+ id: message[:id],
758
+ response: Requests::PrepareRename.new(document, params[:position]).perform,
759
+ ),
760
+ )
761
+ end
762
+
730
763
  sig { params(message: T::Hash[Symbol, T.untyped]).void }
731
764
  def text_document_references(message)
732
765
  params = message[:params]
@@ -981,15 +1014,17 @@ module RubyLsp
981
1014
  next unless file_path.end_with?(".rb")
982
1015
 
983
1016
  load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
984
- indexable = RubyIndexer::IndexablePath.new(load_path_entry, file_path)
1017
+ uri.add_require_path_from_load_entry(load_path_entry) if load_path_entry
1018
+
1019
+ content = File.read(file_path)
985
1020
 
986
1021
  case change[:type]
987
1022
  when Constant::FileChangeType::CREATED
988
- index.index_single(indexable)
1023
+ index.index_single(uri, content)
989
1024
  when Constant::FileChangeType::CHANGED
990
- index.handle_change(indexable)
1025
+ index.handle_change(uri, content)
991
1026
  when Constant::FileChangeType::DELETED
992
- index.delete(indexable)
1027
+ index.delete(uri)
993
1028
  end
994
1029
  end
995
1030
 
@@ -1088,7 +1123,7 @@ module RubyLsp
1088
1123
 
1089
1124
  sig { override.void }
1090
1125
  def shutdown
1091
- Addon.addons.each(&:deactivate)
1126
+ Addon.unload_addons
1092
1127
  end
1093
1128
 
1094
1129
  sig { void }
@@ -152,7 +152,13 @@ module RubyLsp
152
152
  end
153
153
 
154
154
  unless @dependencies["debug"]
155
- parts << 'gem "debug", require: false, group: :development, platforms: :mri'
155
+ # The `mri` platform excludes Windows. We want to install the debug gem only on MRI for any operating system,
156
+ # but that constraint doesn't yet exist in Bundler. On Windows, we are manually checking if the engine is MRI
157
+ parts << if Gem.win_platform?
158
+ 'gem "debug", require: false, group: :development, install_if: -> { RUBY_ENGINE == "ruby" }'
159
+ else
160
+ 'gem "debug", require: false, group: :development, platforms: :mri'
161
+ end
156
162
  end
157
163
 
158
164
  if @rails_app && !@dependencies["ruby-lsp-rails"]
@@ -196,15 +202,15 @@ module RubyLsp
196
202
  env["BUNDLE_PATH"] = File.expand_path(env["BUNDLE_PATH"], @project_path)
197
203
  end
198
204
 
199
- return run_bundle_install_through_command(env) unless @launcher
200
-
201
- # This same check happens conditionally when running through the command. For invoking the CLI directly, it's
202
- # important that we ensure the Bundler version is set to avoid restarts
205
+ # Set the specific Bundler version used by the main app. This avoids issues with Bundler restarts, which clean the
206
+ # environment and lead to the `ruby-lsp` executable not being found
203
207
  if @bundler_version
204
208
  env["BUNDLER_VERSION"] = @bundler_version.to_s
205
209
  install_bundler_if_needed
206
210
  end
207
211
 
212
+ return run_bundle_install_through_command(env) unless @launcher
213
+
208
214
  begin
209
215
  run_bundle_install_directly(env)
210
216
  # If no error occurred, then clear previous errors
@@ -235,12 +241,16 @@ module RubyLsp
235
241
  env
236
242
  end
237
243
 
238
- sig { params(env: T::Hash[String, String]).returns(T::Hash[String, String]) }
239
- def run_bundle_install_directly(env)
244
+ sig { params(env: T::Hash[String, String], force_install: T::Boolean).returns(T::Hash[String, String]) }
245
+ def run_bundle_install_directly(env, force_install: false)
240
246
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
247
+
248
+ # The ENV can only be merged after checking if an update is required because we depend on the original value of
249
+ # ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
250
+ should_update = should_bundle_update?
241
251
  T.unsafe(ENV).merge!(env)
242
252
 
243
- unless should_bundle_update?
253
+ unless should_update && !force_install
244
254
  Bundler::CLI::Install.new({}).run
245
255
  correct_relative_remote_paths if @custom_lockfile.exist?
246
256
  return env
@@ -255,12 +265,13 @@ module RubyLsp
255
265
  correct_relative_remote_paths if @custom_lockfile.exist?
256
266
  @last_updated_path.write(Time.now.iso8601)
257
267
  env
268
+ rescue Bundler::GemNotFound, Bundler::GitError
269
+ # If a gem is not installed, skip the upgrade and try to install it with a single retry
270
+ @retry ? env : run_bundle_install_directly(env, force_install: true)
258
271
  end
259
272
 
260
273
  sig { params(env: T::Hash[String, String]).returns(T::Hash[String, String]) }
261
274
  def run_bundle_install_through_command(env)
262
- base_bundle = base_bundle_command(env)
263
-
264
275
  # If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
265
276
  # to upgrade them or else we'll produce undesired source control changes. If the composed bundle was just created
266
277
  # and any of `ruby-lsp`, `ruby-lsp-rails` or `debug` weren't a part of the Gemfile, then we need to run `bundle
@@ -269,13 +280,20 @@ module RubyLsp
269
280
 
270
281
  # When not updating, we run `(bundle check || bundle install)`
271
282
  # When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
272
- command = +"(#{base_bundle} check"
283
+ bundler_path = File.join(Gem.default_bindir, "bundle")
284
+ base_command = (File.exist?(bundler_path) ? "#{Gem.ruby} #{bundler_path}" : "bundle").dup
285
+
286
+ if env["BUNDLER_VERSION"]
287
+ base_command << " _#{env["BUNDLER_VERSION"]}_"
288
+ end
289
+
290
+ command = +"(#{base_command} check"
273
291
 
274
292
  if should_bundle_update?
275
293
  # If any of `ruby-lsp`, `ruby-lsp-rails` or `debug` are not in the Gemfile, try to update them to the latest
276
294
  # version
277
295
  command.prepend("(")
278
- command << " && #{base_bundle} update "
296
+ command << " && #{base_command} update "
279
297
  command << "ruby-lsp " unless @dependencies["ruby-lsp"]
280
298
  command << "debug " unless @dependencies["debug"]
281
299
  command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
@@ -285,7 +303,7 @@ module RubyLsp
285
303
  @last_updated_path.write(Time.now.iso8601)
286
304
  end
287
305
 
288
- command << " || #{base_bundle} install) "
306
+ command << " || #{base_command} install) "
289
307
 
290
308
  # Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
291
309
  # responses
@@ -395,34 +413,6 @@ module RubyLsp
395
413
  /class .* < (::)?Rails::Application/.match?(application_contents)
396
414
  end
397
415
 
398
- # Returns the base bundle command we should use for this project, which will be:
399
- # - `bundle` if there's no locked Bundler version and no `bin/bundle` binstub in the $PATH
400
- # - `bundle _<version>_` if there's a locked Bundler version
401
- # - `bin/bundle` if there's a `bin/bundle` binstub in the $PATH
402
- sig { params(env: T::Hash[String, String]).returns(String) }
403
- def base_bundle_command(env)
404
- path_parts = if Gem.win_platform?
405
- ENV["Path"] || ENV["PATH"] || ENV["path"] || ""
406
- else
407
- ENV["PATH"] || ""
408
- end.split(File::PATH_SEPARATOR)
409
-
410
- bin_dir = File.expand_path("bin", @project_path)
411
- bundle_binstub = File.join(@project_path, "bin", "bundle")
412
-
413
- if File.exist?(bundle_binstub) && path_parts.any? { |path| File.expand_path(path, @project_path) == bin_dir }
414
- return bundle_binstub
415
- end
416
-
417
- if @bundler_version
418
- env["BUNDLER_VERSION"] = @bundler_version.to_s
419
- install_bundler_if_needed
420
- return "bundle _#{@bundler_version}_"
421
- end
422
-
423
- "bundle"
424
- end
425
-
426
416
  sig { void }
427
417
  def patch_thor_to_print_progress_to_stderr!
428
418
  return unless defined?(Bundler::Thor::Shell::Basic)
@@ -1,11 +1,16 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # NOTE: This module is intended to be used by addons for writing their own tests, so keep that in mind if changing.
5
5
 
6
6
  module RubyLsp
7
7
  module TestHelper
8
+ class TestError < StandardError; end
9
+
8
10
  extend T::Sig
11
+ extend T::Helpers
12
+
13
+ requires_ancestor { Kernel }
9
14
 
10
15
  sig do
11
16
  type_parameters(:T)
@@ -36,20 +41,49 @@ module RubyLsp
36
41
  },
37
42
  },
38
43
  })
44
+
45
+ server.global_state.index.index_single(uri, source)
39
46
  end
40
47
 
41
- server.global_state.index.index_single(
42
- RubyIndexer::IndexablePath.new(nil, T.must(uri.to_standardized_path)),
43
- source,
44
- )
45
48
  server.load_addons(include_project_addons: false) if load_addons
46
- block.call(server, uri)
47
- ensure
48
- if load_addons
49
- RubyLsp::Addon.addons.each(&:deactivate)
50
- RubyLsp::Addon.addons.clear
49
+
50
+ begin
51
+ block.call(server, uri)
52
+ ensure
53
+ if load_addons
54
+ RubyLsp::Addon.addons.each(&:deactivate)
55
+ RubyLsp::Addon.addons.clear
56
+ end
57
+ server.run_shutdown
58
+ end
59
+ end
60
+
61
+ sig { params(server: RubyLsp::Server).returns(RubyLsp::Result) }
62
+ def pop_result(server)
63
+ result = server.pop_response
64
+ result = server.pop_response until result.is_a?(RubyLsp::Result) || result.is_a?(RubyLsp::Error)
65
+
66
+ if result.is_a?(RubyLsp::Error)
67
+ raise TestError, "Failed to execute request #{result.message}"
68
+ else
69
+ result
51
70
  end
52
- T.must(server).run_shutdown
71
+ end
72
+
73
+ def pop_log_notification(message_queue, type)
74
+ log = message_queue.pop
75
+ return log if log.params.type == type
76
+
77
+ log = message_queue.pop until log.params.type == type
78
+ log
79
+ end
80
+
81
+ def pop_message(outgoing_queue, &block)
82
+ message = outgoing_queue.pop
83
+ return message if block.call(message)
84
+
85
+ message = outgoing_queue.pop until block.call(message)
86
+ message
53
87
  end
54
88
  end
55
89
  end
@@ -23,6 +23,9 @@ module RubyLsp
23
23
  Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
24
24
  Prism::SuperNode, Prism::ForwardingSuperNode
25
25
  self_receiver_handling(node_context)
26
+ when Prism::ClassVariableAndWriteNode, Prism::ClassVariableWriteNode, Prism::ClassVariableOperatorWriteNode,
27
+ Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
28
+ infer_receiver_for_class_variables(node_context)
26
29
  end
27
30
  end
28
31
 
@@ -143,6 +146,25 @@ module RubyLsp
143
146
  nil
144
147
  end
145
148
 
149
+ sig { params(node_context: NodeContext).returns(T.nilable(Type)) }
150
+ def infer_receiver_for_class_variables(node_context)
151
+ nesting_parts = node_context.nesting.dup
152
+
153
+ return Type.new("Object") if nesting_parts.empty?
154
+
155
+ nesting_parts.reverse_each do |part|
156
+ break unless part.include?("<Class:")
157
+
158
+ nesting_parts.pop
159
+ end
160
+
161
+ receiver_name = nesting_parts.join("::")
162
+ resolved_receiver = @index.resolve(receiver_name, node_context.nesting)&.first
163
+ return unless resolved_receiver&.name
164
+
165
+ Type.new(resolved_receiver.name)
166
+ end
167
+
146
168
  # A known type
147
169
  class Type
148
170
  extend T::Sig
@@ -173,6 +173,9 @@ module RubyLsp
173
173
  sig { returns(String) }
174
174
  attr_reader :message
175
175
 
176
+ sig { returns(Integer) }
177
+ attr_reader :code
178
+
176
179
  sig { params(id: Integer, code: Integer, message: String, data: T.nilable(T::Hash[Symbol, T.untyped])).void }
177
180
  def initialize(id:, code:, message:, data: nil)
178
181
  @id = id
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.1
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-11-22 00:00:00.000000000 Z
10
+ date: 2025-01-06 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: language_server-protocol
@@ -94,7 +93,6 @@ files:
94
93
  - exe/ruby-lsp
95
94
  - exe/ruby-lsp-check
96
95
  - exe/ruby-lsp-launcher
97
- - lib/core_ext/uri.rb
98
96
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
99
97
  - lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
100
98
  - lib/ruby-lsp.rb
@@ -103,12 +101,13 @@ files:
103
101
  - lib/ruby_indexer/lib/ruby_indexer/enhancement.rb
104
102
  - lib/ruby_indexer/lib/ruby_indexer/entry.rb
105
103
  - lib/ruby_indexer/lib/ruby_indexer/index.rb
106
- - lib/ruby_indexer/lib/ruby_indexer/indexable_path.rb
107
104
  - lib/ruby_indexer/lib/ruby_indexer/location.rb
108
105
  - lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
109
106
  - lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
110
107
  - lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb
108
+ - lib/ruby_indexer/lib/ruby_indexer/uri.rb
111
109
  - lib/ruby_indexer/ruby_indexer.rb
110
+ - lib/ruby_indexer/test/class_variables_test.rb
112
111
  - lib/ruby_indexer/test/classes_and_modules_test.rb
113
112
  - lib/ruby_indexer/test/configuration_test.rb
114
113
  - lib/ruby_indexer/test/constant_test.rb
@@ -121,6 +120,7 @@ files:
121
120
  - lib/ruby_indexer/test/rbs_indexer_test.rb
122
121
  - lib/ruby_indexer/test/reference_finder_test.rb
123
122
  - lib/ruby_indexer/test/test_case.rb
123
+ - lib/ruby_indexer/test/uri_test.rb
124
124
  - lib/ruby_lsp/addon.rb
125
125
  - lib/ruby_lsp/base_server.rb
126
126
  - lib/ruby_lsp/client_capabilities.rb
@@ -157,6 +157,7 @@ files:
157
157
  - lib/ruby_lsp/requests/hover.rb
158
158
  - lib/ruby_lsp/requests/inlay_hints.rb
159
159
  - lib/ruby_lsp/requests/on_type_formatting.rb
160
+ - lib/ruby_lsp/requests/prepare_rename.rb
160
161
  - lib/ruby_lsp/requests/prepare_type_hierarchy.rb
161
162
  - lib/ruby_lsp/requests/range_formatting.rb
162
163
  - lib/ruby_lsp/requests/references.rb
@@ -202,7 +203,6 @@ licenses:
202
203
  metadata:
203
204
  allowed_push_host: https://rubygems.org
204
205
  documentation_uri: https://shopify.github.io/ruby-lsp/
205
- post_install_message:
206
206
  rdoc_options: []
207
207
  require_paths:
208
208
  - lib
@@ -217,8 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
217
  - !ruby/object:Gem::Version
218
218
  version: '0'
219
219
  requirements: []
220
- rubygems_version: 3.5.23
221
- signing_key:
220
+ rubygems_version: 3.6.2
222
221
  specification_version: 4
223
222
  summary: An opinionated language server for Ruby
224
223
  test_files: []
@@ -1,29 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module RubyIndexer
5
- class IndexablePath
6
- extend T::Sig
7
-
8
- sig { returns(T.nilable(String)) }
9
- attr_reader :require_path
10
-
11
- sig { returns(String) }
12
- attr_reader :full_path
13
-
14
- # An IndexablePath is instantiated with a load_path_entry and a full_path. The load_path_entry is where the file can
15
- # be found in the $LOAD_PATH, which we use to determine the require_path. The load_path_entry may be `nil` if the
16
- # indexer is configured to go through files that do not belong in the $LOAD_PATH. For example,
17
- # `sorbet/tapioca/require.rb` ends up being a part of the paths to be indexed because it's a Ruby file inside the
18
- # project, but the `sorbet` folder is not a part of the $LOAD_PATH. That means that both its load_path_entry and
19
- # require_path will be `nil`, since it cannot be required by the project
20
- sig { params(load_path_entry: T.nilable(String), full_path: String).void }
21
- def initialize(load_path_entry, full_path)
22
- @full_path = full_path
23
- @require_path = T.let(
24
- load_path_entry ? full_path.delete_prefix("#{load_path_entry}/").delete_suffix(".rb") : nil,
25
- T.nilable(String),
26
- )
27
- end
28
- end
29
- end