ruby-lsp 0.23.0 → 0.23.2
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 +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +59 -30
- 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 +71 -8
- data/lib/ruby_lsp/base_server.rb +8 -13
- data/lib/ruby_lsp/document.rb +74 -19
- data/lib/ruby_lsp/erb_document.rb +5 -3
- data/lib/ruby_lsp/global_state.rb +9 -0
- 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 +1 -1
- 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/ruby_document.rb +75 -6
- data/lib/ruby_lsp/server.rb +95 -81
- data/lib/ruby_lsp/setup_bundler.rb +1 -1
- data/lib/ruby_lsp/store.rb +16 -12
- data/lib/ruby_lsp/type_inferrer.rb +39 -17
- data/lib/ruby_lsp/utils.rb +43 -0
- metadata +3 -2
@@ -19,8 +19,8 @@ module RubyLsp
|
|
19
19
|
end
|
20
20
|
attr_reader :code_units_cache
|
21
21
|
|
22
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
23
|
-
def initialize(source:, version:, uri:,
|
22
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
23
|
+
def initialize(source:, version:, uri:, global_state:)
|
24
24
|
# This has to be initialized before calling super because we call `parse` in the parent constructor, which
|
25
25
|
# overrides this with the proper virtual host language source
|
26
26
|
@host_language_source = T.let("", String)
|
@@ -63,9 +63,11 @@ module RubyLsp
|
|
63
63
|
).returns(NodeContext)
|
64
64
|
end
|
65
65
|
def locate_node(position, node_types: [])
|
66
|
+
char_position, _ = find_index_by_position(position)
|
67
|
+
|
66
68
|
RubyDocument.locate(
|
67
69
|
@parse_result.value,
|
68
|
-
|
70
|
+
char_position,
|
69
71
|
code_units_cache: @code_units_cache,
|
70
72
|
node_types: node_types,
|
71
73
|
)
|
@@ -29,6 +29,9 @@ module RubyLsp
|
|
29
29
|
sig { returns(ClientCapabilities) }
|
30
30
|
attr_reader :client_capabilities
|
31
31
|
|
32
|
+
sig { returns(URI::Generic) }
|
33
|
+
attr_reader :workspace_uri
|
34
|
+
|
32
35
|
sig { void }
|
33
36
|
def initialize
|
34
37
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
@@ -53,6 +56,12 @@ module RubyLsp
|
|
53
56
|
)
|
54
57
|
@client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
|
55
58
|
@enabled_feature_flags = T.let({}, T::Hash[Symbol, T::Boolean])
|
59
|
+
@mutex = T.let(Mutex.new, Mutex)
|
60
|
+
end
|
61
|
+
|
62
|
+
sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
|
63
|
+
def synchronize(&block)
|
64
|
+
@mutex.synchronize(&block)
|
56
65
|
end
|
57
66
|
|
58
67
|
sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
@@ -8,8 +8,8 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
ParseResultType = type_member { { fixed: T::Array[RBS::AST::Declarations::Base] } }
|
10
10
|
|
11
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
12
|
-
def initialize(source:, version:, uri:,
|
11
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
12
|
+
def initialize(source:, version:, uri:, global_state:)
|
13
13
|
@syntax_error = T.let(false, T::Boolean)
|
14
14
|
super
|
15
15
|
end
|
@@ -92,9 +92,7 @@ module RubyLsp
|
|
92
92
|
source_range = @code_action.dig(:data, :range)
|
93
93
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
94
94
|
|
95
|
-
|
96
|
-
start_index = scanner.find_char_position(source_range[:start])
|
97
|
-
end_index = scanner.find_char_position(source_range[:end])
|
95
|
+
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
98
96
|
extracted_source = T.must(@document.source[start_index...end_index])
|
99
97
|
|
100
98
|
# Find the closest statements node, so that we place the refactor in a valid position
|
@@ -192,9 +190,7 @@ module RubyLsp
|
|
192
190
|
source_range = @code_action.dig(:data, :range)
|
193
191
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
194
192
|
|
195
|
-
|
196
|
-
start_index = scanner.find_char_position(source_range[:start])
|
197
|
-
end_index = scanner.find_char_position(source_range[:end])
|
193
|
+
start_index, end_index = @document.find_index_by_position(source_range[:start], source_range[:end])
|
198
194
|
extracted_source = T.must(@document.source[start_index...end_index])
|
199
195
|
|
200
196
|
# Find the closest method declaration node, so that we place the refactor in a valid position
|
@@ -40,7 +40,8 @@ module RubyLsp
|
|
40
40
|
@dispatcher = dispatcher
|
41
41
|
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
42
42
|
# back by 1, so that we find the right node
|
43
|
-
char_position = document.
|
43
|
+
char_position, _ = document.find_index_by_position(params[:position])
|
44
|
+
char_position -= 1
|
44
45
|
delegate_request_if_needed!(global_state, document, char_position)
|
45
46
|
|
46
47
|
node_context = RubyDocument.locate(
|
@@ -29,7 +29,7 @@ module RubyLsp
|
|
29
29
|
)
|
30
30
|
@dispatcher = dispatcher
|
31
31
|
|
32
|
-
char_position = document.
|
32
|
+
char_position, _ = document.find_index_by_position(position)
|
33
33
|
delegate_request_if_needed!(global_state, document, char_position)
|
34
34
|
|
35
35
|
node_context = RubyDocument.locate(
|
@@ -25,7 +25,7 @@ module RubyLsp
|
|
25
25
|
end
|
26
26
|
def initialize(global_state, document, position, dispatcher)
|
27
27
|
super()
|
28
|
-
char_position = document.
|
28
|
+
char_position, _ = document.find_index_by_position(position)
|
29
29
|
delegate_request_if_needed!(global_state, document, char_position)
|
30
30
|
|
31
31
|
node_context = RubyDocument.locate(
|
@@ -34,7 +34,7 @@ module RubyLsp
|
|
34
34
|
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
35
35
|
super()
|
36
36
|
|
37
|
-
char_position = document.
|
37
|
+
char_position, _ = document.find_index_by_position(position)
|
38
38
|
delegate_request_if_needed!(global_state, document, char_position)
|
39
39
|
|
40
40
|
node_context = RubyDocument.locate(
|
@@ -24,7 +24,7 @@ module RubyLsp
|
|
24
24
|
|
25
25
|
sig { override.returns(T.nilable(Interface::Range)) }
|
26
26
|
def perform
|
27
|
-
char_position = @document.
|
27
|
+
char_position, _ = @document.find_index_by_position(@position)
|
28
28
|
|
29
29
|
node_context = RubyDocument.locate(
|
30
30
|
@document.parse_result.value,
|
@@ -30,7 +30,7 @@ module RubyLsp
|
|
30
30
|
sig { override.returns(T::Array[Interface::Location]) }
|
31
31
|
def perform
|
32
32
|
position = @params[:position]
|
33
|
-
char_position = @document.
|
33
|
+
char_position, _ = @document.find_index_by_position(position)
|
34
34
|
|
35
35
|
node_context = RubyDocument.locate(
|
36
36
|
@document.parse_result.value,
|
@@ -40,7 +40,7 @@ module RubyLsp
|
|
40
40
|
|
41
41
|
sig { override.returns(T.nilable(Interface::WorkspaceEdit)) }
|
42
42
|
def perform
|
43
|
-
char_position = @document.
|
43
|
+
char_position, _ = @document.find_index_by_position(@position)
|
44
44
|
|
45
45
|
node_context = RubyDocument.locate(
|
46
46
|
@document.parse_result.value,
|
@@ -31,10 +31,7 @@ module RubyLsp
|
|
31
31
|
sig { returns(String) }
|
32
32
|
def ast_for_range
|
33
33
|
range = T.must(@range)
|
34
|
-
|
35
|
-
scanner = @document.create_scanner
|
36
|
-
start_char = scanner.find_char_position(range[:start])
|
37
|
-
end_char = scanner.find_char_position(range[:end])
|
34
|
+
start_char, end_char = @document.find_index_by_position(range[:start], range[:end])
|
38
35
|
|
39
36
|
queue = @tree.statements.body.dup
|
40
37
|
found_nodes = []
|
@@ -36,7 +36,7 @@ module RubyLsp
|
|
36
36
|
def initialize(document, global_state, position, context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
37
37
|
super()
|
38
38
|
|
39
|
-
char_position = document.
|
39
|
+
char_position, _ = document.find_index_by_position(position)
|
40
40
|
delegate_request_if_needed!(global_state, document, char_position)
|
41
41
|
|
42
42
|
node_context = RubyDocument.locate(
|
@@ -8,6 +8,22 @@ module RubyLsp
|
|
8
8
|
|
9
9
|
ParseResultType = type_member { { fixed: Prism::ParseResult } }
|
10
10
|
|
11
|
+
METHODS_THAT_CHANGE_DECLARATIONS = [
|
12
|
+
:private_constant,
|
13
|
+
:attr_reader,
|
14
|
+
:attr_writer,
|
15
|
+
:attr_accessor,
|
16
|
+
:alias_method,
|
17
|
+
:include,
|
18
|
+
:prepend,
|
19
|
+
:extend,
|
20
|
+
:public,
|
21
|
+
:protected,
|
22
|
+
:private,
|
23
|
+
:module_function,
|
24
|
+
:private_class_method,
|
25
|
+
].freeze
|
26
|
+
|
11
27
|
class SorbetLevel < T::Enum
|
12
28
|
enums do
|
13
29
|
None = new("none")
|
@@ -142,8 +158,8 @@ module RubyLsp
|
|
142
158
|
end
|
143
159
|
attr_reader :code_units_cache
|
144
160
|
|
145
|
-
sig { params(source: String, version: Integer, uri: URI::Generic,
|
146
|
-
def initialize(source:, version:, uri:,
|
161
|
+
sig { params(source: String, version: Integer, uri: URI::Generic, global_state: GlobalState).void }
|
162
|
+
def initialize(source:, version:, uri:, global_state:)
|
147
163
|
super
|
148
164
|
@code_units_cache = T.let(@parse_result.code_units_cache(@encoding), T.any(
|
149
165
|
T.proc.params(arg0: Integer).returns(Integer),
|
@@ -198,9 +214,8 @@ module RubyLsp
|
|
198
214
|
).returns(T.nilable(Prism::Node))
|
199
215
|
end
|
200
216
|
def locate_first_within_range(range, node_types: [])
|
201
|
-
|
202
|
-
|
203
|
-
end_position = scanner.find_char_position(range[:end])
|
217
|
+
start_position, end_position = find_index_by_position(range[:start], range[:end])
|
218
|
+
|
204
219
|
desired_range = (start_position...end_position)
|
205
220
|
queue = T.let(@parse_result.value.child_nodes.compact, T::Array[T.nilable(Prism::Node)])
|
206
221
|
|
@@ -232,12 +247,66 @@ module RubyLsp
|
|
232
247
|
).returns(NodeContext)
|
233
248
|
end
|
234
249
|
def locate_node(position, node_types: [])
|
250
|
+
char_position, _ = find_index_by_position(position)
|
251
|
+
|
235
252
|
RubyDocument.locate(
|
236
253
|
@parse_result.value,
|
237
|
-
|
254
|
+
char_position,
|
238
255
|
code_units_cache: @code_units_cache,
|
239
256
|
node_types: node_types,
|
240
257
|
)
|
241
258
|
end
|
259
|
+
|
260
|
+
sig { returns(T::Boolean) }
|
261
|
+
def last_edit_may_change_declarations?
|
262
|
+
# This method controls when we should index documents. If there's no recent edit and the document has just been
|
263
|
+
# opened, we need to index it
|
264
|
+
return true unless @last_edit
|
265
|
+
|
266
|
+
case @last_edit
|
267
|
+
when Delete
|
268
|
+
# Not optimized yet. It's not trivial to identify that a declaration has been removed since the source is no
|
269
|
+
# longer there and we don't remember the deleted text
|
270
|
+
true
|
271
|
+
when Insert, Replace
|
272
|
+
position_may_impact_declarations?(@last_edit.range[:start])
|
273
|
+
else
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
sig { params(position: T::Hash[Symbol, Integer]).returns(T::Boolean) }
|
281
|
+
def position_may_impact_declarations?(position)
|
282
|
+
node_context = locate_node(position)
|
283
|
+
node_at_edit = node_context.node
|
284
|
+
|
285
|
+
# Adjust to the parent when editing the constant of a class/module declaration
|
286
|
+
if node_at_edit.is_a?(Prism::ConstantReadNode) || node_at_edit.is_a?(Prism::ConstantPathNode)
|
287
|
+
node_at_edit = node_context.parent
|
288
|
+
end
|
289
|
+
|
290
|
+
case node_at_edit
|
291
|
+
when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefNode,
|
292
|
+
Prism::ConstantPathWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathOperatorWriteNode,
|
293
|
+
Prism::ConstantPathAndWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantWriteNode,
|
294
|
+
Prism::ConstantAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::GlobalVariableAndWriteNode,
|
295
|
+
Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableTargetNode,
|
296
|
+
Prism::GlobalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::InstanceVariableAndWriteNode,
|
297
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
|
298
|
+
Prism::InstanceVariableTargetNode, Prism::AliasMethodNode
|
299
|
+
true
|
300
|
+
when Prism::MultiWriteNode
|
301
|
+
[*node_at_edit.lefts, *node_at_edit.rest, *node_at_edit.rights].any? do |node|
|
302
|
+
node.is_a?(Prism::ConstantTargetNode) || node.is_a?(Prism::ConstantPathTargetNode)
|
303
|
+
end
|
304
|
+
when Prism::CallNode
|
305
|
+
receiver = node_at_edit.receiver
|
306
|
+
(!receiver || receiver.is_a?(Prism::SelfNode)) && METHODS_THAT_CHANGE_DECLARATIONS.include?(node_at_edit.name)
|
307
|
+
else
|
308
|
+
false
|
309
|
+
end
|
310
|
+
end
|
242
311
|
end
|
243
312
|
end
|
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))
|
@@ -377,58 +359,48 @@ module RubyLsp
|
|
377
359
|
|
378
360
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
379
361
|
def text_document_did_open(message)
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
end
|
362
|
+
text_document = message.dig(:params, :textDocument)
|
363
|
+
language_id = case text_document[:languageId]
|
364
|
+
when "erb", "eruby"
|
365
|
+
Document::LanguageId::ERB
|
366
|
+
when "rbs"
|
367
|
+
Document::LanguageId::RBS
|
368
|
+
else
|
369
|
+
Document::LanguageId::Ruby
|
370
|
+
end
|
390
371
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
)
|
372
|
+
document = @store.set(
|
373
|
+
uri: text_document[:uri],
|
374
|
+
source: text_document[:text],
|
375
|
+
version: text_document[:version],
|
376
|
+
language_id: language_id,
|
377
|
+
)
|
398
378
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
379
|
+
if document.past_expensive_limit? && text_document[:uri].scheme == "file"
|
380
|
+
log_message = <<~MESSAGE
|
381
|
+
The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
|
382
|
+
diagnostics will be disabled.
|
383
|
+
MESSAGE
|
404
384
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
),
|
385
|
+
send_message(
|
386
|
+
Notification.new(
|
387
|
+
method: "window/logMessage",
|
388
|
+
params: Interface::LogMessageParams.new(
|
389
|
+
type: Constant::MessageType::WARNING,
|
390
|
+
message: log_message,
|
412
391
|
),
|
413
|
-
)
|
414
|
-
|
392
|
+
),
|
393
|
+
)
|
415
394
|
end
|
416
395
|
end
|
417
396
|
|
418
397
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
419
398
|
def text_document_did_close(message)
|
420
|
-
|
421
|
-
|
422
|
-
@store.delete(uri)
|
399
|
+
uri = message.dig(:params, :textDocument, :uri)
|
400
|
+
@store.delete(uri)
|
423
401
|
|
424
|
-
|
425
|
-
|
426
|
-
Notification.new(
|
427
|
-
method: "textDocument/publishDiagnostics",
|
428
|
-
params: Interface::PublishDiagnosticsParams.new(uri: uri.to_s, diagnostics: []),
|
429
|
-
),
|
430
|
-
)
|
431
|
-
end
|
402
|
+
# Clear diagnostics for the closed file, so that they no longer appear in the problems tab
|
403
|
+
send_message(Notification.publish_diagnostics(uri.to_s, []))
|
432
404
|
end
|
433
405
|
|
434
406
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
@@ -436,9 +408,7 @@ module RubyLsp
|
|
436
408
|
params = message[:params]
|
437
409
|
text_document = params[:textDocument]
|
438
410
|
|
439
|
-
@
|
440
|
-
@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
|
441
|
-
end
|
411
|
+
@store.push_edits(uri: text_document[:uri], edits: params[:contentChanges], version: text_document[:version])
|
442
412
|
end
|
443
413
|
|
444
414
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
@@ -493,7 +463,22 @@ module RubyLsp
|
|
493
463
|
document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
|
494
464
|
code_lens = Requests::CodeLens.new(@global_state, uri, dispatcher)
|
495
465
|
inlay_hint = Requests::InlayHints.new(document, T.must(@store.features_configuration.dig(:inlayHint)), dispatcher)
|
496
|
-
|
466
|
+
|
467
|
+
if document.is_a?(RubyDocument) && document.last_edit_may_change_declarations?
|
468
|
+
# Re-index the file as it is modified. This mode of indexing updates entries only. Require path trees are only
|
469
|
+
# updated on save
|
470
|
+
@global_state.synchronize do
|
471
|
+
send_log_message("Detected that last edit may have modified declarations. Re-indexing #{uri}")
|
472
|
+
|
473
|
+
@global_state.index.handle_change(uri) do |index|
|
474
|
+
index.delete(uri, skip_require_paths_tree: true)
|
475
|
+
RubyIndexer::DeclarationListener.new(index, dispatcher, parse_result, uri, collect_comments: true)
|
476
|
+
dispatcher.dispatch(parse_result.value)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
else
|
480
|
+
dispatcher.dispatch(parse_result.value)
|
481
|
+
end
|
497
482
|
|
498
483
|
# Store all responses retrieve in this round of visits in the cache and then return the response for the request
|
499
484
|
# we actually received
|
@@ -1011,26 +996,55 @@ module RubyLsp
|
|
1011
996
|
uri = URI(change[:uri])
|
1012
997
|
file_path = uri.to_standardized_path
|
1013
998
|
next if file_path.nil? || File.directory?(file_path)
|
1014
|
-
next unless file_path.end_with?(".rb")
|
1015
999
|
|
1016
|
-
|
1017
|
-
|
1000
|
+
if file_path.end_with?(".rb")
|
1001
|
+
handle_ruby_file_change(index, file_path, change[:type])
|
1002
|
+
next
|
1003
|
+
end
|
1018
1004
|
|
1019
|
-
|
1005
|
+
file_name = File.basename(file_path)
|
1020
1006
|
|
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)
|
1007
|
+
if file_name == ".rubocop.yml" || file_name == ".rubocop"
|
1008
|
+
handle_rubocop_config_change(uri)
|
1028
1009
|
end
|
1029
1010
|
end
|
1030
1011
|
|
1031
1012
|
Addon.file_watcher_addons.each { |addon| T.unsafe(addon).workspace_did_change_watched_files(changes) }
|
1032
1013
|
end
|
1033
1014
|
|
1015
|
+
sig { params(index: RubyIndexer::Index, file_path: String, change_type: Integer).void }
|
1016
|
+
def handle_ruby_file_change(index, file_path, change_type)
|
1017
|
+
load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
|
1018
|
+
uri = URI::Generic.from_path(load_path_entry: load_path_entry, path: file_path)
|
1019
|
+
|
1020
|
+
content = File.read(file_path)
|
1021
|
+
|
1022
|
+
case change_type
|
1023
|
+
when Constant::FileChangeType::CREATED
|
1024
|
+
index.index_single(uri, content)
|
1025
|
+
when Constant::FileChangeType::CHANGED
|
1026
|
+
index.handle_change(uri, content)
|
1027
|
+
when Constant::FileChangeType::DELETED
|
1028
|
+
index.delete(uri)
|
1029
|
+
end
|
1030
|
+
end
|
1031
|
+
|
1032
|
+
sig { params(uri: URI::Generic).void }
|
1033
|
+
def handle_rubocop_config_change(uri)
|
1034
|
+
return unless defined?(Requests::Support::RuboCopFormatter)
|
1035
|
+
|
1036
|
+
send_log_message("Reloading RuboCop since #{uri} changed")
|
1037
|
+
@global_state.register_formatter("rubocop", Requests::Support::RuboCopFormatter.new)
|
1038
|
+
|
1039
|
+
# Clear all existing diagnostics since the config changed. This has to happen under a mutex because the `state`
|
1040
|
+
# hash cannot be mutated during iteration or that will throw an error
|
1041
|
+
@global_state.synchronize do
|
1042
|
+
@store.each do |uri, _document|
|
1043
|
+
send_message(Notification.publish_diagnostics(uri.to_s, []))
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
|
1034
1048
|
sig { params(message: T::Hash[Symbol, T.untyped]).void }
|
1035
1049
|
def workspace_symbol(message)
|
1036
1050
|
send_message(
|
@@ -281,7 +281,7 @@ module RubyLsp
|
|
281
281
|
# When not updating, we run `(bundle check || bundle install)`
|
282
282
|
# When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
|
283
283
|
bundler_path = File.join(Gem.default_bindir, "bundle")
|
284
|
-
base_command = (File.exist?(bundler_path) ? "#{Gem.ruby} #{bundler_path}" : "bundle").dup
|
284
|
+
base_command = (!Gem.win_platform? && File.exist?(bundler_path) ? "#{Gem.ruby} #{bundler_path}" : "bundle").dup
|
285
285
|
|
286
286
|
if env["BUNDLER_VERSION"]
|
287
287
|
base_command << " _#{env["BUNDLER_VERSION"]}_"
|
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,18 @@ 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
|
68
|
-
@
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
67
|
+
def set(uri:, source:, version:, language_id:)
|
68
|
+
@global_state.synchronize do
|
69
|
+
@state[uri.to_s] = case language_id
|
70
|
+
when Document::LanguageId::ERB
|
71
|
+
ERBDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
72
|
+
when Document::LanguageId::RBS
|
73
|
+
RBSDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
74
|
+
else
|
75
|
+
RubyDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
76
|
+
end
|
75
77
|
end
|
76
78
|
end
|
77
79
|
|
@@ -92,7 +94,9 @@ module RubyLsp
|
|
92
94
|
|
93
95
|
sig { params(uri: URI::Generic).void }
|
94
96
|
def delete(uri)
|
95
|
-
@
|
97
|
+
@global_state.synchronize do
|
98
|
+
@state.delete(uri.to_s)
|
99
|
+
end
|
96
100
|
end
|
97
101
|
|
98
102
|
sig { params(uri: URI::Generic).returns(T::Boolean) }
|
@@ -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
|