ruby-lsp 0.23.20 → 0.23.21
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/exe/ruby-lsp-test-exec +18 -0
- data/lib/ruby_lsp/listeners/spec_style.rb +77 -61
- data/lib/ruby_lsp/listeners/test_style.rb +31 -11
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +9 -3
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +12 -4
- data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +0 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 336c4740e2cc0c03bec77c7854065bc9115ed14ab88949d41ae8a8da7d58b1da
|
4
|
+
data.tar.gz: 0b1ccf86525c8db95beb310c23977353520e0951f7c627199db567c50b8b29f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffb2a4605bf6c3b7e82dde8ca73cd624c22aa16a2cf91569b7b38d06a7d876a932023efa4a4dea1871ca0e5965a923be97b9e870f2fd9f0b36d70d354213e9ea
|
7
|
+
data.tar.gz: e488ffda15f9d7a0549294d70f607c09620144ff3a1fea3441d834c3c2db06cf691997005c357cffcf596b4e41e83599111c48da07d92ea26bde293ae6edde0a
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
1
|
+
0.23.21
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Append to RUBYOPT the necessary requires to hook our custom test reporters so that results are automatically
|
5
|
+
# reflected in the test explorer
|
6
|
+
rubyopt = [
|
7
|
+
*ENV["RUBYOPT"],
|
8
|
+
"-rbundler/setup",
|
9
|
+
"-r#{File.expand_path("../lib/ruby_lsp/test_reporters/minitest_reporter", __dir__)}",
|
10
|
+
"-r#{File.expand_path("../lib/ruby_lsp/test_reporters/test_unit_reporter", __dir__)}",
|
11
|
+
].join(" ")
|
12
|
+
|
13
|
+
# Replace this process with whatever command was passed. We only want to set RUBYOPT.
|
14
|
+
# The way you use this executable is by prefixing your test command with `ruby-lsp-test-exec`, like so:
|
15
|
+
# ruby-lsp-test-exec bundle exec ruby -Itest test/example_test.rb
|
16
|
+
# ruby-lsp-test-exec bundle exec ruby -Ispec spec/example_spec.rb
|
17
|
+
# ruby-lsp-test-exec bundle exec rspec spec/example_spec.rb
|
18
|
+
exec({ "RUBYOPT" => rubyopt }, *ARGV)
|
@@ -4,12 +4,24 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module Listeners
|
6
6
|
class SpecStyle < TestDiscovery
|
7
|
+
class Group
|
8
|
+
#: String
|
9
|
+
attr_reader :id
|
10
|
+
|
11
|
+
#: (String) -> void
|
12
|
+
def initialize(id)
|
13
|
+
@id = id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ClassGroup < Group; end
|
18
|
+
class DescribeGroup < Group; end
|
19
|
+
|
7
20
|
#: (ResponseBuilders::TestCollection, GlobalState, Prism::Dispatcher, URI::Generic) -> void
|
8
21
|
def initialize(response_builder, global_state, dispatcher, uri)
|
9
22
|
super
|
10
23
|
|
11
|
-
@
|
12
|
-
@spec_class_stack = [] #: Array[bool]
|
24
|
+
@spec_group_id_stack = [] #: Array[Group?]
|
13
25
|
|
14
26
|
dispatcher.register(
|
15
27
|
self,
|
@@ -22,21 +34,21 @@ module RubyLsp
|
|
22
34
|
|
23
35
|
#: (Prism::ClassNode) -> void
|
24
36
|
def on_class_node_enter(node)
|
25
|
-
with_test_ancestor_tracking(node) do |
|
26
|
-
|
27
|
-
@spec_class_stack.push(is_spec)
|
37
|
+
with_test_ancestor_tracking(node) do |name, ancestors|
|
38
|
+
@spec_group_id_stack << (ancestors.include?("Minitest::Spec") ? ClassGroup.new(name) : nil)
|
28
39
|
end
|
29
40
|
end
|
30
41
|
|
31
42
|
#: (Prism::ClassNode) -> void
|
32
43
|
def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
33
44
|
super
|
34
|
-
|
35
|
-
@spec_class_stack.pop
|
45
|
+
@spec_group_id_stack.pop
|
36
46
|
end
|
37
47
|
|
38
48
|
#: (Prism::CallNode) -> void
|
39
49
|
def on_call_node_enter(node)
|
50
|
+
return unless in_spec_context?
|
51
|
+
|
40
52
|
case node.name
|
41
53
|
when :describe
|
42
54
|
handle_describe(node)
|
@@ -49,84 +61,63 @@ module RubyLsp
|
|
49
61
|
def on_call_node_leave(node)
|
50
62
|
return unless node.name == :describe && !node.receiver
|
51
63
|
|
52
|
-
@
|
64
|
+
@spec_group_id_stack.pop
|
53
65
|
end
|
54
66
|
|
55
67
|
private
|
56
68
|
|
57
69
|
#: (Prism::CallNode) -> void
|
58
70
|
def handle_describe(node)
|
71
|
+
# Describes will include the nesting of all classes and all outer describes as part of its ID, unlike classes
|
72
|
+
# that ignore describes
|
59
73
|
return if node.block.nil?
|
60
74
|
|
61
75
|
description = extract_description(node)
|
62
76
|
return unless description
|
63
77
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
description,
|
69
|
-
description,
|
70
|
-
@uri,
|
71
|
-
range_from_node(node),
|
72
|
-
framework: :minitest,
|
73
|
-
)
|
74
|
-
@response_builder.add(test_item)
|
75
|
-
@response_builder.add_code_lens(test_item)
|
78
|
+
parent = latest_group
|
79
|
+
id = case parent
|
80
|
+
when Requests::Support::TestItem
|
81
|
+
"#{parent.id}::#{description}"
|
76
82
|
else
|
77
|
-
|
83
|
+
description
|
78
84
|
end
|
79
85
|
|
80
|
-
|
86
|
+
test_item = Requests::Support::TestItem.new(
|
87
|
+
id,
|
88
|
+
description,
|
89
|
+
@uri,
|
90
|
+
range_from_node(node),
|
91
|
+
framework: :minitest,
|
92
|
+
)
|
93
|
+
|
94
|
+
parent.add(test_item)
|
95
|
+
@response_builder.add_code_lens(test_item)
|
96
|
+
@spec_group_id_stack << DescribeGroup.new(id)
|
81
97
|
end
|
82
98
|
|
83
99
|
#: (Prism::CallNode) -> void
|
84
100
|
def handle_example(node)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
101
|
+
# Minitest formats the descriptions into test method names by using the count of examples with the description
|
102
|
+
# We are not guaranteed to discover examples in the exact order using static analysis, so we use the line number
|
103
|
+
# instead. Note that anonymous examples mixed with meta-programming will not be handled correctly
|
104
|
+
description = extract_description(node) || "anonymous"
|
105
|
+
line = node.location.start_line - 1
|
106
|
+
parent = latest_group
|
107
|
+
return unless parent.is_a?(Requests::Support::TestItem)
|
91
108
|
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
#: (String, Prism::CallNode) -> void
|
96
|
-
def add_to_parent_test_group(description, node)
|
97
|
-
parent_test_group = find_parent_test_group
|
98
|
-
return unless parent_test_group
|
109
|
+
id = "#{parent.id}##{format("test_%04d_%s", line, description)}"
|
99
110
|
|
100
111
|
test_item = Requests::Support::TestItem.new(
|
101
|
-
|
112
|
+
id,
|
102
113
|
description,
|
103
114
|
@uri,
|
104
115
|
range_from_node(node),
|
105
116
|
framework: :minitest,
|
106
117
|
)
|
107
|
-
parent_test_group.add(test_item)
|
108
|
-
@response_builder.add_code_lens(test_item)
|
109
|
-
end
|
110
|
-
|
111
|
-
#: -> Requests::Support::TestItem?
|
112
|
-
def find_parent_test_group
|
113
|
-
root_group_name, nested_describe_groups = if @nesting.empty?
|
114
|
-
[@describe_block_nesting.first, @describe_block_nesting[1..]]
|
115
|
-
else
|
116
|
-
[RubyIndexer::Index.actual_nesting(@nesting, nil).join("::"), @describe_block_nesting]
|
117
|
-
end
|
118
|
-
return unless root_group_name
|
119
|
-
|
120
|
-
test_group = @response_builder[root_group_name] #: Requests::Support::TestItem?
|
121
|
-
return unless test_group
|
122
|
-
|
123
|
-
return test_group unless nested_describe_groups
|
124
118
|
|
125
|
-
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
test_group
|
119
|
+
parent.add(test_item)
|
120
|
+
@response_builder.add_code_lens(test_item)
|
130
121
|
end
|
131
122
|
|
132
123
|
#: (Prism::CallNode) -> String?
|
@@ -144,11 +135,36 @@ module RubyLsp
|
|
144
135
|
end
|
145
136
|
end
|
146
137
|
|
138
|
+
#: -> (Requests::Support::TestItem | ResponseBuilders::TestCollection)
|
139
|
+
def latest_group
|
140
|
+
return @response_builder if @spec_group_id_stack.compact.empty?
|
141
|
+
|
142
|
+
first_class_index = @spec_group_id_stack.rindex { |i| i.is_a?(ClassGroup) } || 0
|
143
|
+
first_class = @spec_group_id_stack[0] #: as !nil
|
144
|
+
item = @response_builder[first_class.id] #: as !nil
|
145
|
+
|
146
|
+
# Descend into child items from the beginning all the way to the latest class group, ignoring describes
|
147
|
+
@spec_group_id_stack[1..first_class_index] #: as !nil
|
148
|
+
.each do |group|
|
149
|
+
next unless group.is_a?(ClassGroup)
|
150
|
+
|
151
|
+
item = item[group.id] #: as !nil
|
152
|
+
end
|
153
|
+
|
154
|
+
# From the class forward, we must take describes into account
|
155
|
+
@spec_group_id_stack[first_class_index + 1..] #: as !nil
|
156
|
+
.each do |group|
|
157
|
+
next unless group
|
158
|
+
|
159
|
+
item = item[group.id] #: as !nil
|
160
|
+
end
|
161
|
+
|
162
|
+
item
|
163
|
+
end
|
164
|
+
|
147
165
|
#: -> bool
|
148
166
|
def in_spec_context?
|
149
|
-
|
150
|
-
|
151
|
-
@spec_class_stack.last #: as !nil
|
167
|
+
@nesting.empty? || @spec_group_id_stack.any? { |id| id }
|
152
168
|
end
|
153
169
|
end
|
154
170
|
end
|
@@ -34,7 +34,7 @@ module RubyLsp
|
|
34
34
|
if tags.include?("test_dir")
|
35
35
|
if children.empty?
|
36
36
|
full_files.concat(Dir.glob(
|
37
|
-
"#{path}/**/{*_test,test_
|
37
|
+
"#{path}/**/{*_test,test_*,*_spec}.rb",
|
38
38
|
File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME,
|
39
39
|
))
|
40
40
|
end
|
@@ -74,7 +74,9 @@ module RubyLsp
|
|
74
74
|
end
|
75
75
|
|
76
76
|
unless full_files.empty?
|
77
|
-
|
77
|
+
specs, tests = full_files.partition { |path| spec?(path) }
|
78
|
+
commands << "#{BASE_COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{tests.join(" ")}" if tests.any?
|
79
|
+
commands << "#{BASE_COMMAND} -Ispec -e \"ARGV.each { |f| require f }\" #{specs.join(" ")}" if specs.any?
|
78
80
|
end
|
79
81
|
|
80
82
|
commands
|
@@ -82,6 +84,11 @@ module RubyLsp
|
|
82
84
|
|
83
85
|
private
|
84
86
|
|
87
|
+
#: (String) -> bool
|
88
|
+
def spec?(path)
|
89
|
+
File.fnmatch?("**/spec/**/*_spec.rb", path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
90
|
+
end
|
91
|
+
|
85
92
|
#: (String, Hash[String, Hash[Symbol, untyped]]) -> String
|
86
93
|
def handle_minitest_groups(file_path, groups_and_examples)
|
87
94
|
regexes = groups_and_examples.flat_map do |group, info|
|
@@ -105,7 +112,8 @@ module RubyLsp
|
|
105
112
|
"(#{regexes.join("|")})"
|
106
113
|
end
|
107
114
|
|
108
|
-
|
115
|
+
load_path = spec?(file_path) ? "-Ispec" : "-Itest"
|
116
|
+
"#{BASE_COMMAND} #{load_path} #{file_path} --name \"/#{regex}/\""
|
109
117
|
end
|
110
118
|
|
111
119
|
#: (String, Hash[String, Hash[Symbol, untyped]]) -> Array[String]
|
@@ -148,6 +156,7 @@ module RubyLsp
|
|
148
156
|
super
|
149
157
|
|
150
158
|
@framework = :minitest #: Symbol
|
159
|
+
@parent_stack = [@response_builder] #: Array[(Requests::Support::TestItem | ResponseBuilders::TestCollection)?]
|
151
160
|
|
152
161
|
dispatcher.register(
|
153
162
|
self,
|
@@ -173,12 +182,21 @@ module RubyLsp
|
|
173
182
|
framework: @framework,
|
174
183
|
)
|
175
184
|
|
176
|
-
|
185
|
+
last_test_group.add(test_item)
|
177
186
|
@response_builder.add_code_lens(test_item)
|
187
|
+
@parent_stack << test_item
|
188
|
+
else
|
189
|
+
@parent_stack << nil
|
178
190
|
end
|
179
191
|
end
|
180
192
|
end
|
181
193
|
|
194
|
+
#: (Prism::ClassNode node) -> void
|
195
|
+
def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
196
|
+
@parent_stack.pop
|
197
|
+
super
|
198
|
+
end
|
199
|
+
|
182
200
|
#: (Prism::DefNode node) -> void
|
183
201
|
def on_def_node_enter(node)
|
184
202
|
return if @visibility_stack.last != :public
|
@@ -187,12 +205,8 @@ module RubyLsp
|
|
187
205
|
return unless name.start_with?("test_")
|
188
206
|
|
189
207
|
current_group_name = RubyIndexer::Index.actual_nesting(@nesting, nil).join("::")
|
190
|
-
|
191
|
-
|
192
|
-
# previously pushed and thus we return early and avoid adding items for a framework this listener is not
|
193
|
-
# interested in
|
194
|
-
test_item = @response_builder[current_group_name]
|
195
|
-
return unless test_item
|
208
|
+
parent = @parent_stack.last
|
209
|
+
return unless parent.is_a?(Requests::Support::TestItem)
|
196
210
|
|
197
211
|
example_item = Requests::Support::TestItem.new(
|
198
212
|
"#{current_group_name}##{name}",
|
@@ -201,7 +215,7 @@ module RubyLsp
|
|
201
215
|
range_from_node(node),
|
202
216
|
framework: @framework,
|
203
217
|
)
|
204
|
-
|
218
|
+
parent.add(example_item)
|
205
219
|
@response_builder.add_code_lens(example_item)
|
206
220
|
end
|
207
221
|
|
@@ -224,6 +238,12 @@ module RubyLsp
|
|
224
238
|
|
225
239
|
private
|
226
240
|
|
241
|
+
#: -> (Requests::Support::TestItem | ResponseBuilders::TestCollection)
|
242
|
+
def last_test_group
|
243
|
+
index = @parent_stack.rindex { |i| i } #: as !nil
|
244
|
+
@parent_stack[index] #: as Requests::Support::TestItem | ResponseBuilders::TestCollection
|
245
|
+
end
|
246
|
+
|
227
247
|
#: (Array[String] attached_ancestors, String fully_qualified_name) -> bool
|
228
248
|
def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
|
229
249
|
return false unless attached_ancestors.include?("Minitest::Test")
|
@@ -6,6 +6,7 @@ require "json"
|
|
6
6
|
require "socket"
|
7
7
|
require "singleton"
|
8
8
|
require "tmpdir"
|
9
|
+
require_relative "../../ruby_indexer/lib/ruby_indexer/uri"
|
9
10
|
|
10
11
|
module RubyLsp
|
11
12
|
class LspReporter
|
@@ -19,15 +20,20 @@ module RubyLsp
|
|
19
20
|
dir_path = File.join(Dir.tmpdir, "ruby-lsp")
|
20
21
|
FileUtils.mkdir_p(dir_path)
|
21
22
|
|
22
|
-
|
23
|
+
# Remove in 1 month once updates have rolled out
|
24
|
+
legacy_port_path = File.join(dir_path, "test_reporter_port")
|
25
|
+
port_db_path = File.join(dir_path, "test_reporter_port_db.json")
|
23
26
|
port = ENV["RUBY_LSP_REPORTER_PORT"]
|
24
27
|
|
25
28
|
@io = begin
|
26
29
|
# The environment variable is only used for tests. The extension always writes to the temporary file
|
27
30
|
if port
|
28
31
|
TCPSocket.new("localhost", port)
|
29
|
-
elsif File.exist?(
|
30
|
-
|
32
|
+
elsif File.exist?(port_db_path)
|
33
|
+
db = JSON.load_file(port_db_path)
|
34
|
+
TCPSocket.new("localhost", db[Dir.pwd])
|
35
|
+
elsif File.exist?(legacy_port_path)
|
36
|
+
TCPSocket.new("localhost", File.read(legacy_port_path))
|
31
37
|
else
|
32
38
|
# For tests that don't spawn the TCP server
|
33
39
|
require "stringio"
|
@@ -8,7 +8,6 @@ rescue LoadError
|
|
8
8
|
end
|
9
9
|
|
10
10
|
require_relative "lsp_reporter"
|
11
|
-
require "ruby_indexer/lib/ruby_indexer/uri"
|
12
11
|
|
13
12
|
module RubyLsp
|
14
13
|
# An override of the default progress reporter in Minitest to add color to the output
|
@@ -54,15 +53,19 @@ module RubyLsp
|
|
54
53
|
uri, line = LspReporter.instance.uri_and_line_for(test_class.instance_method(method_name))
|
55
54
|
return unless uri
|
56
55
|
|
57
|
-
|
56
|
+
id = "#{test_class.name}##{handle_spec_test_id(method_name, line)}"
|
57
|
+
LspReporter.instance.start_test(id: id, uri: uri, line: line)
|
58
58
|
end
|
59
59
|
|
60
60
|
#: (Minitest::Result result) -> void
|
61
61
|
def record(result)
|
62
|
-
|
63
|
-
file_path, _line = result.source_location
|
62
|
+
file_path, line = result.source_location
|
64
63
|
return unless file_path
|
65
64
|
|
65
|
+
zero_based_line = line ? line - 1 : nil
|
66
|
+
name = handle_spec_test_id(result.name, zero_based_line)
|
67
|
+
id = "#{result.klass}##{name}"
|
68
|
+
|
66
69
|
uri = URI::Generic.from_path(path: File.expand_path(file_path))
|
67
70
|
|
68
71
|
if result.error?
|
@@ -82,6 +85,11 @@ module RubyLsp
|
|
82
85
|
def report
|
83
86
|
LspReporter.instance.shutdown
|
84
87
|
end
|
88
|
+
|
89
|
+
#: (String, Integer?) -> String
|
90
|
+
def handle_spec_test_id(method_name, line)
|
91
|
+
method_name.gsub(/(?<=test_)\d{4}(?=_)/, format("%04d", line.to_s))
|
92
|
+
end
|
85
93
|
end
|
86
94
|
end
|
87
95
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.21
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
@@ -84,6 +84,7 @@ executables:
|
|
84
84
|
- ruby-lsp
|
85
85
|
- ruby-lsp-check
|
86
86
|
- ruby-lsp-launcher
|
87
|
+
- ruby-lsp-test-exec
|
87
88
|
extensions: []
|
88
89
|
extra_rdoc_files: []
|
89
90
|
files:
|
@@ -93,6 +94,7 @@ files:
|
|
93
94
|
- exe/ruby-lsp
|
94
95
|
- exe/ruby-lsp-check
|
95
96
|
- exe/ruby-lsp-launcher
|
97
|
+
- exe/ruby-lsp-test-exec
|
96
98
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
97
99
|
- lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
|
98
100
|
- lib/ruby-lsp.rb
|
@@ -228,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
228
230
|
- !ruby/object:Gem::Version
|
229
231
|
version: '0'
|
230
232
|
requirements: []
|
231
|
-
rubygems_version: 3.6.
|
233
|
+
rubygems_version: 3.6.9
|
232
234
|
specification_version: 4
|
233
235
|
summary: An opinionated language server for Ruby
|
234
236
|
test_files: []
|