ruby-lsp 0.23.9 → 0.23.11

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: 149e7978a6f85c9349fa3922283a151f2e2129d12eb14e29d665ad35a3b909e7
4
- data.tar.gz: a931c7b049810fe304eaa93ae4a7a8bdd499951fa9f3ca21b6da9df91cc24e12
3
+ metadata.gz: b93894c0d2c2687def87d9e173c7a9e45e810088613cb902c2a4b1a9d93a8404
4
+ data.tar.gz: 9e92f360c8367cde864ac1a5dd2305170a975fa3164e6fc2c1d6f492396fbbc5
5
5
  SHA512:
6
- metadata.gz: 6fae90263d8e037d3e58a0f20de4e6eaa671d739f28fa1af90a013a9d0ade3a43fa118b5fe15c3e2a3de025a9c3e4a53dc4584426678619896b98d38cca2dcbc
7
- data.tar.gz: c634542b0ec26e9df418222b8eab459f42d3f65e42c196c6a3769139073c94f98bfb0da8cb61b18f055629aa9fbf58d6516d13a40b453f27fbc4a6f1514d3d9d
6
+ metadata.gz: fc2172fba1c50192cb86d2dfc619c633cf756f3ebb574caaaeabb81fe23d283b7b896f48fe23981d29c472fce98248137171d119b33c75d1a7f6fafc73c7a775
7
+ data.tar.gz: 03b0b5987fc8dcc6c989ce6c5df55e16fff906836ef74aa993ddc445b50c5d3458775fee1a31b9e228710df44f80b8338390ef1fdf75d464deac4e38fe822644
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.23.9
1
+ 0.23.11
@@ -87,7 +87,7 @@ module RubyIndexer
87
87
  # Find the group that is either immediately or two lines above the current entry
88
88
  correct_group = grouped.find do |group|
89
89
  comment_end_line = group.last.location.start_line
90
- (comment_end_line - 1..comment_end_line).cover?(@location.start_line - 1)
90
+ (comment_end_line..comment_end_line + 1).cover?(@location.start_line - 1)
91
91
  end
92
92
 
93
93
  # If we found something, we join the comments together. Otherwise, the entry has no documentation and we don't
@@ -21,9 +21,9 @@ module RubyIndexer
21
21
  # Returns the real nesting of a constant name taking into account top level
22
22
  # references that may be included anywhere in the name or nesting where that
23
23
  # constant was found
24
- sig { params(stack: T::Array[String], name: String).returns(T::Array[String]) }
24
+ sig { params(stack: T::Array[String], name: T.nilable(String)).returns(T::Array[String]) }
25
25
  def actual_nesting(stack, name)
26
- nesting = stack + [name]
26
+ nesting = name ? stack + [name] : stack
27
27
  corrected_nesting = []
28
28
 
29
29
  nesting.reverse_each do |name|
@@ -43,11 +43,18 @@ module RubyIndexer
43
43
  Prism::ConstantPathNode,
44
44
  Prism::ConstantReadNode,
45
45
  Prism::ConstantPathTargetNode,
46
+ Prism::CallNode,
47
+ Prism::MissingNode,
46
48
  ),
47
49
  ).returns(T.nilable(String))
48
50
  end
49
51
  def constant_name(node)
50
- node.full_name
52
+ case node
53
+ when Prism::CallNode, Prism::MissingNode
54
+ nil
55
+ else
56
+ node.full_name
57
+ end
51
58
  rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
52
59
  Prism::ConstantPathNode::MissingNodesInConstantPathError
53
60
  nil
@@ -666,5 +666,80 @@ module RubyIndexer
666
666
  method = @index["baz"]&.first
667
667
  assert_equal("Foo::Bar::<Class:Bar>", method.owner.name)
668
668
  end
