ruby-lsp 0.23.11 → 0.23.17

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp-launcher +20 -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 +82 -116
  8. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +134 -183
  9. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +9 -10
  10. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +97 -217
  11. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +139 -281
  12. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
  13. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +18 -19
  14. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +23 -55
  15. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +47 -61
  16. data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
  17. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
  18. data/lib/ruby_indexer/test/class_variables_test.rb +14 -14
  19. data/lib/ruby_indexer/test/classes_and_modules_test.rb +65 -40
  20. data/lib/ruby_indexer/test/configuration_test.rb +48 -7
  21. data/lib/ruby_indexer/test/constant_test.rb +34 -34
  22. data/lib/ruby_indexer/test/enhancements_test.rb +1 -1
  23. data/lib/ruby_indexer/test/index_test.rb +146 -135
  24. data/lib/ruby_indexer/test/instance_variables_test.rb +37 -37
  25. data/lib/ruby_indexer/test/method_test.rb +149 -123
  26. data/lib/ruby_indexer/test/prefix_tree_test.rb +13 -13
  27. data/lib/ruby_indexer/test/rbs_indexer_test.rb +68 -73
  28. data/lib/ruby_indexer/test/test_case.rb +9 -3
  29. data/lib/ruby_indexer/test/uri_test.rb +15 -2
  30. data/lib/ruby_lsp/addon.rb +44 -71
  31. data/lib/ruby_lsp/base_server.rb +29 -32
  32. data/lib/ruby_lsp/client_capabilities.rb +10 -12
  33. data/lib/ruby_lsp/document.rb +40 -54
  34. data/lib/ruby_lsp/erb_document.rb +37 -41
  35. data/lib/ruby_lsp/global_state.rb +52 -57
  36. data/lib/ruby_lsp/internal.rb +2 -0
  37. data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
  38. data/lib/ruby_lsp/listeners/completion.rb +67 -73
  39. data/lib/ruby_lsp/listeners/definition.rb +44 -58
  40. data/lib/ruby_lsp/listeners/document_highlight.rb +123 -150
  41. data/lib/ruby_lsp/listeners/document_link.rb +50 -70
  42. data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
  43. data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
  44. data/lib/ruby_lsp/listeners/hover.rb +92 -110
  45. data/lib/ruby_lsp/listeners/inlay_hints.rb +4 -11
  46. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
  47. data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
  48. data/lib/ruby_lsp/listeners/spec_style.rb +155 -0
  49. data/lib/ruby_lsp/listeners/test_discovery.rb +89 -0
  50. data/lib/ruby_lsp/listeners/test_style.rb +167 -90
  51. data/lib/ruby_lsp/node_context.rb +12 -39
  52. data/lib/ruby_lsp/rbs_document.rb +9 -7
  53. data/lib/ruby_lsp/requests/code_action_resolve.rb +63 -59
  54. data/lib/ruby_lsp/requests/code_actions.rb +14 -26
  55. data/lib/ruby_lsp/requests/code_lens.rb +20 -19
  56. data/lib/ruby_lsp/requests/completion.rb +7 -20
  57. data/lib/ruby_lsp/requests/completion_resolve.rb +6 -6
  58. data/lib/ruby_lsp/requests/definition.rb +7 -17
  59. data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
  60. data/lib/ruby_lsp/requests/discover_tests.rb +18 -5
  61. data/lib/ruby_lsp/requests/document_highlight.rb +5 -15
  62. data/lib/ruby_lsp/requests/document_link.rb +6 -17
  63. data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
  64. data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
  65. data/lib/ruby_lsp/requests/formatting.rb +6 -9
  66. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +85 -0
  67. data/lib/ruby_lsp/requests/hover.rb +10 -20
  68. data/lib/ruby_lsp/requests/inlay_hints.rb +6 -17
  69. data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
  70. data/lib/ruby_lsp/requests/prepare_rename.rb +4 -9
  71. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
  72. data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
  73. data/lib/ruby_lsp/requests/references.rb +9 -53
  74. data/lib/ruby_lsp/requests/rename.rb +20 -46
  75. data/lib/ruby_lsp/requests/request.rb +8 -19
  76. data/lib/ruby_lsp/requests/selection_ranges.rb +6 -6
  77. data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
  78. data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
  79. data/lib/ruby_lsp/requests/signature_help.rb +8 -26
  80. data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
  81. data/lib/ruby_lsp/requests/support/common.rb +15 -55
  82. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
  83. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +11 -14
  84. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +22 -34
  85. data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
  86. data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
  87. data/lib/ruby_lsp/requests/support/source_uri.rb +20 -32
  88. data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
  89. data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
  90. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
  91. data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
  92. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +5 -5
  93. data/lib/ruby_lsp/response_builders/document_symbol.rb +14 -19
  94. data/lib/ruby_lsp/response_builders/hover.rb +11 -14
  95. data/lib/ruby_lsp/response_builders/response_builder.rb +1 -1
  96. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +60 -88
  97. data/lib/ruby_lsp/response_builders/signature_help.rb +5 -6
  98. data/lib/ruby_lsp/response_builders/test_collection.rb +43 -10
  99. data/lib/ruby_lsp/ruby_document.rb +24 -92
  100. data/lib/ruby_lsp/scope.rb +7 -11
  101. data/lib/ruby_lsp/scripts/compose_bundle.rb +6 -4
  102. data/lib/ruby_lsp/server.rb +182 -99
  103. data/lib/ruby_lsp/setup_bundler.rb +65 -60
  104. data/lib/ruby_lsp/static_docs.rb +11 -7
  105. data/lib/ruby_lsp/store.rb +29 -47
  106. data/lib/ruby_lsp/test_helper.rb +2 -12
  107. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +191 -0
  108. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +105 -0
  109. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +94 -0
  110. data/lib/ruby_lsp/type_inferrer.rb +13 -14
  111. data/lib/ruby_lsp/utils.rb +92 -83
  112. metadata +9 -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
 
