ruby-lsp 0.23.11 → 0.26.6

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 (121) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +10 -4
  5. data/exe/ruby-lsp-check +0 -4
  6. data/exe/ruby-lsp-launcher +46 -22
  7. data/exe/ruby-lsp-test-exec +6 -0
  8. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -2
  9. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -6
  10. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +85 -118
  11. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +159 -183
  12. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +10 -14
  13. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +130 -253
  14. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +189 -285
  15. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
  16. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +23 -27
  17. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +31 -59
  18. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +58 -68
  19. data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
  20. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
  21. data/lib/ruby_lsp/addon.rb +88 -86
  22. data/lib/ruby_lsp/base_server.rb +79 -65
  23. data/lib/ruby_lsp/client_capabilities.rb +16 -13
  24. data/lib/ruby_lsp/document.rb +205 -104
  25. data/lib/ruby_lsp/erb_document.rb +45 -47
  26. data/lib/ruby_lsp/global_state.rb +134 -86
  27. data/lib/ruby_lsp/internal.rb +8 -3
  28. data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
  29. data/lib/ruby_lsp/listeners/completion.rb +81 -76
  30. data/lib/ruby_lsp/listeners/definition.rb +78 -72
  31. data/lib/ruby_lsp/listeners/document_highlight.rb +149 -151
  32. data/lib/ruby_lsp/listeners/document_link.rb +93 -86
  33. data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
  34. data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
  35. data/lib/ruby_lsp/listeners/hover.rb +109 -117
  36. data/lib/ruby_lsp/listeners/inlay_hints.rb +8 -13
  37. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
  38. data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
  39. data/lib/ruby_lsp/listeners/spec_style.rb +231 -0
  40. data/lib/ruby_lsp/listeners/test_discovery.rb +107 -0
  41. data/lib/ruby_lsp/listeners/test_style.rb +207 -95
  42. data/lib/ruby_lsp/node_context.rb +12 -39
  43. data/lib/ruby_lsp/rbs_document.rb +10 -11
  44. data/lib/ruby_lsp/requests/code_action_resolve.rb +92 -66
  45. data/lib/ruby_lsp/requests/code_actions.rb +34 -31
  46. data/lib/ruby_lsp/requests/code_lens.rb +31 -21
  47. data/lib/ruby_lsp/requests/completion.rb +8 -21
  48. data/lib/ruby_lsp/requests/completion_resolve.rb +14 -12
  49. data/lib/ruby_lsp/requests/definition.rb +8 -20
  50. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  51. data/lib/ruby_lsp/requests/discover_tests.rb +20 -7
  52. data/lib/ruby_lsp/requests/document_highlight.rb +6 -16
  53. data/lib/ruby_lsp/requests/document_link.rb +6 -17
  54. data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
  55. data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
  56. data/lib/ruby_lsp/requests/formatting.rb +6 -9
  57. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +139 -0
  58. data/lib/ruby_lsp/requests/hover.rb +12 -25
  59. data/lib/ruby_lsp/requests/inlay_hints.rb +8 -19
  60. data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
  61. data/lib/ruby_lsp/requests/prepare_rename.rb +5 -10
  62. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
  63. data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
  64. data/lib/ruby_lsp/requests/references.rb +17 -57
  65. data/lib/ruby_lsp/requests/rename.rb +27 -51
  66. data/lib/ruby_lsp/requests/request.rb +13 -25
  67. data/lib/ruby_lsp/requests/selection_ranges.rb +7 -7
  68. data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
  69. data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
  70. data/lib/ruby_lsp/requests/signature_help.rb +9 -27
  71. data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
  72. data/lib/ruby_lsp/requests/support/common.rb +23 -61
  73. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  74. data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
  75. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
  76. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +13 -16
  77. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +34 -36
  78. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
  79. data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
  80. data/lib/ruby_lsp/requests/support/source_uri.rb +22 -33
  81. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
  82. data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
  83. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
  84. data/lib/ruby_lsp/requests/workspace_symbol.rb +24 -16
  85. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +6 -9
  86. data/lib/ruby_lsp/response_builders/document_symbol.rb +15 -21
  87. data/lib/ruby_lsp/response_builders/hover.rb +12 -18
  88. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  89. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +62 -91
  90. data/lib/ruby_lsp/response_builders/signature_help.rb +6 -8
  91. data/lib/ruby_lsp/response_builders/test_collection.rb +35 -13
  92. data/lib/ruby_lsp/ruby_document.rb +32 -98
  93. data/lib/ruby_lsp/scope.rb +7 -11
  94. data/lib/ruby_lsp/scripts/compose_bundle.rb +7 -5
  95. data/lib/ruby_lsp/server.rb +305 -198
  96. data/lib/ruby_lsp/setup_bundler.rb +160 -97
  97. data/lib/ruby_lsp/static_docs.rb +12 -7
  98. data/lib/ruby_lsp/store.rb +21 -49
  99. data/lib/ruby_lsp/test_helper.rb +3 -16
  100. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +241 -0
  101. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +145 -0
  102. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +92 -0
  103. data/lib/ruby_lsp/type_inferrer.rb +13 -14
  104. data/lib/ruby_lsp/utils.rb +138 -93
  105. data/static_docs/break.md +103 -0
  106. metadata +15 -34
  107. data/lib/ruby_indexer/test/class_variables_test.rb +0 -140
  108. data/lib/ruby_indexer/test/classes_and_modules_test.rb +0 -745
  109. data/lib/ruby_indexer/test/configuration_test.rb +0 -239
  110. data/lib/ruby_indexer/test/constant_test.rb +0 -402
  111. data/lib/ruby_indexer/test/enhancements_test.rb +0 -325
  112. data/lib/ruby_indexer/test/global_variable_test.rb +0 -49
  113. data/lib/ruby_indexer/test/index_test.rb +0 -2186
  114. data/lib/ruby_indexer/test/instance_variables_test.rb +0 -240
  115. data/lib/ruby_indexer/test/method_test.rb +0 -947
  116. data/lib/ruby_indexer/test/prefix_tree_test.rb +0 -150
  117. data/lib/ruby_indexer/test/rbs_indexer_test.rb +0 -386
  118. data/lib/ruby_indexer/test/reference_finder_test.rb +0 -330
  119. data/lib/ruby_indexer/test/test_case.rb +0 -51
  120. data/lib/ruby_indexer/test/uri_test.rb +0 -72
  121. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