669
+
670
+ def test_lazy_comments_with_spaces_are_properly_attributed
671
+ path = File.join(Dir.pwd, "lib", "foo.rb")
672
+ source = <<~RUBY
673
+ require "whatever"
674
+
675
+ # These comments belong to the declaration below
676
+ # They have to be associated with it
677
+
678
+ class Foo
679
+ end
680
+ RUBY
681
+ File.write(path, source)
682
+ @index.index_single(URI::Generic.from_path(path: path), source, collect_comments: false)
683
+
684
+ entry = @index["Foo"].first
685
+
686
+ begin
687
+ assert_equal(<<~COMMENTS.chomp, entry.comments)
688
+ These comments belong to the declaration below
689
+ They have to be associated with it
690
+ COMMENTS
691
+ ensure
692
+ FileUtils.rm(path)
693
+ end
694
+ end
695
+
696
+ def test_lazy_comments_with_no_spaces_are_properly_attributed
697
+ path = File.join(Dir.pwd, "lib", "foo.rb")
698
+ source = <<~RUBY
699
+ require "whatever"
700
+
701
+ # These comments belong to the declaration below
702
+ # They have to be associated with it
703
+ class Foo
704
+ end
705
+ RUBY
706
+ File.write(path, source)
707
+ @index.index_single(URI::Generic.from_path(path: path), source, collect_comments: false)
708
+
709
+ entry = @index["Foo"].first
710
+
711
+ begin
712
+ assert_equal(<<~COMMENTS.chomp, entry.comments)
713
+ These comments belong to the declaration below
714
+ They have to be associated with it
715
+ COMMENTS
716
+ ensure
717
+ FileUtils.rm(path)
718
+ end
719
+ end
720
+
721
+ def test_lazy_comments_with_two_extra_spaces_are_properly_ignored
722
+ path = File.join(Dir.pwd, "lib", "foo.rb")
723
+ source = <<~RUBY
724
+ require "whatever"
725
+
726
+ # These comments don't belong to the declaration below
727
+ # They will not be associated with it
728
+
729
+
730
+ class Foo
731
+ end
732
+ RUBY
733
+ File.write(path, source)
734
+ @index.index_single(URI::Generic.from_path(path: path), source, collect_comments: false)
735
+
736
+ entry = @index["Foo"].first
737
+
738
+ begin
739
+ assert_empty(entry.comments)
740
+ ensure
741
+ FileUtils.rm(path)
742
+ end
743
+ end
669
744
  end
670
745
  end
@@ -2161,5 +2161,26 @@ module RubyIndexer
2161
2161
 
2162
2162
  assert_equal("@@hello", candidates.first&.name)
2163
2163
  end
2164
+
2165
+ def test_actual_nesting
2166
+ assert_equal(["Foo"], Index.actual_nesting([], "Foo"))
2167
+ assert_equal(["TopLevel", "Foo"], Index.actual_nesting(["First", "::TopLevel"], "Foo"))
2168
+ assert_equal(["TopLevel", "Another", "Foo"], Index.actual_nesting(["::TopLevel", "Another"], "Foo"))
2169
+ assert_equal(["TopLevel"], Index.actual_nesting(["First", "::TopLevel"], nil))
2170
+ end
2171
+
2172
+ def test_constant_name
2173
+ node = Prism.parse("class var::Foo; end").value.statements.body.first.constant_path
2174
+ assert_nil(Index.constant_name(node))
2175
+
2176
+ node = Prism.parse("class ; end").value.statements.body.first.constant_path
2177
+ assert_nil(Index.constant_name(node))
2178
+
2179
+ node = Prism.parse("class method_call; end").value.statements.body.first.constant_path
2180
+ assert_nil(Index.constant_name(node))
2181
+
2182
+ node = Prism.parse("class Foo; end").value.statements.body.first.constant_path
2183
+ assert_equal("Foo", Index.constant_name(node))
2184
+ end
2164
2185
  end
2165
2186
  end
@@ -90,7 +90,8 @@ module RubyLsp
90
90
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
91
91
  # else is pushed into the incoming queue
92
92
  case method
93
- when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange"
93
+ when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange",
94
+ "rubyLsp/diagnoseState"
94
95
  process_message(message)
95
96
  when "shutdown"
96
97
  @global_state.synchronize do
@@ -32,6 +32,9 @@ module RubyLsp
32
32
  sig { returns(URI::Generic) }
33
33
  attr_reader :workspace_uri
34
34
 
35
+ sig { returns(T.nilable(String)) }
36
+ attr_reader :telemetry_machine_id
37
+
35
38
  sig { void }
36
39
  def initialize
37
40
  @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
@@ -57,6 +60,7 @@ module RubyLsp
57
60
  @client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
58
61
  @enabled_feature_flags = T.let({}, T::Hash[Symbol, T::Boolean])
59
62
  @mutex = T.let(Mutex.new, Mutex)
