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,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