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