63
+ @telemetry_machine_id = T.let(nil, T.nilable(String))
60
64
  end
61
65
 
62
66
  sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
@@ -175,6 +179,7 @@ module RubyLsp
175
179
  enabled_flags = options.dig(:initializationOptions, :enabledFeatureFlags)
176
180
  @enabled_feature_flags = enabled_flags if enabled_flags
177
181
 
182
+ @telemetry_machine_id = options.dig(:initializationOptions, :telemetryMachineId)
178
183
  notifications
179
184
  end
180
185
 
@@ -24,6 +24,7 @@ require "rbs"
24
24
  require "fileutils"
25
25
  require "open3"
26
26
  require "securerandom"
27
+ require "shellwords"
27
28
 
28
29
  require "ruby-lsp"
29
30
  require "ruby_lsp/base_server"
@@ -50,6 +51,7 @@ require "ruby_lsp/response_builders/document_symbol"
50
51
  require "ruby_lsp/response_builders/hover"
51
52
  require "ruby_lsp/response_builders/semantic_highlighting"
52
53
  require "ruby_lsp/response_builders/signature_help"
54
+ require "ruby_lsp/response_builders/test_collection"
53
55
 
54
56
  # Request support
55
57
  require "ruby_lsp/requests/support/selection_range"
@@ -60,6 +62,7 @@ require "ruby_lsp/requests/support/formatter"
60
62
  require "ruby_lsp/requests/support/rubocop_runner"
61
63
  require "ruby_lsp/requests/support/rubocop_formatter"
62
64
  require "ruby_lsp/requests/support/syntax_tree_formatter"
65
+ require "ruby_lsp/requests/support/test_item"
63
66
 
64
67
  # Requests
65
68
  require "ruby_lsp/requests/request"
@@ -70,6 +73,7 @@ require "ruby_lsp/requests/completion_resolve"
70
73
  require "ruby_lsp/requests/completion"
71
74
  require "ruby_lsp/requests/definition"
72
75
  require "ruby_lsp/requests/diagnostics"
76
+ require "ruby_lsp/requests/discover_tests"
73
77
  require "ruby_lsp/requests/document_highlight"
74
78
  require "ruby_lsp/requests/document_link"
75
79
  require "ruby_lsp/requests/document_symbol"
@@ -471,6 +471,9 @@ module RubyLsp
471
471
  path_node_to_complete,
472
472
  )
473
473
  end
474
+ rescue Errno::EPERM
475
+ # If the user writes a relative require pointing to a path that the editor has no permissions to read, then glob
476
+ # might fail with EPERM
474
477
  end
475
478
 
476
479
  sig { params(node: Prism::CallNode, name: String).void }