4
  require "bundler"
6
5
  require "bundler/cli"
7
6
  require "bundler/cli/install"
@@ -12,63 +11,70 @@ require "digest"
12
11
  require "time"
13
12
  require "uri"
14
13
 
15
- # This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to use
16
- # the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
17
- # exact locked versions of dependencies.
14
+ # This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
15
+ # use the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
16
+ # the exact locked versions of dependencies.
18
17
 
19
18
  Bundler.ui.level = :silent
20
19
 
21
20
  module RubyLsp
22
21
  class SetupBundler
23
- extend T::Sig
24
-
25
22
  class BundleNotLocked < StandardError; end
26
23
  class BundleInstallFailure < StandardError; end
27
24
 
28
- FOUR_HOURS = T.let(4 * 60 * 60, Integer)
25
+ module ThorPatch
26
+ #: -> IO
27
+ def stdout
28
+ $stderr
29
+ end
30
+ end
31
+
32
+ FOUR_HOURS = 4 * 60 * 60 #: Integer
29
33
 
30
- sig { params(project_path: String, options: T.untyped).void }
34
+ # Gems that should be kept up to date in the composed bundle. When updating, any of these gems that are not
35
+ # already in the user's Gemfile will be updated together.
36
+ GEMS_TO_UPDATE = ["ruby-lsp", "debug", "prism", "rbs"].freeze #: Array[String]
37
+
38
+ #: (String project_path, **untyped options) -> void
31
39
  def initialize(project_path, **options)
32
40
  @project_path = project_path
33
- @branch = T.let(options[:branch], T.nilable(String))
34
- @launcher = T.let(options[:launcher], T.nilable(T::Boolean))
35
- patch_thor_to_print_progress_to_stderr! if @launcher
41
+ @branch = options[:branch] #: String?
42
+ @launcher = options[:launcher] #: bool?
43
+ force_output_to_stderr! if @launcher
36
44
 
37
45
  # Regular bundle paths
