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
@@ -0,0 +1,233 @@
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
+ #: -> void
28
+ def initialize
29
+ dir_path = File.join(Dir.tmpdir, "ruby-lsp")
30
+ FileUtils.mkdir_p(dir_path)
31
+
32
+ port_db_path = File.join(dir_path, "test_reporter_port_db.json")
33
+ port = ENV["RUBY_LSP_REPORTER_PORT"]
34
+
35
+ @io = begin
36
+ # The environment variable is only used for tests. The extension always writes to the temporary file
37
+ if port
38
+ TCPSocket.new("localhost", port)
39
+ elsif File.exist?(port_db_path)
40
+ db = JSON.load_file(port_db_path)
41
+ TCPSocket.new("localhost", db[Dir.pwd])
42
+ else
43
+ # For tests that don't spawn the TCP server
44
+ require "stringio"
45
+ StringIO.new
46
+ end
47
+ rescue
48
+ require "stringio"
49
+ StringIO.new
50
+ end #: IO | StringIO
51
+
52
+ @invoked_shutdown = false #: bool
53
+ end
54
+
55
+ #: -> void
56
+ def shutdown
57
+ # When running in coverage mode, we don't want to inform the extension that we finished immediately after running
58
+ # tests. We only do it after we finish processing coverage results, by invoking `internal_shutdown`
59
+ return if ENV["RUBY_LSP_TEST_RUNNER"] == "coverage"
60
+
61
+ internal_shutdown
62
+ end
63
+
64
+ # This method is intended to be used by the RubyLsp::LspReporter class itself only. If you're writing a custom test
65
+ # reporter, use `shutdown` instead
66
+ #: -> void
67
+ def internal_shutdown
68
+ @invoked_shutdown = true
69
+
70
+ send_message("finish")
71
+ @io.close
72
+ end
73
+
74
+ #: (id: String, uri: URI::Generic, ?line: Integer?) -> void
75
+ def start_test(id:, uri:, line: nil)
76
+ send_message("start", id: id, uri: uri.to_s, line: line)
77
+ end
78
+
79
+ #: (id: String, uri: URI::Generic) -> void
80
+ def record_pass(id:, uri:)
81
+ send_message("pass", id: id, uri: uri.to_s)
82
+ end
83
+
84
+ #: (id: String, message: String, uri: URI::Generic) -> void
85
+ def record_fail(id:, message:, uri:)
86
+ send_message("fail", id: id, message: message, uri: uri.to_s)
87
+ end
88
+
89
+ #: (id: String, uri: URI::Generic) -> void
90
+ def record_skip(id:, uri:)
91
+ send_message("skip", id: id, uri: uri.to_s)
92
+ end
93
+
94
+ #: (id: String, message: String?, uri: URI::Generic) -> void
95
+ def record_error(id:, message:, uri:)
96
+ send_message("error", id: id, message: message, uri: uri.to_s)
97
+ end
98
+
99
+ #: (Method | UnboundMethod) -> [URI::Generic, Integer?]?
100
+ def uri_and_line_for(method_object)
101
+ file_path, line = method_object.source_location
102
+ return unless file_path
103
+ return if file_path.start_with?("(eval at ")
104
+
105
+ uri = URI::Generic.from_path(path: File.expand_path(file_path))
106
+ zero_based_line = line ? line - 1 : nil
107
+ [uri, zero_based_line]
108
+ end
109
+
110
+ # Gather the results returned by Coverage.result and format like the VS Code test explorer expects
111
+ #
112
+ # Coverage result format:
113
+ #
114
+ # Lines are reported in order as an array where each number is the number of times it was executed. For example,
115
+ # the following says that line 0 was executed 1 time and line 1 executed 3 times: [1, 3].
116
+ # Nil values represent lines for which coverage is not available, like empty lines, comments or keywords like
117
+ # `else`
118
+ #
119
+ # Branches are a hash containing the name of the branch and the location where it is found in tuples with the
120
+ # following elements: [NAME, ID, START_LINE, START_COLUMN, END_LINE, END_COLUMN] as the keys and the value is the
121
+ # number of times it was executed
122
+ #
123
+ # Methods are a similar hash [ClassName, :method_name, START_LINE, START_COLUMN, END_LINE, END_COLUMN] => NUMBER
124
+ # OF EXECUTIONS
125
+ #
126
+ # Example:
127
+ # {
128
+ # "file_path" => {
129
+ # "lines" => [1, 2, 3, nil],
130
+ # "branches" => {
131
+ # ["&.", 0, 6, 21, 6, 65] => { [:then, 1, 6, 21, 6, 65] => 0, [:else, 5, 7, 0, 7, 87] => 1 }
132
+ # },
133
+ # "methods" => {
134
+ # ["Foo", :bar, 6, 21, 6, 65] => 0
135
+ # }
136
+ # }
137
+ #: -> Hash[String, statement_coverage]
138
+ def gather_coverage_results
139
+ # Ignore coverage results inside dependencies
140
+ bundle_path = Bundler.bundle_path.to_s
141
+
142
+ result = Coverage.result.reject do |file_path, _coverage_info|
143
+ file_path.start_with?(bundle_path) || !file_path.start_with?(Dir.pwd)
144
+ end
145
+
146
+ result.to_h do |file_path, coverage_info|
147
+ # Format the branch coverage information as VS Code expects it and then group it based on the start line of
148
+ # the conditional that causes the branching. We need to match each line coverage data with the branches that
149
+ # spawn from that line
150
+ branch_by_line = coverage_info[:branches]
151
+ .flat_map do |branch, data|
152
+ branch_name, _branch_id, branch_start_line, _branch_start_col, _branch_end_line, _branch_end_col = branch
153
+
154
+ data.map do |then_or_else, execution_count|
155
+ name, _id, start_line, start_column, end_line, end_column = then_or_else
156
+
157
+ {
158
+ groupingLine: branch_start_line,
159
+ executed: execution_count,
160
+ location: {
161
+ start: { line: start_line, character: start_column },
162
+ end: { line: end_line, character: end_column },
163
+ },
164
+ label: "#{branch_name} #{name}",
165
+ }
166
+ end
167
+ end
168
+ .group_by { |branch| branch[:groupingLine] }
169
+
170
+ # Format the line coverage information, gathering any branch coverage data associated with that line
171
+ data = coverage_info[:lines].filter_map.with_index do |execution_count, line_index|
172
+ next if execution_count.nil?
173
+
174
+ {
175
+ executed: execution_count,
176
+ location: { line: line_index, character: 0 },
177
+ branches: branch_by_line[line_index] || [],
178
+ }
179
+ end
180
+
181
+ # The expected format is URI => { executed: number_of_times_executed, location: { ... }, branches: [ ... ] }
182
+ [URI::Generic.from_path(path: File.expand_path(file_path)).to_s, data]
183
+ end
184
+ end
185
+
186
+ #: -> void
187
+ def at_coverage_exit
188
+ coverage_results = gather_coverage_results
189
+ File.write(File.join(".ruby-lsp", "coverage_result.json"), coverage_results.to_json)
190
+ internal_shutdown
191
+ end
192
+
193
+ #: -> void
194
+ def at_exit
195
+ internal_shutdown unless @invoked_shutdown
196
+ end
197
+
198
+ class << self
199
+ #: -> bool
200
+ def start_coverage?
201
+ ENV["RUBY_LSP_TEST_RUNNER"] == "coverage"
202
+ end
203
+
204
+ #: -> bool
205
+ def executed_under_test_runner?
206
+ !!(ENV["RUBY_LSP_TEST_RUNNER"] && ENV["RUBY_LSP_ENV"] != "test")
207
+ end
208
+ end
209
+
210
+ private
211
+
212
+ #: (String?, **untyped) -> void
213
+ def send_message(method_name, **params)
214
+ json_message = { method: method_name, params: params }.to_json
215
+ @io.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
216
+ end
217
+ end
218
+ end
219
+
220
+ if RubyLsp::LspReporter.start_coverage?
221
+ # Auto start coverage when running tests under that profile. This avoids the user from having to configure coverage
222
+ # manually for their project or adding extra dependencies
223
+ require "coverage"
224
+ Coverage.start(:all)
225
+ end
226
+
227
+ if RubyLsp::LspReporter.executed_under_test_runner?
228
+ at_exit do
229
+ # Regular finish events are registered per test reporter. However, if the test crashes during loading the files
230
+ # (e.g.: a bad require), we need to ensure that the execution is finalized so that the extension is not left hanging
231
+ RubyLsp::LspReporter.instance.at_exit if $ERROR_INFO
232
+ end
233
+ 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