ruby-lsp 0.23.1 → 0.23.2
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/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/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(
|
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
|