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.
@@ -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, encoding: Encoding).void }
23
- def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
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
- create_scanner.find_char_position(position),
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, encoding: Encoding).void }
12
- def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
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
- scanner = @document.create_scanner
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
- scanner = @document.create_scanner
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.create_scanner.find_char_position(params[:position]) - 1
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.create_scanner.find_char_position(position)
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.create_scanner.find_char_position(position)
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.create_scanner.find_char_position(position)
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.create_scanner.find_char_position(@position)
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.create_scanner.find_char_position(position)
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.create_scanner.find_char_position(@position)
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.create_scanner.find_char_position(position)
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, encoding: Encoding).void }
146
- def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
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
- scanner = create_scanner
202
- start_position = scanner.find_char_position(range[:start])
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
- create_scanner.find_char_position(position),
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
@@ -107,7 +107,7 @@ module RubyLsp
107
107
  ),
108
108
  )
109
109
  when "$/cancelRequest"
110
- @mutex.synchronize { @cancelled_requests << message[:params][:id] }
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
- Request.new(
303
- id: @current_request_id,
304
- method: "client/registerCapability",
305
- params: Interface::RegistrationParams.new(
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
- @mutex.synchronize do
381
- text_document = message.dig(:params, :textDocument)
382
- language_id = case text_document[:languageId]
383
- when "erb", "eruby"
384
- Document::LanguageId::ERB
385
- when "rbs"
386
- Document::LanguageId::RBS
387
- else
388
- Document::LanguageId::Ruby
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
- document = @store.set(
392
- uri: text_document[:uri],
393
- source: text_document[:text],
394
- version: text_document[:version],
395
- encoding: @global_state.encoding,
396
- language_id: language_id,
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
- if document.past_expensive_limit? && text_document[:uri].scheme == "file"
400
- log_message = <<~MESSAGE
401
- The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
402
- diagnostics will be disabled.
403
- MESSAGE
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
- send_message(
406
- Notification.new(
407
- method: "window/logMessage",
408
- params: Interface::LogMessageParams.new(
409
- type: Constant::MessageType::WARNING,
410
- message: log_message,
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
- end
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
- @mutex.synchronize do
421
- uri = message.dig(:params, :textDocument, :uri)
422
- @store.delete(uri)
399
+ uri = message.dig(:params, :textDocument, :uri)
400
+ @store.delete(uri)
423
401
 
424
- # 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
- )
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
- @mutex.synchronize do
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
- dispatcher.dispatch(parse_result.value)
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
- load_path_entry = $LOAD_PATH.find { |load_path| file_path.start_with?(load_path) }
1017
- uri.add_require_path_from_load_entry(load_path_entry) if load_path_entry
1000
+ if file_path.end_with?(".rb")
1001
+ handle_ruby_file_change(index, file_path, change[:type])
1002
+ next
1003
+ end
1018
1004
 
1019
- content = File.read(file_path)
1005
+ file_name = File.basename(file_path)
1020
1006
 
1021
- case change[:type]
1022
- when Constant::FileChangeType::CREATED
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"]}_"
@@ -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:, encoding: Encoding::UTF_8)
68
- @state[uri.to_s] = case language_id
69
- when Document::LanguageId::ERB
70
- ERBDocument.new(source: source, version: version, uri: uri, encoding: encoding)
71
- when Document::LanguageId::RBS
72
- RBSDocument.new(source: source, version: version, uri: uri, encoding: encoding)
73
- else
74
- RubyDocument.new(source: source, version: version, uri: uri, encoding: encoding)
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
- @state.delete(uri.to_s)
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
- else
94
+ when Prism::CallNode
95
+ raw_receiver = receiver.message
95
96
 
96
- raw_receiver = if receiver.is_a?(Prism::CallNode)
97
- receiver.message
98
- else
99
- receiver.slice
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
- if raw_receiver
103
- guessed_name = raw_receiver
104
- .delete_prefix("@")
105
- .delete_prefix("@@")
106
- .split("_")
107
- .map(&:capitalize)
108
- .join
109
-
110
- entries = @index.resolve(guessed_name, node_context.nesting) || @index.first_unqualified_const(guessed_name)
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