ruby-lsp 0.23.10 → 0.23.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +10 -3
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +75 -0
- data/lib/ruby_indexer/test/index_test.rb +21 -0
- data/lib/ruby_lsp/base_server.rb +2 -1
- data/lib/ruby_lsp/internal.rb +4 -0
- data/lib/ruby_lsp/listeners/test_style.rb +164 -0
- data/lib/ruby_lsp/requests/discover_tests.rb +62 -0
- data/lib/ruby_lsp/requests/support/common.rb +2 -0
- data/lib/ruby_lsp/requests/support/test_item.rb +59 -0
- data/lib/ruby_lsp/response_builders/test_collection.rb +38 -0
- data/lib/ruby_lsp/server.rb +46 -0
- data/lib/ruby_lsp/setup_bundler.rb +3 -2
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b93894c0d2c2687def87d9e173c7a9e45e810088613cb902c2a4b1a9d93a8404
|
4
|
+
data.tar.gz: 9e92f360c8367cde864ac1a5dd2305170a975fa3164e6fc2c1d6f492396fbbc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc2172fba1c50192cb86d2dfc619c633cf756f3ebb574caaaeabb81fe23d283b7b896f48fe23981d29c472fce98248137171d119b33c75d1a7f6fafc73c7a775
|
7
|
+
data.tar.gz: 03b0b5987fc8dcc6c989ce6c5df55e16fff906836ef74aa993ddc445b50c5d3458775fee1a31b9e228710df44f80b8338390ef1fdf75d464deac4e38fe822644
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
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
|
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
|
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
|
data/lib/ruby_lsp/base_server.rb
CHANGED
@@ -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
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -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"
|
@@ -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
|
@@ -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
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -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
|
@@ -1368,5 +1372,47 @@ module RubyLsp
|
|
1368
1372
|
end
|
1369
1373
|
end
|
1370
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
|
1371
1417
|
end
|
1372
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
|
-
|
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 =
|
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.
|
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-
|
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
|