ruby-lsp 0.18.3 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/core_ext/uri.rb +9 -4
  4. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +66 -8
  6. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +56 -32
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -2
  8. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +32 -8
  9. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +262 -0
  10. data/lib/ruby_indexer/ruby_indexer.rb +1 -0
  11. data/lib/ruby_indexer/test/classes_and_modules_test.rb +11 -0
  12. data/lib/ruby_indexer/test/constant_test.rb +8 -0
  13. data/lib/ruby_indexer/test/enhancements_test.rb +2 -0
  14. data/lib/ruby_indexer/test/instance_variables_test.rb +12 -0
  15. data/lib/ruby_indexer/test/method_test.rb +10 -0
  16. data/lib/ruby_indexer/test/rbs_indexer_test.rb +8 -0
  17. data/lib/ruby_indexer/test/reference_finder_test.rb +86 -0
  18. data/lib/ruby_lsp/addon.rb +79 -17
  19. data/lib/ruby_lsp/base_server.rb +6 -0
  20. data/lib/ruby_lsp/erb_document.rb +3 -2
  21. data/lib/ruby_lsp/global_state.rb +8 -0
  22. data/lib/ruby_lsp/internal.rb +3 -1
  23. data/lib/ruby_lsp/listeners/completion.rb +1 -1
  24. data/lib/ruby_lsp/listeners/hover.rb +57 -0
  25. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +24 -21
  26. data/lib/ruby_lsp/requests/completion_resolve.rb +29 -0
  27. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  28. data/lib/ruby_lsp/requests/rename.rb +189 -0
  29. data/lib/ruby_lsp/requests/support/common.rb +2 -2
  30. data/lib/ruby_lsp/requests/support/source_uri.rb +8 -1
  31. data/lib/ruby_lsp/scope.rb +47 -0
  32. data/lib/ruby_lsp/server.rb +64 -32
  33. data/lib/ruby_lsp/static_docs.rb +15 -0
  34. data/lib/ruby_lsp/store.rb +12 -0
  35. data/lib/ruby_lsp/test_helper.rb +1 -1
  36. data/lib/ruby_lsp/type_inferrer.rb +6 -1
  37. data/lib/ruby_lsp/utils.rb +3 -6
  38. data/static_docs/yield.md +81 -0
  39. metadata +19 -8
  40. data/lib/ruby_lsp/parameter_scope.rb +0 -33
@@ -67,6 +67,8 @@ module RubyLsp
67
67
  text_document_definition(message)
68
68
  when "textDocument/prepareTypeHierarchy"
69
69
  text_document_prepare_type_hierarchy(message)
70
+ when "textDocument/rename"
71
+ text_document_rename(message)
70
72
  when "typeHierarchy/supertypes"
71
73
  type_hierarchy_supertypes(message)
72
74
  when "typeHierarchy/subtypes"
@@ -123,9 +125,9 @@ module RubyLsp
123
125
  send_log_message("Error processing #{message[:method]}: #{e.full_message}", type: Constant::MessageType::ERROR)
124
126
  end
125
127
 
126
- sig { void }
127
- def load_addons
128
- errors = Addon.load_addons(@global_state, @outgoing_queue)
128
+ sig { params(include_project_addons: T::Boolean).void }
129
+ def load_addons(include_project_addons: true)
130
+ errors = Addon.load_addons(@global_state, @outgoing_queue, include_project_addons: include_project_addons)
129
131
 
130
132
  if errors.any?
131
133
  send_log_message(
@@ -142,7 +144,7 @@ module RubyLsp
142
144
  method: "window/showMessage",
143
145
  params: Interface::ShowMessageParams.new(
144
146
  type: Constant::MessageType::WARNING,
145
- message: "Error loading addons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
147
+ message: "Error loading add-ons:\n\n#{errored_addons.map(&:formatted_errors).join("\n\n")}",
146
148
  ),
147
149
  ),
148
150
  )
@@ -227,6 +229,7 @@ module RubyLsp
227
229
  workspace_symbol_provider: enabled_features["workspaceSymbol"] && !@global_state.has_type_checker,
228
230
  signature_help_provider: signature_help_provider,
229
231
  type_hierarchy_provider: type_hierarchy_provider,
232
+ rename_provider: !@global_state.has_type_checker,
230
233
  experimental: {
231
234
  addon_detection: true,
232
235
  },
@@ -285,8 +288,9 @@ module RubyLsp
285
288
  rescue RuboCop::Error => e
286
289
  # The user may have provided unknown config switches in .rubocop or
287
290
  # is trying to load a non-existant config file.
288
- send_message(Notification.window_show_error(
291
+ send_message(Notification.window_show_message(
289
292
  "RuboCop configuration error: #{e.message}. Formatting will not be available.",
293
+ type: Constant::MessageType::ERROR,
290
294
  ))
291
295
  end
292
296
  end
@@ -319,14 +323,18 @@ module RubyLsp
319
323
  language_id: language_id,
320
324
  )
321
325
 
322
- if document.past_expensive_limit?
326
+ if document.past_expensive_limit? && text_document[:uri].scheme == "file"
327
+ log_message = <<~MESSAGE
328
+ The file #{text_document[:uri].path} is too long. For performance reasons, semantic highlighting and
329
+ diagnostics will be disabled.
330
+ MESSAGE
331
+
323
332
  send_message(
324
333
  Notification.new(
325
- method: "window/showMessage",
326
- params: Interface::ShowMessageParams.new(
334
+ method: "window/logMessage",
335
+ params: Interface::LogMessageParams.new(
327
336
  type: Constant::MessageType::WARNING,
328
- message: "This file is too long. For performance reasons, semantic highlighting and " \
329
- "diagnostics will be disabled",
337
+ message: log_message,
330
338
  ),
331
339
  ),
332
340
  )
@@ -531,10 +539,16 @@ module RubyLsp
531
539
  response = Requests::Formatting.new(@global_state, document).perform
532
540
  send_message(Result.new(id: message[:id], response: response))
533
541
  rescue Requests::Request::InvalidFormatter => error
534
- send_message(Notification.window_show_error("Configuration error: #{error.message}"))
542
+ send_message(Notification.window_show_message(
543
+ "Configuration error: #{error.message}",
544
+ type: Constant::MessageType::ERROR,
545
+ ))
535
546
  send_empty_response(message[:id])
536
547
  rescue StandardError, LoadError => error
537
- send_message(Notification.window_show_error("Formatting error: #{error.message}"))
548
+ send_message(Notification.window_show_message(
549
+ "Formatting error: #{error.message}",
550
+ type: Constant::MessageType::ERROR,
551
+ ))
538
552
  send_empty_response(message[:id])
539
553
  end
540
554
 
@@ -602,6 +616,26 @@ module RubyLsp
602
616
  )
603
617
  end
604
618
 
619
+ sig { params(message: T::Hash[Symbol, T.untyped]).void }
620
+ def text_document_rename(message)
621
+ params = message[:params]
622
+ document = @store.get(params.dig(:textDocument, :uri))
623
+
624
+ unless document.is_a?(RubyDocument)
625
+ send_empty_response(message[:id])
626
+ return
627
+ end
628
+
629
+ send_message(
630
+ Result.new(
631
+ id: message[:id],
632
+ response: Requests::Rename.new(@global_state, @store, document, params).perform,
633
+ ),
634
+ )
635
+ rescue Requests::Rename::InvalidNameError => e
636
+ send_message(Error.new(id: message[:id], code: Constant::ErrorCodes::REQUEST_FAILED, message: e.message))
637
+ end
638
+
605
639
  sig { params(document: Document[T.untyped]).returns(RubyDocument::SorbetLevel) }
606
640
  def sorbet_level(document)
607
641
  return RubyDocument::SorbetLevel::Ignore unless @global_state.has_type_checker
@@ -673,30 +707,19 @@ module RubyLsp
673
707
  document = @store.get(uri)
674
708
 
675
709
  unless document.is_a?(RubyDocument)
676
- send_message(Notification.window_show_error("Code actions are currently only available for Ruby documents"))
677
- raise Requests::CodeActionResolve::CodeActionError
710
+ fail_request_and_notify(message[:id], "Code actions are currently only available for Ruby documents")
711
+ return
678
712
  end
679
713
 
680
714
  result = Requests::CodeActionResolve.new(document, params).perform
681
715
 
682
716
  case result
683
717
  when Requests::CodeActionResolve::Error::EmptySelection
684
- send_message(Notification.window_show_error("Invalid selection for Extract Variable refactor"))
685
- raise Requests::CodeActionResolve::CodeActionError
718
+ fail_request_and_notify(message[:id], "Invalid selection for extract variable refactor")
686
719
  when Requests::CodeActionResolve::Error::InvalidTargetRange
687
- send_message(
688
- Notification.window_show_error(
689
- "Couldn't find an appropriate location to place extracted refactor",
690
- ),
691
- )
692
- raise Requests::CodeActionResolve::CodeActionError
720
+ fail_request_and_notify(message[:id], "Couldn't find an appropriate location to place extracted refactor")
693
721
  when Requests::CodeActionResolve::Error::UnknownCodeAction
694
- send_message(
695
- Notification.window_show_error(
696
- "Unknown code action",
697
- ),
698
- )
699
- raise Requests::CodeActionResolve::CodeActionError
722
+ fail_request_and_notify(message[:id], "Unknown code action")
700
723
  else
701
724
  send_message(Result.new(id: message[:id], response: result))
702
725
  end
@@ -729,10 +752,16 @@ module RubyLsp
729
752
  ),
730
753
  )
731
754
  rescue Requests::Request::InvalidFormatter => error
732
- send_message(Notification.window_show_error("Configuration error: #{error.message}"))
755
+ send_message(Notification.window_show_message(
756
+ "Configuration error: #{error.message}",
757
+ type: Constant::MessageType::ERROR,
758
+ ))
733
759
  send_empty_response(message[:id])
734
760
  rescue StandardError, LoadError => error
735
- send_message(Notification.window_show_error("Error running diagnostics: #{error.message}"))
761
+ send_message(Notification.window_show_message(
762
+ "Error running diagnostics: #{error.message}",
763
+ type: Constant::MessageType::ERROR,
764
+ ))
736
765
  send_empty_response(message[:id])
737
766
  end
738
767
 
@@ -969,7 +998,9 @@ module RubyLsp
969
998
  false
970
999
  end
971
1000
  rescue StandardError => error
972
- send_message(Notification.window_show_error("Error while indexing: #{error.message}"))
1001
+ message = "Error while indexing (see [troubleshooting steps]" \
1002
+ "(https://shopify.github.io/ruby-lsp/troubleshooting#indexing)): #{error.message}"
1003
+ send_message(Notification.window_show_message(message, type: Constant::MessageType::ERROR))
973
1004
  end
974
1005
 
975
1006
  # Indexing produces a high number of short lived object allocations. That might lead to some fragmentation and
@@ -1054,8 +1085,9 @@ module RubyLsp
1054
1085
  @global_state.formatter = "none"
1055
1086
 
1056
1087
  send_message(
1057
- Notification.window_show_error(
1088
+ Notification.window_show_message(
1058
1089
  "Ruby LSP formatter is set to `rubocop` but RuboCop was not found in the Gemfile or gemspec.",
1090
+ type: Constant::MessageType::ERROR,
1059
1091
  ),
1060
1092
  )
1061
1093
  end
@@ -0,0 +1,15 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # The path to the `static_docs` directory, where we keep long-form static documentation
6
+ STATIC_DOCS_PATH = T.let(File.join(File.dirname(File.dirname(T.must(__dir__))), "static_docs"), String)
7
+
8
+ # A map of keyword => short documentation to be displayed on hover or completion
9
+ KEYWORD_DOCS = T.let(
10
+ {
11
+ "yield" => "Invokes the passed block with the given arguments",
12
+ }.freeze,
13
+ T::Hash[String, String],
14
+ )
15
+ end
@@ -99,6 +99,18 @@ module RubyLsp
99
99
  @state.delete(uri.to_s)
100
100
  end
101
101
 
102
+ sig { params(uri: URI::Generic).returns(T::Boolean) }
103
+ def key?(uri)
104
+ @state.key?(uri.to_s)
105
+ end
106
+
107
+ sig { params(block: T.proc.params(uri: String, document: Document[T.untyped]).void).void }
108
+ def each(&block)
109
+ @state.each do |uri, document|
110
+ block.call(uri, document)
111
+ end
112
+ end
113
+
102
114
  sig do
103
115
  type_parameters(:T)
104
116
  .params(
@@ -42,7 +42,7 @@ module RubyLsp
42
42
  RubyIndexer::IndexablePath.new(nil, T.must(uri.to_standardized_path)),
43
43
  source,
44
44
  )
45
- server.load_addons if load_addons
45
+ server.load_addons(include_project_addons: false) if load_addons
46
46
  block.call(server, uri)
47
47
  ensure
48
48
  if load_addons
@@ -89,7 +89,12 @@ module RubyLsp
89
89
 
90
90
  Type.new("#{parts.join("::")}::#{last}::<Class:#{last}>")
91
91
  else
92
- raw_receiver = node.receiver&.slice
92
+
93
+ raw_receiver = if receiver.is_a?(Prism::CallNode)
94
+ receiver.message
95
+ else
96
+ receiver&.slice
97
+ end
93
98
 
94
99
  if raw_receiver
95
100
  guessed_name = raw_receiver
@@ -64,14 +64,11 @@ module RubyLsp
64
64
  class << self
65
65
  extend T::Sig
66
66
 
67
- sig { params(message: String).returns(Notification) }
68
- def window_show_error(message)
67
+ sig { params(message: String, type: Integer).returns(Notification) }
68
+ def window_show_message(message, type: Constant::MessageType::INFO)
69
69
  new(
70
70
  method: "window/showMessage",
71
- params: Interface::ShowMessageParams.new(
72
- type: Constant::MessageType::ERROR,
73
- message: message,
74
- ),
71
+ params: Interface::ShowMessageParams.new(type: type, message: message),
75
72
  )
76
73
  end
77
74
 
@@ -0,0 +1,81 @@
1
+ # Yield
2
+
3
+ In Ruby, every method implicitly accepts a block, even when not included in the parameters list.
4
+
5
+ ```ruby
6
+ def foo
7
+ end
8
+
9
+ foo { 123 } # works!
10
+ ```
11
+
12
+ The `yield` keyword is used to invoke the block that was passed with arguments.
13
+
14
+ ```ruby
15
+ # Consider this method call. The block being passed to the method `foo` accepts an argument called `a`.
16
+ # It then takes whatever argument was passed and multiplies it by 2
17
+ foo do |a|
18
+ a * 2
19
+ end
20
+
21
+ # In the `foo` method declaration, we can use `yield` to invoke the block that was passed and provide the block
22
+ # with the value for the `a` argument
23
+ def foo
24
+ # Invoke the block passed to `foo` with the number 10 as the argument `a`
25
+ result = yield(10)
26
+ puts result # Will print 20
27
+ end
28
+ ```
29
+
30
+ If `yield` is used to invoke the block, but no block was passed, that will result in a local jump error.
31
+
32
+ ```ruby
33
+ # If we invoke `foo` without a block, trying to `yield` will fail
34
+ foo
35
+
36
+ # `foo': no block given (yield) (LocalJumpError)
37
+ ```
38
+
39
+ We can decide to use `yield` conditionally by using Ruby's `block_given?` method, which will return `true` if a block
40
+ was passed to the method.
41
+
42
+ ```ruby
43
+ def foo
44
+ # If a block is passed when invoking `foo`, call the block with argument 10 and print the result.
45
+ # Otherwise, just print that no block was passed
46
+ if block_given?
47
+ result = yield(10)
48
+ puts result
49
+ else
50
+ puts "No block passed!"
51
+ end
52
+ end
53
+
54
+ foo do |a|
55
+ a * 2
56
+ end
57
+ # => 20
58
+
59
+ foo
60
+ # => No block passed!
61
+ ```
62
+
63
+ ## Block parameter
64
+
65
+ In addition to implicit blocks, Ruby also allows developers to use explicit block parameters as part of the method's
66
+ signature. In this scenario, we can use the reference to the block directly instead of relying on the `yield` keyword.
67
+
68
+ ```ruby
69
+ # Block parameters are prefixed with & and a name
70
+ def foo(&my_block_param)
71
+ # If a block was passed to `foo`, `my_block_param` will be a `Proc` object. Otherwise, it will be `nil`. We can use
72
+ # that to check for its presence
73
+ if my_block_param
74
+ # Explicit block parameters are invoked using the method `call`, which is present in all `Proc` objects
75
+ result = my_block_param.call(10)
76
+ puts result
77
+ else
78
+ puts "No block passed!"
79
+ end
80
+ end
81
+ ```
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.3
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-24 00:00:00.000000000 Z
11
+ date: 2024-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: language_server-protocol
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: prism
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ - - "<"
32
35
  - !ruby/object:Gem::Version
33
- version: '1.0'
36
+ version: '2.0'
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '1.1'
44
+ - - "<"
39
45
  - !ruby/object:Gem::Version
40
- version: '1.0'
46
+ version: '2.0'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: rbs
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -99,6 +105,7 @@ files:
99
105
  - lib/ruby_indexer/lib/ruby_indexer/location.rb
100
106
  - lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
101
107
  - lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
108
+ - lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb
102
109
  - lib/ruby_indexer/ruby_indexer.rb
103
110
  - lib/ruby_indexer/test/classes_and_modules_test.rb
104
111
  - lib/ruby_indexer/test/configuration_test.rb
@@ -109,6 +116,7 @@ files:
109
116
  - lib/ruby_indexer/test/method_test.rb
110
117
  - lib/ruby_indexer/test/prefix_tree_test.rb
111
118
  - lib/ruby_indexer/test/rbs_indexer_test.rb
119
+ - lib/ruby_indexer/test/reference_finder_test.rb
112
120
  - lib/ruby_indexer/test/test_case.rb
113
121
  - lib/ruby_lsp/addon.rb
114
122
  - lib/ruby_lsp/base_server.rb
@@ -129,7 +137,6 @@ files:
129
137
  - lib/ruby_lsp/listeners/signature_help.rb
130
138
  - lib/ruby_lsp/load_sorbet.rb
131
139
  - lib/ruby_lsp/node_context.rb
132
- - lib/ruby_lsp/parameter_scope.rb
133
140
  - lib/ruby_lsp/rbs_document.rb
134
141
  - lib/ruby_lsp/requests/code_action_resolve.rb
135
142
  - lib/ruby_lsp/requests/code_actions.rb
@@ -147,6 +154,7 @@ files:
147
154
  - lib/ruby_lsp/requests/inlay_hints.rb
148
155
  - lib/ruby_lsp/requests/on_type_formatting.rb
149
156
  - lib/ruby_lsp/requests/prepare_type_hierarchy.rb
157
+ - lib/ruby_lsp/requests/rename.rb
150
158
  - lib/ruby_lsp/requests/request.rb
151
159
  - lib/ruby_lsp/requests/selection_ranges.rb
152
160
  - lib/ruby_lsp/requests/semantic_highlighting.rb
@@ -171,12 +179,15 @@ files:
171
179
  - lib/ruby_lsp/response_builders/semantic_highlighting.rb
172
180
  - lib/ruby_lsp/response_builders/signature_help.rb
173
181
  - lib/ruby_lsp/ruby_document.rb
182
+ - lib/ruby_lsp/scope.rb
174
183
  - lib/ruby_lsp/server.rb
175
184
  - lib/ruby_lsp/setup_bundler.rb
185
+ - lib/ruby_lsp/static_docs.rb
176
186
  - lib/ruby_lsp/store.rb
177
187
  - lib/ruby_lsp/test_helper.rb
178
188
  - lib/ruby_lsp/type_inferrer.rb
179
189
  - lib/ruby_lsp/utils.rb
190
+ - static_docs/yield.md
180
191
  homepage: https://github.com/Shopify/ruby-lsp
181
192
  licenses:
182
193
  - MIT
@@ -198,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
209
  - !ruby/object:Gem::Version
199
210
  version: '0'
200
211
  requirements: []
201
- rubygems_version: 3.5.18
212
+ rubygems_version: 3.5.20
202
213
  signing_key:
203
214
  specification_version: 4
204
215
  summary: An opinionated language server for Ruby
@@ -1,33 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module RubyLsp
5
- class ParameterScope
6
- extend T::Sig
7
-
8
- sig { returns(T.nilable(ParameterScope)) }
9
- attr_reader :parent
10
-
11
- sig { params(parent: T.nilable(ParameterScope)).void }
12
- def initialize(parent = nil)
13
- @parent = parent
14
- @parameters = T.let(Set.new, T::Set[Symbol])
15
- end
16
-
17
- sig { params(name: T.any(String, Symbol)).void }
18
- def <<(name)
19
- @parameters << name.to_sym
20
- end
21
-
22
- sig { params(name: T.any(Symbol, String)).returns(Symbol) }
23
- def type_for(name)
24
- parameter?(name) ? :parameter : :variable
25
- end
26
-
27
- sig { params(name: T.any(Symbol, String)).returns(T::Boolean) }
28
- def parameter?(name)
29
- sym = name.to_sym
30
- @parameters.include?(sym) || (!@parent.nil? && @parent.parameter?(sym))
31
- end
32
- end
33
- end