ruby-lsp 0.25.0 → 0.26.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7637d6f68616069c2f8c0d870f3daf31808a1cccd8ab32d2bdaf1f67274f9b59
4
- data.tar.gz: 7ea1d75ad921adbf01f69a0f5d2fba905673311c13b0999d44c08bdf54737053
3
+ metadata.gz: 3e8b51ca9a97aa0e855fbf1109f07733aae195eb25dba844f79840665b36ac9a
4
+ data.tar.gz: 79d5c3af7ba9fb03c2da197b4213a861e366b7be5a60e677ac6ca9ae8269dbea
5
5
  SHA512:
6
- metadata.gz: a354535a3853b0826635408fc46dc85cb7cee97c4993012dd983af11b8917121962d97bddf0853406b54f619faa8c2383cccca9638b4a0624309fb1e4c838249
7
- data.tar.gz: 9e49e2436c63d90fc373b52ac9f6b65e83964fe108abd0a18fd46b655cd99bd55c471dd198624bb8701890f62175af2c9fcb50fcfa2bbf69e706406b9fa1e472
6
+ metadata.gz: 8cb30f7427f02460320becb1b883c502ea0fbfd48f5148192e1339350e94836ed6ba825df4b4b0fe0ff8d32325d6196af6a26ef94d566e4e32ecd235d66ed821
7
+ data.tar.gz: 68d5712e3ae16ecaa602eeccadcab5108d89cce1f1a282b82bd3244b3111131442ee6feda63b1e7049027624c07be25e5f3ae39a00b9df25130f12f43c2dac80
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.25.0
1
+ 0.26.0
@@ -1,18 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Append to RUBYOPT the necessary requires to hook our custom test reporters so that results are automatically
5
- # reflected in the test explorer
6
- rubyopt = [
7
- *ENV["RUBYOPT"],
8
- "-rbundler/setup",
9
- "-r#{File.expand_path("../lib/ruby_lsp/test_reporters/minitest_reporter", __dir__)}",
10
- "-r#{File.expand_path("../lib/ruby_lsp/test_reporters/test_unit_reporter", __dir__)}",
11
- ].join(" ")
12
-
13
- # Replace this process with whatever command was passed. We only want to set RUBYOPT.
14
- # The way you use this executable is by prefixing your test command with `ruby-lsp-test-exec`, like so:
15
- # ruby-lsp-test-exec bundle exec ruby -Itest test/example_test.rb
16
- # ruby-lsp-test-exec bundle exec ruby -Ispec spec/example_spec.rb
17
- # ruby-lsp-test-exec bundle exec rspec spec/example_spec.rb
18
- exec({ "RUBYOPT" => rubyopt }, *ARGV)
4
+ # This executable will be removed thanks to the changes in https://github.com/Shopify/ruby-lsp/pull/3661.
5
+ # Remove this a few months after extension updates have rolled out
6
+ exec(*ARGV)
@@ -333,6 +333,7 @@ module RubyIndexer
333
333
  end
334
334
 
335
335
  class Method < Member
336
+ # @override
336
337
  #: Array[Signature]
337
338
  attr_reader :signatures
338
339
 
@@ -291,6 +291,15 @@ module RubyIndexer
291
291
 
292
292
  # Top level constants
293
293
  entries.concat(@entries_tree.search(name))
294
+
295
+ # Filter only constants since methods may have names that look like constants
296
+ entries.each do |definitions|
297
+ definitions.select! do |entry|
298
+ entry.is_a?(Entry::Constant) || entry.is_a?(Entry::ConstantAlias) ||
299
+ entry.is_a?(Entry::Namespace) || entry.is_a?(Entry::UnresolvedConstantAlias)
300
+ end
301
+ end
302
+
294
303
  entries.uniq!
295
304
  entries #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]]
296
305
  end
@@ -59,7 +59,6 @@ module RubyIndexer
59
59
 
60
60
  assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
61
61
  assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
62
- assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/erb.rb")
63
62
  end
64
63
 
65
64
  def test_indexable_uris_includes_project_files
@@ -1983,6 +1983,18 @@ module RubyIndexer
1983
1983
  assert_equal(["XQRK"], result.map { |entries| entries.first&.name })
1984
1984
  end
1985
1985
 
1986
+ def test_constant_completion_does_not_confuse_uppercase_methods
1987
+ index(<<~RUBY)
1988
+ class Foo
1989
+ def Qux
1990
+ end
1991
+ end
1992
+ RUBY
1993
+
1994
+ candidates = @index.constant_completion_candidates("Q", [])
1995
+ refute_includes(candidates.flat_map { |entries| entries.map(&:name) }, "Qux")
1996
+ end
1997
+
1986
1998
  def test_constant_completion_candidates_for_empty_name
1987
1999
  index(<<~RUBY)
1988
2000
  module Foo
@@ -57,11 +57,26 @@ module RubyLsp
57
57
 
58
58
  if include_project_addons
59
59
  project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
60
-
61
- # Ignore add-ons from dependencies if the bundle is stored inside the project. We already found those with
62
- # `Gem.find_files`
63
60
  bundle_path = Bundler.bundle_path.to_s
64
- project_addons.reject! { |path| path.start_with?(bundle_path) }
61
+ gems_dir = Bundler.bundle_path.join("gems")
62
+
63
+ # Create an array of rejection glob patterns to ignore add-ons already discovered through Gem.find_files if
64
+ # they are also copied inside the workspace for whatever reason. We received reports of projects having gems
65
+ # installed in vendor/bundle despite BUNDLE_PATH pointing elsewhere. Without this mechanism, we will
66
+ # double-require the same add-on, potentially for different versions of the same gem, which leads to incorrect
67
+ # behavior
68
+ reject_glob_patterns = addon_files.map do |path|
69
+ relative_gem_path = Pathname.new(path).relative_path_from(gems_dir)
70
+ first_part, *parts = relative_gem_path.to_s.split(File::SEPARATOR)
71
+ first_part&.gsub!(/-([0-9.]+)$/, "*")
72
+ "**/#{first_part}/#{parts.join("/")}"
73
+ end
74
+
75
+ project_addons.reject! do |path|
76
+ path.start_with?(bundle_path) ||
77
+ reject_glob_patterns.any? { |pattern| File.fnmatch?(pattern, path, File::Constants::FNM_PATHNAME) }
78
+ end
79
+
65
80
  addon_files.concat(project_addons)
66
81
  end
67
82
 
@@ -83,8 +83,7 @@ module RubyLsp
83
83
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
84
84
  # else is pushed into the incoming queue
85
85
  case method
86
- when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange",
87
- "rubyLsp/diagnoseState"
86
+ when "initialize", "initialized", "rubyLsp/diagnoseState"
88
87
  process_message(message)
89
88
  when "shutdown"
90
89
  @global_state.synchronize do
@@ -94,26 +93,13 @@ module RubyLsp
94
93
  @writer.write(Result.new(id: message[:id], response: nil).to_hash)
95
94
  end
96
95
  when "exit"
97
- @global_state.synchronize { exit(@incoming_queue.closed? ? 0 : 1) }
96
+ exit(@incoming_queue.closed? ? 0 : 1)
98
97
  else
99
98
  @incoming_queue << message
100
99
  end
101
100
  end
102
101
  end
103
102
 
104
- #: -> void
105
- def run_shutdown
106
- @incoming_queue.clear
107
- @outgoing_queue.clear
108
- @incoming_queue.close
109
- @outgoing_queue.close
110
- @cancelled_requests.clear
111
-
112
- @worker.terminate
113
- @outgoing_dispatcher.terminate
114
- @store.clear
115
- end
116
-
117
103
  # This method is only intended to be used in tests! Pops the latest response that would be sent to the client
118
104
  #: -> untyped
119
105
  def pop_response
@@ -132,6 +118,26 @@ module RubyLsp
132
118
  raise AbstractMethodInvokedError
133
119
  end
134
120
 
121
+ #: -> bool?
122
+ def test_mode?
123
+ @test_mode
124
+ end
125
+
126
+ #: -> void
127
+ def run_shutdown
128
+ @incoming_queue.clear
129
+ @outgoing_queue.clear
130
+ @incoming_queue.close
131
+ @outgoing_queue.close
132
+ @cancelled_requests.clear
133
+
134
+ @worker.terminate
135
+ @outgoing_dispatcher.terminate
136
+ @store.clear
137
+ end
138
+
139
+ private
140
+
135
141
  # @abstract
136
142
  #: -> void
137
143
  def shutdown
@@ -139,12 +139,10 @@ module RubyLsp
139
139
 
