ruby-lsp 0.17.4 → 0.17.13
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 +11 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +26 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
- data/lib/ruby_indexer/ruby_indexer.rb +1 -8
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/constant_test.rb +17 -17
- data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
- data/lib/ruby_indexer/test/index_test.rb +367 -17
- data/lib/ruby_indexer/test/method_test.rb +58 -25
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +22 -5
- data/lib/ruby_lsp/base_server.rb +8 -3
- data/lib/ruby_lsp/document.rb +27 -46
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +47 -19
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +161 -57
- data/lib/ruby_lsp/listeners/definition.rb +91 -27
- data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
- data/lib/ruby_lsp/listeners/hover.rb +61 -19
- data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
- data/lib/ruby_lsp/node_context.rb +65 -5
- data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
- data/lib/ruby_lsp/requests/code_actions.rb +11 -2
- data/lib/ruby_lsp/requests/completion.rb +4 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
- data/lib/ruby_lsp/requests/definition.rb +18 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
- data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
- data/lib/ruby_lsp/requests/formatting.rb +15 -0
- data/lib/ruby_lsp/requests/hover.rb +5 -5
- data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +11 -2
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
- data/lib/ruby_lsp/ruby_document.rb +74 -0
- data/lib/ruby_lsp/server.rb +129 -54
- data/lib/ruby_lsp/store.rb +33 -9
- data/lib/ruby_lsp/test_helper.rb +3 -1
- data/lib/ruby_lsp/type_inferrer.rb +61 -25
- data/lib/ruby_lsp/utils.rb +13 -0
- metadata +9 -8
- data/exe/ruby-lsp-doctor +0 -23
data/lib/ruby_lsp/server.rb
CHANGED
@@ -19,10 +19,10 @@ module RubyLsp
|
|
19
19
|
def process_message(message)
|
20
20
|
case message[:method]
|
21
21
|
when "initialize"
|
22
|
-
|
22
|
+
send_log_message("Initializing Ruby LSP v#{VERSION}...")
|
23
23
|
run_initialize(message)
|
24
24
|
when "initialized"
|
25
|
-
|
25
|
+
send_log_message("Finished initializing Ruby LSP!") unless @test_mode
|
26
26
|
run_initialized
|
27
27
|
when "textDocument/didOpen"
|
28
28
|
text_document_did_open(message)
|
@@ -98,15 +98,43 @@ module RubyLsp
|
|
98
98
|
rescue StandardError, LoadError => e
|
99
99
|
# If an error occurred in a request, we have to return an error response or else the editor will hang
|
100
100
|
if message[:id]
|
101
|
-
|
101
|
+
# If a document is deleted before we are able to process all of its enqueued requests, we will try to read it
|
102
|
+
# from disk and it raise this error. This is expected, so we don't include the `data` attribute to avoid
|
103
|
+
# reporting these to our telemetry
|
104
|
+
if e.is_a?(Store::NonExistingDocumentError)
|
105
|
+
send_message(Error.new(
|
106
|
+
id: message[:id],
|
107
|
+
code: Constant::ErrorCodes::INVALID_PARAMS,
|
108
|
+
message: e.full_message,
|
109
|
+
))
|
110
|
+
else
|
111
|
+
send_message(Error.new(
|
112
|
+
id: message[:id],
|
113
|
+
code: Constant::ErrorCodes::INTERNAL_ERROR,
|
114
|
+
message: e.full_message,
|
115
|
+
data: {
|
116
|
+
errorClass: e.class.name,
|
117
|
+
errorMessage: e.message,
|
118
|
+
backtrace: e.backtrace&.join("\n"),
|
119
|
+
},
|
120
|
+
))
|
121
|
+
end
|
102
122
|
end
|
103
123
|
|
104
|
-
|
124
|
+
send_log_message("Error processing #{message[:method]}: #{e.full_message}", type: Constant::MessageType::ERROR)
|
105
125
|
end
|
106
126
|
|
107
127
|
sig { void }
|
108
128
|
def load_addons
|
109
|
-
Addon.load_addons(@global_state, @outgoing_queue)
|
129
|
+
errors = Addon.load_addons(@global_state, @outgoing_queue)
|
130
|
+
|
131
|
+
if errors.any?
|
132
|
+
send_log_message(
|
133
|
+
"Error loading addons:\n\n#{errors.map(&:full_message).join("\n\n")}",
|
134
|
+
type: Constant::MessageType::WARNING,
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
110
138
|
errored_addons = Addon.addons.select(&:error?)
|
111
139
|
|
112
140
|
if errored_addons.any?
|
@@ -120,7 +148,12 @@ module RubyLsp
|
|
120
148
|
),
|
121
149
|
)
|
122
150
|
|
123
|
-
|
151
|
+
unless @test_mode
|
152
|
+
send_log_message(
|
153
|
+
errored_addons.map(&:errors_details).join("\n\n"),
|
154
|
+
type: Constant::MessageType::WARNING,
|
155
|
+
)
|
156
|
+
end
|
124
157
|
end
|
125
158
|
end
|
126
159
|
|
@@ -129,7 +162,7 @@ module RubyLsp
|
|
129
162
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
130
163
|
def run_initialize(message)
|
131
164
|
options = message[:params]
|
132
|
-
@global_state.apply_options(options)
|
165
|
+
global_state_notifications = @global_state.apply_options(options)
|
133
166
|
|
134
167
|
client_name = options.dig(:clientInfo, :name)
|
135
168
|
@store.client_name = client_name if client_name
|
@@ -137,7 +170,6 @@ module RubyLsp
|
|
137
170
|
progress = options.dig(:capabilities, :window, :workDoneProgress)
|
138
171
|
@store.supports_progress = progress.nil? ? true : progress
|
139
172
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
140
|
-
@store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
141
173
|
|
142
174
|
configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
|
143
175
|
T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
|
@@ -162,6 +194,7 @@ module RubyLsp
|
|
162
194
|
hover_provider = Requests::Hover.provider if enabled_features["hover"]
|
163
195
|
folding_ranges_provider = Requests::FoldingRanges.provider if enabled_features["foldingRanges"]
|
164
196
|
semantic_tokens_provider = Requests::SemanticHighlighting.provider if enabled_features["semanticHighlighting"]
|
197
|
+
document_formatting_provider = Requests::Formatting.provider if enabled_features["formatting"]
|
165
198
|
diagnostics_provider = Requests::Diagnostics.provider if enabled_features["diagnostics"]
|
166
199
|
on_type_formatting_provider = Requests::OnTypeFormatting.provider if enabled_features["onTypeFormatting"]
|
167
200
|
code_action_provider = Requests::CodeActions.provider if enabled_features["codeActions"]
|
@@ -183,7 +216,7 @@ module RubyLsp
|
|
183
216
|
document_link_provider: document_link_provider,
|
184
217
|
folding_range_provider: folding_ranges_provider,
|
185
218
|
semantic_tokens_provider: semantic_tokens_provider,
|
186
|
-
document_formatting_provider:
|
219
|
+
document_formatting_provider: document_formatting_provider && @global_state.formatter != "none",
|
187
220
|
document_highlight_provider: enabled_features["documentHighlights"],
|
188
221
|
code_action_provider: code_action_provider,
|
189
222
|
document_on_type_formatting_provider: on_type_formatting_provider,
|
@@ -235,7 +268,11 @@ module RubyLsp
|
|
235
268
|
)
|
236
269
|
end
|
237
270
|
|
271
|
+
process_indexing_configuration(options.dig(:initializationOptions, :indexing))
|
272
|
+
|
238
273
|
begin_progress("indexing-progress", "Ruby LSP: indexing files")
|
274
|
+
|
275
|
+
global_state_notifications.each { |notification| send_message(notification) }
|
239
276
|
end
|
240
277
|
|
241
278
|
sig { void }
|
@@ -243,28 +280,6 @@ module RubyLsp
|
|
243
280
|
load_addons
|
244
281
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
245
282
|
|
246
|
-
indexing_config = {}
|
247
|
-
|
248
|
-
# Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
|
249
|
-
index_path = File.join(@global_state.workspace_path, ".index.yml")
|
250
|
-
|
251
|
-
if File.exist?(index_path)
|
252
|
-
begin
|
253
|
-
indexing_config = YAML.parse_file(index_path).to_ruby
|
254
|
-
rescue Psych::SyntaxError => e
|
255
|
-
message = "Syntax error while loading configuration: #{e.message}"
|
256
|
-
send_message(
|
257
|
-
Notification.new(
|
258
|
-
method: "window/showMessage",
|
259
|
-
params: Interface::ShowMessageParams.new(
|
260
|
-
type: Constant::MessageType::WARNING,
|
261
|
-
message: message,
|
262
|
-
),
|
263
|
-
),
|
264
|
-
)
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
283
|
if defined?(Requests::Support::RuboCopFormatter)
|
269
284
|
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
270
285
|
end
|
@@ -272,7 +287,7 @@ module RubyLsp
|
|
272
287
|
@global_state.register_formatter("syntax_tree", Requests::Support::SyntaxTreeFormatter.new)
|
273
288
|
end
|
274
289
|
|
275
|
-
perform_initial_indexing
|
290
|
+
perform_initial_indexing
|
276
291
|
check_formatter_is_available
|
277
292
|
end
|
278
293
|
|
@@ -280,11 +295,18 @@ module RubyLsp
|
|
280
295
|
def text_document_did_open(message)
|
281
296
|
@mutex.synchronize do
|
282
297
|
text_document = message.dig(:params, :textDocument)
|
298
|
+
language_id = case text_document[:languageId]
|
299
|
+
when "erb", "eruby"
|
300
|
+
Document::LanguageId::ERB
|
301
|
+
else
|
302
|
+
Document::LanguageId::Ruby
|
303
|
+
end
|
283
304
|
@store.set(
|
284
305
|
uri: text_document[:uri],
|
285
306
|
source: text_document[:text],
|
286
307
|
version: text_document[:version],
|
287
308
|
encoding: @global_state.encoding,
|
309
|
+
language_id: language_id,
|
288
310
|
)
|
289
311
|
end
|
290
312
|
end
|
@@ -348,15 +370,17 @@ module RubyLsp
|
|
348
370
|
return
|
349
371
|
end
|
350
372
|
|
373
|
+
parse_result = document.parse_result
|
374
|
+
|
351
375
|
# Run requests for the document
|
352
376
|
dispatcher = Prism::Dispatcher.new
|
353
|
-
folding_range = Requests::FoldingRanges.new(
|
377
|
+
folding_range = Requests::FoldingRanges.new(parse_result.comments, dispatcher)
|
354
378
|
document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
|
355
|
-
document_link = Requests::DocumentLink.new(uri,
|
379
|
+
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
356
380
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
357
381
|
|
358
382
|
semantic_highlighting = Requests::SemanticHighlighting.new(@global_state, dispatcher)
|
359
|
-
dispatcher.dispatch(
|
383
|
+
dispatcher.dispatch(parse_result.value)
|
360
384
|
|
361
385
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
362
386
|
# we actually received
|
@@ -388,7 +412,7 @@ module RubyLsp
|
|
388
412
|
|
389
413
|
dispatcher = Prism::Dispatcher.new
|
390
414
|
request = Requests::SemanticHighlighting.new(@global_state, dispatcher, range: start_line..end_line)
|
391
|
-
dispatcher.visit(document.
|
415
|
+
dispatcher.visit(document.parse_result.value)
|
392
416
|
|
393
417
|
response = request.perform
|
394
418
|
send_message(Result.new(id: message[:id], response: response))
|
@@ -411,7 +435,9 @@ module RubyLsp
|
|
411
435
|
return
|
412
436
|
end
|
413
437
|
|
414
|
-
|
438
|
+
document = @store.get(uri)
|
439
|
+
|
440
|
+
response = Requests::Formatting.new(@global_state, document).perform
|
415
441
|
send_message(Result.new(id: message[:id], response: response))
|
416
442
|
rescue Requests::Request::InvalidFormatter => error
|
417
443
|
send_message(Notification.window_show_error("Configuration error: #{error.message}"))
|
@@ -427,19 +453,20 @@ module RubyLsp
|
|
427
453
|
dispatcher = Prism::Dispatcher.new
|
428
454
|
document = @store.get(params.dig(:textDocument, :uri))
|
429
455
|
request = Requests::DocumentHighlight.new(document, params[:position], dispatcher)
|
430
|
-
dispatcher.dispatch(document.
|
456
|
+
dispatcher.dispatch(document.parse_result.value)
|
431
457
|
send_message(Result.new(id: message[:id], response: request.perform))
|
432
458
|
end
|
433
459
|
|
434
460
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
435
461
|
def text_document_on_type_formatting(message)
|
436
462
|
params = message[:params]
|
463
|
+
document = @store.get(params.dig(:textDocument, :uri))
|
437
464
|
|
438
465
|
send_message(
|
439
466
|
Result.new(
|
440
467
|
id: message[:id],
|
441
468
|
response: Requests::OnTypeFormatting.new(
|
442
|
-
|
469
|
+
document,
|
443
470
|
params[:position],
|
444
471
|
params[:ch],
|
445
472
|
@store.client_name,
|
@@ -462,15 +489,18 @@ module RubyLsp
|
|
462
489
|
@global_state,
|
463
490
|
params[:position],
|
464
491
|
dispatcher,
|
465
|
-
|
492
|
+
sorbet_level(document),
|
466
493
|
).perform,
|
467
494
|
),
|
468
495
|
)
|
469
496
|
end
|
470
497
|
|
471
|
-
sig { params(document: Document).returns(
|
472
|
-
def
|
473
|
-
@global_state.has_type_checker
|
498
|
+
sig { params(document: Document).returns(RubyDocument::SorbetLevel) }
|
499
|
+
def sorbet_level(document)
|
500
|
+
return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
|
501
|
+
return RubyDocument::SorbetLevel::Ignore unless document.is_a?(RubyDocument)
|
502
|
+
|
503
|
+
document.sorbet_level
|
474
504
|
end
|
475
505
|
|
476
506
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
@@ -480,7 +510,7 @@ module RubyLsp
|
|
480
510
|
dispatcher = Prism::Dispatcher.new
|
481
511
|
document = @store.get(params.dig(:textDocument, :uri))
|
482
512
|
request = Requests::InlayHints.new(document, params[:range], hints_configurations, dispatcher)
|
483
|
-
dispatcher.visit(document.
|
513
|
+
dispatcher.visit(document.parse_result.value)
|
484
514
|
send_message(Result.new(id: message[:id], response: request.perform))
|
485
515
|
end
|
486
516
|
|
@@ -506,6 +536,12 @@ module RubyLsp
|
|
506
536
|
params = message[:params]
|
507
537
|
uri = URI(params.dig(:data, :uri))
|
508
538
|
document = @store.get(uri)
|
539
|
+
|
540
|
+
unless document.is_a?(RubyDocument)
|
541
|
+
send_message(Notification.window_show_error("Code actions are currently only available for Ruby documents"))
|
542
|
+
raise Requests::CodeActionResolve::CodeActionError
|
543
|
+
end
|
544
|
+
|
509
545
|
result = Requests::CodeActionResolve.new(document, params).perform
|
510
546
|
|
511
547
|
case result
|
@@ -542,7 +578,9 @@ module RubyLsp
|
|
542
578
|
return
|
543
579
|
end
|
544
580
|
|
545
|
-
|
581
|
+
document = @store.get(uri)
|
582
|
+
|
583
|
+
response = document.cache_fetch("textDocument/diagnostic") do |document|
|
546
584
|
Requests::Diagnostics.new(@global_state, document).perform
|
547
585
|
end
|
548
586
|
|
@@ -573,7 +611,7 @@ module RubyLsp
|
|
573
611
|
document,
|
574
612
|
@global_state,
|
575
613
|
params,
|
576
|
-
|
614
|
+
sorbet_level(document),
|
577
615
|
dispatcher,
|
578
616
|
).perform,
|
579
617
|
),
|
@@ -603,7 +641,7 @@ module RubyLsp
|
|
603
641
|
params[:position],
|
604
642
|
params[:context],
|
605
643
|
dispatcher,
|
606
|
-
|
644
|
+
sorbet_level(document),
|
607
645
|
).perform,
|
608
646
|
),
|
609
647
|
)
|
@@ -623,7 +661,7 @@ module RubyLsp
|
|
623
661
|
@global_state,
|
624
662
|
params[:position],
|
625
663
|
dispatcher,
|
626
|
-
|
664
|
+
sorbet_level(document),
|
627
665
|
).perform,
|
628
666
|
),
|
629
667
|
)
|
@@ -736,16 +774,12 @@ module RubyLsp
|
|
736
774
|
Addon.addons.each(&:deactivate)
|
737
775
|
end
|
738
776
|
|
739
|
-
sig {
|
740
|
-
def perform_initial_indexing
|
777
|
+
sig { void }
|
778
|
+
def perform_initial_indexing
|
741
779
|
# The begin progress invocation happens during `initialize`, so that the notification is sent before we are
|
742
780
|
# stuck indexing files
|
743
|
-
RubyIndexer.configuration.apply_config(config_hash)
|
744
|
-
|
745
781
|
Thread.new do
|
746
782
|
begin
|
747
|
-
RubyIndexer::RBSIndexer.new(@global_state.index).index_ruby_core
|
748
|
-
|
749
783
|
@global_state.index.index_all do |percentage|
|
750
784
|
progress("indexing-progress", percentage)
|
751
785
|
true
|
@@ -842,5 +876,46 @@ module RubyLsp
|
|
842
876
|
)
|
843
877
|
end
|
844
878
|
end
|
879
|
+
|
880
|
+
sig { params(indexing_options: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
881
|
+
def process_indexing_configuration(indexing_options)
|
882
|
+
# Need to use the workspace URI, otherwise, this will fail for people working on a project that is a symlink.
|
883
|
+
index_path = File.join(@global_state.workspace_path, ".index.yml")
|
884
|
+
|
885
|
+
if File.exist?(index_path)
|
886
|
+
begin
|
887
|
+
@global_state.index.configuration.apply_config(YAML.parse_file(index_path).to_ruby)
|
888
|
+
send_message(
|
889
|
+
Notification.new(
|
890
|
+
method: "window/showMessage",
|
891
|
+
params: Interface::ShowMessageParams.new(
|
892
|
+
type: Constant::MessageType::WARNING,
|
893
|
+
message: "The .index.yml configuration file is deprecated. " \
|
894
|
+
"Please use editor settings to configure the index",
|
895
|
+
),
|
896
|
+
),
|
897
|
+
)
|
898
|
+
rescue Psych::SyntaxError => e
|
899
|
+
message = "Syntax error while loading configuration: #{e.message}"
|
900
|
+
send_message(
|
901
|
+
Notification.new(
|
902
|
+
method: "window/showMessage",
|
903
|
+
params: Interface::ShowMessageParams.new(
|
904
|
+
type: Constant::MessageType::WARNING,
|
905
|
+
message: message,
|
906
|
+
),
|
907
|
+
),
|
908
|
+
)
|
909
|
+
end
|
910
|
+
return
|
911
|
+
end
|
912
|
+
|
913
|
+
return unless indexing_options
|
914
|
+
|
915
|
+
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
916
|
+
@global_state.index.configuration.apply_config(
|
917
|
+
indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase },
|
918
|
+
)
|
919
|
+
end
|
845
920
|
end
|
846
921
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -5,11 +5,10 @@ module RubyLsp
|
|
5
5
|
class Store
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
-
|
9
|
-
attr_accessor :supports_progress
|
8
|
+
class NonExistingDocumentError < StandardError; end
|
10
9
|
|
11
10
|
sig { returns(T::Boolean) }
|
12
|
-
attr_accessor :
|
11
|
+
attr_accessor :supports_progress
|
13
12
|
|
14
13
|
sig { returns(T::Hash[Symbol, RequestConfig]) }
|
15
14
|
attr_accessor :features_configuration
|
@@ -21,7 +20,6 @@ module RubyLsp
|
|
21
20
|
def initialize
|
22
21
|
@state = T.let({}, T::Hash[String, Document])
|
23
22
|
@supports_progress = T.let(true, T::Boolean)
|
24
|
-
@experimental_features = T.let(false, T::Boolean)
|
25
23
|
@features_configuration = T.let(
|
26
24
|
{
|
27
25
|
inlayHint: RequestConfig.new({
|
@@ -40,14 +38,40 @@ module RubyLsp
|
|
40
38
|
document = @state[uri.to_s]
|
41
39
|
return document unless document.nil?
|
42
40
|
|
43
|
-
path
|
44
|
-
|
41
|
+
# For unsaved files (`untitled:Untitled-1` uris), there's no path to read from. If we don't have the untitled file
|
42
|
+
# already present in the store, then we have to raise non existing document error
|
43
|
+
path = uri.to_standardized_path
|
44
|
+
raise NonExistingDocumentError, uri.to_s unless path
|
45
|
+
|
46
|
+
ext = File.extname(path)
|
47
|
+
language_id = if ext == ".erb" || ext == ".rhtml"
|
48
|
+
Document::LanguageId::ERB
|
49
|
+
else
|
50
|
+
Document::LanguageId::Ruby
|
51
|
+
end
|
52
|
+
|
53
|
+
set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
|
45
54
|
T.must(@state[uri.to_s])
|
55
|
+
rescue Errno::ENOENT
|
56
|
+
raise NonExistingDocumentError, uri.to_s
|
46
57
|
end
|
47
58
|
|
48
|
-
sig
|
49
|
-
|
50
|
-
|
59
|
+
sig do
|
60
|
+
params(
|
61
|
+
uri: URI::Generic,
|
62
|
+
source: String,
|
63
|
+
version: Integer,
|
64
|
+
language_id: Document::LanguageId,
|
65
|
+
encoding: Encoding,
|
66
|
+
).void
|
67
|
+
end
|
68
|
+
def set(uri:, source:, version:, language_id:, encoding: Encoding::UTF_8)
|
69
|
+
document = case language_id
|
70
|
+
when Document::LanguageId::ERB
|
71
|
+
ERBDocument.new(source: source, version: version, uri: uri, encoding: encoding)
|
72
|
+
else
|
73
|
+
RubyDocument.new(source: source, version: version, uri: uri, encoding: encoding)
|
74
|
+
end
|
51
75
|
@state[uri.to_s] = document
|
52
76
|
end
|
53
77
|
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
@@ -21,7 +21,8 @@ module RubyLsp
|
|
21
21
|
&block)
|
22
22
|
server = RubyLsp::Server.new(test_mode: true)
|
23
23
|
server.global_state.stubs(:has_type_checker).returns(false) if stub_no_typechecker
|
24
|
-
server.global_state.apply_options({})
|
24
|
+
server.global_state.apply_options({ initializationOptions: { experimentalFeaturesEnabled: true } })
|
25
|
+
language_id = uri.to_s.end_with?(".erb") ? "erb" : "ruby"
|
25
26
|
|
26
27
|
if source
|
27
28
|
server.process_message({
|
@@ -31,6 +32,7 @@ module RubyLsp
|
|
31
32
|
uri: uri,
|
32
33
|
text: source,
|
33
34
|
version: 1,
|
35
|
+
languageId: language_id,
|
34
36
|
},
|
35
37
|
},
|
36
38
|
})
|
@@ -7,12 +7,16 @@ module RubyLsp
|
|
7
7
|
class TypeInferrer
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
sig { params(
|
11
|
-
|
10
|
+
sig { params(experimental_features: T::Boolean).returns(T::Boolean) }
|
11
|
+
attr_writer :experimental_features
|
12
|
+
|
13
|
+
sig { params(index: RubyIndexer::Index, experimental_features: T::Boolean).void }
|
14
|
+
def initialize(index, experimental_features = true)
|
12
15
|
@index = index
|
16
|
+
@experimental_features = experimental_features
|
13
17
|
end
|
14
18
|
|
15
|
-
sig { params(node_context: NodeContext).returns(T.nilable(
|
19
|
+
sig { params(node_context: NodeContext).returns(T.nilable(Type)) }
|
16
20
|
def infer_receiver_type(node_context)
|
17
21
|
node = node_context.node
|
18
22
|
|
@@ -20,36 +24,21 @@ module RubyLsp
|
|
20
24
|
when Prism::CallNode
|
21
25
|
infer_receiver_for_call_node(node, node_context)
|
22
26
|
when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
|
23
|
-
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode
|
24
|
-
|
25
|
-
|
26
|
-
# inherits from Object
|
27
|
-
return "Object" if nesting.empty?
|
28
|
-
|
29
|
-
fully_qualified_name = node_context.fully_qualified_name
|
30
|
-
return fully_qualified_name if node_context.surrounding_method
|
31
|
-
|
32
|
-
"#{fully_qualified_name}::<Class:#{nesting.last}>"
|
27
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
|
28
|
+
Prism::SuperNode, Prism::ForwardingSuperNode
|
29
|
+
self_receiver_handling(node_context)
|
33
30
|
end
|
34
31
|
end
|
35
32
|
|
36
33
|
private
|
37
34
|
|
38
|
-
sig { params(node: Prism::CallNode, node_context: NodeContext).returns(T.nilable(
|
35
|
+
sig { params(node: Prism::CallNode, node_context: NodeContext).returns(T.nilable(Type)) }
|
39
36
|
def infer_receiver_for_call_node(node, node_context)
|
40
37
|
receiver = node.receiver
|
41
38
|
|
42
39
|
case receiver
|
43
40
|
when Prism::SelfNode, nil
|
44
|
-
|
45
|
-
# If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
|
46
|
-
# inherits from Object
|
47
|
-
return "Object" if nesting.empty?
|
48
|
-
return node_context.fully_qualified_name if node_context.surrounding_method
|
49
|
-
|
50
|
-
# If we're not inside a method, then we're inside the body of a class or module, which is a singleton
|
51
|
-
# context
|
52
|
-
"#{nesting.join("::")}::<Class:#{nesting.last}>"
|
41
|
+
self_receiver_handling(node_context)
|
53
42
|
when Prism::ConstantPathNode, Prism::ConstantReadNode
|
54
43
|
# When the receiver is a constant reference, we have to try to resolve it to figure out the right
|
55
44
|
# receiver. But since the invocation is directly on the constant, that's the singleton context of that
|
@@ -62,12 +51,42 @@ module RubyLsp
|
|
62
51
|
return unless name
|
63
52
|
|
64
53
|
*parts, last = name.split("::")
|
65
|
-
return "#{last}::<Class:#{last}>" if parts.empty?
|
54
|
+
return Type.new("#{last}::<Class:#{last}>") if parts.empty?
|
55
|
+
|
56
|
+
Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
|
57
|
+
else
|
58
|
+
return unless @experimental_features
|
66
59
|
|
67
|
-
|
60
|
+
raw_receiver = node.receiver&.slice
|
61
|
+
|
62
|
+
if raw_receiver
|
63
|
+
guessed_name = raw_receiver
|
64
|
+
.delete_prefix("@")
|
65
|
+
.delete_prefix("@@")
|
66
|
+
.split("_")
|
67
|
+
.map(&:capitalize)
|
68
|
+
.join
|
69
|
+
|
70
|
+
entries = @index.resolve(guessed_name, node_context.nesting) || @index.first_unqualified_const(guessed_name)
|
71
|
+
name = entries&.first&.name
|
72
|
+
GuessedType.new(name) if name
|
73
|
+
end
|
68
74
|
end
|
69
75
|
end
|
70
76
|
|
77
|
+
sig { params(node_context: NodeContext).returns(Type) }
|
78
|
+
def self_receiver_handling(node_context)
|
79
|
+
nesting = node_context.nesting
|
80
|
+
# If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
|
81
|
+
# inherits from Object
|
82
|
+
return Type.new("Object") if nesting.empty?
|
83
|
+
return Type.new(node_context.fully_qualified_name) if node_context.surrounding_method
|
84
|
+
|
85
|
+
# If we're not inside a method, then we're inside the body of a class or module, which is a singleton
|
86
|
+
# context
|
87
|
+
Type.new("#{nesting.join("::")}::<Class:#{nesting.last}>")
|
88
|
+
end
|
89
|
+
|
71
90
|
sig do
|
72
91
|
params(
|
73
92
|
node: T.any(
|
@@ -82,5 +101,22 @@ module RubyLsp
|
|
82
101
|
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
83
102
|
nil
|
84
103
|
end
|
104
|
+
|
105
|
+
# A known type
|
106
|
+
class Type
|
107
|
+
extend T::Sig
|
108
|
+
|
109
|
+
sig { returns(String) }
|
110
|
+
attr_reader :name
|
111
|
+
|
112
|
+
sig { params(name: String).void }
|
113
|
+
def initialize(name)
|
114
|
+
@name = name
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# A type that was guessed based on the receiver raw name
|
119
|
+
class GuessedType < Type
|
120
|
+
end
|
85
121
|
end
|
86
122
|
end
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -25,6 +25,7 @@ module RubyLsp
|
|
25
25
|
end,
|
26
26
|
String,
|
27
27
|
)
|
28
|
+
GUESSED_TYPES_URL = "https://github.com/Shopify/ruby-lsp/blob/main/DESIGN_AND_ROADMAP.md#guessed-types"
|
28
29
|
|
29
30
|
# A notification to be sent to the client
|
30
31
|
class Message
|
@@ -52,6 +53,7 @@ module RubyLsp
|
|
52
53
|
class Notification < Message
|
53
54
|
class << self
|
54
55
|
extend T::Sig
|
56
|
+
|
55
57
|
sig { params(message: String).returns(Notification) }
|
56
58
|
def window_show_error(message)
|
57
59
|
new(
|
@@ -62,6 +64,14 @@ module RubyLsp
|
|
62
64
|
),
|
63
65
|
)
|
64
66
|
end
|
67
|
+
|
68
|
+
sig { params(message: String, type: Integer).returns(Notification) }
|
69
|
+
def window_log_message(message, type: Constant::MessageType::LOG)
|
70
|
+
new(
|
71
|
+
method: "window/logMessage",
|
72
|
+
params: Interface::LogMessageParams.new(type: type, message: message),
|
73
|
+
)
|
74
|
+
end
|
65
75
|
end
|
66
76
|
|
67
77
|
extend T::Sig
|
@@ -121,6 +131,9 @@ module RubyLsp
|
|
121
131
|
sig { returns(T.untyped) }
|
122
132
|
attr_reader :response
|
123
133
|
|
134
|
+
sig { returns(Integer) }
|
135
|
+
attr_reader :id
|
136
|
+
|
124
137
|
sig { params(id: Integer, response: T.untyped).void }
|
125
138
|
def initialize(id:, response:)
|
126
139
|
@id = id
|