ruby-lsp 0.23.7 → 0.23.8

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: 46b72d1b9ba142496f64035b6b1ad54d096c0a28ae52311612bd118bc49e2956
4
- data.tar.gz: 525dc46dcfc71ee57da51955a4034ceace3bbdd1eca7942b542ab2cd32d42ad4
3
+ metadata.gz: 918079e906832b0289a053cd4bfe849422082c25bd0f4bdada25cab3a532005f
4
+ data.tar.gz: 7935b2bdc52a2bb83264376fb98f7ef8d3bfc3c5cb9dadd23642a1ebafbca013
5
5
  SHA512:
6
- metadata.gz: '06690b76d284ca6abc05fc0e836af32a54c0a3cedea1fd53071648330a3b3b6e6d1a59ef205127f54a88e8cd3ca07c3840f25f1293e37737d705b2ea41719cb1'
7
- data.tar.gz: bd6104ef180f122d0bb9d6d751213c43f0d19d0f203deb50e5d94583ff0aaf85559a040ca0371fc24506ff11a0d2742c14f1277226cc34d3a09d02730d46aed7
6
+ metadata.gz: 9fb084d322308bb2e69c2c0d5cfed2d275fe53f5df40aa2a49a848aab4c771858dfa1b5d5df913516c1f1c90ce5101b2f461865093f5e6787c79886cadc40bdb
7
+ data.tar.gz: 06206026d180e9d044edda0a83018ae31895cce3e6d91cb9ae4bdb3c365565a27881dd17ffd6d8861d8126441e327cb8fdf91c41e6003c1d84a4038c8b9003ea
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.23.7
1
+ 0.23.8
@@ -51,7 +51,7 @@ end
51
51
 
52
52
  begin
53
53
  # Wait until the composed Bundle is finished
54
- Process.wait(pid)
54
+ _, status = Process.wait2(pid)
55
55
  rescue Errno::ECHILD
56
56
  # In theory, the child process can finish before we even get to the wait call, but that is not an error
57
57
  end
@@ -105,7 +105,7 @@ end
105
105
  # flow, we are not booting the LSP yet, just checking if the bundle is valid before rebooting
106
106
  if reboot
107
107
  # Use the exit status to signal to the server if composing the bundle succeeded
108
- exit(install_error || setup_error ? 1 : 0)
108
+ exit(install_error || setup_error ? 1 : status&.exitstatus || 0)
109
109
  end
110
110
 
111
111
  # Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
@@ -120,8 +120,16 @@ module RubyIndexer
120
120
  )]))
121
121
  end
122
122
  def first_unqualified_const(name)
123
+ # Look for an exact match first
123
124
  _name, entries = @entries.find do |const_name, _entries|
124
- const_name.end_with?(name)
125
+ const_name == name || const_name.end_with?("::#{name}")
126
+ end
127
+
128
+ # If an exact match is not found, then try to find a constant that ends with the name
129
+ unless entries
130
+ _name, entries = @entries.find do |const_name, _entries|
131
+ const_name.end_with?(name)
132
+ end
125
133
  end
126
134
 
127
135
  T.cast(
@@ -593,7 +601,7 @@ module RubyIndexer
593
601
  entries = self[variable_name]&.grep(Entry::ClassVariable)
594
602
  return unless entries&.any?
595
603
 
596
- ancestors = linearized_ancestors_of(owner_name)
604
+ ancestors = linearized_attached_ancestors(owner_name)
597
605
  return if ancestors.empty?
598
606
 
599
607
  entries.select { |e| ancestors.include?(e.owner&.name) }
@@ -601,12 +609,33 @@ module RubyIndexer
601
609
 
602
610
  # Returns a list of possible candidates for completion of instance variables for a given owner name. The name must
603
611
  # include the `@` prefix
604
- sig { params(name: String, owner_name: String).returns(T::Array[Entry::InstanceVariable]) }
612
+ sig do
613
+ params(name: String, owner_name: String).returns(T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
614
+ end
605
615
  def instance_variable_completion_candidates(name, owner_name)
606
- entries = T.cast(prefix_search(name).flatten, T::Array[Entry::InstanceVariable])
616
+ entries = T.cast(prefix_search(name).flatten, T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
617
+ # Avoid wasting time linearizing ancestors if we didn't find anything
618
+ return entries if entries.empty?
619
+
607
620
  ancestors = linearized_ancestors_of(owner_name)
608
621
 
609
- variables = entries.select { |e| ancestors.any?(e.owner&.name) }
622
+ instance_variables, class_variables = entries.partition { |e| e.is_a?(Entry::InstanceVariable) }
623
+ variables = instance_variables.select { |e| ancestors.any?(e.owner&.name) }
624
+
625
+ # Class variables are only owned by the attached class in our representation. If the owner is in a singleton
626
+ # context, we have to search for ancestors of the attached class
627
+ if class_variables.any?
628
+ name_parts = owner_name.split("::")
629
+
630
+ if name_parts.last&.start_with?("<Class:")
631
+ attached_name = T.must(name_parts[0..-2]).join("::")
632
+ attached_ancestors = linearized_ancestors_of(attached_name)
633
+ variables.concat(class_variables.select { |e| attached_ancestors.any?(e.owner&.name) })
634
+ else
635
+ variables.concat(class_variables.select { |e| ancestors.any?(e.owner&.name) })
636
+ end
637
+ end
638
+
610
639
  variables.uniq!(&:name)
611
640
  variables
612
641
  end
@@ -614,8 +643,10 @@ module RubyIndexer
614
643
  sig { params(name: String, owner_name: String).returns(T::Array[Entry::ClassVariable]) }
615
644
  def class_variable_completion_candidates(name, owner_name)
616
645
  entries = T.cast(prefix_search(name).flatten, T::Array[Entry::ClassVariable])
617
- ancestors = linearized_ancestors_of(owner_name)
646
+ # Avoid wasting time linearizing ancestors if we didn't find anything
647
+ return entries if entries.empty?
618
648
 
649
+ ancestors = linearized_attached_ancestors(owner_name)
619
650
  variables = entries.select { |e| ancestors.any?(e.owner&.name) }
620
651
  variables.uniq!(&:name)
621
652
  variables
@@ -717,6 +748,20 @@ module RubyIndexer
717
748
 
718
749
  private
719
750
 
751
+ # Always returns the linearized ancestors for the attached class, regardless of whether `name` refers to a singleton
752
+ # or attached namespace
753
+ sig { params(name: String).returns(T::Array[String]) }
754
+ def linearized_attached_ancestors(name)
755
+ name_parts = name.split("::")
756
+
757
+ if name_parts.last&.start_with?("<Class:")
758
+ attached_name = T.must(name_parts[0..-2]).join("::")
759
+ linearized_ancestors_of(attached_name)
760
+ else
761
+ linearized_ancestors_of(name)
762
+ end
763
+ end
764
+
720
765
  # Runs the registered included hooks
721
766
  sig { params(fully_qualified_name: String, nesting: T::Array[String]).void }
722
767
  def run_included_hooks(fully_qualified_name, nesting)
@@ -1561,6 +1561,23 @@ module RubyIndexer
1561
1561
  assert_equal("Foo::Bar", entry.name)
1562
1562
  end
1563
1563
 
1564
+ def test_first_unqualified_const_prefers_exact_matches
1565
+ index(<<~RUBY)
1566
+ module Foo
1567
+ class ParseResultType
1568
+ end
1569
+ end
1570
+
1571
+ module Namespace
1572
+ class Type
1573
+ end
1574
+ end
1575
+ RUBY
1576
+
1577
+ entry = T.must(@index.first_unqualified_const("Type")&.first)
1578
+ assert_equal("Namespace::Type", entry.name)
1579
+ end
1580
+
1564
1581
  def test_completion_does_not_duplicate_overridden_methods
1565
1582
  index(<<~RUBY)
1566
1583
  class Foo
@@ -2092,5 +2109,57 @@ module RubyIndexer
2092
2109
  refute_nil(entry, "Expected indexer to be able to handle unsaved URIs")
2093
2110
  assert_equal("I added this comment!", entry.comments)
2094
2111
  end
2112
+
2113
+ def test_instance_variable_completion_returns_class_variables_too
2114
+ index(<<~RUBY)
2115
+ class Parent
2116
+ @@abc = 123
2117
+ end
2118
+
2119
+ class Child < Parent
2120
+ @@adf = 123
2121
+
2122
+ def self.do
2123
+ end
2124
+ end
2125
+ RUBY
2126
+
2127
+ abc, adf = @index.instance_variable_completion_candidates("@", "Child::<Class:Child>")
2128
+
2129
+ refute_nil(abc)
2130
+ refute_nil(adf)
2131
+
2132
+ assert_equal("@@abc", abc.name)
2133
+ assert_equal("@@adf", adf.name)
2134
+ end
2135
+
2136
+ def test_class_variable_completion_from_singleton_context
2137
+ index(<<~RUBY)
2138
+ class Foo
2139
+ @@hello = 123
2140
+
2141
+ def self.do
2142
+ end
2143
+ end
2144
+ RUBY
2145
+
2146
+ candidates = @index.class_variable_completion_candidates("@@", "Foo::<Class:Foo>")
2147
+ refute_empty(candidates)
2148
+
2149
+ assert_equal("@@hello", candidates.first&.name)
2150
+ end
2151
+
2152
+ def test_resolve_class_variable_in_singleton_context
2153
+ index(<<~RUBY)
2154
+ class Foo
2155
+ @@hello = 123
2156
+ end
2157
+ RUBY
2158
+
2159
+ candidates = @index.resolve_class_variable("@@hello", "Foo::<Class:Foo>")
2160
+ refute_empty(candidates)
2161
+
2162
+ assert_equal("@@hello", candidates.first&.name)
2163
+ end
2095
2164
  end
2096
2165
  end
@@ -22,6 +22,7 @@ require "prism/visitor"
22
22
  require "language_server-protocol"
23
23
  require "rbs"
24
24
  require "fileutils"
25
+ require "open3"
25
26
 
26
27
  require "ruby-lsp"
27
28
  require "ruby_lsp/base_server"
@@ -170,7 +170,7 @@ module RubyLsp
170
170
 
171
171
  sig { params(line: Integer, character: Integer).void }
172
172
  def move_cursor_to(line, character)
173
- return unless @client_name.start_with?("Visual Studio Code")
173
+ return unless /Visual Studio Code|Cursor/.match?(@client_name)
174
174
 
175
175
  position = Interface::Position.new(
176
176
  line: line,
@@ -1036,6 +1036,10 @@ module RubyLsp
1036
1036
  when Constant::FileChangeType::DELETED
1037
1037
  index.delete(uri)
1038
1038
  end
1039
+ rescue Errno::ENOENT
1040
+ # If a file is created and then delete immediately afterwards, we will process the created notification before we
1041
+ # receive the deleted one, but the file no longer exists. This may happen when running a test suite that creates
1042
+ # and deletes files automatically.
1039
1043
  end
1040
1044
 
1041
1045
  sig { params(uri: URI::Generic).void }
@@ -1286,25 +1290,40 @@ module RubyLsp
1286
1290
  addon.handle_window_show_message_response(result[:title])
1287
1291
  end
1288
1292
 
1289
- sig { params(message: T::Hash[Symbol, T.untyped]).void }
1293
+ # NOTE: all servers methods are void because they can produce several messages for the client. The only reason this
1294
+ # method returns the created thread is to that we can join it in tests and avoid flakiness. The implementation is
1295
+ # not supposed to rely on the return of this method
1296
+ sig { params(message: T::Hash[Symbol, T.untyped]).returns(T.nilable(Thread)) }
1290
1297
  def compose_bundle(message)
1291
1298
  already_composed_path = File.join(@global_state.workspace_path, ".ruby-lsp", "bundle_is_composed")
1292
- command = "#{Gem.ruby} #{File.expand_path("../../exe/ruby-lsp-launcher", __dir__)} #{@global_state.workspace_uri}"
1293
1299
  id = message[:id]
1294
1300
 
1295
1301
  begin
1296
- Bundler::LockfileParser.new(Bundler.default_lockfile.read)
1302
+ Bundler.with_original_env do
1303
+ Bundler::LockfileParser.new(Bundler.default_lockfile.read)
1304
+ end
1297
1305
  rescue Bundler::LockfileError => e
1298
1306
  send_message(Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: e.message))
1299
1307
  return
1308
+ rescue Bundler::GemfileNotFound, Errno::ENOENT
1309
+ # We still compose the bundle if there's no Gemfile or if the lockfile got deleted
1300
1310
  end
1301
1311
 
1302
1312
  # We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
1303
1313
  # we return the response back to the editor, then the restart is triggered
1304
1314
  Thread.new do
1305
1315
  send_log_message("Recomposing the bundle ahead of restart")
1306
- pid = Process.spawn(command)
1307
- _, status = Process.wait2(pid)
1316
+
1317
+ _stdout, stderr, status = Bundler.with_unbundled_env do
1318
+ Open3.capture3(
1319
+ Gem.ruby,
1320
+ "-I",
1321
+ File.dirname(T.must(__dir__)),
1322
+ File.expand_path("../../exe/ruby-lsp-launcher", __dir__),
1323
+ @global_state.workspace_uri.to_s,
1324
+ chdir: @global_state.workspace_path,
1325
+ )
1326
+ end
1308
1327
 
1309
1328
  if status&.exitstatus == 0
1310
1329
  # Create a signal for the restart that it can skip composing the bundle and launch directly
@@ -1313,7 +1332,9 @@ module RubyLsp
1313
1332
  else
1314
1333
  # This special error code makes the extension avoid restarting in case we already know that the composed
1315
1334
  # bundle is not valid
1316
- send_message(Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle"))
1335
+ send_message(
1336
+ Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
1337
+ )
1317
1338
  end
1318
1339
  end
1319
1340
  end
@@ -241,8 +241,7 @@ module RubyLsp
241
241
 
242
242
  # If either the Gemfile or the lockfile have been modified during the process of setting up the bundle, retry
243
243
  # composing the bundle from scratch
244
-
245
- if @gemfile && @lockfile
244
+ if @gemfile&.exist? && @lockfile&.exist?
246
245
  current_gemfile_hash = Digest::SHA256.hexdigest(@gemfile.read)
247
246
  current_lockfile_hash = Digest::SHA256.hexdigest(@lockfile.read)
248
247
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.7
4
+ version: 0.23.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-01-28 00:00:00.000000000 Z
10
+ date: 2025-01-31 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: language_server-protocol