140
140
  #: (Hash[Symbol, untyped] start_pos, ?Hash[Symbol, untyped]? end_pos) -> [Integer, Integer?]
141
141
  def find_index_by_position(start_pos, end_pos = nil)
142
- @global_state.synchronize do
143
- scanner = create_scanner
144
- start_index = scanner.find_char_position(start_pos)
145
- end_index = scanner.find_char_position(end_pos) if end_pos
146
- [start_index, end_index]
147
- end
142
+ scanner = create_scanner
143
+ start_index = scanner.find_char_position(start_pos)
144
+ end_index = scanner.find_char_position(end_pos) if end_pos
145
+ [start_index, end_index]
148
146
  end
149
147
 
150
148
  private
@@ -445,11 +445,14 @@ module RubyLsp
445
445
  return unless arguments_node
446
446
 
447
447
  path_node_to_complete = arguments_node.arguments.first
448
-
449
448
  return unless path_node_to_complete.is_a?(Prism::StringNode)
450
449
 
451
- origin_dir = Pathname.new(@uri.to_standardized_path).dirname
450
+ # If the file is unsaved (e.g.: untitled:Untitled-1), we can't provide relative completion as we don't know
451
+ # where the user intends to save it
452
+ full_path = @uri.to_standardized_path
453
+ return unless full_path
452
454
 
455
+ origin_dir = Pathname.new(full_path).dirname
453
456
  content = path_node_to_complete.content
454
457
  # if the path is not a directory, glob all possible next characters
455
458
  # for example ../somethi| (where | is the cursor position)
@@ -75,8 +75,9 @@ module RubyLsp
75
75
 
76
76
  unless full_files.empty?
77
77
  specs, tests = full_files.partition { |path| spec?(path) }
78
- commands << "#{BASE_COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{tests.join(" ")}" if tests.any?
79
- commands << "#{BASE_COMMAND} -Ispec -e \"ARGV.each { |f| require f }\" #{specs.join(" ")}" if specs.any?
78
+
79
+ commands << "#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{tests.join(" ")}" if tests.any?
80
+ commands << "#{COMMAND} -Ispec -e \"ARGV.each { |f| require f }\" #{specs.join(" ")}" if specs.any?
80
81
  end
81
82
 
82
83
  commands
@@ -113,7 +114,7 @@ module RubyLsp
113
114
  end
114
115
 
115
116
  load_path = spec?(file_path) ? "-Ispec" : "-Itest"
116
- "#{BASE_COMMAND} #{load_path} #{file_path} --name \"/#{regex}/\""
117
+ "#{COMMAND} #{load_path} #{file_path} --name \"/#{regex}/\""
117
118
  end
118
119
 
119
120
  #: (String, Hash[String, Hash[Symbol, untyped]]) -> Array[String]
@@ -124,7 +125,7 @@ module RubyLsp
124
125
  Shellwords.escape(TestDiscovery::DYNAMIC_REFERENCE_MARKER),
125
126
  ".*",
126
127
  )
127
- command = +"#{BASE_COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}\\$/\""
128
+ command = +"#{COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}\\$/\""
128
129
 
129
130
  unless examples.empty?
130
131
  command << if examples.length == 1
@@ -143,13 +144,14 @@ module RubyLsp
143
144
 
144
145
  MINITEST_REPORTER_PATH = File.expand_path("../test_reporters/minitest_reporter.rb", __dir__) #: String
145
146
  TEST_UNIT_REPORTER_PATH = File.expand_path("../test_reporters/test_unit_reporter.rb", __dir__) #: String
146
- ACCESS_MODIFIERS = [:public, :private, :protected].freeze
147
147
  BASE_COMMAND = begin
148
148
  Bundler.with_original_env { Bundler.default_lockfile }
149
149
  "bundle exec ruby"
150
150
  rescue Bundler::GemfileNotFound
151
151
  "ruby"
152
152
  end #: String
153
+ COMMAND = "#{BASE_COMMAND} -r#{MINITEST_REPORTER_PATH} -r#{TEST_UNIT_REPORTER_PATH}" #: String
154
+ ACCESS_MODIFIERS = [:public, :private, :protected].freeze
153
155
 
154
156
  #: (ResponseBuilders::TestCollection, GlobalState, Prism::Dispatcher, URI::Generic) -> void
