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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp-launcher +18 -9
  4. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +89 -58
  5. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +21 -10
  6. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +36 -0
  7. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  8. data/lib/ruby_indexer/test/index_test.rb +2 -1
  9. data/lib/ruby_indexer/test/instance_variables_test.rb +20 -0
  10. data/lib/ruby_indexer/test/method_test.rb +86 -8
  11. data/lib/ruby_lsp/base_server.rb +11 -21
  12. data/lib/ruby_lsp/document.rb +62 -9
  13. data/lib/ruby_lsp/erb_document.rb +5 -3
  14. data/lib/ruby_lsp/global_state.rb +9 -0
  15. data/lib/ruby_lsp/listeners/definition.rb +7 -2
  16. data/lib/ruby_lsp/rbs_document.rb +2 -2
  17. data/lib/ruby_lsp/requests/code_action_resolve.rb +2 -6
  18. data/lib/ruby_lsp/requests/completion.rb +2 -1
  19. data/lib/ruby_lsp/requests/definition.rb +1 -1
  20. data/lib/ruby_lsp/requests/document_highlight.rb +1 -1
  21. data/lib/ruby_lsp/requests/hover.rb +1 -1
  22. data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
  23. data/lib/ruby_lsp/requests/references.rb +1 -1
  24. data/lib/ruby_lsp/requests/rename.rb +3 -3
  25. data/lib/ruby_lsp/requests/show_syntax_tree.rb +1 -4
  26. data/lib/ruby_lsp/requests/signature_help.rb +1 -1
  27. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +3 -3
  28. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -13
  29. data/lib/ruby_lsp/requests/workspace_symbol.rb +2 -2
  30. data/lib/ruby_lsp/ruby_document.rb +75 -6
  31. data/lib/ruby_lsp/server.rb +69 -49
  32. data/lib/ruby_lsp/store.rb +7 -7
  33. data/lib/ruby_lsp/type_inferrer.rb +39 -17
  34. data/lib/ruby_lsp/utils.rb +43 -0
  35. metadata +3 -2
@@ -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))
@@ -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
- @mutex.synchronize do
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
- @mutex.synchronize do
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
- @mutex.synchronize do
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
- dispatcher.dispatch(parse_result.value)
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
- 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
1006
+ if file_path.end_with?(".rb")
1007
+ handle_ruby_file_change(index, file_path, change[:type])
1008
+ next
1009
+ end
1018
1010
 
1019
- content = File.read(file_path)
1011
+ file_name = File.basename(file_path)
1020
1012
 
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)
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
@@ -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:, encoding: Encoding::UTF_8)
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, encoding: encoding)
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, encoding: encoding)
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, encoding: encoding)
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
- 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
@@ -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.1
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-06 00:00:00.000000000 Z
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