ruby-lsp 0.23.15 → 0.26.9

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/VERSION +1 -1
  3. data/exe/ruby-lsp +17 -14
  4. data/exe/ruby-lsp-check +0 -4
  5. data/exe/ruby-lsp-launcher +41 -14
  6. data/exe/ruby-lsp-test-exec +6 -0
  7. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
  8. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
  9. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +4 -3
  10. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +42 -20
  11. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -7
  12. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +49 -62
  13. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +84 -74
  14. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -9
  15. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +9 -14
  16. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
  17. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +4 -4
  18. data/lib/ruby_lsp/addon.rb +44 -15
  19. data/lib/ruby_lsp/base_server.rb +56 -37
  20. data/lib/ruby_lsp/client_capabilities.rb +6 -1
  21. data/lib/ruby_lsp/document.rb +174 -62
  22. data/lib/ruby_lsp/erb_document.rb +10 -8
  23. data/lib/ruby_lsp/global_state.rb +86 -33
  24. data/lib/ruby_lsp/internal.rb +6 -3
  25. data/lib/ruby_lsp/listeners/completion.rb +22 -11
  26. data/lib/ruby_lsp/listeners/definition.rb +41 -21
  27. data/lib/ruby_lsp/listeners/document_highlight.rb +26 -1
  28. data/lib/ruby_lsp/listeners/document_link.rb +64 -28
  29. data/lib/ruby_lsp/listeners/hover.rb +27 -16
  30. data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
  31. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +2 -2
  32. data/lib/ruby_lsp/listeners/signature_help.rb +2 -2
  33. data/lib/ruby_lsp/listeners/spec_style.rb +155 -79
  34. data/lib/ruby_lsp/listeners/test_discovery.rb +39 -21
  35. data/lib/ruby_lsp/listeners/test_style.rb +75 -35
  36. data/lib/ruby_lsp/rbs_document.rb +3 -6
  37. data/lib/ruby_lsp/requests/code_action_resolve.rb +83 -58
  38. data/lib/ruby_lsp/requests/code_actions.rb +20 -5
  39. data/lib/ruby_lsp/requests/code_lens.rb +27 -6
  40. data/lib/ruby_lsp/requests/completion.rb +3 -3
  41. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -6
  42. data/lib/ruby_lsp/requests/definition.rb +4 -7
  43. data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
  44. data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
  45. data/lib/ruby_lsp/requests/document_link.rb +1 -1
  46. data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
  47. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +64 -12
  48. data/lib/ruby_lsp/requests/hover.rb +3 -6
  49. data/lib/ruby_lsp/requests/inlay_hints.rb +4 -4
  50. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  51. data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
  52. data/lib/ruby_lsp/requests/references.rb +10 -21
  53. data/lib/ruby_lsp/requests/rename.rb +9 -10
  54. data/lib/ruby_lsp/requests/request.rb +8 -8
  55. data/lib/ruby_lsp/requests/selection_ranges.rb +2 -2
  56. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  57. data/lib/ruby_lsp/requests/show_syntax_tree.rb +2 -2
  58. data/lib/ruby_lsp/requests/signature_help.rb +2 -2
  59. data/lib/ruby_lsp/requests/support/annotation.rb +1 -1
  60. data/lib/ruby_lsp/requests/support/common.rb +9 -12
  61. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  62. data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
  63. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +7 -1
  64. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
  65. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -3
  66. data/lib/ruby_lsp/requests/support/source_uri.rb +7 -4
  67. data/lib/ruby_lsp/requests/support/test_item.rb +7 -1
  68. data/lib/ruby_lsp/requests/workspace_symbol.rb +20 -12
  69. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +1 -4
  70. data/lib/ruby_lsp/response_builders/document_symbol.rb +2 -3
  71. data/lib/ruby_lsp/response_builders/hover.rb +1 -4
  72. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  73. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +4 -5
  74. data/lib/ruby_lsp/response_builders/signature_help.rb +1 -2
  75. data/lib/ruby_lsp/response_builders/test_collection.rb +29 -3
  76. data/lib/ruby_lsp/ruby_document.rb +14 -42
  77. data/lib/ruby_lsp/scripts/compose_bundle.rb +3 -3
  78. data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +3 -1
  79. data/lib/ruby_lsp/server.rb +173 -130
  80. data/lib/ruby_lsp/setup_bundler.rb +114 -47
  81. data/lib/ruby_lsp/static_docs.rb +1 -0
  82. data/lib/ruby_lsp/store.rb +6 -16
  83. data/lib/ruby_lsp/test_helper.rb +1 -4
  84. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +121 -17
  85. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +65 -25
  86. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +16 -18
  87. data/lib/ruby_lsp/utils.rb +102 -13
  88. data/static_docs/break.md +103 -0
  89. metadata +8 -33
  90. data/lib/ruby_indexer/test/class_variables_test.rb +0 -140
  91. data/lib/ruby_indexer/test/classes_and_modules_test.rb +0 -770
  92. data/lib/ruby_indexer/test/configuration_test.rb +0 -280
  93. data/lib/ruby_indexer/test/constant_test.rb +0 -402
  94. data/lib/ruby_indexer/test/enhancements_test.rb +0 -325
  95. data/lib/ruby_indexer/test/global_variable_test.rb +0 -49
  96. data/lib/ruby_indexer/test/index_test.rb +0 -2190
  97. data/lib/ruby_indexer/test/instance_variables_test.rb +0 -240
  98. data/lib/ruby_indexer/test/method_test.rb +0 -973
  99. data/lib/ruby_indexer/test/prefix_tree_test.rb +0 -150
  100. data/lib/ruby_indexer/test/rbs_indexer_test.rb +0 -380
  101. data/lib/ruby_indexer/test/reference_finder_test.rb +0 -330
  102. data/lib/ruby_indexer/test/test_case.rb +0 -51
  103. data/lib/ruby_indexer/test/uri_test.rb +0 -85
  104. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -4,151 +4,227 @@
4
4
  module RubyLsp
5
5
  module Listeners
6
6
  class SpecStyle < TestDiscovery
7
- extend T::Sig
7
+ class Group
8
+ #: String
9
+ attr_reader :id
8
10
 
9
- #: (response_builder: ResponseBuilders::TestCollection, global_state: GlobalState, dispatcher: Prism::Dispatcher, uri: URI::Generic) -> void
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
10
21
  def initialize(response_builder, global_state, dispatcher, uri)
11
- super
22
+ super(response_builder, global_state, uri)
12
23
 
13
- @describe_block_nesting = [] #: Array[String]
14
- @spec_class_stack = [] #: Array[bool]
24
+ @spec_group_id_stack = [] #: Array[Group?]
15
25
 
16
- dispatcher.register(
17
- self,
18
- # Common handlers registered in parent class
26
+ register_events(
27
+ dispatcher,
19
28
  :on_class_node_enter,
20
- :on_call_node_enter, # e.g. `describe` or `it`
29
+ :on_call_node_enter,
21
30
  :on_call_node_leave,
22
31
  )
23
32
  end
24
33
 
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)
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)
30
38
  end
31
39
  end
32
40
 
33
- #: (node: Prism::ClassNode) -> void
41
+ #: (Prism::ClassNode) -> void
34
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
35
50
  super
51
+ end
36
52
 
37
- @spec_class_stack.pop
53
+ #: (Prism::ModuleNode) -> void
54
+ def on_module_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
55
+ @spec_group_id_stack.pop
56
+ super
38
57
  end
39
58
 
40
- #: (node: Prism::CallNode) -> void
41
- def on_call_node_enter(node)
59
+ #: (Prism::CallNode) -> void
60
+ def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
42
61
  case node.name
43
62
  when :describe
44
63
  handle_describe(node)
45
64
  when :it, :specify
46
- handle_example(node)
65
+ handle_example(node) if in_spec_context?
47
66
  end
48
67
  end
49
68
 
50
- #: (node: Prism::CallNode) -> void
51
- def on_call_node_leave(node)
69
+ #: (Prism::CallNode) -> void
70
+ def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
52
71
  return unless node.name == :describe && !node.receiver