@@ -0,0 +1,164 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class TestStyle
7
+ extend T::Sig
8
+ include Requests::Support::Common
9
+
10
+ ACCESS_MODIFIERS = [:public, :private, :protected].freeze
11
+ DYNAMIC_REFERENCE_MARKER = "<dynamic_reference>"
12
+
13
+ sig do
14
+ params(
15
+ response_builder: ResponseBuilders::TestCollection,
16
+ global_state: GlobalState,
17
+ dispatcher: Prism::Dispatcher,
18
+ uri: URI::Generic,
19
+ ).void
20
+ end
21
+ def initialize(response_builder, global_state, dispatcher, uri)
22
+ @response_builder = response_builder
23
+ @uri = uri
24
+ @index = T.let(global_state.index, RubyIndexer::Index)
25
+ @visibility_stack = T.let([:public], T::Array[Symbol])
26
+ @nesting = T.let([], T::Array[String])
27
+
28
+ dispatcher.register(
29
+ self,
30
+ :on_class_node_enter,
31
+ :on_class_node_leave,
32
+ :on_module_node_enter,
33
+ :on_module_node_leave,
34
+ :on_def_node_enter,
35
+ :on_call_node_enter,
36
+ :on_call_node_leave,
37
+ )
38
+ end
39
+
40
+ sig { params(node: Prism::ClassNode).void }
41
+ def on_class_node_enter(node)
42
+ @visibility_stack << :public
43
+ name = constant_name(node.constant_path)
44
+ name ||= name_with_dynamic_reference(node.constant_path)
45
+
46
+ fully_qualified_name = RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
47
+
48
+ attached_ancestors = begin
49
+ @index.linearized_ancestors_of(fully_qualified_name)
50
+ rescue RubyIndexer::Index::NonExistingNamespaceError
51
+ # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
52
+ # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
53
+ [node.superclass&.slice].compact
54
+ end
55
+
56
+ if attached_ancestors.include?("Test::Unit::TestCase") ||
57
+ non_declarative_minitest?(attached_ancestors, fully_qualified_name)
58
+
59
+ @response_builder.add(Requests::Support::TestItem.new(
60
+ fully_qualified_name,
61
+ fully_qualified_name,
62
+ @uri,
63
+ range_from_node(node),
64
+ ))
65
+ end
66
+
67
+ @nesting << name
68
+ end
69
+
70
+ sig { params(node: Prism::ModuleNode).void }
71
+ def on_module_node_enter(node)
72
+ @visibility_stack << :public
73
+
74
+ name = constant_name(node.constant_path)
75
+ name ||= name_with_dynamic_reference(node.constant_path)
76
+
77
+ @nesting << name
78
+ end
79
+
80
+ sig { params(node: Prism::ModuleNode).void }
81
+ def on_module_node_leave(node)
82
+ @visibility_stack.pop
83
+ @nesting.pop
84
+ end
85
+
86
+ sig { params(node: Prism::ClassNode).void }
87
+ def on_class_node_leave(node)
88
+ @visibility_stack.pop
89
+ @nesting.pop
90
+ end
91
+
92
+ sig { params(node: Prism::DefNode).void }
93
+ def on_def_node_enter(node)
94
+ return if @visibility_stack.last != :public
95
+
96
+ name = node.name.to_s
97
+ return unless name.start_with?("test_")
98
+
99
+ current_group_name = RubyIndexer::Index.actual_nesting(@nesting, nil).join("::")
100
+
101
+ # If we're finding a test method, but for the wrong framework, then the group test item will not have been
102
+ # previously pushed and thus we return early and avoid adding items for a framework this listener is not
103
+ # interested in
104
+ test_item = @response_builder[current_group_name]
105
+ return unless test_item
106
+
107
+ test_item.add(Requests::Support::TestItem.new(
108
+ "#{current_group_name}##{name}",
109
+ name,
110
+ @uri,
111
+ range_from_node(node),
112
+ ))
113
+ end
114
+
115
+ sig { params(node: Prism::CallNode).void }
116
+ def on_call_node_enter(node)
117
+ name = node.name
118
+ return unless ACCESS_MODIFIERS.include?(name)
119
+
120
+ @visibility_stack << name
121
+ end
122
+
123
+ sig { params(node: Prism::CallNode).void }
124
+ def on_call_node_leave(node)
125
+ name = node.name
126
+ return unless ACCESS_MODIFIERS.include?(name)
127
+ return unless node.arguments&.arguments
128
+
129
+ @visibility_stack.pop
130
+ end
131
+
132
+ private
133
+
134
+ sig { params(attached_ancestors: T::Array[String], fully_qualified_name: String).returns(T::Boolean) }
135
+ def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
136
+ return false unless attached_ancestors.include?("Minitest::Test")
137
+
138
+ # We only support regular Minitest tests. The declarative syntax provided by ActiveSupport is handled by the
139
+ # Rails add-on
140
+ name_parts = fully_qualified_name.split("::")
141
+ singleton_name = "#{name_parts.join("::")}::<Class:#{name_parts.last}>"
142
+ !@index.linearized_ancestors_of(singleton_name).include?("ActiveSupport::Testing::Declarative")
143
+ rescue RubyIndexer::Index::NonExistingNamespaceError
144
+ true
145
+ end
146
+
147
+ sig do
148
+ params(
149
+ node: T.any(
150
+ Prism::ConstantPathNode,
151
+ Prism::ConstantReadNode,
152
+ Prism::ConstantPathTargetNode,
153
+ Prism::CallNode,
154
+ Prism::MissingNode,
155
+ ),
156
+ ).returns(String)
157
+ end
158
+ def name_with_dynamic_reference(node)
159
+ slice = node.slice
160
+ slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,62 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "ruby_lsp/listeners/test_style"
5
+
6
+ module RubyLsp
7
+ module Requests
8
+ # This is a custom request to ask the server to parse a test file and discover all available examples in it. Add-ons
9
+ # can augment the behavior through listeners, allowing them to handle discovery for different frameworks
10
+ class DiscoverTests < Request
11
+ extend T::Sig
12
+ include Support::Common
13
+
14
+ sig { params(global_state: GlobalState, document: RubyDocument, dispatcher: Prism::Dispatcher).void }
15
+ def initialize(global_state, document, dispatcher)
16
+ super()
17
+ @global_state = global_state
18
+ @document = document
19
+ @dispatcher = dispatcher
20
+ @response_builder = T.let(ResponseBuilders::TestCollection.new, ResponseBuilders::TestCollection)
21
+ @index = T.let(global_state.index, RubyIndexer::Index)
22
+ end
23
+
24
+ sig { override.returns(T::Array[Support::TestItem]) }
25
+ def perform
26
+ uri = @document.uri
27
+
28
+ # We normally only index test files once they are opened in the editor to save memory and avoid doing
29
+ # unnecessary work. If the file is already opened and we already indexed it, then we can just discover the tests
30
+ # straight away.
31
+ #
32
+ # However, if the user navigates to a specific test file from the explorer with nothing opened in the UI, then
33
+ # we will not have indexed the test file yet and trying to linearize the ancestor of the class will fail. In
34
+ # this case, we have to instantiate the indexer listener first, so that we insert classes, modules and methods
35
+ # in the index first and then discover the tests, all in the same traversal.
36
+ if @index.entries_for(uri.to_s)
37
+ Listeners::TestStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
38
+ @dispatcher.visit(@document.parse_result.value)
39
+ else
40
+ @global_state.synchronize do
41
+ RubyIndexer::DeclarationListener.new(
42
+ @index,
43
+ @dispatcher,
44
+ @document.parse_result,
45
+ uri,
46
+ collect_comments: true,
47
+ )
48
+
49
+ Listeners::TestStyle.new(@response_builder, @global_state, @dispatcher, @document.uri)
50
+
51
+ # Dispatch the events both for indexing the test file and discovering the tests. The order here is
52
+ # important because we need the index to be aware of the existing classes/modules/methods before the test
53
+ # listeners can do their work
54
+ @dispatcher.visit(@document.parse_result.value)
55
+ end
56
+ end
57
+
58
+ @response_builder.response
59
+ end
60
+ end
61
+ end
62
+ end
@@ -141,6 +141,8 @@ module RubyLsp
141
141
  Prism::ConstantPathNode,
