ruby-lsp 0.23.7 → 0.23.8

Sign up to get free protection for your applications and to get access to all the features.
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