ruby-lsp 0.24.1 → 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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp +10 -4
- data/exe/ruby-lsp-check +0 -4
- data/exe/ruby-lsp-launcher +18 -9
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +3 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +2 -2
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +2 -2
- data/lib/ruby_lsp/addon.rb +20 -6
- data/lib/ruby_lsp/base_server.rb +9 -5
- data/lib/ruby_lsp/document.rb +151 -50
- data/lib/ruby_lsp/global_state.rb +21 -0
- data/lib/ruby_lsp/internal.rb +0 -2
- data/lib/ruby_lsp/listeners/hover.rb +7 -0
- data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/code_lens.rb +9 -3
- data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +1 -1
- data/lib/ruby_lsp/requests/request.rb +3 -1
- data/lib/ruby_lsp/requests/support/formatter.rb +9 -3
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -3
- data/lib/ruby_lsp/response_builders/response_builder.rb +3 -3
- data/lib/ruby_lsp/ruby_document.rb +1 -1
- data/lib/ruby_lsp/server.rb +43 -21
- data/lib/ruby_lsp/setup_bundler.rb +48 -22
- data/lib/ruby_lsp/static_docs.rb +1 -0
- data/lib/ruby_lsp/store.rb +0 -10
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +13 -5
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +17 -4
- data/lib/ruby_lsp/utils.rb +44 -5
- data/static_docs/break.md +103 -0
- metadata +2 -16
- data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -365,7 +365,7 @@ module RubyLsp
|
|
365
365
|
end
|
366
366
|
end
|
367
367
|
|
368
|
-
node = node #: as Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode
|
368
|
+
node = node #: as Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode
|
369
369
|
|
370
370
|
node_context = @document.locate_node(
|
371
371
|
{
|
@@ -27,10 +27,16 @@ module RubyLsp
|
|
27
27
|
@document = document
|
28
28
|
@test_builder = ResponseBuilders::TestCollection.new #: ResponseBuilders::TestCollection
|
29
29
|
uri = document.uri
|
30
|
+
file_path = uri.full_path
|
31
|
+
code_lens_config = global_state.feature_configuration(:codeLens)
|
32
|
+
test_lenses_enabled = (!code_lens_config || code_lens_config.enabled?(:enableTestCodeLens)) &&
|
33
|
+
file_path && File.fnmatch?(TEST_PATH_PATTERN, file_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
30
34
|
|
31
35
|
if global_state.enabled_feature?(:fullTestDiscovery)
|
32
|
-
|
33
|
-
|
36
|
+
if test_lenses_enabled
|
37
|
+
Listeners::TestStyle.new(@test_builder, global_state, dispatcher, uri)
|
38
|
+
Listeners::SpecStyle.new(@test_builder, global_state, dispatcher, uri)
|
39
|
+
end
|
34
40
|
else
|
35
41
|
Listeners::CodeLens.new(@response_builder, global_state, uri, dispatcher)
|
36
42
|
end
|
@@ -38,7 +44,7 @@ module RubyLsp
|
|
38
44
|
Addon.addons.each do |addon|
|
39
45
|
addon.create_code_lens_listener(@response_builder, uri, dispatcher)
|
40
46
|
|
41
|
-
if global_state.enabled_feature?(:fullTestDiscovery)
|
47
|
+
if global_state.enabled_feature?(:fullTestDiscovery) && test_lenses_enabled
|
42
48
|
addon.create_discover_tests_listener(@test_builder, dispatcher, uri)
|
43
49
|
end
|
44
50
|
end
|
@@ -16,13 +16,13 @@ module RubyLsp
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
#: ((RubyDocument | ERBDocument)
|
20
|
-
def initialize(
|
19
|
+
#: (GlobalState, (RubyDocument | ERBDocument), Prism::Dispatcher) -> void
|
20
|
+
def initialize(global_state, document, dispatcher)
|
21
21
|
super()
|
22
22
|
|
23
23
|
@response_builder = ResponseBuilders::CollectionResponseBuilder
|
24
24
|
.new #: ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint]
|
25
|
-
Listeners::InlayHints.new(@response_builder,
|
25
|
+
Listeners::InlayHints.new(global_state, @response_builder, dispatcher)
|
26
26
|
end
|
27
27
|
|
28
28
|
# @override
|
@@ -162,7 +162,7 @@ module RubyLsp
|
|
162
162
|
|
163
163
|
#: (Integer line, Integer character) -> void
|
164
164
|
def move_cursor_to(line, character)
|
165
|
-
return unless /Visual Studio Code|Cursor|VSCodium/.match?(@client_name)
|
165
|
+
return unless /Visual Studio Code|Cursor|VSCodium|Windsurf/.match?(@client_name)
|
166
166
|
|
167
167
|
position = Interface::Position.new(
|
168
168
|
line: line,
|
@@ -55,7 +55,7 @@ module RubyLsp
|
|
55
55
|
)
|
56
56
|
end
|
57
57
|
|
58
|
-
target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode,
|
58
|
+
target = target #: as Prism::ConstantReadNode | Prism::ConstantPathNode | Prism::ConstantPathTargetNode | Prism::InstanceVariableAndWriteNode | Prism::InstanceVariableOperatorWriteNode | Prism::InstanceVariableOrWriteNode | Prism::InstanceVariableReadNode | Prism::InstanceVariableTargetNode | Prism::InstanceVariableWriteNode | Prism::CallNode | Prism::DefNode,
|
59
59
|
|
60
60
|
reference_target = create_reference_target(target, node_context)
|
61
61
|
return @locations unless reference_target
|
@@ -9,15 +9,21 @@ module RubyLsp
|
|
9
9
|
module Formatter
|
10
10
|
# @abstract
|
11
11
|
#: (URI::Generic, RubyLsp::RubyDocument) -> String?
|
12
|
-
def run_formatting(uri, document)
|
12
|
+
def run_formatting(uri, document)
|
13
|
+
raise AbstractMethodInvokedError
|
14
|
+
end
|
13
15
|
|
14
16
|
# @abstract
|
15
17
|
#: (URI::Generic, String, Integer) -> String?
|
16
|
-
def run_range_formatting(uri, source, base_indentation)
|
18
|
+
def run_range_formatting(uri, source, base_indentation)
|
19
|
+
raise AbstractMethodInvokedError
|
20
|
+
end
|
17
21
|
|
18
22
|
# @abstract
|
19
23
|
#: (URI::Generic, RubyLsp::RubyDocument) -> Array[Interface::Diagnostic]?
|
20
|
-
def run_diagnostic(uri, document)
|
24
|
+
def run_diagnostic(uri, document)
|
25
|
+
raise AbstractMethodInvokedError
|
26
|
+
end
|
21
27
|
end
|
22
28
|
end
|
23
29
|
end
|
@@ -24,7 +24,7 @@ module RubyLsp
|
|
24
24
|
filename = uri.to_standardized_path || uri.opaque #: as !nil
|
25
25
|
|
26
26
|
# Invoke RuboCop with just this file in `paths`
|
27
|
-
@format_runner.run(filename, document.source)
|
27
|
+
@format_runner.run(filename, document.source, document.parse_result)
|
28
28
|
@format_runner.formatted_source
|
29
29
|
end
|
30
30
|
|
@@ -40,7 +40,7 @@ module RubyLsp
|
|
40
40
|
def run_diagnostic(uri, document)
|
41
41
|
filename = uri.to_standardized_path || uri.opaque #: as !nil
|
42
42
|
# Invoke RuboCop with just this file in `paths`
|
43
|
-
@diagnostic_runner.run(filename, document.source)
|
43
|
+
@diagnostic_runner.run(filename, document.source, document.parse_result)
|
44
44
|
|
45
45
|
@diagnostic_runner.offenses.map do |offense|
|
46
46
|
Support::RuboCopDiagnostic.new(
|
@@ -81,6 +81,7 @@ module RubyLsp
|
|
81
81
|
@offenses = [] #: Array[::RuboCop::Cop::Offense]
|
82
82
|
@errors = [] #: Array[String]
|
83
83
|
@warnings = [] #: Array[String]
|
84
|
+
@prism_result = nil #: Prism::ParseLexResult?
|
84
85
|
|
85
86
|
args += DEFAULT_ARGS
|
86
87
|
rubocop_options = ::RuboCop::Options.new.parse(args).first
|
@@ -92,14 +93,15 @@ module RubyLsp
|
|
92
93
|
super(rubocop_options, config_store)
|
93
94
|
end
|
94
95
|
|
95
|
-
#: (String
|
96
|
-
def run(path, contents)
|
96
|
+
#: (String, String, Prism::ParseLexResult) -> void
|
97
|
+
def run(path, contents, prism_result)
|
97
98
|
# Clear Runner state between runs since we get a single instance of this class
|
98
99
|
# on every use site.
|
99
100
|
@errors = []
|
100
101
|
@warnings = []
|
101
102
|
@offenses = []
|
102
103
|
@options[:stdin] = contents
|
104
|
+
@prism_result = prism_result
|
103
105
|
|
104
106
|
super([path])
|
105
107
|
|
@@ -111,7 +113,11 @@ module RubyLsp
|
|
111
113
|
rescue ::RuboCop::ValidationError => error
|
112
114
|
raise ConfigurationError, error.message
|
113
115
|
rescue StandardError => error
|
114
|
-
|
116
|
+
# Maintain the original backtrace so that debugging cops that are breaking is easier, but re-raise as a
|
117
|
+
# different error class
|
118
|
+
internal_error = InternalRuboCopError.new(error)
|
119
|
+
internal_error.set_backtrace(error.backtrace)
|
120
|
+
raise internal_error
|
115
121
|
end
|
116
122
|
|
117
123
|
#: -> String
|
@@ -26,7 +26,7 @@ module RubyLsp
|
|
26
26
|
queue = node.child_nodes.compact #: Array[Prism::Node?]
|
27
27
|
closest = node
|
28
28
|
parent = nil #: Prism::Node?
|
29
|
-
nesting_nodes = [] #: Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)]
|
29
|
+
nesting_nodes = [] #: Array[(Prism::ClassNode | Prism::ModuleNode | Prism::SingletonClassNode | Prism::DefNode | Prism::BlockNode | Prism::LambdaNode | Prism::ProgramNode)]
|
30
30
|
|
31
31
|
nesting_nodes << node if node.is_a?(Prism::ProgramNode)
|
32
32
|
call_node = nil #: Prism::CallNode?
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -94,7 +94,13 @@ module RubyLsp
|
|
94
94
|
id: message[:id],
|
95
95
|
response:
|
96
96
|
Addon.addons.map do |addon|
|
97
|
-
|
97
|
+
version = begin
|
98
|
+
addon.version
|
99
|
+
rescue AbstractMethodInvokedError
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
{ name: addon.name, version: version, errored: addon.error? }
|
98
104
|
end,
|
99
105
|
),
|
100
106
|
)
|
@@ -115,7 +121,7 @@ module RubyLsp
|
|
115
121
|
end
|
116
122
|
rescue DelegateRequestError
|
117
123
|
send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
|
118
|
-
rescue StandardError, LoadError => e
|
124
|
+
rescue StandardError, LoadError, SystemExit => e
|
119
125
|
# If an error occurred in a request, we have to return an error response or else the editor will hang
|
120
126
|
if message[:id]
|
121
127
|
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
|
@@ -203,10 +209,6 @@ module RubyLsp
|
|
203
209
|
|
204
210
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
205
211
|
|
206
|
-
configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
|
207
|
-
@store.features_configuration.dig(:inlayHint) #: as !nil
|
208
|
-
.configuration.merge!(configured_hints) if configured_hints
|
209
|
-
|
210
212
|
enabled_features = case configured_features
|
211
213
|
when Array
|
212
214
|
# If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
|
@@ -360,6 +362,7 @@ module RubyLsp
|
|
360
362
|
|
361
363
|
perform_initial_indexing
|
362
364
|
check_formatter_is_available
|
365
|
+
update_server if @global_state.enabled_feature?(:launcher)
|
363
366
|
end
|
364
367
|
|
365
368
|
#: (Hash[Symbol, untyped] message) -> void
|
@@ -473,8 +476,8 @@ module RubyLsp
|
|
473
476
|
document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
|
474
477
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
475
478
|
inlay_hint = Requests::InlayHints.new(
|
479
|
+
@global_state,
|
476
480
|
document,
|
477
|
-
@store.features_configuration.dig(:inlayHint), #: as !nil
|
478
481
|
dispatcher,
|
479
482
|
)
|
480
483
|
|
@@ -821,7 +824,6 @@ module RubyLsp
|
|
821
824
|
return
|
822
825
|
end
|
823
826
|
|
824
|
-
hints_configurations = @store.features_configuration.dig(:inlayHint) #: as !nil
|
825
827
|
dispatcher = Prism::Dispatcher.new
|
826
828
|
|
827
829
|
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
@@ -829,7 +831,7 @@ module RubyLsp
|
|
829
831
|
return
|
830
832
|
end
|
831
833
|
|
832
|
-
request = Requests::InlayHints.new(
|
834
|
+
request = Requests::InlayHints.new(@global_state, document, dispatcher)
|
833
835
|
dispatcher.visit(document.ast)
|
834
836
|
result = request.perform
|
835
837
|
document.cache_set("textDocument/inlayHint", result)
|
@@ -1405,8 +1407,38 @@ module RubyLsp
|
|
1405
1407
|
|
1406
1408
|
# We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
|
1407
1409
|
# we return the response back to the editor, then the restart is triggered
|
1410
|
+
launch_bundle_compose("Recomposing the bundle ahead of restart") do |stderr, status|
|
1411
|
+
if status&.exitstatus == 0
|
1412
|
+
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
1413
|
+
FileUtils.touch(already_composed_path)
|
1414
|
+
send_message(Result.new(id: id, response: { success: true }))
|
1415
|
+
else
|
1416
|
+
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1417
|
+
# bundle is not valid
|
1418
|
+
send_message(
|
1419
|
+
Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
|
1420
|
+
)
|
1421
|
+
end
|
1422
|
+
end
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
#: -> void
|
1426
|
+
def update_server
|
1427
|
+
return unless File.exist?(File.join(@global_state.workspace_path, ".ruby-lsp", "needs_update"))
|
1428
|
+
|
1429
|
+
launch_bundle_compose("Trying to update server") do |stderr, status|
|
1430
|
+
if status&.exitstatus == 0
|
1431
|
+
send_log_message("Successfully updated the server")
|
1432
|
+
else
|
1433
|
+
send_log_message("Failed to update server\n#{stderr}", type: Constant::MessageType::ERROR)
|
1434
|
+
end
|
1435
|
+
end
|
1436
|
+
end
|
1437
|
+
|
1438
|
+
#: (String) { (IO, Process::Status?) -> void } -> Thread
|
1439
|
+
def launch_bundle_compose(log, &block)
|
1408
1440
|
Thread.new do
|
1409
|
-
send_log_message(
|
1441
|
+
send_log_message(log)
|
1410
1442
|
|
1411
1443
|
_stdout, stderr, status = Bundler.with_unbundled_env do
|
1412
1444
|
Open3.capture3(
|
@@ -1421,17 +1453,7 @@ module RubyLsp
|
|
1421
1453
|
)
|
1422
1454
|
end
|
1423
1455
|
|
1424
|
-
|
1425
|
-
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
1426
|
-
FileUtils.touch(already_composed_path)
|
1427
|
-
send_message(Result.new(id: id, response: { success: true }))
|
1428
|
-
else
|
1429
|
-
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1430
|
-
# bundle is not valid
|
1431
|
-
send_message(
|
1432
|
-
Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
|
1433
|
-
)
|
1434
|
-
end
|
1456
|
+
block.call(stderr, status)
|
1435
1457
|
end
|
1436
1458
|
end
|
1437
1459
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require "sorbet-runtime"
|
5
4
|
require "bundler"
|
6
5
|
require "bundler/cli"
|
7
6
|
require "bundler/cli/install"
|
@@ -20,11 +19,16 @@ Bundler.ui.level = :silent
|
|
20
19
|
|
21
20
|
module RubyLsp
|
22
21
|
class SetupBundler
|
23
|
-
extend T::Sig
|
24
|
-
|
25
22
|
class BundleNotLocked < StandardError; end
|
26
23
|
class BundleInstallFailure < StandardError; end
|
27
24
|
|
25
|
+
module ThorPatch
|
26
|
+
#: -> IO
|
27
|
+
def stdout
|
28
|
+
$stderr
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
28
32
|
FOUR_HOURS = 4 * 60 * 60 #: Integer
|
29
33
|
|
30
34
|
#: (String project_path, **untyped options) -> void
|
@@ -61,6 +65,7 @@ module RubyLsp
|
|
61
65
|
@bundler_version = bundler_version #: Gem::Version?
|
62
66
|
@rails_app = rails_app? #: bool
|
63
67
|
@retry = false #: bool
|
68
|
+
@needs_update_path = @custom_dir + "needs_update" #: Pathname
|
64
69
|
end
|
65
70
|
|
66
71
|
# Sets up the composed bundle and returns the `BUNDLE_GEMFILE`, `BUNDLE_PATH` and `BUNDLE_APP_CONFIG` that should be
|
@@ -256,19 +261,50 @@ module RubyLsp
|
|
256
261
|
#: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
|
257
262
|
def run_bundle_install_directly(env, force_install: false)
|
258
263
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
264
|
+
return update(env) if @needs_update_path.exist?
|
259
265
|
|
260
266
|
# The ENV can only be merged after checking if an update is required because we depend on the original value of
|
261
267
|
# ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
|
262
|
-
|
263
|
-
ENV
|
264
|
-
.merge!(env)
|
268
|
+
FileUtils.touch(@needs_update_path) if should_bundle_update?
|
269
|
+
ENV.merge!(env)
|
265
270
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
271
|
+
$stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
|
272
|
+
missing_gems = bundle_check
|
273
|
+
|
274
|
+
unless missing_gems.empty?
|
275
|
+
$stderr.puts(<<~MESSAGE)
|
276
|
+
Ruby LSP> Running bundle install because the following gems are not installed:
|
277
|
+
#{missing_gems.map { |g| "#{g.name}: #{g.version}" }.join("\n")}
|
278
|
+
MESSAGE
|
279
|
+
|
280
|
+
bundle_install
|
270
281
|
end
|
271
282
|
|
283
|
+
$stderr.puts("Ruby LSP> Bundle already satisfied")
|
284
|
+
env
|
285
|
+
rescue => e
|
286
|
+
$stderr.puts("Ruby LSP> Running bundle install because #{e.message}")
|
287
|
+
bundle_install
|
288
|
+
env
|
289
|
+
end
|
290
|
+
|
291
|
+
# Essentially the same as bundle check, but simplified
|
292
|
+
#: -> Array[Gem::Specification]
|
293
|
+
def bundle_check
|
294
|
+
definition = Bundler.definition
|
295
|
+
definition.validate_runtime!
|
296
|
+
definition.check!
|
297
|
+
definition.missing_specs
|
298
|
+
end
|
299
|
+
|
300
|
+
#: -> void
|
301
|
+
def bundle_install
|
302
|
+
Bundler::CLI::Install.new({ "no-cache" => true }).run
|
303
|
+
correct_relative_remote_paths if @custom_lockfile.exist?
|
304
|
+
end
|
305
|
+
|
306
|
+
#: (Hash[String, String]) -> Hash[String, String]
|
307
|
+
def update(env)
|
272
308
|
# Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
|
273
309
|
# source control changes
|
274
310
|
gems = ["ruby-lsp", "debug", "prism"].reject { |dep| @dependencies[dep] }
|
@@ -276,11 +312,9 @@ module RubyLsp
|
|
276
312
|
|
277
313
|
Bundler::CLI::Update.new({ conservative: true }, gems).run
|
278
314
|
correct_relative_remote_paths if @custom_lockfile.exist?
|
315
|
+
@needs_update_path.delete
|
279
316
|
@last_updated_path.write(Time.now.iso8601)
|
280
317
|
env
|
281
|
-
rescue Bundler::GemNotFound, Bundler::GitError
|
282
|
-
# If a gem is not installed, skip the upgrade and try to install it with a single retry
|
283
|
-
@retry ? env : run_bundle_install_directly(env, force_install: true)
|
284
318
|
end
|
285
319
|
|
286
320
|
#: (Hash[String, String] env) -> Hash[String, String]
|
@@ -440,15 +474,7 @@ module RubyLsp
|
|
440
474
|
def patch_thor_to_print_progress_to_stderr!
|
441
475
|
return unless defined?(Bundler::Thor::Shell::Basic)
|
442
476
|
|
443
|
-
Bundler::Thor::Shell::Basic.prepend(
|
444
|
-
extend T::Sig
|
445
|
-
|
446
|
-
sig { returns(IO) }
|
447
|
-
def stdout
|
448
|
-
$stderr
|
449
|
-
end
|
450
|
-
end)
|
451
|
-
|
477
|
+
Bundler::Thor::Shell::Basic.prepend(ThorPatch)
|
452
478
|
Bundler.ui.level = :info
|
453
479
|
end
|
454
480
|
end
|
data/lib/ruby_lsp/static_docs.rb
CHANGED
@@ -14,6 +14,7 @@ module RubyLsp
|
|
14
14
|
|
15
15
|
# A map of keyword => short documentation to be displayed on hover or completion
|
16
16
|
KEYWORD_DOCS = {
|
17
|
+
"break" => "Terminates the execution of a block or loop",
|
17
18
|
"yield" => "Invokes the passed block with the given arguments",
|
18
19
|
}.freeze #: Hash[String, String]
|
19
20
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -5,9 +5,6 @@ module RubyLsp
|
|
5
5
|
class Store
|
6
6
|
class NonExistingDocumentError < StandardError; end
|
7
7
|
|
8
|
-
#: Hash[Symbol, RequestConfig]
|
9
|
-
attr_accessor :features_configuration
|
10
|
-
|
11
8
|
#: String
|
12
9
|
attr_accessor :client_name
|
13
10
|
|
@@ -15,13 +12,6 @@ module RubyLsp
|
|
15
12
|
def initialize(global_state)
|
16
13
|
@global_state = global_state
|
17
14
|
@state = {} #: Hash[String, Document[untyped]]
|
18
|
-
@features_configuration = {
|
19
|
-
inlayHint: RequestConfig.new({
|
20
|
-
enableAll: false,
|
21
|
-
implicitRescue: false,
|
22
|
-
implicitHashValue: false,
|
23
|
-
}),
|
24
|
-
} #: Hash[Symbol, RequestConfig]
|
25
15
|
@client_name = "Unknown" #: String
|
26
16
|
end
|
27
17
|
|
@@ -12,6 +12,18 @@ module RubyLsp
|
|
12
12
|
class LspReporter
|
13
13
|
include Singleton
|
14
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
|
+
|
15
27
|
#: bool
|
16
28
|
attr_reader :invoked_shutdown
|
17
29
|
|
@@ -20,8 +32,6 @@ module RubyLsp
|
|
20
32
|
dir_path = File.join(Dir.tmpdir, "ruby-lsp")
|
21
33
|
FileUtils.mkdir_p(dir_path)
|
22
34
|
|
23
|
-
# Remove in 1 month once updates have rolled out
|
24
|
-
legacy_port_path = File.join(dir_path, "test_reporter_port")
|
25
35
|
port_db_path = File.join(dir_path, "test_reporter_port_db.json")
|
26
36
|
port = ENV["RUBY_LSP_REPORTER_PORT"]
|
27
37
|
|
@@ -32,8 +42,6 @@ module RubyLsp
|
|
32
42
|
elsif File.exist?(port_db_path)
|
33
43
|
db = JSON.load_file(port_db_path)
|
34
44
|
TCPSocket.new("localhost", db[Dir.pwd])
|
35
|
-
elsif File.exist?(legacy_port_path)
|
36
|
-
TCPSocket.new("localhost", File.read(legacy_port_path))
|
37
45
|
else
|
38
46
|
# For tests that don't spawn the TCP server
|
39
47
|
require "stringio"
|
@@ -129,7 +137,7 @@ module RubyLsp
|
|
129
137
|
# ["Foo", :bar, 6, 21, 6, 65] => 0
|
130
138
|
# }
|
131
139
|
# }
|
132
|
-
#: -> Hash[String,
|
140
|
+
#: -> Hash[String, statement_coverage]
|
133
141
|
def gather_coverage_results
|
134
142
|
# Ignore coverage results inside dependencies
|
135
143
|
bundle_path = Bundler.bundle_path.to_s
|
@@ -74,12 +74,25 @@ module RubyLsp
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
#: (
|
78
|
-
def prerecord(
|
79
|
-
|
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))
|
80
93
|
return unless uri
|
81
94
|
|
82
|
-
id = "#{
|
95
|
+
id = "#{name}##{handle_spec_test_id(method_name, line)}"
|
83
96
|
LspReporter.instance.start_test(id: id, uri: uri, line: line)
|
84
97
|
end
|
85
98
|
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -5,7 +5,6 @@ module RubyLsp
|
|
5
5
|
# rubocop:disable RubyLsp/UseLanguageServerAliases
|
6
6
|
Interface = LanguageServer::Protocol::Interface
|
7
7
|
Constant = LanguageServer::Protocol::Constant
|
8
|
-
Transport = LanguageServer::Protocol::Transport
|
9
8
|
# rubocop:enable RubyLsp/UseLanguageServerAliases
|
10
9
|
|
11
10
|
# Used to indicate that a request shouldn't return a response
|
@@ -20,6 +19,7 @@ module RubyLsp
|
|
20
19
|
"Gemfile"
|
21
20
|
end #: String
|
22
21
|
GUESSED_TYPES_URL = "https://shopify.github.io/ruby-lsp/#guessed-types"
|
22
|
+
TEST_PATH_PATTERN = "**/{test,spec,features}/**/{*_test.rb,test_*.rb,*_spec.rb,*.feature}"
|
23
23
|
|
24
24
|
# Request delegation for embedded languages is not yet standardized into the language server specification. Here we
|
25
25
|
# use this custom error class as a way to return a signal to the client that the request should be delegated to the
|
@@ -31,6 +31,8 @@ module RubyLsp
|
|
31
31
|
CODE = -32900
|
32
32
|
end
|
33
33
|
|
34
|
+
class AbstractMethodInvokedError < StandardError; end
|
35
|
+
|
34
36
|
BUNDLE_COMPOSE_FAILED_CODE = -33000
|
35
37
|
|
36
38
|
# A notification to be sent to the client
|
@@ -50,7 +52,9 @@ module RubyLsp
|
|
50
52
|
|
51
53
|
# @abstract
|
52
54
|
#: -> Hash[Symbol, untyped]
|
53
|
-
def to_hash
|
55
|
+
def to_hash
|
56
|
+
raise AbstractMethodInvokedError
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
56
60
|
class Notification < Message
|
@@ -243,9 +247,6 @@ module RubyLsp
|
|
243
247
|
|
244
248
|
# A request configuration, to turn on/off features
|
245
249
|
class RequestConfig
|
246
|
-
#: Hash[Symbol, bool]
|
247
|
-
attr_accessor :configuration
|
248
|
-
|
249
250
|
#: (Hash[Symbol, bool] configuration) -> void
|
250
251
|
def initialize(configuration)
|
251
252
|
@configuration = configuration
|
@@ -255,6 +256,11 @@ module RubyLsp
|
|
255
256
|
def enabled?(feature)
|
256
257
|
@configuration[:enableAll] || @configuration[feature]
|
257
258
|
end
|
259
|
+
|
260
|
+
#: (Hash[Symbol, bool]) -> void
|
261
|
+
def merge!(hash)
|
262
|
+
@configuration.merge!(hash)
|
263
|
+
end
|
258
264
|
end
|
259
265
|
|
260
266
|
class SorbetLevel
|
@@ -299,4 +305,37 @@ module RubyLsp
|
|
299
305
|
#: -> bool
|
300
306
|
def true_or_higher? = @level == :true || @level == :strict
|
301
307
|
end
|
308
|
+
|
309
|
+
# Reads JSON RPC messages from the given IO in a loop
|
310
|
+
class MessageReader
|
311
|
+
#: (IO) -> void
|
312
|
+
def initialize(io)
|
313
|
+
@io = io
|
314
|
+
end
|
315
|
+
|
316
|
+
#: () { (Hash[Symbol, untyped]) -> void } -> void
|
317
|
+
def each_message(&block)
|
318
|
+
while (headers = @io.gets("\r\n\r\n"))
|
319
|
+
raw_message = @io.read(headers[/Content-Length: (\d+)/i, 1].to_i) #: as !nil
|
320
|
+
block.call(JSON.parse(raw_message, symbolize_names: true))
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Writes JSON RPC messages to the given IO
|
326
|
+
class MessageWriter
|
327
|
+
#: (IO) -> void
|
328
|
+
def initialize(io)
|
329
|
+
@io = io
|
330
|
+
end
|
331
|
+
|
332
|
+
#: (Hash[Symbol, untyped]) -> void
|
333
|
+
def write(message)
|
334
|
+
message[:jsonrpc] = "2.0"
|
335
|
+
json_message = message.to_json
|
336
|
+
|
337
|
+
@io.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
338
|
+
@io.flush
|
339
|
+
end
|
340
|
+
end
|
302
341
|
end
|