142
142
  Prism::ConstantReadNode,
143
143
  Prism::ConstantPathTargetNode,
144
+ Prism::CallNode,
145
+ Prism::MissingNode,
144
146
  ),
145
147
  ).returns(T.nilable(String))
146
148
  end
@@ -0,0 +1,59 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ module Support
7
+ # Represents a test item as defined by the VS Code interface to be used in the test explorer
8
+ # See https://code.visualstudio.com/api/references/vscode-api#TestItem
9
+ #
10
+ # Note: this test item object can only represent test groups or examples discovered inside files. It cannot be
11
+ # used to represent test files, directories or workspaces
12
+ class TestItem
13
+ extend T::Sig
14
+
15
+ sig { returns(String) }
16
+ attr_reader :id, :label
17
+
18
+ sig { params(id: String, label: String, uri: URI::Generic, range: Interface::Range).void }
19
+ def initialize(id, label, uri, range)
20
+ @id = id
21
+ @label = label
22
+ @uri = uri
23
+ @range = range
24
+ @children = T.let({}, T::Hash[String, TestItem])
25
+ end
26
+
27
+ sig { params(item: TestItem).void }
28
+ def add(item)
29
+ if @children.key?(item.id)
30
+ raise ResponseBuilders::TestCollection::DuplicateIdError, "TestItem ID is already in use"
31
+ end
32
+
33
+ @children[item.id] = item
34
+ end
35
+
36
+ sig { params(id: String).returns(T.nilable(TestItem)) }
37
+ def [](id)
38
+ @children[id]
39
+ end
40
+
41
+ sig { returns(T::Array[TestItem]) }
42
+ def children
43
+ @children.values
44
+ end
45
+
46
+ sig { returns(T::Hash[Symbol, T.untyped]) }
47
+ def to_hash
48
+ {
49
+ id: @id,
50
+ label: @label,
51
+ uri: @uri,
52
+ range: @range,
53
+ children: children.map(&:to_hash),
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,38 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module ResponseBuilders
6
+ class TestCollection < ResponseBuilder
7
+ class DuplicateIdError < StandardError; end
8
+
9
+ extend T::Sig
10
+ extend T::Generic
11
+
12
+ ResponseType = type_member { { fixed: Requests::Support::TestItem } }
13
+
14
+ sig { void }
15
+ def initialize
16
+ super
17
+ @items = T.let({}, T::Hash[String, ResponseType])
18
+ end
19
+
20
+ sig { params(item: ResponseType).void }
21
+ def add(item)
22
+ raise DuplicateIdError, "TestItem ID is already in use" if @items.key?(item.id)
23
+
24
+ @items[item.id] = item
25
+ end
26
+
27
+ sig { params(id: String).returns(T.nilable(ResponseType)) }
28
+ def [](id)
29
+ @items[id]
30
+ end
31
+
32
+ sig { override.returns(T::Array[ResponseType]) }
33
+ def response
34
+ @items.values
35
+ end
36
+ end
37
+ end
38
+ end
@@ -108,6 +108,10 @@ module RubyLsp
108
108
  )