@@ -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,155 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Listeners
6
+ class SpecStyle < TestDiscovery
7
+ #: (ResponseBuilders::TestCollection, GlobalState, Prism::Dispatcher, URI::Generic) -> void
8
+ def initialize(response_builder, global_state, dispatcher, uri)
9
+ super
10
+
11
+ @describe_block_nesting = [] #: Array[String]
12
+ @spec_class_stack = [] #: Array[bool]
13
+
14
+ dispatcher.register(
15
+ self,
16
+ # Common handlers registered in parent class
17
+ :on_class_node_enter,
18
+ :on_call_node_enter, # e.g. `describe` or `it`
19
+ :on_call_node_leave,
20
+ )
21
+ end
22
+
23
+ #: (Prism::ClassNode) -> void
24
+ def on_class_node_enter(node)
25
+ with_test_ancestor_tracking(node) do |_, ancestors|
26
+ is_spec = ancestors.include?("Minitest::Spec")
27
+ @spec_class_stack.push(is_spec)
28
+ end
29
+ end
30
+
31
+ #: (Prism::ClassNode) -> void
32
+ def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
33
+ super
34
+
35
+ @spec_class_stack.pop
36
+ end
37
+
38
+ #: (Prism::CallNode) -> void
39
+ def on_call_node_enter(node)
40
+ case node.name
41
+ when :describe
42
+ handle_describe(node)
43
+ when :it, :specify
44
+ handle_example(node)
45
+ end
46
+ end
47
+
48
+ #: (Prism::CallNode) -> void
49
+ def on_call_node_leave(node)
50
+ return unless node.name == :describe && !node.receiver
51
+
52
+ @describe_block_nesting.pop
53
+ end
54
+
55
+ private
56
+
57
+ #: (Prism::CallNode) -> void
58
+ def handle_describe(node)
59
+ return if node.block.nil?
60
+
61
+ description = extract_description(node)
62
+ return unless description
63
+
64
+ return unless in_spec_context?
65
+
66
+ if @nesting.empty? && @describe_block_nesting.empty?
67
+ test_item = Requests::Support::TestItem.new(
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)
76
+ else
77
+ add_to_parent_test_group(description, node)
78
+ end
79
+
80
+ @describe_block_nesting << description
81
+ end
82
+
83
+ #: (Prism::CallNode) -> void
84
+ def handle_example(node)
85
+ return unless in_spec_context?
86
+
87
+ return if @describe_block_nesting.empty? && @nesting.empty?
88
+
89
+ description = extract_description(node)
90
+ return unless description
91
+
92
+ add_to_parent_test_group(description, node)
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
99
+
100
+ test_item = Requests::Support::TestItem.new(
101
+ description,
102
+ description,
103
+ @uri,
104
+ range_from_node(node),
105
+ framework: :minitest,
106
+ )
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
+
125
+ nested_describe_groups.each do |description|
126
+ test_group = test_group[description]
127
+ end
128
+
129
+ test_group
130
+ end
131
+
132
+ #: (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
+ @spec_class_stack.last #: as !nil
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