ruby-lsp 0.23.10 → 0.23.13

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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp-launcher +12 -11
  5. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -1
  6. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -5
  7. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +81 -115
  8. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +117 -166
  9. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +9 -7
  10. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +89 -201
  11. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +63 -192
  12. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
  13. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +14 -16
  14. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +22 -45
  15. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +42 -60
  16. data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +9 -16
  17. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +5 -9
  18. data/lib/ruby_indexer/test/classes_and_modules_test.rb +75 -0
  19. data/lib/ruby_indexer/test/configuration_test.rb +42 -3
  20. data/lib/ruby_indexer/test/index_test.rb +21 -0
  21. data/lib/ruby_indexer/test/method_test.rb +28 -2
  22. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  23. data/lib/ruby_lsp/addon.rb +44 -71
  24. data/lib/ruby_lsp/base_server.rb +31 -33
  25. data/lib/ruby_lsp/client_capabilities.rb +10 -12
  26. data/lib/ruby_lsp/document.rb +34 -45
  27. data/lib/ruby_lsp/erb_document.rb +24 -36
  28. data/lib/ruby_lsp/global_state.rb +51 -56
  29. data/lib/ruby_lsp/internal.rb +6 -0
  30. data/lib/ruby_lsp/listeners/code_lens.rb +81 -88
  31. data/lib/ruby_lsp/listeners/completion.rb +36 -55
  32. data/lib/ruby_lsp/listeners/definition.rb +37 -51
  33. data/lib/ruby_lsp/listeners/document_highlight.rb +123 -150
  34. data/lib/ruby_lsp/listeners/document_link.rb +43 -62
  35. data/lib/ruby_lsp/listeners/document_symbol.rb +35 -49
  36. data/lib/ruby_lsp/listeners/folding_ranges.rb +32 -39
  37. data/lib/ruby_lsp/listeners/hover.rb +81 -100
  38. data/lib/ruby_lsp/listeners/inlay_hints.rb +4 -11
  39. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +42 -51
  40. data/lib/ruby_lsp/listeners/signature_help.rb +6 -25
  41. data/lib/ruby_lsp/listeners/spec_style.rb +155 -0
  42. data/lib/ruby_lsp/listeners/test_discovery.rb +89 -0
  43. data/lib/ruby_lsp/listeners/test_style.rb +236 -0
  44. data/lib/ruby_lsp/node_context.rb +12 -39
  45. data/lib/ruby_lsp/rbs_document.rb +8 -6
  46. data/lib/ruby_lsp/requests/code_action_resolve.rb +10 -10
  47. data/lib/ruby_lsp/requests/code_actions.rb +14 -26
  48. data/lib/ruby_lsp/requests/code_lens.rb +6 -17
  49. data/lib/ruby_lsp/requests/completion.rb +7 -20
  50. data/lib/ruby_lsp/requests/completion_resolve.rb +5 -5
  51. data/lib/ruby_lsp/requests/definition.rb +8 -17
  52. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  53. data/lib/ruby_lsp/requests/discover_tests.rb +75 -0
  54. data/lib/ruby_lsp/requests/document_highlight.rb +5 -15
  55. data/lib/ruby_lsp/requests/document_link.rb +6 -17
  56. data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
  57. data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
  58. data/lib/ruby_lsp/requests/formatting.rb +6 -9
  59. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +87 -0
  60. data/lib/ruby_lsp/requests/hover.rb +8 -18
  61. data/lib/ruby_lsp/requests/inlay_hints.rb +6 -17
  62. data/lib/ruby_lsp/requests/on_type_formatting.rb +28 -38
  63. data/lib/ruby_lsp/requests/prepare_rename.rb +4 -9
  64. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +4 -13
  65. data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
  66. data/lib/ruby_lsp/requests/references.rb +6 -36
  67. data/lib/ruby_lsp/requests/rename.rb +11 -37
  68. data/lib/ruby_lsp/requests/request.rb +7 -19
  69. data/lib/ruby_lsp/requests/selection_ranges.rb +5 -5
  70. data/lib/ruby_lsp/requests/semantic_highlighting.rb +12 -31
  71. data/lib/ruby_lsp/requests/show_syntax_tree.rb +5 -6
  72. data/lib/ruby_lsp/requests/signature_help.rb +8 -26
  73. data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
  74. data/lib/ruby_lsp/requests/support/common.rb +13 -48
  75. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
  76. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +9 -12
  77. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +22 -34
  78. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
  79. data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
  80. data/lib/ruby_lsp/requests/support/source_uri.rb +16 -30
  81. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
  82. data/lib/ruby_lsp/requests/support/test_item.rb +55 -0
  83. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
  84. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
  85. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +5 -5
  86. data/lib/ruby_lsp/response_builders/document_symbol.rb +10 -16
  87. data/lib/ruby_lsp/response_builders/hover.rb +10 -13
  88. data/lib/ruby_lsp/response_builders/response_builder.rb +1 -1
  89. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +59 -87
  90. data/lib/ruby_lsp/response_builders/signature_help.rb +5 -6
  91. data/lib/ruby_lsp/response_builders/test_collection.rb +34 -0
  92. data/lib/ruby_lsp/ruby_document.rb +22 -60
  93. data/lib/ruby_lsp/ruby_lsp_reporter_plugin.rb +109 -0
  94. data/lib/ruby_lsp/scope.rb +7 -11
  95. data/lib/ruby_lsp/server.rb +177 -72
  96. data/lib/ruby_lsp/setup_bundler.rb +61 -59
  97. data/lib/ruby_lsp/static_docs.rb +4 -7
  98. data/lib/ruby_lsp/store.rb +21 -40
  99. data/lib/ruby_lsp/test_helper.rb +3 -12
  100. data/lib/ruby_lsp/test_reporter.rb +207 -0
  101. data/lib/ruby_lsp/test_unit_test_runner.rb +98 -0
  102. data/lib/ruby_lsp/type_inferrer.rb +9 -13
  103. data/lib/ruby_lsp/utils.rb +37 -81
  104. metadata +13 -3
@@ -5,31 +5,22 @@ module RubyLsp
5
5
  module Listeners
6
6
  class SemanticHighlighting
7
7
  include Requests::Support::Common
8
- extend T::Sig
9
-
10
- SPECIAL_RUBY_METHODS = T.let(
11
- [
12
- Module.instance_methods(false),
13
- Kernel.instance_methods(false),
14
- Kernel.methods(false),
15
- Bundler::Dsl.instance_methods(false),
16
- Module.private_instance_methods(false),
17
- ].flatten.map(&:to_s).freeze,
18
- T::Array[String],
19
- )
20
-
21
- sig do
22
- params(
23
- dispatcher: Prism::Dispatcher,
24
- response_builder: ResponseBuilders::SemanticHighlighting,
25
- ).void
26
- end
8
+
9
+ SPECIAL_RUBY_METHODS = [
10
+ Module.instance_methods(false),
11
+ Kernel.instance_methods(false),
12
+ Kernel.methods(false),
13
+ Bundler::Dsl.instance_methods(false),
14
+ Module.private_instance_methods(false),
15
+ ].flatten.map(&:to_s).freeze #: Array[String]
16
+
17
+ #: (Prism::Dispatcher dispatcher, ResponseBuilders::SemanticHighlighting response_builder) -> void
27
18
  def initialize(dispatcher, response_builder)
28
19
  @response_builder = response_builder
29
- @special_methods = T.let(nil, T.nilable(T::Array[String]))
30
- @current_scope = T.let(Scope.new, Scope)
31
- @inside_regex_capture = T.let(false, T::Boolean)
32
- @inside_implicit_node = T.let(false, T::Boolean)
20
+ @special_methods = nil #: Array[String]?
21
+ @current_scope = Scope.new #: Scope
22
+ @inside_regex_capture = false #: bool
23
+ @inside_implicit_node = false #: bool
33
24
 
34
25
  dispatcher.register(
35
26
  self,
@@ -62,7 +53,7 @@ module RubyLsp
62
53
  )
63
54
  end
64
55
 
65
- sig { params(node: Prism::CallNode).void }
56
+ #: (Prism::CallNode node) -> void
66
57
  def on_call_node_enter(node)
67
58
  return if @inside_implicit_node
68
59
 
@@ -85,7 +76,7 @@ module RubyLsp
85
76
  end
86
77
  end
87
78
 
88
- sig { params(node: Prism::MatchWriteNode).void }
79
+ #: (Prism::MatchWriteNode node) -> void
89
80
  def on_match_write_node_enter(node)
90
81
  call = node.call
91
82
 
@@ -95,86 +86,86 @@ module RubyLsp
95
86
  end
96
87
  end
97
88
 
98
- sig { params(node: Prism::MatchWriteNode).void }
89
+ #: (Prism::MatchWriteNode node) -> void
99
90
  def on_match_write_node_leave(node)
100
91
  @inside_regex_capture = true if node.call.message == "=~"
101
92
  end
102
93
 
103
- sig { params(node: Prism::DefNode).void }
94
+ #: (Prism::DefNode node) -> void
104
95
  def on_def_node_enter(node)
105
96
  @current_scope = Scope.new(@current_scope)
106
97
  end
107
98
 
108
- sig { params(node: Prism::DefNode).void }
99
+ #: (Prism::DefNode node) -> void
109
100
  def on_def_node_leave(node)
110
101
  @current_scope = T.must(@current_scope.parent)
111
102
  end
112
103
 
113
- sig { params(node: Prism::BlockNode).void }
104
+ #: (Prism::BlockNode node) -> void
114
105
  def on_block_node_enter(node)
115
106
  @current_scope = Scope.new(@current_scope)
116
107
  end
117
108
 
118
- sig { params(node: Prism::BlockNode).void }
109
+ #: (Prism::BlockNode node) -> void
119
110
  def on_block_node_leave(node)
120
111
  @current_scope = T.must(@current_scope.parent)
121
112
  end
122
113
 
123
- sig { params(node: Prism::BlockLocalVariableNode).void }
114
+ #: (Prism::BlockLocalVariableNode node) -> void
124
115
  def on_block_local_variable_node_enter(node)
125
116
  @response_builder.add_token(node.location, :variable)
126
117
  end
127
118
 
128
- sig { params(node: Prism::BlockParameterNode).void }
119
+ #: (Prism::BlockParameterNode node) -> void
129
120
  def on_block_parameter_node_enter(node)
130
121
  name = node.name
131
122
  @current_scope.add(name.to_sym, :parameter) if name
132
123
  end
133
124
 
134
- sig { params(node: Prism::RequiredKeywordParameterNode).void }
125
+ #: (Prism::RequiredKeywordParameterNode node) -> void
135
126
  def on_required_keyword_parameter_node_enter(node)
136
127
  @current_scope.add(node.name, :parameter)
137
128
  end
138
129
 
139
- sig { params(node: Prism::OptionalKeywordParameterNode).void }
130
+ #: (Prism::OptionalKeywordParameterNode node) -> void
140
131
  def on_optional_keyword_parameter_node_enter(node)
141
132
  @current_scope.add(node.name, :parameter)
142
133
  end
143
134
 
144
- sig { params(node: Prism::KeywordRestParameterNode).void }
135
+ #: (Prism::KeywordRestParameterNode node) -> void
145
136
  def on_keyword_rest_parameter_node_enter(node)
146
137
  name = node.name
147
138
  @current_scope.add(name.to_sym, :parameter) if name
148
139
  end
149
140
 
150
- sig { params(node: Prism::OptionalParameterNode).void }
141
+ #: (Prism::OptionalParameterNode node) -> void
151
142
  def on_optional_parameter_node_enter(node)
152
143
  @current_scope.add(node.name, :parameter)
153
144
  end
154
145
 
155
- sig { params(node: Prism::RequiredParameterNode).void }
146
+ #: (Prism::RequiredParameterNode node) -> void
156
147
  def on_required_parameter_node_enter(node)
157
148
  @current_scope.add(node.name, :parameter)
158
149
  end
159
150
 
160
- sig { params(node: Prism::RestParameterNode).void }
151
+ #: (Prism::RestParameterNode node) -> void
161
152
  def on_rest_parameter_node_enter(node)
162
153
  name = node.name
163
154
  @current_scope.add(name.to_sym, :parameter) if name
164
155
  end
165
156
 
166
- sig { params(node: Prism::SelfNode).void }
157
+ #: (Prism::SelfNode node) -> void
167
158
  def on_self_node_enter(node)
168
159
  @response_builder.add_token(node.location, :variable, [:default_library])
169
160
  end
170
161
 
171
- sig { params(node: Prism::LocalVariableWriteNode).void }
162
+ #: (Prism::LocalVariableWriteNode node) -> void
172
163
  def on_local_variable_write_node_enter(node)
173
164
  local = @current_scope.lookup(node.name)
174
165
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
175
166
  end
176
167
 
177
- sig { params(node: Prism::LocalVariableReadNode).void }
168
+ #: (Prism::LocalVariableReadNode node) -> void
178
169
  def on_local_variable_read_node_enter(node)
179
170
  return if @inside_implicit_node
180
171
 
@@ -188,25 +179,25 @@ module RubyLsp
188
179
  @response_builder.add_token(node.location, local&.type || :variable)
189
180
  end
190
181
 
191
- sig { params(node: Prism::LocalVariableAndWriteNode).void }
182
+ #: (Prism::LocalVariableAndWriteNode node) -> void
192
183
  def on_local_variable_and_write_node_enter(node)
193
184
  local = @current_scope.lookup(node.name)
194
185
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
195
186
  end
196
187
 
197
- sig { params(node: Prism::LocalVariableOperatorWriteNode).void }
188
+ #: (Prism::LocalVariableOperatorWriteNode node) -> void
198
189
  def on_local_variable_operator_write_node_enter(node)
199
190
  local = @current_scope.lookup(node.name)
200
191
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
201
192
  end
202
193
 
203
- sig { params(node: Prism::LocalVariableOrWriteNode).void }
194
+ #: (Prism::LocalVariableOrWriteNode node) -> void
204
195
  def on_local_variable_or_write_node_enter(node)
205
196
  local = @current_scope.lookup(node.name)
206
197
  @response_builder.add_token(node.name_loc, :parameter) if local&.type == :parameter
207
198
  end
208
199
 
209
- sig { params(node: Prism::LocalVariableTargetNode).void }
200
+ #: (Prism::LocalVariableTargetNode node) -> void
210
201
  def on_local_variable_target_node_enter(node)
211
202
  # If we're inside a regex capture, Prism will add LocalVariableTarget nodes for each captured variable.
212
203
  # Unfortunately, if the regex contains a backslash, the location will be incorrect and we'll end up highlighting
@@ -218,7 +209,7 @@ module RubyLsp
218
209
  @response_builder.add_token(node.location, local&.type || :variable)
219
210
  end
220
211
 
221
- sig { params(node: Prism::ClassNode).void }
212
+ #: (Prism::ClassNode node) -> void
222
213
  def on_class_node_enter(node)
223
214
  constant_path = node.constant_path
224
215
 
@@ -257,7 +248,7 @@ module RubyLsp
257
248
  end
258
249
  end
259
250
 
260
- sig { params(node: Prism::ModuleNode).void }
251
+ #: (Prism::ModuleNode node) -> void
261
252
  def on_module_node_enter(node)
262
253
  constant_path = node.constant_path
263
254
 
@@ -278,12 +269,12 @@ module RubyLsp
278
269
  end
279
270
  end
280
271
 
281
- sig { params(node: Prism::ImplicitNode).void }
272
+ #: (Prism::ImplicitNode node) -> void
282
273
  def on_implicit_node_enter(node)
283
274
  @inside_implicit_node = true
284
275
  end
285
276
 
286
- sig { params(node: Prism::ImplicitNode).void }
277
+ #: (Prism::ImplicitNode node) -> void
287
278
  def on_implicit_node_leave(node)
288
279
  @inside_implicit_node = false
289
280
  end
@@ -292,12 +283,12 @@ module RubyLsp
292
283
 
293
284
  # Textmate provides highlighting for a subset of these special Ruby-specific methods. We want to utilize that
294
285
  # highlighting, so we avoid making a semantic token for it.
295
- sig { params(method_name: String).returns(T::Boolean) }
286
+ #: (String method_name) -> bool
296
287
  def special_method?(method_name)
297
288
  SPECIAL_RUBY_METHODS.include?(method_name)
298
289
  end
299
290
 
300
- sig { params(node: Prism::CallNode).void }
291
+ #: (Prism::CallNode node) -> void
301
292
  def process_regexp_locals(node)
302
293
  receiver = node.receiver
303
294
 
@@ -4,29 +4,20 @@
4
4
  module RubyLsp
5
5
  module Listeners
6
6
  class SignatureHelp
7
- extend T::Sig
8
7
  include Requests::Support::Common
9
8
 
10
- sig do
11
- params(
12
- response_builder: ResponseBuilders::SignatureHelp,
13
- global_state: GlobalState,
14
- node_context: NodeContext,
15
- dispatcher: Prism::Dispatcher,
16
- sorbet_level: RubyDocument::SorbetLevel,
17
- ).void
18
- end
9
+ #: (ResponseBuilders::SignatureHelp response_builder, GlobalState global_state, NodeContext node_context, Prism::Dispatcher dispatcher, RubyDocument::SorbetLevel sorbet_level) -> void
19
10
  def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
20
11
  @sorbet_level = sorbet_level
21
12
  @response_builder = response_builder
22
13
  @global_state = global_state