109
109
  when "rubyLsp/composeBundle"
110
110
  compose_bundle(message)
111
+ when "rubyLsp/diagnoseState"
112
+ diagnose_state(message)
113
+ when "rubyLsp/discoverTests"
114
+ discover_tests(message)
111
115
  when "$/cancelRequest"
112
116
  @global_state.synchronize { @cancelled_requests << message[:params][:id] }
113
117
  when nil
@@ -1032,7 +1036,14 @@ module RubyLsp
1032
1036
  end
1033
1037
  end
1034
1038
 
1035
- Addon.file_watcher_addons.each { |addon| T.unsafe(addon).workspace_did_change_watched_files(changes) }
1039
+ Addon.file_watcher_addons.each do |addon|
1040
+ T.unsafe(addon).workspace_did_change_watched_files(changes)
1041
+ rescue => e
1042
+ send_log_message(
1043
+ "Error in #{addon.name} add-on while processing watched file notifications: #{e.full_message}",
1044
+ type: Constant::MessageType::ERROR,
1045
+ )
1046
+ end
1036
1047
  end
1037
1048
 
1038
1049
  sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
@@ -1044,9 +1055,13 @@ module RubyLsp
1044
1055
 
1045
1056
  case change_type
1046
1057
  when Constant::FileChangeType::CREATED
1047
- index.index_single(uri, content)
1058
+ # If we receive a late created notification for a file that has already been claimed by the client, we want to
1059
+ # handle change for that URI so that the require path tree is updated
1060
+ @store.key?(uri) ? index.handle_change(uri, content) : index.index_single(uri, content)
1048
1061
  when Constant::FileChangeType::CHANGED
1049
- index.handle_change(uri, content)
1062
+ # We only handle changes on file watched notifications if the client is not the one managing this URI.
1063
+ # Otherwise, these changes are handled when running the combined requests
1064
+ index.handle_change(uri, content) unless @store.key?(uri)
1050
1065
  when Constant::FileChangeType::DELETED
1051
1066
  index.delete(uri)
1052
1067
  end
@@ -1357,5 +1372,47 @@ module RubyLsp
1357
1372
  end
1358
1373
  end
1359
1374
  end
1375
+
1376
+ # Returns internal state information for debugging purposes
1377
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
1378
+ def diagnose_state(message)
1379
+ documents = {}
1380
+ @store.each { |uri, document| documents[uri] = document.source }
1381
+
1382
+ send_message(
1383
+ Result.new(
1384
+ id: message[:id],
1385
+ response: {
1386
+ workerAlive: @worker.alive?,
1387
+ backtrace: @worker.backtrace,
1388
+ documents: documents,
1389
+ incomingQueueSize: @incoming_queue.length,
1390
+ },
1391
+ ),
1392
+ )
1393
+ end
1394
+
1395
+ # Discovers all available test groups and examples in a given file taking into consideration the merged response of
1396
+ # all add-ons
1397
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
1398
+ def discover_tests(message)
1399
+ document = @store.get(message.dig(:params, :textDocument, :uri))
1400
+
1401
+ unless document.is_a?(RubyDocument)
1402
+ send_empty_response(message[:id])
1403
+ return
1404
+ end
1405
+
1406
+ cached_response = document.cache_get("rubyLsp/discoverTests")
1407
+ if cached_response != Document::EMPTY_CACHE
1408
+ send_message(Result.new(id: message[:id], response: cached_response.map(&:to_hash)))
1409
+ return
1410
+ end
1411
+
1412
+ items = Requests::DiscoverTests.new(@global_state, document, Prism::Dispatcher.new).perform
1413
+ document.cache_set("rubyLsp/discoverTests", items)
1414
+
1415
+ send_message(Result.new(id: message[:id], response: items.map(&:to_hash)))
1416
+ end
1360
1417
  end
