ruby-lsp 0.23.15 → 0.26.9

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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +17 -14
  4. data/exe/ruby-lsp-check +0 -4
  5. data/exe/ruby-lsp-launcher +41 -14
  6. data/exe/ruby-lsp-test-exec +6 -0
  7. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
  8. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
  9. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +4 -3
  10. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +42 -20
  11. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +1 -7
  12. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +49 -62
  13. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +84 -74
  14. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +6 -9
  15. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +9 -14
  16. data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +12 -8
  17. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +4 -4
  18. data/lib/ruby_lsp/addon.rb +44 -15
  19. data/lib/ruby_lsp/base_server.rb +56 -37
  20. data/lib/ruby_lsp/client_capabilities.rb +6 -1
  21. data/lib/ruby_lsp/document.rb +174 -62
  22. data/lib/ruby_lsp/erb_document.rb +10 -8
  23. data/lib/ruby_lsp/global_state.rb +86 -33
  24. data/lib/ruby_lsp/internal.rb +6 -3
  25. data/lib/ruby_lsp/listeners/completion.rb +22 -11
  26. data/lib/ruby_lsp/listeners/definition.rb +41 -21
  27. data/lib/ruby_lsp/listeners/document_highlight.rb +26 -1
  28. data/lib/ruby_lsp/listeners/document_link.rb +64 -28
  29. data/lib/ruby_lsp/listeners/hover.rb +27 -16
  30. data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
  31. data/lib/ruby_lsp/listeners/semantic_highlighting.rb +2 -2
  32. data/lib/ruby_lsp/listeners/signature_help.rb +2 -2
  33. data/lib/ruby_lsp/listeners/spec_style.rb +155 -79
  34. data/lib/ruby_lsp/listeners/test_discovery.rb +39 -21
  35. data/lib/ruby_lsp/listeners/test_style.rb +75 -35
  36. data/lib/ruby_lsp/rbs_document.rb +3 -6
  37. data/lib/ruby_lsp/requests/code_action_resolve.rb +83 -58
  38. data/lib/ruby_lsp/requests/code_actions.rb +20 -5
  39. data/lib/ruby_lsp/requests/code_lens.rb +27 -6
  40. data/lib/ruby_lsp/requests/completion.rb +3 -3
  41. data/lib/ruby_lsp/requests/completion_resolve.rb +8 -6
  42. data/lib/ruby_lsp/requests/definition.rb +4 -7
  43. data/lib/ruby_lsp/requests/discover_tests.rb +2 -2
  44. data/lib/ruby_lsp/requests/document_highlight.rb +2 -2
  45. data/lib/ruby_lsp/requests/document_link.rb +1 -1
  46. data/lib/ruby_lsp/requests/folding_ranges.rb +1 -1
  47. data/lib/ruby_lsp/requests/go_to_relevant_file.rb +64 -12
  48. data/lib/ruby_lsp/requests/hover.rb +3 -6
  49. data/lib/ruby_lsp/requests/inlay_hints.rb +4 -4
  50. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  51. data/lib/ruby_lsp/requests/prepare_rename.rb +1 -1
  52. data/lib/ruby_lsp/requests/references.rb +10 -21
  53. data/lib/ruby_lsp/requests/rename.rb +9 -10
  54. data/lib/ruby_lsp/requests/request.rb +8 -8
  55. data/lib/ruby_lsp/requests/selection_ranges.rb +2 -2
  56. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -1
  57. data/lib/ruby_lsp/requests/show_syntax_tree.rb +2 -2
  58. data/lib/ruby_lsp/requests/signature_help.rb +2 -2
  59. data/lib/ruby_lsp/requests/support/annotation.rb +1 -1
  60. data/lib/ruby_lsp/requests/support/common.rb +9 -12
  61. data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
  62. data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
  63. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +7 -1
  64. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
  65. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +13 -3
  66. data/lib/ruby_lsp/requests/support/source_uri.rb +7 -4
  67. data/lib/ruby_lsp/requests/support/test_item.rb +7 -1
  68. data/lib/ruby_lsp/requests/workspace_symbol.rb +20 -12
  69. data/lib/ruby_lsp/response_builders/collection_response_builder.rb +1 -4
  70. data/lib/ruby_lsp/response_builders/document_symbol.rb +2 -3
  71. data/lib/ruby_lsp/response_builders/hover.rb +1 -4
  72. data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
  73. data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +4 -5
  74. data/lib/ruby_lsp/response_builders/signature_help.rb +1 -2
  75. data/lib/ruby_lsp/response_builders/test_collection.rb +29 -3
  76. data/lib/ruby_lsp/ruby_document.rb +14 -42
  77. data/lib/ruby_lsp/scripts/compose_bundle.rb +3 -3
  78. data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +3 -1
  79. data/lib/ruby_lsp/server.rb +173 -130
  80. data/lib/ruby_lsp/setup_bundler.rb +114 -47
  81. data/lib/ruby_lsp/static_docs.rb +1 -0
  82. data/lib/ruby_lsp/store.rb +6 -16
  83. data/lib/ruby_lsp/test_helper.rb +1 -4
  84. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +121 -17
  85. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +65 -25
  86. data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +16 -18
  87. data/lib/ruby_lsp/utils.rb +102 -13
  88. data/static_docs/break.md +103 -0
  89. metadata +8 -33
  90. data/lib/ruby_indexer/test/class_variables_test.rb +0 -140
  91. data/lib/ruby_indexer/test/classes_and_modules_test.rb +0 -770
  92. data/lib/ruby_indexer/test/configuration_test.rb +0 -280
  93. data/lib/ruby_indexer/test/constant_test.rb +0 -402
  94. data/lib/ruby_indexer/test/enhancements_test.rb +0 -325
  95. data/lib/ruby_indexer/test/global_variable_test.rb +0 -49
  96. data/lib/ruby_indexer/test/index_test.rb +0 -2190
  97. data/lib/ruby_indexer/test/instance_variables_test.rb +0 -240
  98. data/lib/ruby_indexer/test/method_test.rb +0 -973
  99. data/lib/ruby_indexer/test/prefix_tree_test.rb +0 -150
  100. data/lib/ruby_indexer/test/rbs_indexer_test.rb +0 -380
  101. data/lib/ruby_indexer/test/reference_finder_test.rb +0 -330
  102. data/lib/ruby_indexer/test/test_case.rb +0 -51
  103. data/lib/ruby_indexer/test/uri_test.rb +0 -85
  104. data/lib/ruby_lsp/load_sorbet.rb +0 -62
@@ -50,7 +50,7 @@ module RubyLsp
50
50
  "__LINE__",
51
51
  ].freeze
52
52
 
53
- #: (ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem] response_builder, GlobalState global_state, NodeContext node_context, RubyDocument::SorbetLevel sorbet_level, Prism::Dispatcher dispatcher, URI::Generic uri, String? trigger_character) -> void
53
+ #: (ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem] response_builder, GlobalState global_state, NodeContext node_context, SorbetLevel sorbet_level, Prism::Dispatcher dispatcher, URI::Generic uri, String? trigger_character) -> void
54
54
  def initialize( # rubocop:disable Metrics/ParameterLists
55
55
  response_builder,
56
56
  global_state,
@@ -100,7 +100,7 @@ module RubyLsp
100
100
  def on_constant_read_node_enter(node)
101
101
  # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
102
102
  # no sigil, Sorbet will still provide completion for constants
103
- return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
103
+ return unless @sorbet_level.ignore?
104
104
 
105
105
  name = RubyIndexer::Index.constant_name(node)
106
106
  return if name.nil?
@@ -125,7 +125,7 @@ module RubyLsp
125
125
  def on_constant_path_node_enter(node)
126
126
  # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
127
127
  # no sigil, Sorbet will still provide completion for constants
128
- return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
128
+ return unless @sorbet_level.ignore?
129
129
 
130
130
  name = begin
131
131
  node.full_name
@@ -143,7 +143,7 @@ module RubyLsp
143
143
  def on_call_node_enter(node)
144
144
  # The only scenario where Sorbet doesn't provide constant completion is on ignored files. Even if the file has
145
145
  # no sigil, Sorbet will still provide completion for constants
146
- if @sorbet_level == RubyDocument::SorbetLevel::Ignore
146
+ if @sorbet_level.ignore?
147
147
  receiver = node.receiver
148
148
 
149
149
  # When writing `Foo::`, the AST assigns a method call node (because you can use that syntax to invoke
@@ -390,7 +390,7 @@ module RubyLsp
390
390
  def handle_instance_variable_completion(name, location)
391
391
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
392
392
  # to provide all features for them
393
- return if @sorbet_level == RubyDocument::SorbetLevel::Strict
393
+ return if @sorbet_level.strict?
394
394
 
395
395
  type = @type_inferrer.infer_receiver_type(@node_context)
396
396
  return unless type
@@ -445,11 +445,14 @@ module RubyLsp
445
445
  return unless arguments_node
446
446
 
447
447
  path_node_to_complete = arguments_node.arguments.first
448
-
449
448
  return unless path_node_to_complete.is_a?(Prism::StringNode)
450
449
 
451
- origin_dir = Pathname.new(@uri.to_standardized_path).dirname
450
+ # If the file is unsaved (e.g.: untitled:Untitled-1), we can't provide relative completion as we don't know
451
+ # where the user intends to save it
452
+ full_path = @uri.to_standardized_path
453
+ return unless full_path
452
454
 
455
+ origin_dir = Pathname.new(full_path).dirname
453
456
  content = path_node_to_complete.content
454
457
  # if the path is not a directory, glob all possible next characters
455
458
  # for example ../somethi| (where | is the cursor position)
@@ -475,13 +478,13 @@ module RubyLsp
475
478
  def complete_methods(node, name)
476
479
  # If the node has a receiver, then we don't need to provide local nor keyword completions. Sorbet can provide
477
480
  # local and keyword completion for any file with a Sorbet level of true or higher
478
- if !sorbet_level_true_or_higher?(@sorbet_level) && !node.receiver
481
+ if !@sorbet_level.true_or_higher? && !node.receiver
479
482
  add_local_completions(node, name)
480
483
  add_keyword_completions(node, name)
481
484
  end
482
485
 
483
486
  # Sorbet can provide completion for methods invoked on self on typed true or higher files
484
- return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
487
+ return if @sorbet_level.true_or_higher? && self_receiver?(node)
485
488
 
486
489
  type = @type_inferrer.infer_receiver_type(@node_context)
487
490
  return unless type
@@ -512,10 +515,18 @@ module RubyLsp
512
515
  external_references = @node_context.fully_qualified_name != type.name
513
516
 
514
517
  @index.method_completion_candidates(method_name, type.name).each do |entry|
515
- next if entry.visibility != RubyIndexer::Entry::Visibility::PUBLIC && external_references
518
+ next if entry.visibility != :public && external_references
516
519
 
517
520
  entry_name = entry.name
518
521
  owner_name = entry.owner&.name
522
+ new_text = entry_name
523
+
524
+ if entry_name.end_with?("=")
525
+ method_name = entry_name.delete_suffix("=")
526
+
527
+ # For writer methods, format as assignment and prefix "self." when no receiver is specified
528
+ new_text = node.receiver.nil? ? "self.#{method_name} = " : "#{method_name} = "
529
+ end
519
530
 
520
531
  label_details = Interface::CompletionItemLabelDetails.new(
521
532
  description: entry.file_name,
@@ -525,7 +536,7 @@ module RubyLsp
525
536
  label: entry_name,
526
537
  filter_text: entry_name,
527
538
  label_details: label_details,
528
- text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
539
+ text_edit: Interface::TextEdit.new(range: range, new_text: new_text),
529
540
  kind: Constant::CompletionItemKind::METHOD,
530
541
  data: {
531
542
  owner_name: owner_name,
@@ -8,7 +8,7 @@ module RubyLsp
8
8
 
9
9
  MAX_NUMBER_OF_DEFINITION_CANDIDATES_WITHOUT_RECEIVER = 10
10
10
 
11
- #: (ResponseBuilders::CollectionResponseBuilder[(Interface::Location | Interface::LocationLink)] response_builder, GlobalState global_state, Document::LanguageId language_id, URI::Generic uri, NodeContext node_context, Prism::Dispatcher dispatcher, RubyDocument::SorbetLevel sorbet_level) -> void
11
+ #: (ResponseBuilders::CollectionResponseBuilder[(Interface::Location | Interface::LocationLink)] response_builder, GlobalState global_state, Symbol language_id, URI::Generic uri, NodeContext node_context, Prism::Dispatcher dispatcher, SorbetLevel sorbet_level) -> void
12
12
  def initialize(response_builder, global_state, language_id, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
13
13
  @response_builder = response_builder
14
14
  @global_state = global_state
@@ -53,7 +53,7 @@ module RubyLsp
53
53
  #: (Prism::CallNode node) -> void
54
54
  def on_call_node_enter(node)
55
55
  # Sorbet can handle go to definition for methods invoked on self on typed true or higher
56
- return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
56
+ return if @sorbet_level.true_or_higher? && self_receiver?(node)
57
57
 
58
58
  message = node.message
59
59
  return unless message
@@ -62,7 +62,7 @@ module RubyLsp
62
62
 
63
63
  # Until we can properly infer the receiver type in erb files (maybe with ruby-lsp-rails),
64
64
  # treating method calls' type as `nil` will allow users to get some completion support first
65
- if @language_id == Document::LanguageId::ERB && inferrer_receiver_type&.name == "Object"
65
+ if @language_id == :erb && inferrer_receiver_type&.name == "Object"
66
66
  inferrer_receiver_type = nil
67
67
  end
68
68
 
@@ -71,24 +71,26 @@ module RubyLsp
71
71
 
72
72
  #: (Prism::StringNode node) -> void
73
73
  def on_string_node_enter(node)
74
- enclosing_call = @node_context.call_node
75
- return unless enclosing_call
76
-
77
- name = enclosing_call.name
78
- return unless name == :require || name == :require_relative
79
-
80
- handle_require_definition(node, name)
74
+ with_enclosing_call(node) do |enclosing_call, name|
75
+ case name
76
+ when :require, :require_relative
77
+ handle_require_definition(node, name)
78
+ when :send, :public_send
79
+ handle_send_or_public_send_definition(enclosing_call, node) { node.content }
80
+ end
81
+ end
81
82
  end
82
83
 
83
84
  #: (Prism::SymbolNode node) -> void
84
85
  def on_symbol_node_enter(node)
85
- enclosing_call = @node_context.call_node
86
- return unless enclosing_call
87
-
88
- name = enclosing_call.name
89
- return unless name == :autoload
90
-
91
- handle_autoload_definition(enclosing_call)
86
+ with_enclosing_call(node) do |enclosing_call, name|
87
+ case name
88
+ when :autoload
89
+ handle_autoload_definition(enclosing_call)
90
+ when :send, :public_send
91
+ handle_send_or_public_send_definition(enclosing_call, node) { node.unescaped }
92
+ end
93
+ end
92
94
  end
93
95
 
94
96
  #: (Prism::BlockArgumentNode node) -> void
@@ -220,10 +222,28 @@ module RubyLsp
220
222
 
221
223
  private
222
224
 
225
+ #: (Prism::Node node) { (Prism::CallNode, Symbol) -> void } -> void
226
+ def with_enclosing_call(node, &block)
227
+ enclosing_call = @node_context.call_node
228
+ return unless enclosing_call
229
+
230
+ block.call(enclosing_call, enclosing_call.name)
231
+ end
232
+
233
+ #: (Prism::CallNode enclosing_call, Prism::Node node) { -> String } -> void
234
+ def handle_send_or_public_send_definition(enclosing_call, node, &block)
235
+ first_argument = enclosing_call.arguments&.arguments&.first
236
+ return unless first_argument.eql?(node)
237
+
238
+ method_name = block.call
239
+
240
+ handle_method_definition(method_name, nil)
241
+ end
242
+
223
243
  #: -> void
224
244
  def handle_super_node_definition
225
245
  # Sorbet can handle super hover on typed true or higher
226
- return if sorbet_level_true_or_higher?(@sorbet_level)
246
+ return if @sorbet_level.true_or_higher?
227
247
 
228
248
  surrounding_method = @node_context.surrounding_method
229
249
  return unless surrounding_method
@@ -276,7 +296,7 @@ module RubyLsp
276
296
  def handle_instance_variable_definition(name)
277
297
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
278
298
  # to provide all features for them
279
- return if @sorbet_level == RubyDocument::SorbetLevel::Strict
299
+ return if @sorbet_level.strict?
280
300
 
281
301
  type = @type_inferrer.infer_receiver_type(@node_context)
282
302
  return unless type
@@ -317,7 +337,7 @@ module RubyLsp
317
337
  methods.each do |target_method|
318
338
  uri = target_method.uri
319
339
  full_path = uri.full_path
320
- next if sorbet_level_true_or_higher?(@sorbet_level) && (!full_path || not_in_dependencies?(full_path))
340
+ next if @sorbet_level.true_or_higher? && (!full_path || not_in_dependencies?(full_path))
321
341
 
322
342
  @response_builder << Interface::LocationLink.new(
323
343
  target_uri: uri.to_s,
@@ -392,7 +412,7 @@ module RubyLsp
392
412
  uri = entry.uri
393
413
  full_path = uri.full_path
394
414
 
395
- if @sorbet_level != RubyDocument::SorbetLevel::Ignore && (!full_path || not_in_dependencies?(full_path))
415
+ if !@sorbet_level.ignore? && (!full_path || not_in_dependencies?(full_path))
396
416
  next
397
417
  end
398
418
 
@@ -92,7 +92,8 @@ module RubyLsp
92
92
  Prism::RequiredParameterNode, Prism::RestParameterNode
93
93
  [target, node_value(target)]
94
94
  when Prism::ModuleNode, Prism::ClassNode, Prism::SingletonClassNode, Prism::DefNode, Prism::CaseNode,
95
- Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::IfNode, Prism::UnlessNode
95
+ Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::IfNode, Prism::UnlessNode, Prism::BlockNode,
96
+ Prism::LambdaNode, Prism::BeginNode
96
97
  [target, nil]
97
98
  end
98
99
 
@@ -157,6 +158,9 @@ module RubyLsp
157
158
  :on_for_node_enter,
158
159
  :on_if_node_enter,
159
160
  :on_unless_node_enter,
161
+ :on_block_node_enter,
162
+ :on_lambda_node_enter,
163
+ :on_begin_node_enter,
160
164
  )
161
165
  end
162
166
  end
@@ -551,6 +555,27 @@ module RubyLsp
551
555
  add_matching_end_highlights(node.keyword_loc, node.end_keyword_loc)
552
556
  end
553
557
 
558
+ #: (Prism::BlockNode node) -> void
559
+ def on_block_node_enter(node)
560
+ return unless @target.is_a?(Prism::BlockNode)
561
+
562
+ add_matching_end_highlights(node.opening_loc, node.closing_loc)
563
+ end
564
+
565
+ #: (Prism::LambdaNode node) -> void
566
+ def on_lambda_node_enter(node)
567
+ return unless @target.is_a?(Prism::LambdaNode)
568
+
569
+ add_matching_end_highlights(node.opening_loc, node.closing_loc)
570
+ end
571
+
572
+ #: (Prism::BeginNode node) -> void
573
+ def on_begin_node_enter(node)
574
+ return unless @target.is_a?(Prism::BeginNode)
575
+
576
+ add_matching_end_highlights(node.begin_keyword_loc, node.end_keyword_loc)
577
+ end
578
+
554
579
  private
555
580
 
556
581
  #: (Prism::Node node, Array[singleton(Prism::Node)] classes) -> bool?
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "ruby_lsp/requests/support/source_uri"
5
+ require "ruby_lsp/requests/support/package_url"
5
6
 
6
7
  module RubyLsp
7
8
  module Listeners
@@ -59,6 +60,7 @@ module RubyLsp
59
60
  @lines_to_comments = comments.to_h do |comment|
60
61
  [comment.location.end_line, comment]
61
62
  end #: Hash[Integer, Prism::Comment]
63
+ @sig_comments = {} #: Hash[Integer, Prism::Comment]
62
64
 
63
65
  dispatcher.register(
64
66
  self,
@@ -67,9 +69,20 @@ module RubyLsp
67
69
  :on_module_node_enter,
68
70
  :on_constant_write_node_enter,
69
71
  :on_constant_path_write_node_enter,
72
+ :on_call_node_enter,
70
73
  )
71
74
  end
72
75
 
76
+ #: (Prism::CallNode node) -> void
77
+ def on_call_node_enter(node)
78
+ return unless node.name == :sig
79
+
80
+ comment = @lines_to_comments[node.location.start_line - 1]
81
+ return unless comment
82
+
83
+ @sig_comments[node.location.end_line] = comment
84
+ end
85
+
73
86
  #: (Prism::DefNode node) -> void
74
87
  def on_def_node_enter(node)
75
88
  extract_document_link(node)
@@ -99,58 +112,81 @@ module RubyLsp
99
112
 
100
113
  #: (Prism::Node node) -> void
101
114
  def extract_document_link(node)
102
- comment = @lines_to_comments[node.location.start_line - 1]
115
+ comment = @lines_to_comments[node.location.start_line - 1] || @sig_comments[node.location.start_line - 1]
103
116
  return unless comment
104
117
 
105
- match = comment.location.slice.match(%r{source://.*#\d+$})
118
+ match = comment.location.slice.match(%r{(source://.*#\d+|pkg:gem/.*#.*)$})
106
119
  return unless match
107
120
 
108
- uri = T.cast(
109
- begin
110
- URI(
111
- match[0], #: as !nil
112
- )
113
- rescue URI::Error
114
- nil
115
- end,
116
- T.nilable(URI::Source),
121
+ uri_string = match[0] #: as !nil
122
+
123
+ file_path, line_number = if uri_string.start_with?("pkg:gem/")
124
+ parse_package_url(uri_string)
125
+ else
126
+ parse_source_uri(uri_string)
127
+ end
128
+
129
+ return unless file_path
130
+
131
+ @response_builder << Interface::DocumentLink.new(
132
+ range: range_from_location(comment.location),
133
+ target: "file://#{file_path}##{line_number}",
134
+ tooltip: "Jump to #{file_path}##{line_number}",
117
135
  )
136
+ end
137
+
138
+ #: (String uri_string) -> [String, String]?
139
+ def parse_package_url(uri_string)
140
+ purl = PackageURL.parse(uri_string) #: as PackageURL?
141
+ return unless purl
142
+
143
+ gem_version = resolve_version(purl.version, purl.name)
144
+ return if gem_version.nil?
145
+
146
+ path, line_number = purl.subpath.split(":", 2)
147
+ return unless path
148
+
149
+ gem_name = purl.name
150
+ file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
151
+ return if file_path.nil?
152
+
153
+ [file_path, line_number]
154
+ rescue PackageURL::InvalidPackageURL
155
+ nil
156
+ end
157
+
158
+ #: (String uri_string) -> [String, String]?
159
+ def parse_source_uri(uri_string)
160
+ uri = begin
161
+ URI(uri_string)
162
+ rescue URI::Error
163
+ nil
164
+ end #: as URI::Source?
118
165
  return unless uri
119
166
 
120
- gem_version = resolve_version(uri)
167
+ gem_version = resolve_version(uri.gem_version, uri.gem_name)
121
168
  return if gem_version.nil?
122
169
 
123
170
  path = uri.path
124
171
  return unless path
125
172
 
126
- gem_name = uri.gem_name
127
- return unless gem_name
128
-
129
- file_path = self.class.gem_paths.dig(gem_name, gem_version, CGI.unescape(path))
173
+ file_path = self.class.gem_paths.dig(uri.gem_name, gem_version, CGI.unescape(path))
130
174
  return if file_path.nil?
131
175
 
132
- @response_builder << Interface::DocumentLink.new(
133
- range: range_from_location(comment.location),
134
- target: "file://#{file_path}##{uri.line_number}",
135
- tooltip: "Jump to #{file_path}##{uri.line_number}",
136
- )
176
+ [file_path, uri.line_number || "0"]
137
177
  end
138
178
 
139
179
  # Try to figure out the gem version for a source:// link. The order of precedence is:
140
180
  # 1. The version in the URI
141
181
  # 2. The version in the RBI file name
142
182
  # 3. The version from the gemspec
143
- #: (URI::Source uri) -> String?
144
- def resolve_version(uri)
145
- version = uri.gem_version
183
+ #: (String? version, String? gem_name) -> String?
184
+ def resolve_version(version, gem_name)
146
185
  return version unless version.nil? || version.empty?
147
186
 
148
187
  return @gem_version unless @gem_version.nil? || @gem_version.empty?
149
188
 
150
- gem_name = uri.gem_name
151
- return unless gem_name
152
-
153
- GEM_TO_VERSION_MAP[gem_name]
189
+ GEM_TO_VERSION_MAP[gem_name.to_s]
154
190
  end
155
191
  end
156
192
  end
@@ -7,6 +7,7 @@ module RubyLsp
7
7
  include Requests::Support::Common
8
8
 
9
9
  ALLOWED_TARGETS = [
10
+ Prism::BreakNode,
10
11
  Prism::CallNode,
11
12
  Prism::ConstantReadNode,
12
13
  Prism::ConstantWriteNode,
@@ -42,7 +43,7 @@ module RubyLsp
42
43
  "https://gitlab.com",
43
44
  ].freeze #: Array[String]
44
45
 
45
- #: (ResponseBuilders::Hover response_builder, GlobalState global_state, URI::Generic uri, NodeContext node_context, Prism::Dispatcher dispatcher, RubyDocument::SorbetLevel sorbet_level) -> void
46
+ #: (ResponseBuilders::Hover response_builder, GlobalState global_state, URI::Generic uri, NodeContext node_context, Prism::Dispatcher dispatcher, SorbetLevel sorbet_level) -> void
46
47
  def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
47
48
  @response_builder = response_builder
48
49
  @global_state = global_state
@@ -54,6 +55,7 @@ module RubyLsp
54
55
 
55
56
  dispatcher.register(
56
57
  self,
58
+ :on_break_node_enter,
57
59
  :on_constant_read_node_enter,
58
60
  :on_constant_write_node_enter,
59
61
  :on_constant_path_node_enter,
@@ -84,8 +86,21 @@ module RubyLsp
84
86
  )
85
87
  end
86
88
 
89
+ #: (Prism::BreakNode node) -> void
90
+ def on_break_node_enter(node)
91
+ handle_keyword_documentation(node.keyword)
92
+ end
93
+
87
94
  #: (Prism::StringNode node) -> void
88
95
  def on_string_node_enter(node)
96
+ if @path && File.basename(@path) == GEMFILE_NAME
97
+ call_node = @node_context.call_node
98
+ if call_node && call_node.name == :gem && call_node.arguments&.arguments&.first == node
99
+ generate_gem_hover(call_node)
100
+ return
101
+ end
102
+ end
103
+
89
104
  generate_heredoc_hover(node)
90
105
  end
91
106
 
@@ -96,7 +111,7 @@ module RubyLsp
96
111
 
97
112
  #: (Prism::ConstantReadNode node) -> void
98
113
  def on_constant_read_node_enter(node)
99
- return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
114
+ return unless @sorbet_level.ignore?
100
115
 
101
116
  name = RubyIndexer::Index.constant_name(node)
102
117
  return if name.nil?
@@ -106,14 +121,14 @@ module RubyLsp
106
121
 
107
122
  #: (Prism::ConstantWriteNode node) -> void
108
123
  def on_constant_write_node_enter(node)
109
- return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
124
+ return unless @sorbet_level.ignore?
110
125
 
111
126
  generate_hover(node.name.to_s, node.name_loc)
112
127
  end
113
128
 
114
129
  #: (Prism::ConstantPathNode node) -> void
115
130
  def on_constant_path_node_enter(node)
116
- return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
131
+ return unless @sorbet_level.ignore?
117
132
 
118
133
  name = RubyIndexer::Index.constant_name(node)
119
134
  return if name.nil?
@@ -123,12 +138,7 @@ module RubyLsp
123
138
 
124
139
  #: (Prism::CallNode node) -> void
125
140
  def on_call_node_enter(node)
126
- if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem
127
- generate_gem_hover(node)
128
- return
129
- end
130
-
131
- return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
141
+ return if @sorbet_level.true_or_higher? && self_receiver?(node)
132
142
 
133
143
  message = node.message
134
144
  return unless message
@@ -273,17 +283,17 @@ module RubyLsp
273
283
  content = KEYWORD_DOCS[keyword]
274
284
  return unless content
275
285
 
276
- doc_path = File.join(STATIC_DOCS_PATH, "#{keyword}.md")
286
+ doc_uri = URI::Generic.from_path(path: File.join(STATIC_DOCS_PATH, "#{keyword}.md"))
277
287
 
278
288
  @response_builder.push("```ruby\n#{keyword}\n```", category: :title)
279
- @response_builder.push("[Read more](#{doc_path})", category: :links)
289
+ @response_builder.push("[Read more](#{doc_uri})", category: :links)
280
290
  @response_builder.push(content, category: :documentation)
281
291
  end
282
292
 
283
293
  #: -> void
284
294
  def handle_super_node_hover
285
295
  # Sorbet can handle super hover on typed true or higher
286
- return if sorbet_level_true_or_higher?(@sorbet_level)
296
+ return if @sorbet_level.true_or_higher?
287
297
 
288
298
  surrounding_method = @node_context.surrounding_method
289
299
  return unless surrounding_method
@@ -318,7 +328,7 @@ module RubyLsp
318
328
  def handle_instance_variable_hover(name)
319
329
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
320
330
  # to provide all features for them
321
- return if @sorbet_level == RubyDocument::SorbetLevel::Strict
331
+ return if @sorbet_level.strict?
322
332
 
323
333
  type = @type_inferrer.infer_receiver_type(@node_context)
324
334
  return unless type
@@ -366,9 +376,10 @@ module RubyLsp
366
376
  # We should only show hover for private constants if the constant is defined in the same namespace as the
367
377
  # reference
368
378
  first_entry = entries.first #: as !nil
369
- return if first_entry.private? && first_entry.name != "#{@node_context.fully_qualified_name}::#{name}"
379
+ full_name = first_entry.name
380
+ return if first_entry.private? && full_name != "#{@node_context.fully_qualified_name}::#{name}"
370
381
 
371
- categorized_markdown_from_index_entries(name, entries).each do |category, content|
382
+ categorized_markdown_from_index_entries(full_name, entries).each do |category, content|
372
383
  @response_builder.push(content, category: category)
373
384
  end
374
385
  end
@@ -8,10 +8,12 @@ module RubyLsp
8
8
 
9
9
  RESCUE_STRING_LENGTH = "rescue".length #: Integer
10
10
 
11
- #: (ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint] response_builder, RequestConfig hints_configuration, Prism::Dispatcher dispatcher) -> void
12
- def initialize(response_builder, hints_configuration, dispatcher)
11
+ #: (GlobalState, ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint], Prism::Dispatcher) -> void
12
+ def initialize(global_state, response_builder, dispatcher)
13
13
  @response_builder = response_builder
14
- @hints_configuration = hints_configuration
14
+ @hints_configuration = ( # rubocop:disable Style/RedundantParentheses
15
+ global_state.feature_configuration(:inlayHint) #: as !nil
16
+ ) #: RequestConfig
15
17
 
16
18
  dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
17
19
  end
@@ -94,7 +94,7 @@ module RubyLsp
94
94
 
95
95
  #: (Prism::MatchWriteNode node) -> void
96
96
  def on_match_write_node_leave(node)
97
- @inside_regex_capture = true if node.call.message == "=~"
97
+ @inside_regex_capture = false if node.call.message == "=~"
98
98
  end
99
99
 
100
100
  #: (Prism::DefNode node) -> void
@@ -162,7 +162,7 @@ module RubyLsp
162
162
 
163
163
  #: (Prism::SelfNode node) -> void
164
164
  def on_self_node_enter(node)
165
- @response_builder.add_token(node.location, :variable, [:default_library])
165
+ @response_builder.add_token(node.location, :variable, [:defaultLibrary])
166
166
  end
167
167
 
168
168
  #: (Prism::LocalVariableWriteNode node) -> void
@@ -6,7 +6,7 @@ module RubyLsp
6
6
  class SignatureHelp
7
7
  include Requests::Support::Common
8
8
 
9
- #: (ResponseBuilders::SignatureHelp response_builder, GlobalState global_state, NodeContext node_context, Prism::Dispatcher dispatcher, RubyDocument::SorbetLevel sorbet_level) -> void
9
+ #: (ResponseBuilders::SignatureHelp response_builder, GlobalState global_state, NodeContext node_context, Prism::Dispatcher dispatcher, SorbetLevel sorbet_level) -> void
10
10
  def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
11
11
  @sorbet_level = sorbet_level
12
12
  @response_builder = response_builder
@@ -19,7 +19,7 @@ module RubyLsp
19
19
 
20
20
  #: (Prism::CallNode node) -> void
21
21
  def on_call_node_enter(node)
22
- return if sorbet_level_true_or_higher?(@sorbet_level)
22
+ return if @sorbet_level.true_or_higher?
23
23
 
24
24
  message = node.message
25
25
  return unless message