155
157
  def initialize(response_builder, global_state, dispatcher, uri)
@@ -81,7 +81,7 @@ module RubyLsp
81
81
  @offenses = [] #: Array[::RuboCop::Cop::Offense]
82
82
  @errors = [] #: Array[String]
83
83
  @warnings = [] #: Array[String]
84
- @prism_result = nil #: Prism::ParseLexResult?
84
+ # @prism_result = nil #: Prism::ParseLexResult?
85
85
 
86
86
  args += DEFAULT_ARGS
87
87
  rubocop_options = ::RuboCop::Options.new.parse(args).first
@@ -101,7 +101,11 @@ module RubyLsp
101
101
  @warnings = []
102
102
  @offenses = []
103
103
  @options[:stdin] = contents
104
- @prism_result = prism_result
104
+
105
+ # Setting the Prism result before running the RuboCop runner makes it reuse the existing AST and avoids
106
+ # double-parsing. Unfortunately, this leads to a bunch of cops failing to execute properly under LSP mode.
107
+ # Uncomment this once reusing the Prism result is more stable
108
+ # @prism_result = prism_result
105
109
 
106
110
  super([path])
107
111
 
@@ -167,6 +167,7 @@ module RubyLsp
167
167
  return if @setup_error
168
168
 
169
169
  errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
170
+ return if test_mode?
170
171
 
171
172
  if errors.any?