38
- @gemfile = T.let(
39
- begin
40
- Bundler.default_gemfile
41
- rescue Bundler::GemfileNotFound
42
- nil
43
- end,
44
- T.nilable(Pathname),
45
- )
46
- @lockfile = T.let(@gemfile ? Bundler.default_lockfile : nil, T.nilable(Pathname))
46
+ @gemfile = begin
47
+ Bundler.default_gemfile
48
+ rescue Bundler::GemfileNotFound
49
+ nil
50
+ end #: Pathname?
51
+ @lockfile = @gemfile ? Bundler.default_lockfile : nil #: Pathname?
47
52
 
48
- @gemfile_hash = T.let(@gemfile ? Digest::SHA256.hexdigest(@gemfile.read) : nil, T.nilable(String))
49
- @lockfile_hash = T.let(@lockfile&.exist? ? Digest::SHA256.hexdigest(@lockfile.read) : nil, T.nilable(String))
53
+ @gemfile_hash = @gemfile ? Digest::SHA256.hexdigest(@gemfile.read) : nil #: String?
54
+ @lockfile_hash = @lockfile&.exist? ? Digest::SHA256.hexdigest(@lockfile.read) : nil #: String?
50
55
 
51
- @gemfile_name = T.let(@gemfile&.basename&.to_s || "Gemfile", String)
56
+ @gemfile_name = @gemfile&.basename&.to_s || "Gemfile" #: String
52
57
 
53
58
  # Custom bundle paths
54
- @custom_dir = T.let(Pathname.new(".ruby-lsp").expand_path(@project_path), Pathname)
55
- @custom_gemfile = T.let(@custom_dir + @gemfile_name, Pathname)
56
- @custom_lockfile = T.let(@custom_dir + (@lockfile&.basename || "Gemfile.lock"), Pathname)
57
- @lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
58
- @last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
59
- @error_path = T.let(@custom_dir + "install_error", Pathname)
60
- @already_composed_path = T.let(@custom_dir + "bundle_is_composed", Pathname)
59
+ @custom_dir = Pathname.new(".ruby-lsp").expand_path(@project_path) #: Pathname
60
+ @custom_gemfile = @custom_dir + @gemfile_name #: Pathname
61
+ @custom_lockfile = @custom_dir + (@lockfile&.basename || "Gemfile.lock") #: Pathname
62
+ @lockfile_hash_path = @custom_dir + "main_lockfile_hash" #: Pathname
63
+ @last_updated_path = @custom_dir + "last_updated" #: Pathname
64
+ @error_path = @custom_dir + "install_error" #: Pathname
65
+ @already_composed_path = @custom_dir + "bundle_is_composed" #: Pathname
61
66
 
62
67
  dependencies, bundler_version = load_dependencies
63
- @dependencies = T.let(dependencies, T::Hash[String, T.untyped])
64
- @bundler_version = T.let(bundler_version, T.nilable(Gem::Version))
65
- @rails_app = T.let(rails_app?, T::Boolean)
66
- @retry = T.let(false, T::Boolean)
68
+ @dependencies = dependencies #: Hash[String, untyped]
69
+ @bundler_version = bundler_version #: Gem::Version?
70
+ @rails_app = rails_app? #: bool
71
+ @retry = false #: bool
72
+ @needs_update_path = @custom_dir + "needs_update" #: Pathname
67
73
  end
68
74
 
69
75
  # Sets up the composed bundle and returns the `BUNDLE_GEMFILE`, `BUNDLE_PATH` and `BUNDLE_APP_CONFIG` that should be
70
76
  # used for running the server
71
- sig { returns(T::Hash[String, String]) }
77
+ #: -> Hash[String, String]
72
78
  def setup!
73
79
  raise BundleNotLocked if !@launcher && @gemfile&.exist? && !@lockfile&.exist?
74
80
 
@@ -128,26 +134,23 @@ module RubyLsp
128
134
 
129
135
  private
130
136
 
131
- sig { returns(T::Hash[String, T.untyped]) }
137
+ #: -> Hash[String, untyped]
132
138
  def composed_bundle_dependencies