23
- @index = T.let(global_state.index, RubyIndexer::Index)
24
- @type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
14
+ @index = global_state.index #: RubyIndexer::Index
15
+ @type_inferrer = global_state.type_inferrer #: TypeInferrer
25
16
  @node_context = node_context
26
17
  dispatcher.register(self, :on_call_node_enter)
27
18
  end
28
19
 
29
- sig { params(node: Prism::CallNode).void }
20
+ #: (Prism::CallNode node) -> void
30
21
  def on_call_node_enter(node)
31
22
  return if sorbet_level_true_or_higher?(@sorbet_level)
32
23
 
@@ -67,9 +58,7 @@ module RubyLsp
67
58
 
68
59
  private
69
60
 
70
- sig do
71
- params(node: Prism::CallNode, signatures: T::Array[RubyIndexer::Entry::Signature]).returns([Integer, Integer])
72
- end
61
+ #: (Prism::CallNode node, Array[RubyIndexer::Entry::Signature] signatures) -> [Integer, Integer]
73
62
  def determine_active_signature_and_parameter(node, signatures)
74
63
  arguments_node = node.arguments
75
64
  arguments = arguments_node&.arguments || []
@@ -93,15 +82,7 @@ module RubyLsp
93
82
  [active_sig_index, active_parameter]
94
83
  end
95
84
 
96
- sig do
97
- params(
98
- signatures: T::Array[RubyIndexer::Entry::Signature],
99
- method_name: String,
100
- methods: T::Array[RubyIndexer::Entry],
101
- title: String,
102
- extra_links: T.nilable(String),
103
- ).returns(T::Array[Interface::SignatureInformation])
104
- end
85
+ #: (Array[RubyIndexer::Entry::Signature] signatures, String method_name, Array[RubyIndexer::Entry] methods, String title, String? extra_links) -> Array[Interface::SignatureInformation]
105
86
  def generate_signatures(signatures, method_name, methods, title, extra_links)
106
87
  signatures.map do |signature|