172
173
  send_log_message(
@@ -179,21 +180,13 @@ module RubyLsp
179
180
 
180
181
  if errored_addons.any?
181
182
  send_message(
182
- Notification.new(
183
- method: "window/showMessage",
184
- params: Interface::ShowMessageParams.new(
185
- type: Constant::MessageType::WARNING,
186
- message: "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
187
- ),
183
+ Notification.window_show_message(
184
+ "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
185
+ type: Constant::MessageType::WARNING,
188
186
  ),
189
187
  )
190
188
 
191
- unless @test_mode
192
- send_log_message(
193
- errored_addons.map(&:errors_details).join("\n\n"),
194
- type: Constant::MessageType::WARNING,
195
- )
196
- end
189
+ send_log_message(errored_addons.map(&:errors_details).join("\n\n"), type: Constant::MessageType::WARNING)
197
190
  end
198
191
  end
199
192
 
@@ -367,52 +360,48 @@ module RubyLsp
367
360
 
368
361
  #: (Hash[Symbol, untyped] message) -> void
369
362
  def text_document_did_open(message)
370
- @global_state.synchronize do
371
- text_document = message.dig(:params, :textDocument)
372
- language_id = case text_document[:languageId]
373
- when "erb", "eruby"
374
- :erb
375
- when "rbs"
376
- :rbs
377
- else
378
- :ruby
379
- end
363
+ text_document = message.dig(:params, :textDocument)
364
+ language_id = case text_document[:languageId]
365
+ when "erb", "eruby"
366
+ :erb
367
+ when "rbs"
368
+ :rbs
369
+ else
370
+ :ruby
371
+ end
380
372
 
381
- document = @store.set(
382
- uri: text_document[:uri],
383
- source: text_document[:text],
384
- version: text_document[:version],
385
- language_id: language_id,
386
- )
373
+ document = @store.set(
374
+ uri: text_document[:uri],
375
+ source: text_document[:text],
376
+ version: text_document[:version],
377
+ language_id: language_id,
378
+ )
387
379
 
388
- if document.past_expensive_limit? && text_document[:uri].scheme == "file"
389
- log_message = <<~MESSAGE
390
- The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
391
- diagnostics will be disabled.
392
- MESSAGE
380
+ if document.past_expensive_limit? && text_document[:uri].scheme == "file"
381
+ log_message = <<~MESSAGE
382
+ The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
383
+ diagnostics will be disabled.
384
+ MESSAGE
393
385
 
394
- send_message(
395
- Notification.new(
396
- method: "window/logMessage",
397
- params: Interface::LogMessageParams.new(
398
- type: Constant::MessageType::WARNING,
399
- message: log_message,
400
- ),
386
+ send_message(
387
+ Notification.new(
388
+ method: "window/logMessage",
389
+ params: Interface::LogMessageParams.new(
390
+ type: Constant::MessageType::WARNING,
391
+ message: log_message,
401
392
  ),
402
- )
403
- end
393
+ ),
394
+ )
404
395
  end
405
396
  end
406
397
 
407
398
  #: (Hash[Symbol, untyped] message) -> void
408
399
  def text_document_did_close(message)
409
- @global_state.synchronize do
410
- uri = message.dig(:params, :textDocument, :uri)
411
- @store.delete(uri)
400
+ uri = message.dig(:params, :textDocument, :uri)
401
+ @store.delete(uri)
412
402
 
413
- # Clear diagnostics for the closed file, so that they no longer appear in the problems tab
414
- send_message(Notification.publish_diagnostics(uri.to_s, []))
415
- end
403
+ # Clear diagnostics for the closed file, so that they no longer appear in the problems tab
404
+ send_message(Notification.publish_diagnostics(uri.to_s, []))
416
405
  end
417
406
 
418
407
  #: (Hash[Symbol, untyped] message) -> void
@@ -420,9 +409,7 @@ module RubyLsp
420
409
  params = message[:params]
421
410
  text_document = params[:textDocument]
422
411
 
423
- @global_state.synchronize do
424
- @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
425
- end
412
+ @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
426
413
  end
427
414
 
428
415
  #: (Hash[Symbol, untyped] message) -> void
@@ -1105,11 +1092,7 @@ module RubyLsp
1105
1092
  @global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
1106
1093
 
1107
1094
  # Clear all document caches for pull diagnostics
1108
- @global_state.synchronize do
1109
- @store.each do |_uri, document|
1110
- document.clear_cache("textDocument/diagnostic")
1111
- end
1112
- end
1095
+ @store.each { |_uri, document| document.clear_cache("textDocument/diagnostic") }
1113
1096
 
1114
1097
  # Request a pull diagnostic refresh from the editor
1115
1098
  if @global_state.client_capabilities.supports_diagnostic_refresh
@@ -1224,7 +1207,7 @@ module RubyLsp
1224
1207
  }
1225
1208
  end
1226
1209
  end
1227
- rescue Bundler::GemNotFound
1210
+ rescue Bundler::GemNotFound, Bundler::GemfileNotFound
1228
1211
  []
1229
1212
  end
1230
1213
 
@@ -1510,10 +1493,7 @@ module RubyLsp
1510
1493
 
1511
1494
  send_message(Result.new(
1512
1495
  id: message[:id],
1513
- response: {
1514
- commands: commands,
1515
- reporterPaths: [Listeners::TestStyle::MINITEST_REPORTER_PATH, Listeners::TestStyle::TEST_UNIT_REPORTER_PATH],
1516
- },
1496
+ response: { commands: commands },
1517
1497
  ))
1518
1498
  end
1519
1499
 
@@ -234,6 +234,14 @@ module RubyLsp
234
234
  # If no error occurred, then clear previous errors
235
235
  @error_path.delete if @error_path.exist?
236
236
  $stderr.puts("Ruby LSP> Composed bundle installation complete")
237
+ rescue Errno::EPIPE
238
+ # If the $stderr pipe was closed by the client, for example when closing the editor during running bundle
239
+ # install, we don't want to write the error to a file or else we will report to telemetry on the next launch and
240
+ # it does not represent an actual error.
241
+ #
242
+ # This situation may happen because while running bundle install, the server is not yet ready to receive
243
+ # shutdown requests and we may continue doing work until the process is killed.
244
+ @error_path.delete if @error_path.exist?
237
245
  rescue => e
238
246
  # Write the error object to a file so that we can read it from the parent process
239
247
  @error_path.write(Marshal.dump(e))
@@ -24,9 +24,6 @@ module RubyLsp
24
24
  # https://code.visualstudio.com/api/references/vscode-api#StatementCoverage
25
25
  #: type statement_coverage = { executed: Integer, location: position, branches: Array[branch_coverage] }
26
26
 
27
- #: bool
28
- attr_reader :invoked_shutdown
29
-
30
27
  #: -> void
31
28
  def initialize
32
29
  dir_path = File.join(Dir.tmpdir, "ruby-lsp")
@@ -195,7 +192,7 @@ module RubyLsp
195
192
 
196
193
  #: -> void
197
194
  def at_exit
198
- internal_shutdown unless invoked_shutdown
195
+ internal_shutdown unless @invoked_shutdown
199
196
  end
200
197
 
201
198
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.0
4
+ version: 0.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify