ruby-lsp 0.23.20 → 0.26.1
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 +10 -4
- data/exe/ruby-lsp-check +0 -4
- data/exe/ruby-lsp-launcher +25 -11
- data/exe/ruby-lsp-test-exec +6 -0
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +7 -1
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -4
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +10 -19
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +29 -7
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -2
- data/lib/ruby_indexer/test/index_test.rb +39 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +24 -0
- data/lib/ruby_indexer/test/method_test.rb +17 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +2 -2
- data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
- data/lib/ruby_lsp/addon.rb +44 -15
- data/lib/ruby_lsp/base_server.rb +34 -26
- data/lib/ruby_lsp/document.rb +162 -52
- data/lib/ruby_lsp/erb_document.rb +8 -3
- data/lib/ruby_lsp/global_state.rb +21 -0
- data/lib/ruby_lsp/internal.rb +0 -2
- data/lib/ruby_lsp/listeners/completion.rb +14 -3
- data/lib/ruby_lsp/listeners/hover.rb +7 -0
- data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
- data/lib/ruby_lsp/listeners/spec_style.rb +126 -67
- data/lib/ruby_lsp/listeners/test_discovery.rb +18 -15
- data/lib/ruby_lsp/listeners/test_style.rb +56 -23
- data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +14 -5
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +10 -6
- data/lib/ruby_lsp/requests/rename.rb +8 -6
- data/lib/ruby_lsp/requests/request.rb +6 -7
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +1 -3
- data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -3
- data/lib/ruby_lsp/response_builders/response_builder.rb +6 -8
- data/lib/ruby_lsp/ruby_document.rb +10 -5
- data/lib/ruby_lsp/server.rb +95 -110
- data/lib/ruby_lsp/setup_bundler.rb +59 -25
- data/lib/ruby_lsp/static_docs.rb +1 -0
- data/lib/ruby_lsp/store.rb +0 -10
- data/lib/ruby_lsp/test_helper.rb +1 -4
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +18 -7
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +54 -7
- data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +0 -1
- data/lib/ruby_lsp/utils.rb +47 -11
- data/static_docs/break.md +103 -0
- metadata +7 -19
- data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -56,6 +56,17 @@ module RubyLsp
|
|
56
56
|
@enabled_feature_flags = {} #: Hash[Symbol, bool]
|
57
57
|
@mutex = Mutex.new #: Mutex
|
58
58
|
@telemetry_machine_id = nil #: String?
|
59
|
+
@feature_configuration = {
|
60
|
+
inlayHint: RequestConfig.new({
|
61
|
+
enableAll: false,
|
62
|
+
implicitRescue: false,
|
63
|
+
implicitHashValue: false,
|
64
|
+
}),
|
65
|
+
codeLens: RequestConfig.new({
|
66
|
+
enableAll: false,
|
67
|
+
enableTestCodeLens: true,
|
68
|
+
}),
|
69
|
+
} #: Hash[Symbol, RequestConfig]
|
59
70
|
end
|
60
71
|
|
61
72
|
#: [T] { -> T } -> T
|
@@ -175,9 +186,19 @@ module RubyLsp
|
|
175
186
|
@enabled_feature_flags = enabled_flags if enabled_flags
|
176
187
|
|
177
188
|
@telemetry_machine_id = options.dig(:initializationOptions, :telemetryMachineId)
|
189
|
+
|
190
|
+
options.dig(:initializationOptions, :featuresConfiguration)&.each do |feature_name, config|
|
191
|
+
@feature_configuration[feature_name]&.merge!(config)
|
192
|
+
end
|
193
|
+
|
178
194
|
notifications
|
179
195
|
end
|
180
196
|
|
197
|
+
#: (Symbol) -> RequestConfig?
|
198
|
+
def feature_configuration(feature_name)
|
199
|
+
@feature_configuration[feature_name]
|
200
|
+
end
|
201
|
+
|
181
202
|
#: (Symbol flag) -> bool?
|
182
203
|
def enabled_feature?(flag)
|
183
204
|
@enabled_feature_flags[:all] || @enabled_feature_flags[flag]
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -6,8 +6,6 @@
|
|
6
6
|
yarp_require_paths = Gem.loaded_specs["yarp"]&.full_require_paths
|
7
7
|
$LOAD_PATH.delete_if { |path| yarp_require_paths.include?(path) } if yarp_require_paths
|
8
8
|
|
9
|
-
require "sorbet-runtime"
|
10
|
-
|
11
9
|
# Set Bundler's UI level to silent as soon as possible to prevent any prints to STDOUT
|
12
10
|
require "bundler"
|
13
11
|
Bundler.ui.level = :silent
|
@@ -445,11 +445,14 @@ module RubyLsp
|
|
445
445
|
return unless arguments_node
|
446
446
|
|
447
447
|
path_node_to_complete = arguments_node.arguments.first
|
448
|
-
|
449
448
|
return unless path_node_to_complete.is_a?(Prism::StringNode)
|
450
449
|
|
451
|
-
|
450
|
+
# If the file is unsaved (e.g.: untitled:Untitled-1), we can't provide relative completion as we don't know
|
451
|
+
# where the user intends to save it
|
452
|
+
full_path = @uri.to_standardized_path
|
453
|
+
return unless full_path
|
452
454
|
|
455
|
+
origin_dir = Pathname.new(full_path).dirname
|
453
456
|
content = path_node_to_complete.content
|
454
457
|
# if the path is not a directory, glob all possible next characters
|
455
458
|
# for example ../somethi| (where | is the cursor position)
|
@@ -516,6 +519,14 @@ module RubyLsp
|
|
516
519
|
|
517
520
|
entry_name = entry.name
|
518
521
|
owner_name = entry.owner&.name
|
522
|
+
new_text = entry_name
|
523
|
+
|
524
|
+
if entry_name.end_with?("=")
|
525
|
+
method_name = entry_name.delete_suffix("=")
|
526
|
+
|
527
|
+
# For writer methods, format as assignment and prefix "self." when no receiver is specified
|
528
|
+
new_text = node.receiver.nil? ? "self.#{method_name} = " : "#{method_name} = "
|
529
|
+
end
|
519
530
|
|
520
531
|
label_details = Interface::CompletionItemLabelDetails.new(
|
521
532
|
description: entry.file_name,
|
@@ -525,7 +536,7 @@ module RubyLsp
|
|
525
536
|
label: entry_name,
|
526
537
|
filter_text: entry_name,
|
527
538
|
label_details: label_details,
|
528
|
-
text_edit: Interface::TextEdit.new(range: range, new_text:
|
539
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: new_text),
|
529
540
|
kind: Constant::CompletionItemKind::METHOD,
|
530
541
|
data: {
|
531
542
|
owner_name: owner_name,
|
@@ -7,6 +7,7 @@ module RubyLsp
|
|
7
7
|
include Requests::Support::Common
|
8
8
|
|
9
9
|
ALLOWED_TARGETS = [
|
10
|
+
Prism::BreakNode,
|
10
11
|
Prism::CallNode,
|
11
12
|
Prism::ConstantReadNode,
|
12
13
|
Prism::ConstantWriteNode,
|
@@ -54,6 +55,7 @@ module RubyLsp
|
|
54
55
|
|
55
56
|
dispatcher.register(
|
56
57
|
self,
|
58
|
+
:on_break_node_enter,
|
57
59
|
:on_constant_read_node_enter,
|
58
60
|
:on_constant_write_node_enter,
|
59
61
|
:on_constant_path_node_enter,
|
@@ -84,6 +86,11 @@ module RubyLsp
|
|
84
86
|
)
|
85
87
|
end
|
86
88
|
|
89
|
+
#: (Prism::BreakNode node) -> void
|
90
|
+
def on_break_node_enter(node)
|
91
|
+
handle_keyword_documentation(node.keyword)
|
92
|
+
end
|
93
|
+
|
87
94
|
#: (Prism::StringNode node) -> void
|
88
95
|
def on_string_node_enter(node)
|
89
96
|
if @path && File.basename(@path) == GEMFILE_NAME
|
@@ -8,10 +8,12 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
RESCUE_STRING_LENGTH = "rescue".length #: Integer
|
10
10
|
|
11
|
-
#: (ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint]
|
12
|
-
def initialize(
|
11
|
+
#: (GlobalState, ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint], Prism::Dispatcher) -> void
|
12
|
+
def initialize(global_state, response_builder, dispatcher)
|
13
13
|
@response_builder = response_builder
|
14
|
-
@hints_configuration =
|
14
|
+
@hints_configuration = ( # rubocop:disable Style/RedundantParentheses
|
15
|
+
global_state.feature_configuration(:inlayHint) #: as !nil
|
16
|
+
) #: RequestConfig
|
15
17
|
|
16
18
|
dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
|
17
19
|
end
|
@@ -4,39 +4,62 @@
|
|
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
|
-
super
|
22
|
+
super(response_builder, global_state, uri)
|
10
23
|
|
11
|
-
@
|
12
|
-
@spec_class_stack = [] #: Array[bool]
|
24
|
+
@spec_group_id_stack = [] #: Array[Group?]
|
13
25
|
|
14
|
-
|
15
|
-
|
16
|
-
# Common handlers registered in parent class
|
26
|
+
register_events(
|
27
|
+
dispatcher,
|
17
28
|
:on_class_node_enter,
|
18
|
-
:on_call_node_enter,
|
29
|
+
:on_call_node_enter,
|
19
30
|
:on_call_node_leave,
|
20
31
|
)
|
21
32
|
end
|
22
33
|
|
23
34
|
#: (Prism::ClassNode) -> void
|
24
|
-
def on_class_node_enter(node)
|
25
|
-
with_test_ancestor_tracking(node) do |
|
26
|
-
|
27
|
-
@spec_class_stack.push(is_spec)
|
35
|
+
def on_class_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
36
|
+
with_test_ancestor_tracking(node) do |name, ancestors|
|
37
|
+
@spec_group_id_stack << (ancestors.include?("Minitest::Spec") ? ClassGroup.new(name) : nil)
|
28
38
|
end
|
29
39
|
end
|
30
40
|
|
31
41
|
#: (Prism::ClassNode) -> void
|
32
42
|
def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
43
|
+
@spec_group_id_stack.pop
|
33
44
|
super
|
45
|
+
end
|
34
46
|
|
35
|
-
|
47
|
+
#: (Prism::ModuleNode) -> void
|
48
|
+
def on_module_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
49
|
+
@spec_group_id_stack << nil
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
#: (Prism::ModuleNode) -> void
|
54
|
+
def on_module_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
55
|
+
@spec_group_id_stack.pop
|
56
|
+
super
|
36
57
|
end
|
37
58
|
|
38
59
|
#: (Prism::CallNode) -> void
|
39
|
-
def on_call_node_enter(node)
|
60
|
+
def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
61
|
+
return unless in_spec_context?
|
62
|
+
|
40
63
|
case node.name
|
41
64
|
when :describe
|
42
65
|
handle_describe(node)
|
@@ -46,87 +69,74 @@ module RubyLsp
|
|
46
69
|
end
|
47
70
|
|
48
71
|
#: (Prism::CallNode) -> void
|
49
|
-
def on_call_node_leave(node)
|
72
|
+
def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
50
73
|
return unless node.name == :describe && !node.receiver
|
51
74
|
|
52
|
-
@
|
75
|
+
current_group = @spec_group_id_stack.last
|
76
|
+
return unless current_group.is_a?(DescribeGroup)
|
77
|
+
|
78
|
+
description = extract_description(node)
|
79
|
+
return unless description && current_group.id.end_with?(description)
|
80
|
+
|
81
|
+
@spec_group_id_stack.pop
|
53
82
|
end
|
54
83
|
|
55
84
|
private
|
56
85
|
|
57
86
|
#: (Prism::CallNode) -> void
|
58
87
|
def handle_describe(node)
|
88
|
+
# Describes will include the nesting of all classes and all outer describes as part of its ID, unlike classes
|
89
|
+
# that ignore describes
|
59
90
|
return if node.block.nil?
|
60
91
|
|
61
92
|
description = extract_description(node)
|
62
93
|
return unless description
|
63
94
|
|
64
|
-
|
95
|
+
parent = latest_group
|
96
|
+
return unless parent
|
65
97
|
|
66
|
-
|
67
|
-
|
68
|
-
|
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)
|
98
|
+
id = case parent
|
99
|
+
when Requests::Support::TestItem
|
100
|
+
"#{parent.id}::#{description}"
|
76
101
|
else
|
77
|
-
|
102
|
+
description
|
78
103
|
end
|
79
104
|
|
80
|
-
|
105
|
+
test_item = Requests::Support::TestItem.new(
|
106
|
+
id,
|
107
|
+
description,
|
108
|
+
@uri,
|
109
|
+
range_from_node(node),
|
110
|
+
framework: :minitest,
|
111
|
+
)
|
112
|
+
|
113
|
+
parent.add(test_item)
|
114
|
+
@response_builder.add_code_lens(test_item)
|
115
|
+
@spec_group_id_stack << DescribeGroup.new(id)
|
81
116
|
end
|
82
117
|
|
83
118
|
#: (Prism::CallNode) -> void
|
84
119
|
def handle_example(node)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
120
|
+
# Minitest formats the descriptions into test method names by using the count of examples with the description
|
121
|
+
# We are not guaranteed to discover examples in the exact order using static analysis, so we use the line number
|
122
|
+
# instead. Note that anonymous examples mixed with meta-programming will not be handled correctly
|
123
|
+
description = extract_description(node) || "anonymous"
|
124
|
+
line = node.location.start_line - 1
|
125
|
+
parent = latest_group
|
126
|
+
return unless parent.is_a?(Requests::Support::TestItem)
|
91
127
|
|
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
|
128
|
+
id = "#{parent.id}##{format("test_%04d_%s", line, description)}"
|
99
129
|
|
100
130
|
test_item = Requests::Support::TestItem.new(
|
101
|
-
|
131
|
+
id,
|
102
132
|
description,
|
103
133
|
@uri,
|
104
134
|
range_from_node(node),
|
105
135
|
framework: :minitest,
|
106
136
|
)
|
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
137
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
return test_group unless nested_describe_groups
|
124
|
-
|
125
|
-
nested_describe_groups.each do |description|
|
126
|
-
test_group = test_group[description]
|
127
|
-
end
|
128
|
-
|
129
|
-
test_group
|
138
|
+
parent.add(test_item)
|
139
|
+
@response_builder.add_code_lens(test_item)
|
130
140
|
end
|
131
141
|
|
132
142
|
#: (Prism::CallNode) -> String?
|
@@ -144,11 +154,60 @@ module RubyLsp
|
|
144
154
|
end
|
145
155
|
end
|
146
156
|
|
157
|
+
#: -> (Requests::Support::TestItem | ResponseBuilders::TestCollection)?
|
158
|
+
def latest_group
|
159
|
+
# If we haven't found anything yet, then return the response builder
|
160
|
+
return @response_builder if @spec_group_id_stack.compact.empty?
|
161
|
+
# If we found something that isn't a group last, then we're inside a random module or class, but not a spec
|
162
|
+
# group
|
163
|
+
return unless @spec_group_id_stack.last
|
164
|
+
|
165
|
+
# Specs using at least one class as a group require special handling
|
166
|
+
closest_class_index = @spec_group_id_stack.rindex { |i| i.is_a?(ClassGroup) }
|
167
|
+
|
168
|
+
if closest_class_index
|
169
|
+
first_class_index = @spec_group_id_stack.index { |i| i.is_a?(ClassGroup) } #: as !nil
|
170
|
+
first_class = @spec_group_id_stack[first_class_index] #: as !nil
|
171
|
+
item = @response_builder[first_class.id] #: as !nil
|
172
|
+
|
173
|
+
# Descend into child items from the beginning all the way to the latest class group, ignoring describes
|
174
|
+
@spec_group_id_stack[first_class_index + 1..closest_class_index] #: as !nil
|
175
|
+
.each do |group|
|
176
|
+
next unless group.is_a?(ClassGroup)
|
177
|
+
|
178
|
+
item = item[group.id] #: as !nil
|
179
|
+
end
|
180
|
+
|
181
|
+
# From the class forward, we must take describes into account
|
182
|
+
@spec_group_id_stack[closest_class_index + 1..] #: as !nil
|
183
|
+
.each do |group|
|
184
|
+
next unless group
|
185
|
+
|
186
|
+
item = item[group.id] #: as !nil
|
187
|
+
end
|
188
|
+
|
189
|
+
return item
|
190
|
+
end
|
191
|
+
|
192
|
+
# Specs only using describes
|
193
|
+
first_group = @spec_group_id_stack.find { |i| i.is_a?(DescribeGroup) }
|
194
|
+
return unless first_group
|
195
|
+
|
196
|
+
item = @response_builder[first_group.id] #: as !nil
|
197
|
+
|
198
|
+
@spec_group_id_stack[1..] #: as !nil
|
199
|
+
.each do |group|
|
200
|
+
next unless group.is_a?(DescribeGroup)
|
201
|
+
|
202
|
+
item = item[group.id] #: as !nil
|
203
|
+
end
|
204
|
+
|
205
|
+
item
|
206
|
+
end
|
207
|
+
|
147
208
|
#: -> bool
|
148
209
|
def in_spec_context?
|
149
|
-
|
150
|
-
|
151
|
-
@spec_class_stack.last #: as !nil
|
210
|
+
@nesting.empty? || @spec_group_id_stack.any? { |id| id }
|
152
211
|
end
|
153
212
|
end
|
154
213
|
end
|
@@ -3,32 +3,23 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
module Listeners
|
6
|
+
# @abstract
|
6
7
|
class TestDiscovery
|
7
|
-
extend T::Helpers
|
8
|
-
abstract!
|
9
|
-
|
10
8
|
include Requests::Support::Common
|
11
9
|
|
12
10
|
DYNAMIC_REFERENCE_MARKER = "<dynamic_reference>"
|
13
11
|
|
14
|
-
#: (ResponseBuilders::TestCollection response_builder, GlobalState global_state,
|
15
|
-
def initialize(response_builder, global_state,
|
12
|
+
#: (ResponseBuilders::TestCollection response_builder, GlobalState global_state, URI::Generic uri) -> void
|
13
|
+
def initialize(response_builder, global_state, uri)
|
16
14
|
@response_builder = response_builder
|
17
15
|
@uri = uri
|
18
16
|
@index = global_state.index #: RubyIndexer::Index
|
19
17
|
@visibility_stack = [:public] #: Array[Symbol]
|
20
18
|
@nesting = [] #: Array[String]
|
21
|
-
|
22
|
-
dispatcher.register(
|
23
|
-
self,
|
24
|
-
:on_class_node_leave,
|
25
|
-
:on_module_node_enter,
|
26
|
-
:on_module_node_leave,
|
27
|
-
)
|
28
19
|
end
|
29
20
|
|
30
21
|
#: (Prism::ModuleNode node) -> void
|
31
|
-
def on_module_node_enter(node)
|
22
|
+
def on_module_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
32
23
|
@visibility_stack << :public
|
33
24
|
|
34
25
|
name = constant_name(node.constant_path)
|
@@ -38,19 +29,31 @@ module RubyLsp
|
|
38
29
|
end
|
39
30
|
|
40
31
|
#: (Prism::ModuleNode node) -> void
|
41
|
-
def on_module_node_leave(node)
|
32
|
+
def on_module_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
42
33
|
@visibility_stack.pop
|
43
34
|
@nesting.pop
|
44
35
|
end
|
45
36
|
|
46
37
|
#: (Prism::ClassNode node) -> void
|
47
|
-
def on_class_node_leave(node)
|
38
|
+
def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
48
39
|
@visibility_stack.pop
|
49
40
|
@nesting.pop
|
50
41
|
end
|
51
42
|
|
52
43
|
private
|
53
44
|
|
45
|
+
#: (Prism::Dispatcher, *Symbol) -> void
|
46
|
+
def register_events(dispatcher, *events)
|
47
|
+
unique_events = events.dup.push(
|
48
|
+
:on_class_node_leave,
|
49
|
+
:on_module_node_enter,
|
50
|
+
:on_module_node_leave,
|
51
|
+
)
|
52
|
+
|
53
|
+
unique_events.uniq!
|
54
|
+
dispatcher.register(self, *unique_events)
|
55
|
+
end
|
56
|
+
|
54
57
|
#: (String? name) -> String
|
55
58
|
def calc_fully_qualified_name(name)
|
56
59
|
RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
|
@@ -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
|
@@ -44,7 +44,7 @@ module RubyLsp
|
|
44
44
|
# If all of the children of the current test group are other groups, then there's no need to add it to the
|
45
45
|
# aggregated examples
|
46
46
|
unless children.any? && children.all? { |child| child[:tags].include?("test_group") }
|
47
|
-
aggregated_tests[path][item[:
|
47
|
+
aggregated_tests[path][item[:id]] = { tags: tags, examples: [] }
|
48
48
|
end
|
49
49
|
else
|
50
50
|
class_name, method_name = item[:id].split("#")
|
@@ -74,7 +74,10 @@ module RubyLsp
|
|
74
74
|
end
|
75
75
|
|
76
76
|
unless full_files.empty?
|
77
|
-
|
77
|
+
specs, tests = full_files.partition { |path| spec?(path) }
|
78
|
+
|
79
|
+
commands << "#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{tests.join(" ")}" if tests.any?
|
80
|
+
commands << "#{COMMAND} -Ispec -e \"ARGV.each { |f| require f }\" #{specs.join(" ")}" if specs.any?
|
78
81
|
end
|
79
82
|
|
80
83
|
commands
|
@@ -82,10 +85,15 @@ module RubyLsp
|
|
82
85
|
|
83
86
|
private
|
84
87
|
|
88
|
+
#: (String) -> bool
|
89
|
+
def spec?(path)
|
90
|
+
File.fnmatch?("**/spec/**/*_spec.rb", path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
91
|
+
end
|
92
|
+
|
85
93
|
#: (String, Hash[String, Hash[Symbol, untyped]]) -> String
|
86
94
|
def handle_minitest_groups(file_path, groups_and_examples)
|
87
95
|
regexes = groups_and_examples.flat_map do |group, info|
|
88
|
-
examples = info[:examples]
|
96
|
+
examples = info[:examples].map { |e| Shellwords.escape(e).gsub(/test_\d{4}/, "test_\\d{4}") }
|
89
97
|
group_regex = Shellwords.escape(group).gsub(
|
90
98
|
Shellwords.escape(TestDiscovery::DYNAMIC_REFERENCE_MARKER),
|
91
99
|
".*",
|
@@ -105,7 +113,8 @@ module RubyLsp
|
|
105
113
|
"(#{regexes.join("|")})"
|
106
114
|
end
|
107
115
|
|
108
|
-
|
116
|
+
load_path = spec?(file_path) ? "-Ispec" : "-Itest"
|
117
|
+
"#{COMMAND} #{load_path} #{file_path} --name \"/#{regex}/\""
|
109
118
|
end
|
110
119
|
|
111
120
|
#: (String, Hash[String, Hash[Symbol, untyped]]) -> Array[String]
|
@@ -116,7 +125,7 @@ module RubyLsp
|
|
116
125
|
Shellwords.escape(TestDiscovery::DYNAMIC_REFERENCE_MARKER),
|
117
126
|
".*",
|
118
127
|
)
|
119
|
-
command = +"#{
|
128
|
+
command = +"#{COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}\\$/\""
|
120
129
|
|
121
130
|
unless examples.empty?
|
122
131
|
command << if examples.length == 1
|
@@ -135,23 +144,24 @@ module RubyLsp
|
|
135
144
|
|
136
145
|
MINITEST_REPORTER_PATH = File.expand_path("../test_reporters/minitest_reporter.rb", __dir__) #: String
|
137
146
|
TEST_UNIT_REPORTER_PATH = File.expand_path("../test_reporters/test_unit_reporter.rb", __dir__) #: String
|
138
|
-
ACCESS_MODIFIERS = [:public, :private, :protected].freeze
|
139
147
|
BASE_COMMAND = begin
|
140
148
|
Bundler.with_original_env { Bundler.default_lockfile }
|
141
149
|
"bundle exec ruby"
|
142
150
|
rescue Bundler::GemfileNotFound
|
143
151
|
"ruby"
|
144
152
|
end #: String
|
153
|
+
COMMAND = "#{BASE_COMMAND} -r#{MINITEST_REPORTER_PATH} -r#{TEST_UNIT_REPORTER_PATH}" #: String
|
154
|
+
ACCESS_MODIFIERS = [:public, :private, :protected].freeze
|
145
155
|
|
146
156
|
#: (ResponseBuilders::TestCollection, GlobalState, Prism::Dispatcher, URI::Generic) -> void
|
147
157
|
def initialize(response_builder, global_state, dispatcher, uri)
|
148
|
-
super
|
158
|
+
super(response_builder, global_state, uri)
|
149
159
|
|
150
160
|
@framework = :minitest #: Symbol
|
161
|
+
@parent_stack = [@response_builder] #: Array[(Requests::Support::TestItem | ResponseBuilders::TestCollection)?]
|
151
162
|
|
152
|
-
|
153
|
-
|
154
|
-
# Common handlers registered in parent class
|
163
|
+
register_events(
|
164
|
+
dispatcher,
|
155
165
|
:on_class_node_enter,
|
156
166
|
:on_def_node_enter,
|
157
167
|
:on_call_node_enter,
|
@@ -160,7 +170,7 @@ module RubyLsp
|
|
160
170
|
end
|
161
171
|
|
162
172
|
#: (Prism::ClassNode node) -> void
|
163
|
-
def on_class_node_enter(node)
|
173
|
+
def on_class_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
164
174
|
with_test_ancestor_tracking(node) do |name, ancestors|
|
165
175
|
@framework = :test_unit if ancestors.include?("Test::Unit::TestCase")
|
166
176
|
|
@@ -173,26 +183,43 @@ module RubyLsp
|
|
173
183
|
framework: @framework,
|
174
184
|
)
|
175
185
|
|
176
|
-
|
186
|
+
last_test_group.add(test_item)
|
177
187
|
@response_builder.add_code_lens(test_item)
|
188
|
+
@parent_stack << test_item
|
189
|
+
else
|
190
|
+
@parent_stack << nil
|
178
191
|
end
|
179
192
|
end
|
180
193
|
end
|
181
194
|
|
195
|
+
#: (Prism::ClassNode node) -> void
|
196
|
+
def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
197
|
+
@parent_stack.pop
|
198
|
+
super
|
199
|
+
end
|
200
|
+
|
201
|
+
#: (Prism::ModuleNode node) -> void
|
202
|
+
def on_module_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
203
|
+
@parent_stack << nil
|
204
|
+
super
|
205
|
+
end
|
206
|
+
|
207
|
+
#: (Prism::ModuleNode node) -> void
|
208
|
+
def on_module_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
209
|
+
@parent_stack.pop
|
210
|
+
super
|
211
|
+
end
|
212
|
+
|
182
213
|
#: (Prism::DefNode node) -> void
|
183
|
-
def on_def_node_enter(node)
|
214
|
+
def on_def_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
184
215
|
return if @visibility_stack.last != :public
|
185
216
|
|
186
217
|
name = node.name.to_s
|
187
218
|
return unless name.start_with?("test_")
|
188
219
|
|
189
220
|
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
|
221
|
+
parent = @parent_stack.last
|
222
|
+
return unless parent.is_a?(Requests::Support::TestItem)
|
196
223
|
|
197
224
|
example_item = Requests::Support::TestItem.new(
|
198
225
|
"#{current_group_name}##{name}",
|
@@ -201,12 +228,12 @@ module RubyLsp
|
|
201
228
|
range_from_node(node),
|
202
229
|
framework: @framework,
|
203
230
|
)
|
204
|
-
|
231
|
+
parent.add(example_item)
|
205
232
|
@response_builder.add_code_lens(example_item)
|
206
233
|
end
|
207
234
|
|
208
235
|
#: (Prism::CallNode node) -> void
|
209
|
-
def on_call_node_enter(node)
|
236
|
+
def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
210
237
|
name = node.name
|
211
238
|
return unless ACCESS_MODIFIERS.include?(name)
|
212
239
|
|
@@ -214,7 +241,7 @@ module RubyLsp
|
|
214
241
|
end
|
215
242
|
|
216
243
|
#: (Prism::CallNode node) -> void
|
217
|
-
def on_call_node_leave(node)
|
244
|
+
def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
218
245
|
name = node.name
|
219
246
|
return unless ACCESS_MODIFIERS.include?(name)
|
220
247
|
return unless node.arguments&.arguments
|
@@ -224,6 +251,12 @@ module RubyLsp
|
|
224
251
|
|
225
252
|
private
|
226
253
|
|
254
|
+
#: -> (Requests::Support::TestItem | ResponseBuilders::TestCollection)
|
255
|
+
def last_test_group
|
256
|
+
index = @parent_stack.rindex { |i| i } #: as !nil
|
257
|
+
@parent_stack[index] #: as Requests::Support::TestItem | ResponseBuilders::TestCollection
|
258
|
+
end
|
259
|
+
|
227
260
|
#: (Array[String] attached_ancestors, String fully_qualified_name) -> bool
|
228
261
|
def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
|
229
262
|
return false unless attached_ancestors.include?("Minitest::Test")
|