ruby-lsp 0.23.1 → 0.23.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp-launcher +18 -9
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +89 -58
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +21 -10
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/index_test.rb +2 -1
- data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
- data/lib/ruby_indexer/test/method_test.rb +86 -8
- data/lib/ruby_lsp/base_server.rb +11 -21
- data/lib/ruby_lsp/document.rb +62 -9
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +9 -0
- data/lib/ruby_lsp/listeners/definition.rb +7 -2
- data/lib/ruby_lsp/rbs_document.rb +2 -2
- data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -6
- data/lib/ruby_lsp/requests/completion.rb +2 -1
- data/lib/ruby_lsp/requests/definition.rb +1 -1
- data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
- data/lib/ruby_lsp/requests/hover.rb +1 -1
- data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
- data/lib/ruby_lsp/requests/references.rb +1 -1
- data/lib/ruby_lsp/requests/rename.rb +3 -3
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
- data/lib/ruby_lsp/requests/signature_help.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +3 -3
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -13
- data/lib/ruby_lsp/requests/workspace_symbol.rb +2 -2
- data/lib/ruby_lsp/ruby_document.rb +75 -6
- data/lib/ruby_lsp/server.rb +69 -49
- data/lib/ruby_lsp/store.rb +7 -7
- data/lib/ruby_lsp/type_inferrer.rb +39 -17
- data/lib/ruby_lsp/utils.rb +43 -0
- metadata +3 -2
data/lib/ruby_lsp/server.rb
CHANGED
@@ -107,7 +107,7 @@ module RubyLsp
|
|
107
107
|
),
|
108
108
|
)
|
109
109
|
when "$/cancelRequest"
|
110
|
-
@
|
110
|
+
@global_state.synchronize { @cancelled_requests << message[:params][:id] }
|
111
111
|
when nil
|
112
112
|
process_response(message) if message[:result]
|
113
113
|
end
|
@@ -298,29 +298,11 @@ module RubyLsp
|
|
298
298
|
|
299
299
|
# Not every client supports dynamic registration or file watching
|
300
300
|
if @global_state.client_capabilities.supports_watching_files
|
301
|
-
send_message(
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
registrations: [
|
307
|
-
# Register watching Ruby files
|
308
|
-
Interface::Registration.new(
|
309
|
-
id: "workspace/didChangeWatchedFiles",
|
310
|
-
method: "workspace/didChangeWatchedFiles",
|
311
|
-
register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
|
312
|
-
watchers: [
|
313
|
-
Interface::FileSystemWatcher.new(
|
314
|
-
glob_pattern: "**/*.rb",
|
315
|
-
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE,
|
316
|
-
),
|
317
|
-
],
|
318
|
-
),
|
319
|
-
),
|
320
|
-
],
|
321
|
-
),
|
322
|
-
),
|
323
|
-
)
|
301
|
+
send_message(Request.register_watched_files(@current_request_id, "**/*.rb"))
|
302
|
+
send_message(Request.register_watched_files(
|
303
|
+
@current_request_id,
|
304
|
+
Interface::RelativePattern.new(base_uri: @global_state.workspace_uri.to_s, pattern: ".rubocop.yml"),
|
305
|
+
))
|
324
306
|
end
|
325
307
|
|
326
308
|
process_indexing_configuration(options.dig(:initializationOptions, :indexing))
|
@@ -357,7 +339,7 @@ module RubyLsp
|
|
357
339
|
if defined?(Requests::Support::RuboCopFormatter)
|
358
340
|
begin
|
359
341
|
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
360
|
-
rescue RuboCop::Error => e
|
342
|
+
rescue ::RuboCop::Error => e
|
361
343
|
# The user may have provided unknown config switches in .rubocop or
|
362
344
|
# is trying to load a non-existent config file.
|
363
345
|
send_message(Notification.window_show_message(
|
@@ -377,7 +359,7 @@ module RubyLsp
|
|
377
359
|
|
378
360
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
379
361
|
def text_document_did_open(message)
|
380
|
-
@
|
362
|
+
@global_state.synchronize do
|
381
363
|
text_document = message.dig(:params, :textDocument)
|
382
364
|
language_id = case text_document[:languageId]
|
383
365
|
when "erb", "eruby"
|
@@ -392,7 +374,6 @@ module RubyLsp
|
|
392
374
|
uri: text_document[:uri],
|
393
375
|
source: text_document[:text],
|
394
376
|
version: text_document[:version],
|
395
|
-
encoding: @global_state.encoding,
|
396
377
|
language_id: language_id,
|
397
378
|
)
|
398
379
|
|
@@ -417,17 +398,12 @@ module RubyLsp
|
|
417
398
|
|
418
399
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
419
400
|
def text_document_did_close(message)
|
420
|
-
@
|
401
|
+
@global_state.synchronize do
|
421
402
|
uri = message.dig(:params, :textDocument, :uri)
|
422
403
|
@store.delete(uri)
|
423
404
|
|
424
405
|
# Clear diagnostics for the closed file, so that they no longer appear in the problems tab
|
425
|
-
send_message(
|
426
|
-
Notification.new(
|
427
|
-
method: "textDocument/publishDiagnostics",
|
428
|
-
params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
|
429
|
-
),
|
430
|
-
)
|
406
|
+
send_message(Notification.publish_diagnostics(uri.to_s, []))
|
431
407
|
end
|
432
408
|
end
|
433
409
|
|
@@ -436,7 +412,7 @@ module RubyLsp
|
|
436
412
|
params = message[:params]
|
437
413
|
text_document = params[:textDocument]
|
438
414
|
|
439
|
-
@
|
415
|
+
@global_state.synchronize do
|
440
416
|
@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
|
441
417
|
end
|
442
418
|
end
|
@@ -493,7 +469,22 @@ module RubyLsp
|
|
493
469
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
494
470
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
495
471
|
inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)
|
496
|
-
|
472
|
+
|
473
|
+
if document.is_a?(RubyDocument) && document.last_edit_may_change_declarations?
|
474
|
+
# Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
|
475
|
+
# updated on save
|
476
|
+
@global_state.synchronize do
|
477
|
+
send_log_message("Detected that last edit may have modified declarations. Re-indexing #{uri}")
|
478
|
+
|
479
|
+
@global_state.index.handle_change(uri) do |index|
|
480
|
+
index.delete(uri, skip_require_paths_tree: true)
|
481
|
+
RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
|
482
|
+
dispatcher.dispatch(parse_result.value)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
else
|
486
|
+
dispatcher.dispatch(parse_result.value)
|
487
|
+
end
|
497
488
|
|
498
489
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
499
490
|
# we actually received
|
@@ -1011,26 +1002,55 @@ module RubyLsp
|
|
1011
1002
|
uri = URI(change[:uri])
|
1012
1003
|
file_path = uri.to_standardized_path
|
1013
1004
|
next if file_path.nil? || File.directory?(file_path)
|
1014
|
-
next unless file_path.end_with?(".rb")
|
1015
1005
|
|
1016
|
-
|
1017
|
-
|
1006
|
+
if file_path.end_with?(".rb")
|
1007
|
+
handle_ruby_file_change(index, file_path, change[:type])
|
1008
|
+
next
|
1009
|
+
end
|
1018
1010
|
|
1019
|
-
|
1011
|
+
file_name = File.basename(file_path)
|
1020
1012
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
index.index_single(uri, content)
|
1024
|
-
when Constant::FileChangeType::CHANGED
|
1025
|
-
index.handle_change(uri, content)
|
1026
|
-
when Constant::FileChangeType::DELETED
|
1027
|
-
index.delete(uri)
|
1013
|
+
if file_name == ".rubocop.yml" || file_name == ".rubocop"
|
1014
|
+
handle_rubocop_config_change(uri)
|
1028
1015
|
end
|
1029
1016
|
end
|
1030
1017
|
|
1031
1018
|
Addon.file_watcher_addons.each { |addon| T.unsafe(addon).workspace_did_change_watched_files(changes) }
|
1032
1019
|
end
|
1033
1020
|
|
1021
|
+
sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
|
1022
|
+
def handle_ruby_file_change(index, file_path, change_type)
|
1023
|
+
load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
|
1024
|
+
uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
|
1025
|
+
|
1026
|
+
content = File.read(file_path)
|
1027
|
+
|
1028
|
+
case change_type
|
1029
|
+
when Constant::FileChangeType::CREATED
|
1030
|
+
index.index_single(uri, content)
|
1031
|
+
when Constant::FileChangeType::CHANGED
|
1032
|
+
index.handle_change(uri, content)
|
1033
|
+
when Constant::FileChangeType::DELETED
|
1034
|
+
index.delete(uri)
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
sig { params(uri: URI::Generic).void }
|
1039
|
+
def handle_rubocop_config_change(uri)
|
1040
|
+
return unless defined?(Requests::Support::RuboCopFormatter)
|
1041
|
+
|
1042
|
+
send_log_message("Reloading RuboCop since #{uri} changed")
|
1043
|
+
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
1044
|
+
|
1045
|
+
# Clear all existing diagnostics since the config changed. This has to happen under a mutex because the `state`
|
1046
|
+
# hash cannot be mutated during iteration or that will throw an error
|
1047
|
+
@global_state.synchronize do
|
1048
|
+
@store.each do |uri, _document|
|
1049
|
+
send_message(Notification.publish_diagnostics(uri.to_s, []))
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
|
1034
1054
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1035
1055
|
def workspace_symbol(message)
|
1036
1056
|
send_message(
|
@@ -1240,10 +1260,10 @@ module RubyLsp
|
|
1240
1260
|
return
|
1241
1261
|
end
|
1242
1262
|
|
1243
|
-
return unless indexing_options
|
1244
|
-
|
1245
1263
|
configuration = @global_state.index.configuration
|
1246
1264
|
configuration.workspace_path = @global_state.workspace_path
|
1265
|
+
return unless indexing_options
|
1266
|
+
|
1247
1267
|
# The index expects snake case configurations, but VS Code standardizes on camel case settings
|
1248
1268
|
configuration.apply_config(indexing_options.transform_keys { |key| key.to_s.gsub(/([A-Z])/, "_\\1").downcase })
|
1249
1269
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -13,8 +13,9 @@ module RubyLsp
|
|
13
13
|
sig { returns(String) }
|
14
14
|
attr_accessor :client_name
|
15
15
|
|
16
|
-
sig { void }
|
17
|
-
def initialize
|
16
|
+
sig { params(global_state: GlobalState).void }
|
17
|
+
def initialize(global_state)
|
18
|
+
@global_state = global_state
|
18
19
|
@state = T.let({}, T::Hash[String, Document[T.untyped]])
|
19
20
|
@features_configuration = T.let(
|
20
21
|
{
|
@@ -61,17 +62,16 @@ module RubyLsp
|
|
61
62
|
source: String,
|
62
63
|
version: Integer,
|
63
64
|
language_id: Document::LanguageId,
|
64
|
-
encoding: Encoding,
|
65
65
|
).returns(Document[T.untyped])
|
66
66
|
end
|
67
|
-
def set(uri:, source:, version:, language_id
|
67
|
+
def set(uri:, source:, version:, language_id:)
|
68
68
|
@state[uri.to_s] = case language_id
|
69
69
|
when Document::LanguageId::ERB
|
70
|
-
ERBDocument.new(source: source, version: version, uri: uri,
|
70
|
+
ERBDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
71
71
|
when Document::LanguageId::RBS
|
72
|
-
RBSDocument.new(source: source, version: version, uri: uri,
|
72
|
+
RBSDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
73
73
|
else
|
74
|
-
RubyDocument.new(source: source, version: version, uri: uri,
|
74
|
+
RubyDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -91,29 +91,45 @@ module RubyLsp
|
|
91
91
|
return Type.new("#{last}::<Class:#{last}>") if parts.empty?
|
92
92
|
|
93
93
|
Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
|
94
|
-
|
94
|
+
when Prism::CallNode
|
95
|
+
raw_receiver = receiver.message
|
95
96
|
|
96
|
-
raw_receiver
|
97
|
-
receiver
|
98
|
-
|
99
|
-
receiver
|
100
|
-
end
|
97
|
+
if raw_receiver == "new"
|
98
|
+
# When invoking `new`, we recursively infer the type of the receiver to get the class type its being invoked
|
99
|
+
# on and then return the attached version of that type, since it's being instantiated.
|
100
|
+
type = infer_receiver_for_call_node(receiver, node_context)
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
name = entries&.first&.name
|
112
|
-
GuessedType.new(name) if name
|
102
|
+
return unless type
|
103
|
+
|
104
|
+
# If the method `new` was overridden, then we cannot assume that it will return a new instance of the class
|
105
|
+
new_method = @index.resolve_method("new", type.name)&.first
|
106
|
+
return if new_method && new_method.owner&.name != "Class"
|
107
|
+
|
108
|
+
type.attached
|
109
|
+
elsif raw_receiver
|
110
|
+
guess_type(raw_receiver, node_context.nesting)
|
113
111
|
end
|
112
|
+
else
|
113
|
+
guess_type(receiver.slice, node_context.nesting)
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
sig { params(raw_receiver: String, nesting: T::Array[String]).returns(T.nilable(GuessedType)) }
|
118
|
+
def guess_type(raw_receiver, nesting)
|
119
|
+
guessed_name = raw_receiver
|
120
|
+
.delete_prefix("@")
|
121
|
+
.delete_prefix("@@")
|
122
|
+
.split("_")
|
123
|
+
.map(&:capitalize)
|
124
|
+
.join
|
125
|
+
|
126
|
+
entries = @index.resolve(guessed_name, nesting) || @index.first_unqualified_const(guessed_name)
|
127
|
+
name = entries&.first&.name
|
128
|
+
return unless name
|
129
|
+
|
130
|
+
GuessedType.new(name)
|
131
|
+
end
|
132
|
+
|
117
133
|
sig { params(node_context: NodeContext).returns(Type) }
|
118
134
|
def self_receiver_handling(node_context)
|
119
135
|
nesting = node_context.nesting
|
@@ -176,6 +192,12 @@ module RubyLsp
|
|
176
192
|
def initialize(name)
|
177
193
|
@name = name
|
178
194
|
end
|
195
|
+
|
196
|
+
# Returns the attached version of this type by removing the `<Class:...>` part from its name
|
197
|
+
sig { returns(Type) }
|
198
|
+
def attached
|
199
|
+
Type.new(T.must(@name.split("::")[..-2]).join("::"))
|
200
|
+
end
|
179
201
|
end
|
180
202
|
|
181
203
|
# A type that was guessed based on the receiver raw name
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -142,6 +142,20 @@ module RubyLsp
|
|
142
142
|
),
|
143
143
|
)
|
144
144
|
end
|
145
|
+
|
146
|
+
sig do
|
147
|
+
params(
|
148
|
+
uri: String,
|
149
|
+
diagnostics: T::Array[Interface::Diagnostic],
|
150
|
+
version: T.nilable(Integer),
|
151
|
+
).returns(Notification)
|
152
|
+
end
|
153
|
+
def publish_diagnostics(uri, diagnostics, version: nil)
|
154
|
+
new(
|
155
|
+
method: "textDocument/publishDiagnostics",
|
156
|
+
params: Interface::PublishDiagnosticsParams.new(uri: uri, diagnostics: diagnostics, version: version),
|
157
|
+
)
|
158
|
+
end
|
145
159
|
end
|
146
160
|
|
147
161
|
extend T::Sig
|
@@ -155,6 +169,35 @@ module RubyLsp
|
|
155
169
|
class Request < Message
|
156
170
|
extend T::Sig
|
157
171
|
|
172
|
+
class << self
|
173
|
+
extend T::Sig
|
174
|
+
|
175
|
+
sig { params(id: Integer, pattern: T.any(Interface::RelativePattern, String), kind: Integer).returns(Request) }
|
176
|
+
def register_watched_files(
|
177
|
+
id,
|
178
|
+
pattern,
|
179
|
+
kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE
|
180
|
+
)
|
181
|
+
new(
|
182
|
+
id: id,
|
183
|
+
method: "client/registerCapability",
|
184
|
+
params: Interface::RegistrationParams.new(
|
185
|
+
registrations: [
|
186
|
+
Interface::Registration.new(
|
187
|
+
id: "workspace/didChangeWatchedFiles",
|
188
|
+
method: "workspace/didChangeWatchedFiles",
|
189
|
+
register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
|
190
|
+
watchers: [
|
191
|
+
Interface::FileSystemWatcher.new(glob_pattern: pattern, kind: kind),
|
192
|
+
],
|
193
|
+
),
|
194
|
+
),
|
195
|
+
],
|
196
|
+
),
|
197
|
+
)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
158
201
|
sig { params(id: T.any(Integer, String), method: String, params: Object).void }
|
159
202
|
def initialize(id:, method:, params:)
|
160
203
|
@id = id
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.23.
|
4
|
+
version: 0.23.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-01-
|
10
|
+
date: 2025-01-10 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: language_server-protocol
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
|
107
107
|
- lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb
|
108
108
|
- lib/ruby_indexer/lib/ruby_indexer/uri.rb
|
109
|
+
- lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb
|
109
110
|
- lib/ruby_indexer/ruby_indexer.rb
|
110
111
|
- lib/ruby_indexer/test/class_variables_test.rb
|
111
112
|
- lib/ruby_indexer/test/classes_and_modules_test.rb
|