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 +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/global_state.rb +5 -0
- data/lib/ruby_lsp/internal.rb +4 -0
- data/lib/ruby_lsp/listeners/completion.rb +3 -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 +60 -3
- 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
|
@@ -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
|
|
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"
|
@@ -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
|
@@ -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
|
@@ -1032,7 +1036,14 @@ module RubyLsp
|
|
1032
1036
|
end
|
1033
1037
|
end
|
1034
1038
|
|
1035
|
-
Addon.file_watcher_addons.each
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|