ruby-lsp 0.23.11 → 0.25.0

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 +18 -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 +106 -236
  14. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +155 -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 -8
  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 +170 -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 +73 -86
  35. data/lib/ruby_lsp/base_server.rb +41 -42
  36. data/lib/ruby_lsp/client_capabilities.rb +16 -13
  37. data/lib/ruby_lsp/document.rb +201 -98
  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 +76 -74
  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 +203 -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 +30 -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 +266 -143
  108. data/lib/ruby_lsp/setup_bundler.rb +113 -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 +236 -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
@@ -0,0 +1,236 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "English"
5
+ require "json"
6
+ require "socket"
7
+ require "singleton"
8
+ require "tmpdir"
9
+ require_relative "../../ruby_indexer/lib/ruby_indexer/uri"
10
+
11
+ module RubyLsp
12
+ class LspReporter
13
+ include Singleton
14
+
15
+ # https://code.visualstudio.com/api/references/vscode-api#Position
16
+ #: type position = { line: Integer, character: Integer }
17
+
18
+ # https://code.visualstudio.com/api/references/vscode-api#Range
19
+ #: type range = { start: position, end: position }
20
+
21
+ # https://code.visualstudio.com/api/references/vscode-api#BranchCoverage
22
+ #: type branch_coverage = { executed: Integer, label: String, location: range }
23
+
24
+ # https://code.visualstudio.com/api/references/vscode-api#StatementCoverage
25
+ #: type statement_coverage = { executed: Integer, location: position, branches: Array[branch_coverage] }
26
+
27
+ #: bool
28
+ attr_reader :invoked_shutdown
29
+
30
+ #: -> void
31
+ def initialize
32
+ dir_path = File.join(Dir.tmpdir, "ruby-lsp")
33
+ FileUtils.mkdir_p(dir_path)
34
+
35
+ port_db_path = File.join(dir_path, "test_reporter_port_db.json")
36
+ port = ENV["RUBY_LSP_REPORTER_PORT"]
37
+
38
+ @io = begin
39
+ # The environment variable is only used for tests. The extension always writes to the temporary file
40
+ if port
41
+ TCPSocket.new("localhost", port)
42
+ elsif File.exist?(port_db_path)
43
+ db = JSON.load_file(port_db_path)
44
+ TCPSocket.new("localhost", db[Dir.pwd])
45
+ else
46
+ # For tests that don't spawn the TCP server
47
+ require "stringio"
48
+ StringIO.new
49
+ end
50
+ rescue
51
+ require "stringio"
52
+ StringIO.new
53
+ end #: IO | StringIO
54
+
55
+ @invoked_shutdown = false #: bool
56
+ end
57
+
58
+ #: -> void
59
+ def shutdown
60
+ # When running in coverage mode, we don't want to inform the extension that we finished immediately after running
61
+ # tests. We only do it after we finish processing coverage results, by invoking `internal_shutdown`
62
+ return if ENV["RUBY_LSP_TEST_RUNNER"] == "coverage"
63
+
64
+ internal_shutdown
65
+ end
66
+
67
+ # This method is intended to be used by the RubyLsp::LspReporter class itself only. If you're writing a custom test
68
+ # reporter, use `shutdown` instead
69
+ #: -> void
70
+ def internal_shutdown
71
+ @invoked_shutdown = true
72
+
73
+ send_message("finish")
74
+ @io.close
75
+ end
76
+
77
+ #: (id: String, uri: URI::Generic, ?line: Integer?) -> void
78
+ def start_test(id:, uri:, line: nil)
79
+ send_message("start", id: id, uri: uri.to_s, line: line)
80
+ end
81
+
82
+ #: (id: String, uri: URI::Generic) -> void
83
+ def record_pass(id:, uri:)
84
+ send_message("pass", id: id, uri: uri.to_s)
85
+ end
86
+
87
+ #: (id: String, message: String, uri: URI::Generic) -> void
88
+ def record_fail(id:, message:, uri:)
89
+ send_message("fail", id: id, message: message, uri: uri.to_s)
90
+ end
91
+
92
+ #: (id: String, uri: URI::Generic) -> void
93
+ def record_skip(id:, uri:)
94
+ send_message("skip", id: id, uri: uri.to_s)
95
+ end
96
+
97
+ #: (id: String, message: String?, uri: URI::Generic) -> void
98
+ def record_error(id:, message:, uri:)
99
+ send_message("error", id: id, message: message, uri: uri.to_s)
100
+ end
101
+
102
+ #: (Method | UnboundMethod) -> [URI::Generic, Integer?]?
103
+ def uri_and_line_for(method_object)
104
+ file_path, line = method_object.source_location
105
+ return unless file_path
106
+ return if file_path.start_with?("(eval at ")
107
+
108
+ uri = URI::Generic.from_path(path: File.expand_path(file_path))
109
+ zero_based_line = line ? line - 1 : nil
110
+ [uri, zero_based_line]
111
+ end
112
+
113
+ # Gather the results returned by Coverage.result and format like the VS Code test explorer expects
114
+ #
115
+ # Coverage result format:
116
+ #
117
+ # Lines are reported in order as an array where each number is the number of times it was executed. For example,
118
+ # the following says that line 0 was executed 1 time and line 1 executed 3 times: [1, 3].
119
+ # Nil values represent lines for which coverage is not available, like empty lines, comments or keywords like
120
+ # `else`
121
+ #
122
+ # Branches are a hash containing the name of the branch and the location where it is found in tuples with the
123
+ # following elements: [NAME, ID, START_LINE, START_COLUMN, END_LINE, END_COLUMN] as the keys and the value is the
124
+ # number of times it was executed
125
+ #
126
+ # Methods are a similar hash [ClassName, :method_name, START_LINE, START_COLUMN, END_LINE, END_COLUMN] => NUMBER
127
+ # OF EXECUTIONS
128
+ #
129
+ # Example:
130
+ # {
131
+ # "file_path" => {
132
+ # "lines" => [1, 2, 3, nil],
133
+ # "branches" => {
134
+ # ["&.", 0, 6, 21, 6, 65] => { [:then, 1, 6, 21, 6, 65] => 0, [:else, 5, 7, 0, 7, 87] => 1 }
135
+ # },
136
+ # "methods" => {
137
+ # ["Foo", :bar, 6, 21, 6, 65] => 0
138
+ # }
139
+ # }
140
+ #: -> Hash[String, statement_coverage]
141
+ def gather_coverage_results
142
+ # Ignore coverage results inside dependencies
143
+ bundle_path = Bundler.bundle_path.to_s
144
+
145
+ result = Coverage.result.reject do |file_path, _coverage_info|
146
+ file_path.start_with?(bundle_path) || !file_path.start_with?(Dir.pwd)
147
+ end
148
+
149
+ result.to_h do |file_path, coverage_info|
150
+ # Format the branch coverage information as VS Code expects it and then group it based on the start line of
151
+ # the conditional that causes the branching. We need to match each line coverage data with the branches that
152
+ # spawn from that line
153
+ branch_by_line = coverage_info[:branches]
154
+ .flat_map do |branch, data|
155
+ branch_name, _branch_id, branch_start_line, _branch_start_col, _branch_end_line, _branch_end_col = branch
156
+
157
+ data.map do |then_or_else, execution_count|
158
+ name, _id, start_line, start_column, end_line, end_column = then_or_else
159
+
160
+ {
161
+ groupingLine: branch_start_line,
162
+ executed: execution_count,
163
+ location: {
164
+ start: { line: start_line, character: start_column },
165
+ end: { line: end_line, character: end_column },
166
+ },
167
+ label: "#{branch_name} #{name}",
168
+ }
169
+ end
170
+ end
171
+ .group_by { |branch| branch[:groupingLine] }
172
+
173
+ # Format the line coverage information, gathering any branch coverage data associated with that line
174
+ data = coverage_info[:lines].filter_map.with_index do |execution_count, line_index|
175
+ next if execution_count.nil?
176
+
177
+ {
178
+ executed: execution_count,
179
+ location: { line: line_index, character: 0 },
180
+ branches: branch_by_line[line_index] || [],
181
+ }
182
+ end
183
+
184
+ # The expected format is URI => { executed: number_of_times_executed, location: { ... }, branches: [ ... ] }
185
+ [URI::Generic.from_path(path: File.expand_path(file_path)).to_s, data]
186
+ end
187
+ end
188
+
189
+ #: -> void
190
+ def at_coverage_exit
191
+ coverage_results = gather_coverage_results
192
+ File.write(File.join(".ruby-lsp", "coverage_result.json"), coverage_results.to_json)
193
+ internal_shutdown
194
+ end
195
+
196
+ #: -> void
197
+ def at_exit
198
+ internal_shutdown unless invoked_shutdown
199
+ end
200
+
201
+ class << self
202
+ #: -> bool
203
+ def start_coverage?
204
+ ENV["RUBY_LSP_TEST_RUNNER"] == "coverage"
205
+ end
206
+
207
+ #: -> bool
208
+ def executed_under_test_runner?
209
+ !!(ENV["RUBY_LSP_TEST_RUNNER"] && ENV["RUBY_LSP_ENV"] != "test")
210
+ end
211
+ end
212
+
213
+ private
214
+
215
+ #: (String?, **untyped) -> void
216
+ def send_message(method_name, **params)
217
+ json_message = { method: method_name, params: params }.to_json
218
+ @io.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
219
+ end
220
+ end
221
+ end
222
+
223
+ if RubyLsp::LspReporter.start_coverage?
224
+ # Auto start coverage when running tests under that profile. This avoids the user from having to configure coverage
225
+ # manually for their project or adding extra dependencies
226
+ require "coverage"
227
+ Coverage.start(:all)
228
+ end
229
+
230
+ if RubyLsp::LspReporter.executed_under_test_runner?
231
+ at_exit do
232
+ # Regular finish events are registered per test reporter. However, if the test crashes during loading the files
233
+ # (e.g.: a bad require), we need to ensure that the execution is finalized so that the extension is not left hanging
234
+ RubyLsp::LspReporter.instance.at_exit if $ERROR_INFO
235
+ end
236
+ end
@@ -0,0 +1,145 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "minitest"
6
+ rescue LoadError
7
+ return
8
+ end
9
+
10
+ require_relative "lsp_reporter"
11
+
12
+ module RubyLsp
13
+ # An override of the default progress reporter in Minitest to add color to the output
14
+ class ProgressReporterWithColor < Minitest::ProgressReporter
15
+ #: (Minitest::Result) -> void
16
+ def record(result)
17
+ color = if result.error?
18
+ "\e[31m" # red
19
+ elsif result.passed?
20
+ "\e[32m" # green
21
+ elsif result.skipped?
22
+ "\e[33m" # yellow
23
+ elsif result.failure
24
+ "\e[31m" # red
25
+ else
26
+ "\e[0m" # no color
27
+ end
28
+
29
+ io.print("#{color}#{result.result_code}\e[0m") # Reset color after printing
30
+ end
31
+ end
32
+
33
+ # This patch is here to prevent other gems from overriding or adding more Minitest reporters. Otherwise, they may
34
+ # break the integration between the server and extension
35
+ module PreventReporterOverridePatch
36
+ @lsp_reporters = [] #: Array[Minitest::AbstractReporter]
37
+
38
+ class << self
39
+ #: Array[Minitest::AbstractReporter]
40
+ attr_accessor :lsp_reporters
41
+ end
42
+
43
+ # Patch the writer to prevent replacing the entire array
44
+ #: (untyped) -> void
45
+ def reporters=(reporters)
46
+ # Do nothing. We don't want other gems to override our reporter
47
+ end
48
+
49
+ # Patch the reader to prevent appending more reporters. This method always returns a temporary copy of the real
50
+ # reporters so that if any gem mutates it, it continues to return the original reporters
51
+ #: -> Array[untyped]
52
+ def reporters
53
+ PreventReporterOverridePatch.lsp_reporters.dup
54
+ end
55
+ end
56
+
57
+ class MinitestReporter < Minitest::AbstractReporter
58
+ class << self
59
+ #: (Hash[untyped, untyped]) -> void
60
+ def minitest_plugin_init(_options)
61
+ # Remove the original progress reporter, so that we replace it with our own. We only do this if no other
62
+ # reporters were included by the application itself to avoid double reporting
63
+ reporters = Minitest.reporter.reporters
64
+
65
+ if reporters.all? { |r| r.is_a?(Minitest::ProgressReporter) || r.is_a?(Minitest::SummaryReporter) }
66
+ reporters.delete_if { |r| r.is_a?(Minitest::ProgressReporter) }
67
+ reporters << ProgressReporterWithColor.new
68
+ end
69
+
70
+ # Add the JSON RPC reporter
71
+ reporters << MinitestReporter.new
72
+ PreventReporterOverridePatch.lsp_reporters = reporters
73
+ Minitest.reporter.class.prepend(PreventReporterOverridePatch)
74
+ end
75
+ end
76
+
77
+ #: (untyped, String) -> void
78
+ def prerecord(test_class_or_wrapper, method_name)
79
+ # In frameworks like Rails, they can control the Minitest execution by wrapping the test class
80
+ # But they conform to responding to `name`, so we can use that as a guarantee
81
+ # We are interested in the test class, not the wrapper
82
+ name = test_class_or_wrapper.name
83
+
84
+ klass = begin
85
+ Object.const_get(name) # rubocop:disable Sorbet/ConstantsFromStrings
86
+ rescue NameError
87
+ # Handle Minitest specs that create classes with invalid constant names like "MySpec::when something is true"
88
+ # If we can't resolve the constant, it means we were given the actual test class object, not the wrapper
89
+ test_class_or_wrapper
90
+ end
91
+
92
+ uri, line = LspReporter.instance.uri_and_line_for(klass.instance_method(method_name))
93
+ return unless uri
94
+
95
+ id = "#{name}##{handle_spec_test_id(method_name, line)}"
96
+ LspReporter.instance.start_test(id: id, uri: uri, line: line)
97
+ end
98
+
99
+ #: (Minitest::Result result) -> void
100
+ def record(result)
101
+ file_path, line = result.source_location
102
+ return unless file_path
103
+
104
+ zero_based_line = line ? line - 1 : nil
105
+ name = handle_spec_test_id(result.name, zero_based_line)
106
+ id = "#{result.klass}##{name}"
107
+
108
+ uri = URI::Generic.from_path(path: File.expand_path(file_path))
109
+
110
+ if result.error?
111
+ message = result.failures.first.message
112
+ LspReporter.instance.record_error(id: id, uri: uri, message: message)
113
+ elsif result.passed?
114
+ LspReporter.instance.record_pass(id: id, uri: uri)
115
+ elsif result.skipped?
116
+ LspReporter.instance.record_skip(id: id, uri: uri)
117
+ elsif result.failure
118
+ message = result.failure.message
119
+ LspReporter.instance.record_fail(id: id, uri: uri, message: message)
120
+ end
121
+ end
122
+
123
+ #: -> void
124
+ def report
125
+ LspReporter.instance.shutdown
126
+ end
127
+
128
+ #: (String, Integer?) -> String
129
+ def handle_spec_test_id(method_name, line)
130
+ method_name.gsub(/(?<=test_)\d{4}(?=_)/, format("%04d", line.to_s))
131
+ end
132
+ end
133
+ end
134
+
135
+ Minitest.extensions << RubyLsp::MinitestReporter
136
+
137
+ if RubyLsp::LspReporter.start_coverage?
138
+ Minitest.after_run do
139
+ RubyLsp::LspReporter.instance.at_coverage_exit
140
+ end
141
+ elsif RubyLsp::LspReporter.executed_under_test_runner?
142
+ Minitest.after_run do
143
+ RubyLsp::LspReporter.instance.at_exit
144
+ end
145
+ end
@@ -0,0 +1,92 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ begin
5
+ require "test/unit"
6
+ require "test/unit/ui/testrunner"
7
+ require "test/unit/ui/console/testrunner"
8
+ rescue LoadError
9
+ return
10
+ end
11
+
12
+ require_relative "lsp_reporter"
13
+
14
+ module RubyLsp
15
+ class TestUnitReporter < Test::Unit::UI::Console::TestRunner
16
+ def initialize(suite, options = {})
17
+ super
18
+ @current_uri = nil #: URI::Generic?
19
+ @current_test_id = nil #: String?
20
+ end
21
+
22
+ private
23
+
24
+ #: (::Test::Unit::TestCase test) -> void
25
+ def test_started(test)
26
+ super
27
+
28
+ uri, line = LspReporter.instance.uri_and_line_for(test.method(test.method_name))
29
+ return unless uri
30
+
31
+ @current_uri = uri
32
+ @current_test_id = "#{test.class.name}##{test.method_name}"
33
+ LspReporter.instance.start_test(id: @current_test_id, uri: @current_uri, line: line)
34
+ end
35
+
36
+ #: (::Test::Unit::TestCase test) -> void
37
+ def test_finished(test)
38
+ super
39
+ return unless test.passed? && @current_uri && @current_test_id
40
+
41
+ LspReporter.instance.record_pass(id: @current_test_id, uri: @current_uri)
42
+ end
43
+
44
+ #: (::Test::Unit::Failure | ::Test::Unit::Error | ::Test::Unit::Pending result) -> void
45
+ def add_fault(result)
46
+ super
47
+ return unless @current_uri && @current_test_id
48
+
49
+ case result
50
+ when ::Test::Unit::Failure
51
+ LspReporter.instance.record_fail(id: @current_test_id, message: result.message, uri: @current_uri)
52
+ when ::Test::Unit::Error
53
+ LspReporter.instance.record_error(id: @current_test_id, message: result.message, uri: @current_uri)
54
+ when ::Test::Unit::Pending
55
+ LspReporter.instance.record_skip(id: @current_test_id, uri: @current_uri)
56
+ end
57
+ end
58
+
59
+ #: (Float) -> void
60
+ def finished(elapsed_time)
61
+ super
62
+ LspReporter.instance.shutdown
63
+ end
64
+
65
+ #: -> void
66
+ def attach_to_mediator
67
+ # Events we care about
68
+ @mediator.add_listener(Test::Unit::TestResult::FAULT, &method(:add_fault))
69
+ @mediator.add_listener(Test::Unit::TestCase::STARTED_OBJECT, &method(:test_started))
70
+ @mediator.add_listener(Test::Unit::TestCase::FINISHED_OBJECT, &method(:test_finished))
71
+ @mediator.add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED, &method(:finished))
72
+
73
+ # Other events needed for the console test runner to print
74
+ @mediator.add_listener(Test::Unit::UI::TestRunnerMediator::STARTED, &method(:started))
75
+ @mediator.add_listener(Test::Unit::TestSuite::STARTED_OBJECT, &method(:test_suite_started))
76
+ @mediator.add_listener(Test::Unit::TestSuite::FINISHED_OBJECT, &method(:test_suite_finished))
77
+ end
78
+ end
79
+ end
80
+
81
+ Test::Unit::AutoRunner.register_runner(:ruby_lsp) { |_auto_runner| RubyLsp::TestUnitReporter }
82
+ Test::Unit::AutoRunner.default_runner = :ruby_lsp
83
+
84
+ if RubyLsp::LspReporter.start_coverage?
85
+ Test::Unit.at_exit do
86
+ RubyLsp::LspReporter.instance.at_coverage_exit
87
+ end
88
+ elsif RubyLsp::LspReporter.executed_under_test_runner?
89
+ Test::Unit.at_exit do
90
+ RubyLsp::LspReporter.instance.at_exit
91
+ end
92
+ end
@@ -5,14 +5,12 @@ module RubyLsp
5
5
  # A minimalistic type checker to try to resolve types that can be inferred without requiring a type system or
6
6
  # annotations
7
7
  class TypeInferrer
8
- extend T::Sig
9
-
10
- sig { params(index: RubyIndexer::Index).void }
8
+ #: (RubyIndexer::Index index) -> void
11
9
  def initialize(index)
12
10
  @index = index
13
11
  end
14
12
 
15
- sig { params(node_context: NodeContext).returns(T.nilable(Type)) }
13
+ #: (NodeContext node_context) -> Type?
16
14
  def infer_receiver_type(node_context)
17
15
  node = node_context.node
18
16
 
@@ -31,7 +29,7 @@ module RubyLsp
31
29
 
32
30
  private
33
31
 
34
- sig { params(node: Prism::CallNode, node_context: NodeContext).returns(T.nilable(Type)) }
32
+ #: (Prism::CallNode node, NodeContext node_context) -> Type?
35
33
  def infer_receiver_for_call_node(node, node_context)
36
34
  receiver = node.receiver
37
35
 
@@ -114,7 +112,7 @@ module RubyLsp
114
112
  end
115
113
  end
116
114
 
117
- sig { params(raw_receiver: String, nesting: T::Array[String]).returns(T.nilable(GuessedType)) }
115
+ #: (String raw_receiver, Array[String] nesting) -> GuessedType?
118
116
  def guess_type(raw_receiver, nesting)
119
117
  guessed_name = raw_receiver
120
118
  .delete_prefix("@")
@@ -130,7 +128,7 @@ module RubyLsp
130
128
  GuessedType.new(name)
131
129
  end
132
130
 
133
- sig { params(node_context: NodeContext).returns(Type) }
131
+ #: (NodeContext node_context) -> Type
134
132
  def self_receiver_handling(node_context)
135
133
  nesting = node_context.nesting
136
134
  # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
@@ -147,7 +145,7 @@ module RubyLsp
147
145
  Type.new("#{parts.join("::")}::<Class:#{parts.last}>")
148
146
  end
149
147
 
150
- sig { params(node_context: NodeContext).returns(T.nilable(Type)) }
148
+ #: (NodeContext node_context) -> Type?
151
149
  def infer_receiver_for_class_variables(node_context)
152
150
  nesting_parts = node_context.nesting.dup
153
151
 
@@ -168,20 +166,21 @@ module RubyLsp
168
166
 
169
167
  # A known type
170
168
  class Type
171
- extend T::Sig
172
-
173
- sig { returns(String) }
169
+ #: String
174
170
  attr_reader :name
175
171
 
176
- sig { params(name: String).void }
172
+ #: (String name) -> void
177
173
  def initialize(name)
178
174
  @name = name
179
175
  end
180
176
 
181
177
  # Returns the attached version of this type by removing the `<Class:...>` part from its name
182
- sig { returns(Type) }
178
+ #: -> Type
183
179
  def attached
184
- Type.new(T.must(@name.split("::")[..-2]).join("::"))
180
+ Type.new(
181
+ @name.split("::")[..-2] #: as !nil
182
+ .join("::"),
183
+ )
185
184
  end
186
185
  end
187
186