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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -4
- data/exe/ruby-lsp-check +0 -4
- data/exe/ruby-lsp-launcher +45 -22
- data/exe/ruby-lsp-test-exec +6 -0
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -2
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -6
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +82 -116
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +140 -183
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +10 -14
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +107 -236
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +166 -281
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +23 -27
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +25 -57
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +58 -68
- data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
- data/lib/ruby_indexer/test/class_variables_test.rb +14 -14
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +65 -40
- data/lib/ruby_indexer/test/configuration_test.rb +49 -9
- data/lib/ruby_indexer/test/constant_test.rb +34 -34
- data/lib/ruby_indexer/test/enhancements_test.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +185 -135
- data/lib/ruby_indexer/test/instance_variables_test.rb +61 -37
- data/lib/ruby_indexer/test/method_test.rb +166 -123
- data/lib/ruby_indexer/test/prefix_tree_test.rb +21 -21
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +70 -75
- data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
- data/lib/ruby_indexer/test/test_case.rb +9 -3
- data/lib/ruby_indexer/test/uri_test.rb +15 -2
- data/lib/ruby_lsp/addon.rb +88 -86
- data/lib/ruby_lsp/base_server.rb +59 -54
- data/lib/ruby_lsp/client_capabilities.rb +16 -13
- data/lib/ruby_lsp/document.rb +205 -104
- data/lib/ruby_lsp/erb_document.rb +45 -47
- data/lib/ruby_lsp/global_state.rb +73 -57
- data/lib/ruby_lsp/internal.rb +8 -3
- data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
- data/lib/ruby_lsp/listeners/completion.rb +81 -76
- data/lib/ruby_lsp/listeners/definition.rb +44 -58
- data/lib/ruby_lsp/listeners/document_highlight.rb +123 -150
- data/lib/ruby_lsp/listeners/document_link.rb +50 -70
- data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
- data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
- data/lib/ruby_lsp/listeners/hover.rb +107 -115
- data/lib/ruby_lsp/listeners/inlay_hints.rb +8 -13
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
- data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
- data/lib/ruby_lsp/listeners/spec_style.rb +214 -0
- data/lib/ruby_lsp/listeners/test_discovery.rb +92 -0
- data/lib/ruby_lsp/listeners/test_style.rb +205 -95
- data/lib/ruby_lsp/node_context.rb +12 -39
- data/lib/ruby_lsp/rbs_document.rb +10 -11
- data/lib/ruby_lsp/requests/code_action_resolve.rb +65 -61
- data/lib/ruby_lsp/requests/code_actions.rb +14 -26
- data/lib/ruby_lsp/requests/code_lens.rb +31 -21
- data/lib/ruby_lsp/requests/completion.rb +8 -21
- data/lib/ruby_lsp/requests/completion_resolve.rb +6 -6
- data/lib/ruby_lsp/requests/definition.rb +8 -20
- data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
- data/lib/ruby_lsp/requests/discover_tests.rb +20 -7
- data/lib/ruby_lsp/requests/document_highlight.rb +6 -16
- data/lib/ruby_lsp/requests/document_link.rb +6 -17
- data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
- data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
- data/lib/ruby_lsp/requests/formatting.rb +6 -9
- data/lib/ruby_lsp/requests/go_to_relevant_file.rb +85 -0
- data/lib/ruby_lsp/requests/hover.rb +12 -25
- data/lib/ruby_lsp/requests/inlay_hints.rb +8 -19
- data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
- data/lib/ruby_lsp/requests/prepare_rename.rb +5 -10
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
- data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
- data/lib/ruby_lsp/requests/references.rb +17 -57
- data/lib/ruby_lsp/requests/rename.rb +27 -51
- data/lib/ruby_lsp/requests/request.rb +13 -25
- data/lib/ruby_lsp/requests/selection_ranges.rb +7 -7
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
- data/lib/ruby_lsp/requests/signature_help.rb +9 -27
- data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
- data/lib/ruby_lsp/requests/support/common.rb +16 -58
- data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +13 -16
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +34 -36
- data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
- data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
- data/lib/ruby_lsp/requests/support/source_uri.rb +20 -32
- data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
- data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -4
- data/lib/ruby_lsp/response_builders/collection_response_builder.rb +6 -9
- data/lib/ruby_lsp/response_builders/document_symbol.rb +15 -21
- data/lib/ruby_lsp/response_builders/hover.rb +12 -18
- data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +62 -91
- data/lib/ruby_lsp/response_builders/signature_help.rb +6 -8
- data/lib/ruby_lsp/response_builders/test_collection.rb +35 -13
- data/lib/ruby_lsp/ruby_document.rb +32 -98
- data/lib/ruby_lsp/scope.rb +7 -11
- data/lib/ruby_lsp/scripts/compose_bundle.rb +6 -4
- data/lib/ruby_lsp/server.rb +303 -196
- data/lib/ruby_lsp/setup_bundler.rb +121 -82
- data/lib/ruby_lsp/static_docs.rb +12 -7
- data/lib/ruby_lsp/store.rb +21 -49
- data/lib/ruby_lsp/test_helper.rb +3 -16
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +233 -0
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +145 -0
- data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +92 -0
- data/lib/ruby_lsp/type_inferrer.rb +13 -14
- data/lib/ruby_lsp/utils.rb +138 -93
- data/static_docs/break.md +103 -0
- metadata +14 -20
- 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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
172
|
-
|
173
|
-
sig { returns(String) }
|
169
|
+
#: String
|
174
170
|
attr_reader :name
|
175
171
|
|
176
|
-
|
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
|
-
|
178
|
+
#: -> Type
|
183
179
|
def attached
|
184
|
-
Type.new(
|
180
|
+
Type.new(
|
181
|
+
@name.split("::")[..-2] #: as !nil
|
182
|
+
.join("::"),
|
183
|
+
)
|
185
184
|
end
|
186
185
|
end
|
187
186
|
|