133
- @composed_bundle_dependencies ||= T.let(
134
- begin
135
- original_bundle_gemfile = ENV["BUNDLE_GEMFILE"]
136
-
137
- if @custom_lockfile.exist?
138
- ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
139
- Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
140
- else
141
- {}
142
- end
143
- ensure
144
- ENV["BUNDLE_GEMFILE"] = original_bundle_gemfile
145
- end,
146
- T.nilable(T::Hash[String, T.untyped]),
147
- )
139
+ @composed_bundle_dependencies ||= begin
140
+ original_bundle_gemfile = ENV["BUNDLE_GEMFILE"]
141
+
142
+ if @custom_lockfile.exist?
143
+ ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
144
+ Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
145
+ else
146
+ {}
147
+ end
148
+ ensure
149
+ ENV["BUNDLE_GEMFILE"] = original_bundle_gemfile
150
+ end #: Hash[String, untyped]?
148
151
  end
149
152
 
150
- sig { void }
153
+ #: -> void
151
154
  def write_custom_gemfile
152
155
  parts = [
153
156
  "# This custom gemfile is automatically generated by the Ruby LSP.",
@@ -188,7 +191,7 @@ module RubyLsp
188
191
  @custom_gemfile.write(content) unless @custom_gemfile.exist? && @custom_gemfile.read == content
189
192
  end
190
193
 
191
- sig { returns([T::Hash[String, T.untyped], T.nilable(Gem::Version)]) }
194
+ #: -> [Hash[String, untyped], Gem::Version?]
192
195
  def load_dependencies
193
196
  return [{}, nil] unless @lockfile&.exist?
194
197
 
@@ -208,7 +211,7 @@ module RubyLsp
208
211
  [dependencies, lockfile_parser.bundler_version]
209
212
  end
210
213
 
211
- sig { params(bundle_gemfile: T.nilable(Pathname)).returns(T::Hash[String, String]) }
214
+ #: (?Pathname? bundle_gemfile) -> Hash[String, String]
212
215
  def run_bundle_install(bundle_gemfile = @gemfile)
213
216
  env = bundler_settings_as_env
214
217
  env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
@@ -235,6 +238,15 @@ module RubyLsp
235
238
  # If no error occurred, then clear previous errors
236
239
  @error_path.delete if @error_path.exist?
237
240
  $stderr.puts("Ruby LSP> Composed bundle installation complete")
241
+ rescue Errno::EPIPE, Bundler::HTTPError
242
+ # There are cases where we expect certain errors to happen occasionally, and we don't want to write them to
243
+ # a file, which would report to telemetry on the next launch.
244
+ #
245
+ # - The $stderr pipe might be closed by the client, for example when closing the editor during running bundle
246
+ # install. This situation may happen because, while running bundle install, the server is not yet ready to
247
+ # receive shutdown requests and we may continue doing work until the process is killed.
248
+ # - Bundler might also encounter a network error.
249
+ @error_path.delete if @error_path.exist?
238
250
  rescue => e
239
251
  # Write the error object to a file so that we can read it from the parent process
240
252
  @error_path.write(Marshal.dump(e))
@@ -259,45 +271,90 @@ module RubyLsp
259
271
  env
260
272
  end
261
273
 
262
- sig { params(env: T::Hash[String, String], force_install: T::Boolean).returns(T::Hash[String, String]) }
274
+ #: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
263
275
  def run_bundle_install_directly(env, force_install: false)
264
276
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
265
277
 
266
- # The ENV can only be merged after checking if an update is required because we depend on the original value of
267
- # ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
268
- should_update = should_bundle_update?
269
- T.unsafe(ENV).merge!(env)
278
+ # The should_bundle_update? check needs to run on the original Bundler environment, but everything else (like
279
+ # updating or running install) requires the modified environment. Here we compute the check ahead of time and
280
+ # merge the environment to ensure correct results.
281
+ #
282
+ # The symptoms of having these operations in the wrong order is seeing unwanted modifications in the application's
283
+ # main lockfile because we accidentally run update on the main bundle instead of the composed one.
284
+ needs_update = should_bundle_update?
285
+ ENV.merge!(env)
286
+
287
+ return update(env) if @needs_update_path.exist?
288
+
289
+ FileUtils.touch(@needs_update_path) if needs_update
270
290
 
271
- unless should_update && !force_install
272
- Bundler::CLI::Install.new({}).run
273
- correct_relative_remote_paths if @custom_lockfile.exist?
291
+ $stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
292
+
293
+ begin
294
+ missing_gems = bundle_check
295
+ rescue Errno::EPIPE, Bundler::HTTPError
296
+ # These are errors cases where we cannot recover
297
+ raise
298
+ rescue => e
299
+ # If anything fails with bundle check, try to bundle install
300
+ $stderr.puts("Ruby LSP> Running bundle install because #{e.message}")
301
+ bundle_install
274
302
  return env
275
303
  end
276
304
 
305
+ if missing_gems.empty?
306
+ $stderr.puts("Ruby LSP> Bundle already satisfied")
307
+ else
308
+ $stderr.puts(<<~MESSAGE)
309
+ Ruby LSP> Running bundle install because the following gems are not installed:
310
+ #{missing_gems.map { |g| "#{g.name}: #{g.version}" }.join("\n")}
311
+ MESSAGE
312
+
313
+ bundle_install
314
+ end
315
+
316
+ env
317
+ end
318
+
319
+ # Essentially the same as bundle check, but simplified
320
+ #: -> Array[Gem::Specification]
321
+ def bundle_check
322
+ definition = Bundler.definition
323
+ definition.validate_runtime!
324
+ definition.check!
325
+ definition.missing_specs
326
+ end
327
+
328
+ #: -> void
329
+ def bundle_install
330
+ Bundler::CLI::Install.new({ "no-cache" => true }).run
331
+ correct_relative_remote_paths if @custom_lockfile.exist?
332
+ end
333
+
334
+ #: (Hash[String, String]) -> Hash[String, String]
335
+ def update(env)
277
336
  # Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
278
337
  # source control changes
279
- gems = ["ruby-lsp", "debug", "prism"].reject { |dep| @dependencies[dep] }
338
+ gems = GEMS_TO_UPDATE.reject { |dep| @dependencies[dep] }
280
339
  gems << "ruby-lsp-rails" if @rails_app && !@dependencies["ruby-lsp-rails"]
281
340
 
282
341
  Bundler::CLI::Update.new({ conservative: true }, gems).run
283
342
  correct_relative_remote_paths if @custom_lockfile.exist?
343
+ @needs_update_path.delete
284
344
  @last_updated_path.write(Time.now.iso8601)
285
345
  env
286
- rescue Bundler::GemNotFound, Bundler::GitError
287
- # If a gem is not installed, skip the upgrade and try to install it with a single retry
288
- @retry ? env : run_bundle_install_directly(env, force_install: true)
289
346
  end
290
347
 
291
- sig { params(env: T::Hash[String, String]).returns(T::Hash[String, String]) }
348
+ #: (Hash[String, String] env) -> Hash[String, String]
292
349
  def run_bundle_install_through_command(env)
293
- # If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
294
- # to upgrade them or else we'll produce undesired source control changes. If the composed bundle was just created
295
- # and any of `ruby-lsp`, `ruby-lsp-rails` or `debug` weren't a part of the Gemfile, then we need to run `bundle
296
- # install` for the first time to generate the Gemfile.lock with them included or else Bundler will complain that
297
- # they're missing. We can only update if the custom `.ruby-lsp/Gemfile.lock` already exists and includes all gems
350
+ # If the gems in GEMS_TO_UPDATE (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't
351
+ # try to upgrade them or else we'll produce undesired source control changes. If the composed bundle was just
352
+ # created and any of those gems weren't a part of the Gemfile, then we need to run `bundle install` for the first
353
+ # time to generate the Gemfile.lock with them included or else Bundler will complain that they're missing. We can
354
+ # only update if the custom `.ruby-lsp/Gemfile.lock` already exists and includes all gems
298
355
 
299
356
  # When not updating, we run `(bundle check || bundle install)`
300
- # When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
357
+ # When updating, we run `((bundle check && bundle update <GEMS_TO_UPDATE>) || bundle install)`
301
358
  bundler_path = File.join(Gem.default_bindir, "bundle")
302
359
  base_command = (!Gem.win_platform? && File.exist?(bundler_path) ? "#{Gem.ruby} #{bundler_path}" : "bundle").dup
303
360
 
@@ -308,12 +365,11 @@ module RubyLsp
308
365
  command = +"(#{base_command} check"
309
366
 
310
367
  if should_bundle_update?
311
- # If any of `ruby-lsp`, `ruby-lsp-rails` or `debug` are not in the Gemfile, try to update them to the latest
312
- # version
368
+ # If any of the gems in GEMS_TO_UPDATE (or `ruby-lsp-rails` for Rails apps) are not in the Gemfile, try to
369
+ # update them to the latest version
313
370
  command.prepend("(")
314
371
  command << " && #{base_command} update "
315
- command << "ruby-lsp " unless @dependencies["ruby-lsp"]
316
- command << "debug " unless @dependencies["debug"]
372
+ GEMS_TO_UPDATE.each { |gem| command << "#{gem} " unless @dependencies[gem] }
317
373
  command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
318
374
  command.delete_suffix!(" ")
319
375
  command << ")"
@@ -345,7 +401,7 @@ module RubyLsp
345
401
  end
346
402
 
347
403
  # Gather all Bundler settings (global and local) and return them as a hash that can be used as the environment
348
- sig { returns(T::Hash[String, String]) }
404
+ #: -> Hash[String, String]
349
405
  def bundler_settings_as_env
350
406
  local_config_path = File.join(@project_path, ".bundle")
351
407
 
@@ -357,10 +413,17 @@ module RubyLsp
357
413
  Bundler::Settings.new
358
414
  end
359
415
 
416
+ # List of Bundler settings that don't make sense for the composed bundle and are better controlled manually by the
417
+ # user
418
+ ignored_settings = ["bin", "cache_all", "cache_all_platforms"]
419
+
360
420
  # Map all settings to their environment variable names with `key_for` and their values. For example, the if the
361
421
  # setting name `e` is `path` with a value of `vendor/bundle`, then it will return `"BUNDLE_PATH" =>
362
422
  # "vendor/bundle"`
363
- settings.all.to_h do |e|
423
+ settings
424
+ .all
425
+ .reject { |setting| ignored_settings.include?(setting) }
426
+ .to_h do |e|
364
427
  key = settings.key_for(e)
365
428
  value = Array(settings[e]).join(":").tr(" ", ":")
366
429
 
@@ -368,7 +431,7 @@ module RubyLsp
368
431
  end
369
432
  end
370
433
 
371
- sig { void }
434
+ #: -> void
372
435
  def install_bundler_if_needed
373
436
  # Try to find the bundler version specified in the lockfile in installed gems. If not found, install it
374
437
  requirement = Gem::Requirement.new(@bundler_version.to_s)
@@ -377,7 +440,7 @@ module RubyLsp
377
440
  Gem.install("bundler", @bundler_version.to_s)
378
441
  end
379
442
 
380
- sig { returns(T::Boolean) }
443
+ #: -> bool
381
444
  def should_bundle_update?
382
445
  # If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
383
446
  # will produce version control changes
@@ -400,16 +463,19 @@ module RubyLsp
400
463
 
401
464
  # When a lockfile has remote references based on relative file paths, we need to ensure that they are pointing to
402
465
  # the correct place since after copying the relative path is no longer valid
403
- sig { void }
466
+ #: -> void
404
467
  def correct_relative_remote_paths
405
468
  content = @custom_lockfile.read
406
469
  content.gsub!(/remote: (.*)/) do |match|
407
- path = T.must(Regexp.last_match)[1]
470
+ last_match = Regexp.last_match #: as !nil
471
+ path = last_match[1]
408
472
 
409
473
  # We should only apply the correction if the remote is a relative path. It might also be a URI, like
410
474
  # `https://rubygems.org` or an absolute path, in which case we shouldn't do anything
411
475
  if path && !URI(path).scheme
412
- "remote: #{File.expand_path(path, T.must(@gemfile).dirname)}"
476
+ bundle_dir = @gemfile #: as !nil
477
+ .dirname
478
+ "remote: #{File.expand_path(path, bundle_dir)}"
413
479
  else
414
480
  match
415
481
  end
@@ -422,7 +488,7 @@ module RubyLsp
422
488
  end
423
489
 
424
490
  # Detects if the project is a Rails app by looking if the superclass of the main class is `Rails::Application`
425
- sig { returns(T::Boolean) }
491
+ #: -> bool
426
492
  def rails_app?
427
493
  config = Pathname.new("config/application.rb").expand_path
428
494
  application_contents = config.read(external_encoding: Encoding::UTF_8) if config.exist?
@@ -431,20 +497,17 @@ module RubyLsp
431
497
  /class .* < (::)?Rails::Application/.match?(application_contents)
432
498
  end
433
499
 
434
- sig { void }
435
- def patch_thor_to_print_progress_to_stderr!
436
- return unless defined?(Bundler::Thor::Shell::Basic)
437
-
438
- Bundler::Thor::Shell::Basic.prepend(Module.new do
439
- extend T::Sig
500
+ #: -> void
501
+ def force_output_to_stderr!
502
+ # Bundler and RubyGems have different UI objects used for printing. We need to ensure that both are configured to
503
+ # print only to stderr or else they'll break the connection with the editor
504
+ Gem::DefaultUserInteraction.ui = Gem::StreamUI.new($stdin, $stderr, $stderr, false)
440
505
 
441
- sig { returns(IO) }
442
- def stdout
443
- $stderr
444
- end
445
- end)
506
+ ui = Bundler.ui
507
+ ui.output_stream = :stderr if ui.respond_to?(:output_stream=)
508
+ ui.level = :info
446
509
 
447
- Bundler.ui.level = :info
510
+ Bundler::Thor::Shell::Basic.prepend(ThorPatch) if defined?(Bundler::Thor::Shell::Basic)
448
511
  end
449
512
  end
450
513
  end
@@ -3,13 +3,18 @@
3
3
 
4
4
  module RubyLsp
5
5
  # The path to the `static_docs` directory, where we keep long-form static documentation
6
- STATIC_DOCS_PATH = T.let(File.join(File.dirname(File.dirname(T.must(__dir__))), "static_docs"), String)
6
+ STATIC_DOCS_PATH = File.join(
7
+ File.dirname(
8
+ File.dirname(
9
+ __dir__, #: as !nil
10
+ ),
11
+ ),
12
+ "static_docs",
13
+ ) #: String
7
14
 
8
15
  # A map of keyword => short documentation to be displayed on hover or completion
9
- KEYWORD_DOCS = T.let(
10
- {
11
- "yield" => "Invokes the passed block with the given arguments",
12
- }.freeze,
13
- T::Hash[String, String],
14
- )
16
+ KEYWORD_DOCS = {
17
+ "break" => "Terminates the execution of a block or loop",
18
+ "yield" => "Invokes the passed block with the given arguments",
19
+ }.freeze #: Hash[String, String]
15
20
  end
@@ -3,34 +3,19 @@
3
3
 
4
4
  module RubyLsp
5
5
  class Store
6
- extend T::Sig
7
-
8
6
  class NonExistingDocumentError < StandardError; end
9
7
 
10
- sig { returns(T::Hash[Symbol, RequestConfig]) }
11
- attr_accessor :features_configuration
12
-
13
- sig { returns(String) }
8
+ #: String
14
9
  attr_accessor :client_name
15
10
 
16
- sig { params(global_state: GlobalState).void }
11
+ #: (GlobalState global_state) -> void
17
12
  def initialize(global_state)
18
13
  @global_state = global_state
19
- @state = T.let({}, T::Hash[String, Document[T.untyped]])
20
- @features_configuration = T.let(
21
- {
22
- inlayHint: RequestConfig.new({
23
- enableAll: false,
24
- implicitRescue: false,
25
- implicitHashValue: false,
26
- }),
27
- },
28
- T::Hash[Symbol, RequestConfig],
29
- )
30
- @client_name = T.let("Unknown", String)
14
+ @state = {} #: Hash[String, Document[untyped]]
15
+ @client_name = "Unknown" #: String
31
16
  end
32
17
 
33
- sig { params(uri: URI::Generic).returns(Document[T.untyped]) }
18
+ #: (URI::Generic uri) -> Document[untyped]
34
19
  def get(uri)
35
20
  document = @state[uri.to_s]
36
21
  return document unless document.nil?
@@ -43,78 +28,65 @@ module RubyLsp
43
28
  ext = File.extname(path)
44
29
  language_id = case ext
45
30
  when ".erb", ".rhtml"
46
- Document::LanguageId::ERB
31
+ :erb
47
32
  when ".rbs"
48
- Document::LanguageId::RBS
33
+ :rbs
49
34
  else
50
- Document::LanguageId::Ruby
35
+ :ruby
51
36
  end
52
37
 
53
38
  set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
54
- T.must(@state[uri.to_s])
39
+ @state[uri.to_s] #: as !nil
55
40
  rescue Errno::ENOENT
56
41
  raise NonExistingDocumentError, uri.to_s
57
42
  end
58
43
 
59
- sig do
60
- params(
61
- uri: URI::Generic,
62
- source: String,
63
- version: Integer,
64
- language_id: Document::LanguageId,
65
- ).returns(Document[T.untyped])
66
- end
44
+ #: (uri: URI::Generic, source: String, version: Integer, language_id: Symbol) -> Document[untyped]
67
45
  def set(uri:, source:, version:, language_id:)
68
46
  @state[uri.to_s] = case language_id
69
- when Document::LanguageId::ERB
47
+ when :erb
70
48
  ERBDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
71
- when Document::LanguageId::RBS
49
+ when :rbs
72
50
  RBSDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
73
51
  else
74
52
  RubyDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
75
53
  end
76
54
  end
77
55
 
78
- sig { params(uri: URI::Generic, edits: T::Array[T::Hash[Symbol, T.untyped]], version: Integer).void }
56
+ #: (uri: URI::Generic, edits: Array[Hash[Symbol, untyped]], version: Integer) -> void
79
57
  def push_edits(uri:, edits:, version:)
80
- T.must(@state[uri.to_s]).push_edits(edits, version: version)
58
+ @state[uri.to_s] #: as !nil
59
+ .push_edits(edits, version: version)
81
60
  end
82
61
 
83
- sig { void }
62
+ #: -> void
84
63
  def clear
85
64
  @state.clear
86
65
  end
87
66
 
88
- sig { returns(T::Boolean) }
67
+ #: -> bool
89
68
  def empty?
90
69
  @state.empty?
91
70
  end
92
71
 
93
- sig { params(uri: URI::Generic).void }
72
+ #: (URI::Generic uri) -> void
94
73
  def delete(uri)
95
74
  @state.delete(uri.to_s)
96
75
  end
97
76
 
98
- sig { params(uri: URI::Generic).returns(T::Boolean) }
77
+ #: (URI::Generic uri) -> bool
99
78
  def key?(uri)
100
79
  @state.key?(uri.to_s)
101
80
  end
102
81
 
103
- sig { params(block: T.proc.params(uri: String, document: Document[T.untyped]).void).void }
82
+ #: { (String uri, Document[untyped] document) -> void } -> void
104
83
  def each(&block)
105
84
  @state.each do |uri, document|
106
85
  block.call(uri, document)
107
86
  end
108
87
  end
109
88
 
110
- sig do
111
- type_parameters(:T)
112
- .params(
113
- uri: URI::Generic,
114
- request_name: String,
115
- block: T.proc.params(document: Document[T.untyped]).returns(T.type_parameter(:T)),
116
- ).returns(T.type_parameter(:T))
117
- end
89
+ #: [T] (URI::Generic uri, String request_name) { (Document[untyped] document) -> T } -> T
118
90
  def cache_fetch(uri, request_name, &block)
119
91
  get(uri).cache_fetch(request_name, &block)
120
92
  end
@@ -4,24 +4,11 @@
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
+ # @requires_ancestor: Kernel
7
8
  module TestHelper
8
9
  class TestError < StandardError; end
9
10
 
10
- extend T::Sig
11
- extend T::Helpers
12
-
13
- requires_ancestor { Kernel }
14
-
15
- sig do
16
- type_parameters(:T)
17
- .params(
18
- source: T.nilable(String),
19
- uri: URI::Generic,
20
- stub_no_typechecker: T::Boolean,
21
- load_addons: T::Boolean,
22
- block: T.proc.params(server: RubyLsp::Server, uri: URI::Generic).returns(T.type_parameter(:T)),
23
- ).returns(T.type_parameter(:T))
24
- end
11
+ #: [T] (?String? source, ?URI::Generic uri, ?stub_no_typechecker: bool, ?load_addons: bool) { (RubyLsp::Server server, URI::Generic uri) -> T } -> T
25
12
  def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true,
26
13
  &block)
27
14
  server = RubyLsp::Server.new(test_mode: true)
@@ -58,7 +45,7 @@ module RubyLsp
58
45
  end
59
46
  end
60
47
 
61
- sig { params(server: RubyLsp::Server).returns(RubyLsp::Result) }
48
+ #: (RubyLsp::Server server) -> RubyLsp::Result
62
49
  def pop_result(server)
63
50
  result = server.pop_response
64
51
  result = server.pop_response until result.is_a?(RubyLsp::Result) || result.is_a?(RubyLsp::Error)