53
72
 
54
- @describe_block_nesting.pop
73
+ current_group = @spec_group_id_stack.last
74
+ return unless current_group.is_a?(DescribeGroup)
75
+
76
+ description = extract_description(node)
77
+ return unless description && current_group.id.end_with?(description)
78
+
79
+ @spec_group_id_stack.pop
55
80
  end
56
81
 
57
82
  private
58
83
 
59
- #: (node: Prism::CallNode) -> void
84
+ #: (Prism::CallNode) -> void
60
85
  def handle_describe(node)
86
+ # Describes will include the nesting of all classes and all outer describes as part of its ID, unlike classes
87
+ # that ignore describes
61
88
  return if node.block.nil?
62
89
 
63
90
  description = extract_description(node)
64
91
  return unless description
65
92
 
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)
93
+ parent = latest_group
94
+ return unless parent
95
+
96
+ id = case parent
97
+ when Requests::Support::TestItem
98
+ "#{parent.id}::#{description}"
77
99
  else
78
- add_to_parent_test_group(description, node)
100
+ description
79
101
  end
80
102
 
81
- @describe_block_nesting << description
103
+ test_item = Requests::Support::TestItem.new(
104
+ id,
105
+ description,
106
+ @uri,
107
+ range_from_node(node),
108
+ framework: :minitest,
109
+ )
110
+
111
+ parent.add(test_item)
112
+ @response_builder.add_code_lens(test_item)
113
+ @spec_group_id_stack << DescribeGroup.new(id)
82
114
  end
83
115
 
84
- #: (node: Prism::CallNode) -> void
116
+ #: (Prism::CallNode) -> void
85
117
  def handle_example(node)
86
- return unless in_spec_context?
118
+ description = extract_it_description(node)
119
+ line = node.location.start_line - 1
120
+ parent = latest_group
121
+ return unless parent.is_a?(Requests::Support::TestItem)
87
122
 
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
123
+ id = "#{parent.id}##{format("test_%04d_%s", line, description)}"
100
124
 
101
125
  test_item = Requests::Support::TestItem.new(
102
- description,
126
+ id,
103
127
  description,
104
128
  @uri,
105
129
  range_from_node(node),
106
130
  framework: :minitest,
107
131
  )
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
132
 
120
- test_group = @response_builder[root_group_name] #: Requests::Support::TestItem?
121
- return unless test_group
133
+ parent.add(test_item)
134
+ @response_builder.add_code_lens(test_item)
135
+ end
122
136
 
123
- return test_group unless nested_describe_groups
137
+ #: (Prism::CallNode) -> String?
138
+ def extract_description(node)
139
+ arguments = node.arguments&.arguments
140
+ return unless arguments
124
141
 
125
- nested_describe_groups.each do |description|
126
- test_group = test_group[description]
127
- end
142
+ parts = arguments.map { |arg| extract_argument_content(arg) }
143
+ return if parts.empty?
128
144
 
129
- test_group
145
+ parts.join("::")
130
146
  end
131
147
 
132
- #: (node: Prism::CallNode) -> String?
133
- def extract_description(node)
148
+ #: (Prism::CallNode) -> String
149
+ def extract_it_description(node)
150
+ # Minitest formats the descriptions into test method names by using the count of examples with the description
151
+ # We are not guaranteed to discover examples in the exact order using static analysis, so we use the line number
152
+ # instead. Note that anonymous examples mixed with meta-programming will not be handled correctly
134
153
  first_argument = node.arguments&.arguments&.first
135
- return unless first_argument
154
+ return "anonymous" unless first_argument
155
+
156
+ extract_argument_content(first_argument) || "anonymous"
157
+ end
136
158
 
137
- case first_argument
159
+ #: (Prism::Node) -> String?
160
+ def extract_argument_content(arg)
161
+ case arg
138
162
  when Prism::StringNode
139
- first_argument.content
163
+ arg.content
164
+ when Prism::SymbolNode
165
+ arg.value
140
166
  when Prism::ConstantReadNode, Prism::ConstantPathNode
141
- constant_name(first_argument)
167
+ constant_name(arg)
142
168
  else
143
- first_argument.slice
169
+ arg.slice
144
170
  end
145
171
  end
146
172
 
173
+ #: -> (Requests::Support::TestItem | ResponseBuilders::TestCollection)?
174
+ def latest_group
175
+ # If we haven't found anything yet, then return the response builder
176
+ return @response_builder if @spec_group_id_stack.compact.empty?
177
+ # If we found something that isn't a group last, then we're inside a random module or class, but not a spec
178
+ # group
179
+ return unless @spec_group_id_stack.last
180
+
181
+ # Specs using at least one class as a group require special handling
182
+ closest_class_index = @spec_group_id_stack.rindex { |i| i.is_a?(ClassGroup) }
183
+
184
+ if closest_class_index
185
+ first_class_index = @spec_group_id_stack.index { |i| i.is_a?(ClassGroup) } #: as !nil
186
+ first_class = @spec_group_id_stack[first_class_index] #: as !nil
187
+ item = @response_builder[first_class.id] #: as !nil
188
+
189
+ # Descend into child items from the beginning all the way to the latest class group, ignoring describes
190
+ @spec_group_id_stack[first_class_index + 1..closest_class_index] #: as !nil
191
+ .each do |group|
192
+ next unless group.is_a?(ClassGroup)
193
+
194
+ item = item[group.id] #: as !nil
195
+ end
196
+
197
+ # From the class forward, we must take describes into account
198
+ @spec_group_id_stack[closest_class_index + 1..] #: as !nil
199
+ .each do |group|
200
+ next unless group
201
+
202
+ item = item[group.id] #: as !nil
203
+ end
204
+
205
+ return item
206
+ end
207
+
208
+ # Specs only using describes
209
+ first_group_index = @spec_group_id_stack.index { |i| i.is_a?(DescribeGroup) }
210
+ return unless first_group_index
211
+
212
+ first_group = @spec_group_id_stack[first_group_index] #: as !nil
213
+ item = @response_builder[first_group.id] #: as !nil
214
+
215
+ @spec_group_id_stack[first_group_index + 1..] #: as !nil
216
+ .each do |group|
217
+ next unless group.is_a?(DescribeGroup)
218
+
219
+ item = item[group.id] #: as !nil
220
+ end
221
+
222
+ item
223
+ end
224
+
147
225
  #: -> bool
148
226
  def in_spec_context?
149
- return true if @nesting.empty?
150
-
151
- @spec_class_stack.last #: as !nil
227
+ @nesting.empty? || @spec_group_id_stack.any? { |id| id }
152
228
  end
153
229
  end
154
230
  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, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
15
- def initialize(response_builder, global_state, dispatcher, uri)
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("::")
@@ -58,11 +61,26 @@ module RubyLsp
58
61
 
59
62
  #: (Prism::ClassNode node, String fully_qualified_name) -> Array[String]
60
63
  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
64
+ superclass = node.superclass
65
+
66
+ begin
67
+ ancestors = @index.linearized_ancestors_of(fully_qualified_name)
68
+ # If the project has no bundle and a test class inherits from `Minitest::Test`, the linearized ancestors will
69
+ # not include the parent class because we never indexed it in the first place. Here we add the superclass
70
+ # directly, so that we can support running tests in projects without a bundle
71
+ return ancestors if ancestors.length > 1
72
+
73
+ # If all we found is the class itself, then manually include the parent class
74
+ if ancestors.first == fully_qualified_name && superclass
75
+ return [*ancestors, superclass.slice]
76
+ end
77
+
78
+ ancestors
79
+ rescue RubyIndexer::Index::NonExistingNamespaceError
80
+ # When there are dynamic parts in the constant path, we will not have indexed the namespace. We can still
81
+ # provide test functionality if the class inherits directly from Test::Unit::TestCase or Minitest::Test
82
+ [superclass&.slice].compact
83
+ end
66
84
  end
67
85
 
68
86
  #: (Prism::ConstantPathNode | Prism::ConstantReadNode | Prism::ConstantPathTargetNode | Prism::CallNode | Prism::MissingNode node) -> String
@@ -71,7 +89,7 @@ module RubyLsp
71
89
  slice.gsub(/((?<=::)|^)[a-z]\w*/, DYNAMIC_REFERENCE_MARKER)
72
90
  end
73
91
 
74
- #: (Prism::ClassNode node, ^(String name, Array[String] ancestors) -> void block) -> void
92
+ #: (Prism::ClassNode node) { (String name, Array[String] ancestors) -> void } -> void
75
93
  def with_test_ancestor_tracking(node, &block)
76
94
  @visibility_stack << :public
77
95
  name = constant_name(node.constant_path)
@@ -33,18 +33,20 @@ module RubyLsp
33
33
 
34
34
  if tags.include?("test_dir")
35
35
  if children.empty?
36
- full_files.concat(Dir.glob(
37
- "#{path}/**/{*_test,test_*}.rb",
38
- File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME,
39
- ))
36
+ full_files.concat(
37
+ Dir.glob(
38
+ "#{path}/**/{*_test,test_*,*_spec}.rb",
39
+ File::Constants::FNM_EXTGLOB | File::Constants::FNM_PATHNAME,
40
+ ).map! { |p| Shellwords.escape(p) },
41
+ )
40
42
  end
41
43
  elsif tags.include?("test_file")
42
- full_files << path if children.empty?
44
+ full_files << Shellwords.escape(path) if children.empty?
43
45
  elsif tags.include?("test_group")
44
46
  # If all of the children of the current test group are other groups, then there's no need to add it to the
45
47
  # aggregated examples
46
48
  unless children.any? && children.all? { |child| child[:tags].include?("test_group") }
47
- aggregated_tests[path][item[:label]] = { tags: tags, examples: [] }
49
+ aggregated_tests[path][item[:id]] = { tags: tags, examples: [] }
48
50
  end
49
51
  else
50
52
  class_name, method_name = item[:id].split("#")
@@ -74,7 +76,10 @@ module RubyLsp
74
76
  end
75
77
 
76
78
  unless full_files.empty?
77
- commands << "#{BASE_COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{full_files.join(" ")}"
79
+ specs, tests = full_files.partition { |path| spec?(path) }
80
+
81
+ commands << "#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{tests.join(" ")}" if tests.any?
82
+ commands << "#{COMMAND} -Ispec -e \"ARGV.each { |f| require f }\" #{specs.join(" ")}" if specs.any?
78
83
  end
79
84
 
80
85
  commands
@@ -82,10 +87,15 @@ module RubyLsp
82
87
 
83
88
  private
84
89
 
90
+ #: (String) -> bool
91
+ def spec?(path)
92
+ File.fnmatch?("**/spec/**/*_spec.rb", path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
93
+ end
94
+
85
95
  #: (String, Hash[String, Hash[Symbol, untyped]]) -> String
86
96
  def handle_minitest_groups(file_path, groups_and_examples)
87
97
  regexes = groups_and_examples.flat_map do |group, info|
88
- examples = info[:examples]
98
+ examples = info[:examples].map { |e| Shellwords.escape(e).gsub(/test_\d{4}/, "test_\\d{4}") }
89
99
  group_regex = Shellwords.escape(group).gsub(
90
100
  Shellwords.escape(TestDiscovery::DYNAMIC_REFERENCE_MARKER),
91
101
  ".*",
@@ -93,9 +103,9 @@ module RubyLsp
93
103
  if examples.empty?
94
104
  "^#{group_regex}(#|::)"
95
105
  elsif examples.length == 1
96
- "^#{group_regex}##{examples[0]}$"
106
+ "^#{group_regex}##{examples[0]}\\$"
97
107
  else
98
- "^#{group_regex}#(#{examples.join("|")})$"
108
+ "^#{group_regex}#(#{examples.join("|")})\\$"
99
109
  end
100
110
  end
101
111
 
@@ -105,7 +115,8 @@ module RubyLsp
105
115
  "(#{regexes.join("|")})"
106
116
  end
107
117
 
108
- "#{BASE_COMMAND} -Itest #{file_path} --name \"/#{regex}/\""
118
+ load_path = spec?(file_path) ? "-Ispec" : "-Itest"
119
+ "#{COMMAND} #{load_path} #{Shellwords.escape(file_path)} --name \"/#{regex}/\""
109
120
  end
110
121
 
111
122
  #: (String, Hash[String, Hash[Symbol, untyped]]) -> Array[String]
@@ -116,13 +127,13 @@ module RubyLsp
116
127
  Shellwords.escape(TestDiscovery::DYNAMIC_REFERENCE_MARKER),
117
128
  ".*",
118
129
  )
119
- command = +"#{BASE_COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}$/\""
130
+ command = +"#{COMMAND} -Itest #{Shellwords.escape(file_path)} --testcase \"/^#{group_regex}\\$/\""
120
131
 
121
132
  unless examples.empty?
122
133
  command << if examples.length == 1
123
- " --name \"/#{examples[0]}$/\""
134
+ " --name \"/#{examples[0]}\\$/\""
124
135
  else
125
- " --name \"/(#{examples.join("|")})$/\""
136
+ " --name \"/(#{examples.join("|")})\\$/\""
126
137
  end
127
138
  end
128
139
 
@@ -135,23 +146,24 @@ module RubyLsp
135
146
 
136
147
  MINITEST_REPORTER_PATH = File.expand_path("../test_reporters/minitest_reporter.rb", __dir__) #: String
137
148
  TEST_UNIT_REPORTER_PATH = File.expand_path("../test_reporters/test_unit_reporter.rb", __dir__) #: String
138
- ACCESS_MODIFIERS = [:public, :private, :protected].freeze
139
149
  BASE_COMMAND = begin
140
- Bundler.with_original_env { Bundler.default_lockfile }
150
+ Bundler.with_unbundled_env { Bundler.default_lockfile }
141
151
  "bundle exec ruby"
142
152
  rescue Bundler::GemfileNotFound
143
153
  "ruby"
144
154
  end #: String
155
+ COMMAND = "#{BASE_COMMAND} -r#{MINITEST_REPORTER_PATH} -r#{TEST_UNIT_REPORTER_PATH}" #: String
156
+ ACCESS_MODIFIERS = [:public, :private, :protected].freeze
145
157
 
146
- #: (ResponseBuilders::TestCollection response_builder, GlobalState global_state, Prism::Dispatcher dispatcher, URI::Generic uri) -> void
158
+ #: (ResponseBuilders::TestCollection, GlobalState, Prism::Dispatcher, URI::Generic) -> void
147
159
  def initialize(response_builder, global_state, dispatcher, uri)
148
- super
160
+ super(response_builder, global_state, uri)
149
161
 
150
162
  @framework = :minitest #: Symbol
163
+ @parent_stack = [@response_builder] #: Array[(Requests::Support::TestItem | ResponseBuilders::TestCollection)?]
151
164
 
152
- dispatcher.register(
153
- self,
154
- # Common handlers registered in parent class
165
+ register_events(
166
+ dispatcher,
155
167
  :on_class_node_enter,
156
168
  :on_def_node_enter,
157
169
  :on_call_node_enter,
@@ -160,48 +172,70 @@ module RubyLsp
160
172
  end
161
173
 
162
174
  #: (Prism::ClassNode node) -> void
163
- def on_class_node_enter(node)
175
+ def on_class_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
164
176
  with_test_ancestor_tracking(node) do |name, ancestors|
165
177
  @framework = :test_unit if ancestors.include?("Test::Unit::TestCase")
166
178
 
167
179
  if @framework == :test_unit || non_declarative_minitest?(ancestors, name)
168
- @response_builder.add(Requests::Support::TestItem.new(
180
+ test_item = Requests::Support::TestItem.new(
169
181
  name,
170
182
  name,
171
183
  @uri,
172
184
  range_from_node(node),
173
185
  framework: @framework,
174
- ))
186
+ )
187
+
188
+ last_test_group.add(test_item)
189
+ @response_builder.add_code_lens(test_item)
190
+ @parent_stack << test_item
191
+ else
192
+ @parent_stack << nil
175
193
  end
176
194
  end
177
195
  end
178
196
 
197
+ #: (Prism::ClassNode node) -> void
198
+ def on_class_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
199
+ @parent_stack.pop
200
+ super
201
+ end
202
+
203
+ #: (Prism::ModuleNode node) -> void
204
+ def on_module_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
205
+ @parent_stack << nil
206
+ super
207
+ end
208
+
209
+ #: (Prism::ModuleNode node) -> void
210
+ def on_module_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
211
+ @parent_stack.pop
212
+ super
213
+ end
214
+
179
215
  #: (Prism::DefNode node) -> void
180
- def on_def_node_enter(node)
216
+ def on_def_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
181
217
  return if @visibility_stack.last != :public
182
218
 
183
219
  name = node.name.to_s
184
220
  return unless name.start_with?("test_")
185
221
 
186
222
  current_group_name = RubyIndexer::Index.actual_nesting(@nesting, nil).join("::")
223
+ parent = @parent_stack.last
224
+ return unless parent.is_a?(Requests::Support::TestItem)
187
225
 
188
- # If we're finding a test method, but for the wrong framework, then the group test item will not have been
189
- # previously pushed and thus we return early and avoid adding items for a framework this listener is not
190
- # interested in
191
- test_item = @response_builder[current_group_name]
192
- return unless test_item
193
-
194
- test_item.add(Requests::Support::TestItem.new(
226
+ example_item = Requests::Support::TestItem.new(
195
227
  "#{current_group_name}##{name}",
196
228
  name,
197
229
  @uri,
198
230
  range_from_node(node),
199
231
  framework: @framework,
200
- ))
232
+ )
233
+ parent.add(example_item)
234
+ @response_builder.add_code_lens(example_item)
201
235
  end
202
236
 
203
237
  #: (Prism::CallNode node) -> void
204
- def on_call_node_enter(node)
238
+ def on_call_node_enter(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
205
239
  name = node.name
206
240
  return unless ACCESS_MODIFIERS.include?(name)
207
241
 
@@ -209,7 +243,7 @@ module RubyLsp
209
243
  end
210
244
 
211
245
  #: (Prism::CallNode node) -> void
212
- def on_call_node_leave(node)
246
+ def on_call_node_leave(node) # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
213
247
  name = node.name
214
248
  return unless ACCESS_MODIFIERS.include?(name)
215
249
  return unless node.arguments&.arguments
@@ -219,6 +253,12 @@ module RubyLsp
219
253
 
220
254
  private
221
255
 
256
+ #: -> (Requests::Support::TestItem | ResponseBuilders::TestCollection)
257
+ def last_test_group
258
+ index = @parent_stack.rindex { |i| i } #: as !nil
259
+ @parent_stack[index] #: as Requests::Support::TestItem | ResponseBuilders::TestCollection
260
+ end
261
+
222
262
  #: (Array[String] attached_ancestors, String fully_qualified_name) -> bool
223
263
  def non_declarative_minitest?(attached_ancestors, fully_qualified_name)
224
264
  return false unless attached_ancestors.include?("Minitest::Test")
@@ -2,11 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
+ #: [ParseResultType = Array[RBS::AST::Declarations::Base]]
5
6
  class RBSDocument < Document
6
- extend T::Generic
7
-
8
- ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
9
-
10
7
  #: (source: String, version: Integer, uri: URI::Generic, global_state: GlobalState) -> void
11
8
  def initialize(source:, version:, uri:, global_state:)
12
9
  @syntax_error = false #: bool
@@ -36,9 +33,9 @@ module RubyLsp
36
33
  end
37
34
 
38
35
  # @override
39
- #: -> LanguageId
36
+ #: -> Symbol
40
37
  def language_id
41
- LanguageId::RBS
38
+ :rbs
42
39
  end
43
40
  end
44
41
  end