1361
1418
  end
@@ -158,7 +158,8 @@ module RubyLsp
158
158
  # If there's a top level Gemfile, we want to evaluate from the composed bundle. We get the source from the top
159
159
  # level Gemfile, so if there isn't one we need to add a default source
160
160
  if @gemfile&.exist? && @lockfile&.exist?
161
- parts << "eval_gemfile(File.expand_path(\"../#{@gemfile_name}\", __dir__))"
161
+ gemfile_path = @gemfile.relative_path_from(@custom_dir.realpath)
162
+ parts << "eval_gemfile(File.expand_path(\"#{gemfile_path}\", __dir__))"
162
163
  else
163
164
  parts.unshift('source "https://rubygems.org"')
164
165
  end
@@ -360,7 +361,7 @@ module RubyLsp
360
361
  # setting name `e` is `path` with a value of `vendor/bundle`, then it will return `"BUNDLE_PATH" =>
361
362
  # "vendor/bundle"`
362
363
  settings.all.to_h do |e|
363
- key = Bundler::Settings.key_for(e)
364
+ key = settings.key_for(e)
364
365
  value = Array(settings[e]).join(":").tr(" ", ":")
365
366
 
366
367
  [key, value]
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.9
4
+ version: 0.23.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-06 00:00:00.000000000 Z
10
+ date: 2025-02-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: language_server-protocol
@@ -140,6 +140,7 @@ files:
140
140
  - lib/ruby_lsp/listeners/inlay_hints.rb
141
141
  - lib/ruby_lsp/listeners/semantic_highlighting.rb
142
142
  - lib/ruby_lsp/listeners/signature_help.rb
143
+ - lib/ruby_lsp/listeners/test_style.rb
143
144
  - lib/ruby_lsp/load_sorbet.rb
144
145
  - lib/ruby_lsp/node_context.rb
145
146
  - lib/ruby_lsp/rbs_document.rb
@@ -150,6 +151,7 @@ files:
150
151
  - lib/ruby_lsp/requests/completion_resolve.rb
151
152
  - lib/ruby_lsp/requests/definition.rb
152
153
  - lib/ruby_lsp/requests/diagnostics.rb
154
+ - lib/ruby_lsp/requests/discover_tests.rb
153
155
  - lib/ruby_lsp/requests/document_highlight.rb
154
156
  - lib/ruby_lsp/requests/document_link.rb
155
157
  - lib/ruby_lsp/requests/document_symbol.rb
@@ -178,6 +180,7 @@ files:
178
180
  - lib/ruby_lsp/requests/support/sorbet.rb
179
181
  - lib/ruby_lsp/requests/support/source_uri.rb
180
182
  - lib/ruby_lsp/requests/support/syntax_tree_formatter.rb
183
+ - lib/ruby_lsp/requests/support/test_item.rb
181
184
  - lib/ruby_lsp/requests/type_hierarchy_supertypes.rb
182
185
  - lib/ruby_lsp/requests/workspace_symbol.rb
183
186
  - lib/ruby_lsp/response_builders/collection_response_builder.rb
@@ -186,6 +189,7 @@ files:
186
189
  - lib/ruby_lsp/response_builders/response_builder.rb
187
190
  - lib/ruby_lsp/response_builders/semantic_highlighting.rb
188
191
  - lib/ruby_lsp/response_builders/signature_help.rb
192
+ - lib/ruby_lsp/response_builders/test_collection.rb
189
193
  - lib/ruby_lsp/ruby_document.rb
190
194
  - lib/ruby_lsp/scope.rb
191
195
  - lib/ruby_lsp/scripts/compose_bundle.rb