107
88
  Interface::SignatureInformation.new(
@@ -0,0 +1,155 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class SpecStyle < TestDiscovery
7
+ extend T::Sig
8
+
9
+ #: (response_builder: ResponseBuilders::TestCollection, global_state: GlobalState, dispatcher: Prism::Dispatcher, uri: URI::Generic) -> void
10
+ def initialize(response_builder, global_state, dispatcher, uri)
11
+ super
12
+
13
+ @describe_block_nesting = [] #: Array[String]
14
+ @spec_class_stack = [] #: Array[bool]
15
+
16
+ dispatcher.register(
17
+ self,
18
+ # Common handlers registered in parent class
19
+ :on_class_node_enter,
20
+ :on_call_node_enter, # e.g. `describe` or `it`
21
+ :on_call_node_leave,
22
+ )
23
+ end
24
+
25
+ #: (node: Prism::ClassNode) -> void
26
+ def on_class_node_enter(node)
27
+ with_test_ancestor_tracking(node) do |_, ancestors|
28
+ is_spec = ancestors.include?("Minitest::Spec")
29
+ @spec_class_stack.push(is_spec)
30
+ end
31
+ end
32
+
33
+ #: (node: Prism::ClassNode) -> void
34
+ def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
35
+ super
36
+
37
+ @spec_class_stack.pop
38
+ end
39
+
40
+ #: (node: Prism::CallNode) -> void
41
+ def on_call_node_enter(node)
42
+ case node.name
43
+ when :describe
44
+ handle_describe(node)
45
+ when :it, :specify
46
+ handle_example(node)
47
+ end
48
+ end
49
+
50
+ #: (node: Prism::CallNode) -> void
51
+ def on_call_node_leave(node)
52
+ return unless node.name == :describe && !node.receiver
53
+
54
+ @describe_block_nesting.pop
55
+ end
56
+
57
+ private
58
+
59
+ #: (node: Prism::CallNode) -> void
60
+ def handle_describe(node)
61
+ return if node.block.nil?
62
+
63
+ description = extract_description(node)
64
+ return unless description
65
+
66
+ return unless in_spec_context?
67
+
68
+ if @nesting.empty? && @describe_block_nesting.empty?
69
+ test_item = Requests::Support::TestItem.new(
70
+ description,
71
+ description,
72
+ @uri,
73
+ range_from_node(node),
74
+ framework: :minitest,
75
+ )
76
+ @response_builder.add(test_item)
77
+ else
78
+ add_to_parent_test_group(description, node)
79
+ end
80
+
81
+ @describe_block_nesting << description
82
+ end
83
+
84
+ #: (node: Prism::CallNode) -> void
85
+ def handle_example(node)
86
+ return unless in_spec_context?
87
+
88
+ return if @describe_block_nesting.empty? && @nesting.empty?
89
+
90
+ description = extract_description(node)
91
+ return unless description
92
+
93
+ add_to_parent_test_group(description, node)
94
+ end
95
+
96
+ #: (description: String, node: Prism::CallNode) -> void
97
+ def add_to_parent_test_group(description, node)
98
+ parent_test_group = find_parent_test_group
99
+ return unless parent_test_group
100
+
101
+ test_item = Requests::Support::TestItem.new(
102
+ description,
103
+ description,
104
+ @uri,
105
+ range_from_node(node),
106
+ framework: :minitest,
107
+ )
108
+ parent_test_group.add(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
+
125
+ nested_describe_groups.each do |description|
126
+ test_group = test_group[description]
127
+ end
128
+
129
+ test_group
130
+ end
131
+
132
+ #: (node: Prism::CallNode) -> String?
133
+ def extract_description(node)
134
+ first_argument = node.arguments&.arguments&.first
135
+ return unless first_argument
136
+
137
+ case first_argument
138
+ when Prism::StringNode
139
+ first_argument.content
140
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
141
+ constant_name(first_argument)
142
+ else
143
+ first_argument.slice
144
+ end
145
+ end
146
+
147
+ #: -> bool
148
+ def in_spec_context?
149
+ return true if @nesting.empty?
150
+
151
+ T.must(@spec_class_stack.last)
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,89 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class TestDiscovery
7
+ extend T::Helpers
8
+ abstract!
9
+
10
+ include Requests::Support::Common
11
+
12
+ DYNAMIC_REFERENCE_MARKER = "<dynamic_reference>"
13
+
14
+ #: (ResponseBuilders::TestCollection response_builder, GlobalState global_state, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
15
+ def initialize(response_builder, global_state, dispatcher, uri)
16
+ @response_builder = response_builder
17
+ @uri = uri
18
+ @index = global_state.index #: RubyIndexer::Index
19
+ @visibility_stack = [:public] #: Array[Symbol]
20
+ @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
+ end
29
+
30
+ #: (Prism::ModuleNode node) -> void
31
+ def on_module_node_enter(node)
32
+ @visibility_stack << :public
33
+
34
+ name = constant_name(node.constant_path)
35
+ name ||= name_with_dynamic_reference(node.constant_path)
36
+
37
+ @nesting << name
38
+ end
39
+
40
+ #: (Prism::ModuleNode node) -> void
41
+ def on_module_node_leave(node)
42
+ @visibility_stack.pop
43
+ @nesting.pop
44
+ end
45
+
46
+ #: (Prism::ClassNode node) -> void
47
+ def on_class_node_leave(node)
48
+ @visibility_stack.pop
49
+ @nesting.pop
50
+ end
51
+
52
+ private
53
+
54
+ #: (String? name) -> String
55
+ def calc_fully_qualified_name(name)
56
+ RubyIndexer::Index.actual_nesting(@nesting, name).join("::")
57
+ end
58
+
59
+ #: (Prism::ClassNode node, String fully_qualified_name) -> Array[String]
60
+ def calc_attached_ancestors(node, fully_qualified_name)
61
+ @index.linearized_ancestors_of(fully_qualified_name)
62
+ rescue RubyIndexer::Index::NonExistingNamespaceError
63
+ # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
64
+ # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
65
+ [node.superclass&.slice].compact
66
+ end
67
+
68
+ #: (Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode node) -> String
69
+ def name_with_dynamic_reference(node)
70
+ slice = node.slice
71
+ slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
72
+ end
73
+
74
+ #: (Prism::ClassNode node, ^(String name, Array[String] ancestors) -> void block) -> void
75
+ def with_test_ancestor_tracking(node, &block)
76
+ @visibility_stack << :public
77
+ name = constant_name(node.constant_path)
78
+ name ||= name_with_dynamic_reference(node.constant_path)
79
+
80
+ fully_qualified_name = calc_fully_qualified_name(name)
81
+ attached_ancestors = calc_attached_ancestors(node, fully_qualified_name)
82
+
83
+ block.call(fully_qualified_name, attached_ancestors)
84
+
85
+ @nesting << name
86
+ end
87
+ end
88
+ end
89
+ end