ruby-lsp 0.25.0 → 0.26.1

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: c80f549675508ffbb28d649de04506adabec01eac9aa4c6eee057ec848adf858
4
+ data.tar.gz: 71ea1a4d628444b98bc1173748f5aecf0d71bdc8d3dc80f33b2779c9c78d9de0
5
5
  SHA512:
6
- metadata.gz: a354535a3853b0826635408fc46dc85cb7cee97c4993012dd983af11b8917121962d97bddf0853406b54f619faa8c2383cccca9638b4a0624309fb1e4c838249
7
- data.tar.gz: 9e49e2436c63d90fc373b52ac9f6b65e83964fe108abd0a18fd46b655cd99bd55c471dd198624bb8701890f62175af2c9fcb50fcfa2bbf69e706406b9fa1e472
6
+ metadata.gz: 7261bf15c095154ff36152492aa252cbd84b84f6e83eb458623c7bb9790a57957885a855c2ee10683b7035267177e323c013988befc02e96f3fdf0274d2312ca
7
+ data.tar.gz: 74bcea4844e876230713400776bf7def1db44492bcf913526195b5d01919efd24ac69b517594ce2499e332184bf7ef4881e17e7694574bf4d35afabeda25b8c4
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.25.0
1
+ 0.26.1
@@ -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,17 @@ 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.select! 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
+
302
+ definitions.any?
303
+ end
304
+
294
305
  entries.uniq!
295
306
  entries #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]]
296
307
  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,21 @@ 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
+
1997
+ candidates = @index.constant_completion_candidates("Qux", [])
1998
+ assert_equal(0, candidates.length)
1999
+ end
2000
+
1986
2001
  def test_constant_completion_candidates_for_empty_name
1987
2002
  index(<<~RUBY)
1988
2003
  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
 
@@ -126,8 +126,12 @@ module RubyLsp
126
126
  if message[:id]
127
127
  # If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
128
128
  # from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
129
- # reporting these to our telemetry
130
- if e.is_a?(Store::NonExistingDocumentError)
129
+ # reporting these to our telemetry.
130
+ #
131
+ # Similarly, if we receive a location for an invalid position in the
132
+ # document, we don't report it to telemetry
133
+ case e
134
+ when Store::NonExistingDocumentError, Document::InvalidLocationError
131
135
  send_message(Error.new(
132
136
  id: message[:id],
133
137
  code: Constant::ErrorCodes::INVALID_PARAMS,
@@ -167,6 +171,7 @@ module RubyLsp
167
171
  return if @setup_error
168
172
 
169
173
  errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
174
+ return if test_mode?
170
175
 
171
176
  if errors.any?
172
177
  send_log_message(
@@ -179,21 +184,13 @@ module RubyLsp
179
184
 
180
185
  if errored_addons.any?
181
186
  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
- ),
187
+ Notification.window_show_message(
188
+ "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
189
+ type: Constant::MessageType::WARNING,
188
190
  ),
189
191
  )
190
192
 
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
193
+ send_log_message(errored_addons.map(&:errors_details).join("\n\n"), type: Constant::MessageType::WARNING)
197
194
  end
198
195
  end
199
196
 
@@ -367,52 +364,48 @@ module RubyLsp
367
364
 
368
365
  #: (Hash[Symbol, untyped] message) -> void
369
366
  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
367
+ text_document = message.dig(:params, :textDocument)
368
+ language_id = case text_document[:languageId]
369
+ when "erb", "eruby"
370
+ :erb
371
+ when "rbs"
372
+ :rbs
373
+ else
374
+ :ruby
375
+ end
380
376
 
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
- )
377
+ document = @store.set(
378
+ uri: text_document[:uri],
379
+ source: text_document[:text],
380
+ version: text_document[:version],
381
+ language_id: language_id,
382
+ )
387
383
 
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
384
+ if document.past_expensive_limit? && text_document[:uri].scheme == "file"
385
+ log_message = <<~MESSAGE
386
+ The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
387
+ diagnostics will be disabled.
388
+ MESSAGE
393
389
 
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
- ),
390
+ send_message(
391
+ Notification.new(
392
+ method: "window/logMessage",
393
+ params: Interface::LogMessageParams.new(
394
+ type: Constant::MessageType::WARNING,
395
+ message: log_message,
401
396
  ),
402
- )
403
- end
397
+ ),
398
+ )
404
399
  end
405
400
  end
406
401
 
407
402
  #: (Hash[Symbol, untyped] message) -> void
408
403
  def text_document_did_close(message)
409
- @global_state.synchronize do
410
- uri = message.dig(:params, :textDocument, :uri)
411
- @store.delete(uri)
404
+ uri = message.dig(:params, :textDocument, :uri)
405
+ @store.delete(uri)
412
406
 
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
407
+ # Clear diagnostics for the closed file, so that they no longer appear in the problems tab
408
+ send_message(Notification.publish_diagnostics(uri.to_s, []))
416
409
  end
417
410
 
418
411
  #: (Hash[Symbol, untyped] message) -> void
@@ -420,9 +413,7 @@ module RubyLsp
420
413
  params = message[:params]
421
414
  text_document = params[:textDocument]
422
415
 
423
- @global_state.synchronize do
424
- @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
425
- end
416
+ @store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
426
417
  end
427
418
 
428
419
  #: (Hash[Symbol, untyped] message) -> void
@@ -1105,11 +1096,7 @@ module RubyLsp
1105
1096
  @global_state.register_formatter("rubocop_internal", Requests::Support::RuboCopFormatter.new)
1106
1097
 
1107
1098
  # 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
1099
+ @store.each { |_uri, document| document.clear_cache("textDocument/diagnostic") }
1113
1100
 
1114
1101
  # Request a pull diagnostic refresh from the editor
1115
1102
  if @global_state.client_capabilities.supports_diagnostic_refresh
@@ -1224,7 +1211,7 @@ module RubyLsp
1224
1211
  }
1225
1212
  end
1226
1213
  end
1227
- rescue Bundler::GemNotFound
1214
+ rescue Bundler::GemNotFound, Bundler::GemfileNotFound
1228
1215
  []
1229
1216
  end
1230
1217
 
@@ -1510,10 +1497,7 @@ module RubyLsp
1510
1497
 
1511
1498
  send_message(Result.new(
1512
1499
  id: message[:id],
1513
- response: {
1514
- commands: commands,
1515
- reporterPaths: [Listeners::TestStyle::MINITEST_REPORTER_PATH, Listeners::TestStyle::TEST_UNIT_REPORTER_PATH],
1516
- },
1500
+ response: { commands: commands },
1517
1501
  ))
1518
1502
  end
1519
1503
 
@@ -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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify