ruby-lsp 0.23.21 → 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 +25 -11
- 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/declaration_listener.rb +7 -1
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -4
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +9 -19
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +18 -7
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +24 -0
- data/lib/ruby_indexer/test/instance_variables_test.rb +24 -0
- data/lib/ruby_indexer/test/method_test.rb +17 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +2 -2
- data/lib/ruby_indexer/test/reference_finder_test.rb +79 -14
- data/lib/ruby_lsp/addon.rb +29 -15
- data/lib/ruby_lsp/base_server.rb +14 -12
- data/lib/ruby_lsp/document.rb +158 -46
- data/lib/ruby_lsp/erb_document.rb +8 -3
- data/lib/ruby_lsp/global_state.rb +21 -0
- data/lib/ruby_lsp/internal.rb +0 -2
- data/lib/ruby_lsp/listeners/completion.rb +9 -1
- data/lib/ruby_lsp/listeners/hover.rb +7 -0
- data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
- data/lib/ruby_lsp/listeners/spec_style.rb +63 -20
- data/lib/ruby_lsp/listeners/test_discovery.rb +18 -15
- data/lib/ruby_lsp/listeners/test_style.rb +21 -10
- data/lib/ruby_lsp/requests/code_action_resolve.rb +3 -3
- data/lib/ruby_lsp/requests/code_lens.rb +14 -5
- data/lib/ruby_lsp/requests/completion.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- 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/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +10 -6
- data/lib/ruby_lsp/requests/rename.rb +8 -6
- data/lib/ruby_lsp/requests/request.rb +6 -7
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -1
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/common.rb +1 -3
- data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
- 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 +6 -8
- data/lib/ruby_lsp/ruby_document.rb +10 -5
- data/lib/ruby_lsp/server.rb +50 -49
- data/lib/ruby_lsp/setup_bundler.rb +51 -25
- data/lib/ruby_lsp/static_docs.rb +1 -0
- data/lib/ruby_lsp/store.rb +0 -10
- data/lib/ruby_lsp/test_helper.rb +1 -4
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +13 -5
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +43 -4
- data/lib/ruby_lsp/utils.rb +47 -11
- data/static_docs/break.md +103 -0
- metadata +4 -18
- data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -4,25 +4,26 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module Requests
|
6
6
|
module Support
|
7
|
+
# Empty module to avoid the runtime component. This is an interface defined in sorbet/rbi/shims/ruby_lsp.rbi
|
8
|
+
# @interface
|
7
9
|
module Formatter
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
sig { abstract.params(uri: URI::Generic, document: RubyDocument).returns(T.nilable(String)) }
|
14
|
-
def run_formatting(uri, document); end
|
10
|
+
# @abstract
|
11
|
+
#: (URI::Generic, RubyLsp::RubyDocument) -> String?
|
12
|
+
def run_formatting(uri, document)
|
13
|
+
raise AbstractMethodInvokedError
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
# @abstract
|
17
|
+
#: (URI::Generic, String, Integer) -> String?
|
18
|
+
def run_range_formatting(uri, source, base_indentation)
|
19
|
+
raise AbstractMethodInvokedError
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
).returns(T.nilable(T::Array[Interface::Diagnostic]))
|
22
|
+
# @abstract
|
23
|
+
#: (URI::Generic, RubyLsp::RubyDocument) -> Array[Interface::Diagnostic]?
|
24
|
+
def run_diagnostic(uri, document)
|
25
|
+
raise AbstractMethodInvokedError
|
24
26
|
end
|
25
|
-
def run_diagnostic(uri, document); end
|
26
27
|
end
|
27
28
|
end
|
28
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
|
@@ -3,15 +3,13 @@
|
|
3
3
|
|
4
4
|
module RubyLsp
|
5
5
|
module ResponseBuilders
|
6
|
+
# @abstract
|
6
7
|
class ResponseBuilder
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
sig { abstract.returns(T.anything) }
|
14
|
-
def response; end
|
8
|
+
# @abstract
|
9
|
+
#: -> top
|
10
|
+
def response
|
11
|
+
raise AbstractMethodInvokedError
|
12
|
+
end
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyLsp
|
5
|
-
#: [ParseResultType = Prism::
|
5
|
+
#: [ParseResultType = Prism::ParseLexResult]
|
6
6
|
class RubyDocument < Document
|
7
7
|
METHODS_THAT_CHANGE_DECLARATIONS = [
|
8
8
|
:private_constant,
|
@@ -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?
|
@@ -129,11 +129,16 @@ module RubyLsp
|
|
129
129
|
return false unless @needs_parsing
|
130
130
|
|
131
131
|
@needs_parsing = false
|
132
|
-
@parse_result = Prism.
|
132
|
+
@parse_result = Prism.parse_lex(@source)
|
133
133
|
@code_units_cache = @parse_result.code_units_cache(@encoding)
|
134
134
|
true
|
135
135
|
end
|
136
136
|
|
137
|
+
#: -> Prism::ProgramNode
|
138
|
+
def ast
|
139
|
+
@parse_result.value.first
|
140
|
+
end
|
141
|
+
|
137
142
|
# @override
|
138
143
|
#: -> bool
|
139
144
|
def syntax_error?
|
@@ -151,7 +156,7 @@ module RubyLsp
|
|
151
156
|
start_position, end_position = find_index_by_position(range[:start], range[:end])
|
152
157
|
|
153
158
|
desired_range = (start_position...end_position)
|
154
|
-
queue =
|
159
|
+
queue = ast.child_nodes.compact #: Array[Prism::Node?]
|
155
160
|
|
156
161
|
until queue.empty?
|
157
162
|
candidate = queue.shift
|
@@ -179,7 +184,7 @@ module RubyLsp
|
|
179
184
|
char_position, _ = find_index_by_position(position)
|
180
185
|
|
181
186
|
RubyDocument.locate(
|
182
|
-
|
187
|
+
ast,
|
183
188
|
char_position,
|
184
189
|
code_units_cache: @code_units_cache,
|
185
190
|
node_types: node_types,
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -94,13 +94,10 @@ module RubyLsp
|
|
94
94
|
id: message[:id],
|
95
95
|
response:
|
96
96
|
Addon.addons.map do |addon|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
# Therefore, we only call the method if it's defined by the add-on itself
|
102
|
-
if version_method.owner != Addon
|
103
|
-
version = addon.version
|
97
|
+
version = begin
|
98
|
+
addon.version
|
99
|
+
rescue AbstractMethodInvokedError
|
100
|
+
nil
|
104
101
|
end
|
105
102
|
|
106
103
|
{ name: addon.name, version: version, errored: addon.error? }
|
@@ -124,30 +121,18 @@ module RubyLsp
|
|
124
121
|
end
|
125
122
|
rescue DelegateRequestError
|
126
123
|
send_message(Error.new(id: message[:id], code: DelegateRequestError::CODE, message: "DELEGATE_REQUEST"))
|
127
|
-
rescue StandardError, LoadError => e
|
124
|
+
rescue StandardError, LoadError, SystemExit => e
|
128
125
|
# If an error occurred in a request, we have to return an error response or else the editor will hang
|
129
126
|
if message[:id]
|
130
127
|
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
|
131
128
|
# from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
|
132
129
|
# reporting these to our telemetry
|
133
|
-
|
134
|
-
when Store::NonExistingDocumentError
|
130
|
+
if e.is_a?(Store::NonExistingDocumentError)
|
135
131
|
send_message(Error.new(
|
136
132
|
id: message[:id],
|
137
133
|
code: Constant::ErrorCodes::INVALID_PARAMS,
|
138
134
|
message: e.full_message,
|
139
135
|
))
|
140
|
-
when Document::LocationNotFoundError
|
141
|
-
send_message(Error.new(
|
142
|
-
id: message[:id],
|
143
|
-
code: Constant::ErrorCodes::REQUEST_FAILED,
|
144
|
-
message: <<~MESSAGE,
|
145
|
-
Request #{message[:method]} failed to find the target position.
|
146
|
-
The file might have been modified while the server was in the middle of searching for the target.
|
147
|
-
If you experience this regularly, please report any findings and extra information on
|
148
|
-
https://github.com/Shopify/ruby-lsp/issues/2446
|
149
|
-
MESSAGE
|
150
|
-
))
|
151
136
|
else
|
152
137
|
send_message(Error.new(
|
153
138
|
id: message[:id],
|
@@ -224,10 +209,6 @@ module RubyLsp
|
|
224
209
|
|
225
210
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
226
211
|
|
227
|
-
configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
|
228
|
-
@store.features_configuration.dig(:inlayHint) #: as !nil
|
229
|
-
.configuration.merge!(configured_hints) if configured_hints
|
230
|
-
|
231
212
|
enabled_features = case configured_features
|
232
213
|
when Array
|
233
214
|
# If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
|
@@ -381,6 +362,7 @@ module RubyLsp
|
|
381
362
|
|
382
363
|
perform_initial_indexing
|
383
364
|
check_formatter_is_available
|
365
|
+
update_server if @global_state.enabled_feature?(:launcher)
|
384
366
|
end
|
385
367
|
|
386
368
|
#: (Hash[Symbol, untyped] message) -> void
|
@@ -494,8 +476,8 @@ module RubyLsp
|
|
494
476
|
document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
|
495
477
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
496
478
|
inlay_hint = Requests::InlayHints.new(
|
479
|
+
@global_state,
|
497
480
|
document,
|
498
|
-
@store.features_configuration.dig(:inlayHint), #: as !nil
|
499
481
|
dispatcher,
|
500
482
|
)
|
501
483
|
|
@@ -512,13 +494,13 @@ module RubyLsp
|
|
512
494
|
@global_state.index.handle_change(uri) do |index|
|
513
495
|
index.delete(uri, skip_require_paths_tree: true)
|
514
496
|
RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
|
515
|
-
code_lens = Requests::CodeLens.new(@global_state,
|
516
|
-
dispatcher.dispatch(
|
497
|
+
code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
|
498
|
+
dispatcher.dispatch(document.ast)
|
517
499
|
end
|
518
500
|
end
|
519
501
|
else
|
520
|
-
code_lens = Requests::CodeLens.new(@global_state,
|
521
|
-
dispatcher.dispatch(
|
502
|
+
code_lens = Requests::CodeLens.new(@global_state, document, dispatcher)
|
503
|
+
dispatcher.dispatch(document.ast)
|
522
504
|
end
|
523
505
|
|
524
506
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
@@ -557,7 +539,7 @@ module RubyLsp
|
|
557
539
|
|
558
540
|
dispatcher = Prism::Dispatcher.new
|
559
541
|
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher, document, nil)
|
560
|
-
dispatcher.visit(document.
|
542
|
+
dispatcher.visit(document.ast)
|
561
543
|
|
562
544
|
send_message(Result.new(id: message[:id], response: semantic_highlighting.perform))
|
563
545
|
end
|
@@ -583,7 +565,7 @@ module RubyLsp
|
|
583
565
|
document,
|
584
566
|
message.dig(:params, :previousResultId),
|
585
567
|
)
|
586
|
-
dispatcher.visit(document.
|
568
|
+
dispatcher.visit(document.ast)
|
587
569
|
send_message(Result.new(id: message[:id], response: request.perform))
|
588
570
|
end
|
589
571
|
|
@@ -612,7 +594,7 @@ module RubyLsp
|
|
612
594
|
nil,
|
613
595
|
range: range.dig(:start, :line)..range.dig(:end, :line),
|
614
596
|
)
|
615
|
-
dispatcher.visit(document.
|
597
|
+
dispatcher.visit(document.ast)
|
616
598
|
send_message(Result.new(id: message[:id], response: request.perform))
|
617
599
|
end
|
618
600
|
|
@@ -704,7 +686,7 @@ module RubyLsp
|
|
704
686
|
end
|
705
687
|
|
706
688
|
request = Requests::DocumentHighlight.new(@global_state, document, params[:position], dispatcher)
|
707
|
-
dispatcher.dispatch(document.
|
689
|
+
dispatcher.dispatch(document.ast)
|
708
690
|
send_message(Result.new(id: message[:id], response: request.perform))
|
709
691
|
end
|
710
692
|
|
@@ -842,7 +824,6 @@ module RubyLsp
|
|
842
824
|
return
|
843
825
|
end
|
844
826
|
|
845
|
-
hints_configurations = @store.features_configuration.dig(:inlayHint) #: as !nil
|
846
827
|
dispatcher = Prism::Dispatcher.new
|
847
828
|
|
848
829
|
unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
|
@@ -850,8 +831,8 @@ module RubyLsp
|
|
850
831
|
return
|
851
832
|
end
|
852
833
|
|
853
|
-
request = Requests::InlayHints.new(
|
854
|
-
dispatcher.visit(document.
|
834
|
+
request = Requests::InlayHints.new(@global_state, document, dispatcher)
|
835
|
+
dispatcher.visit(document.ast)
|
855
836
|
result = request.perform
|
856
837
|
document.cache_set("textDocument/inlayHint", result)
|
857
838
|
|
@@ -1426,8 +1407,38 @@ module RubyLsp
|
|
1426
1407
|
|
1427
1408
|
# We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
|
1428
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)
|
1429
1440
|
Thread.new do
|
1430
|
-
send_log_message(
|
1441
|
+
send_log_message(log)
|
1431
1442
|
|
1432
1443
|
_stdout, stderr, status = Bundler.with_unbundled_env do
|
1433
1444
|
Open3.capture3(
|
@@ -1442,17 +1453,7 @@ module RubyLsp
|
|
1442
1453
|
)
|
1443
1454
|
end
|
1444
1455
|
|
1445
|
-
|
1446
|
-
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
1447
|
-
FileUtils.touch(already_composed_path)
|
1448
|
-
send_message(Result.new(id: id, response: { success: true }))
|
1449
|
-
else
|
1450
|
-
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1451
|
-
# bundle is not valid
|
1452
|
-
send_message(
|
1453
|
-
Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
|
1454
|
-
)
|
1455
|
-
end
|
1456
|
+
block.call(stderr, status)
|
1456
1457
|
end
|
1457
1458
|
end
|
1458
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"
|
@@ -12,19 +11,24 @@ require "digest"
|
|
12
11
|
require "time"
|
13
12
|
require "uri"
|
14
13
|
|
15
|
-
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
16
|
-
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
17
|
-
# exact locked versions of dependencies.
|
14
|
+
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
15
|
+
# use the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
16
|
+
# the exact locked versions of dependencies.
|
18
17
|
|
19
18
|
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
|
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
@@ -4,13 +4,10 @@
|
|
4
4
|
# NOTE: This module is intended to be used by addons for writing their own tests, so keep that in mind if changing.
|
5
5
|
|
6
6
|
module RubyLsp
|
7
|
+
# @requires_ancestor: Kernel
|
7
8
|
module TestHelper
|
8
9
|
class TestError < StandardError; end
|
9
10
|
|
10
|
-
extend T::Helpers
|
11
|
-
|
12
|
-
requires_ancestor { Kernel }
|
13
|
-
|
14
11
|
#: [T] (?String? source, ?URI::Generic uri, ?stub_no_typechecker: bool, ?load_addons: bool) { (RubyLsp::Server server, URI::Generic uri) -> T } -> T
|
15
12
|
def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true,
|
16
13
|
&block)
|
@@ -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
|
@@ -30,6 +30,30 @@ module RubyLsp
|
|
30
30
|
end
|
31
31
|
end
|
32
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
|
+
|
33
57
|
class MinitestReporter < Minitest::AbstractReporter
|
34
58
|
class << self
|
35
59
|
#: (Hash[untyped, untyped]) -> void
|
@@ -45,15 +69,30 @@ module RubyLsp
|
|
45
69
|
|
46
70
|
# Add the JSON RPC reporter
|
47
71
|
reporters << MinitestReporter.new
|
72
|
+
PreventReporterOverridePatch.lsp_reporters = reporters
|
73
|
+
Minitest.reporter.class.prepend(PreventReporterOverridePatch)
|
48
74
|
end
|
49
75
|
end
|
50
76
|
|
51
|
-
#: (
|
52
|
-
def prerecord(
|
53
|
-
|
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))
|
54
93
|
return unless uri
|
55
94
|
|
56
|
-
id = "#{
|
95
|
+
id = "#{name}##{handle_spec_test_id(method_name, line)}"
|
57
96
|
LspReporter.instance.start_test(id: id, uri: uri, line: line)
|
58
97
|
end
|
59
98
|
|