ruby-lsp 0.17.17 → 0.18.3

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -110
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +5 -11
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +14 -6
  6. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +162 -27
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +110 -8
  8. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +24 -10
  10. data/lib/ruby_indexer/test/constant_test.rb +4 -4
  11. data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
  12. data/lib/ruby_indexer/test/index_test.rb +68 -0
  13. data/lib/ruby_indexer/test/method_test.rb +257 -2
  14. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  15. data/lib/ruby_lsp/base_server.rb +21 -1
  16. data/lib/ruby_lsp/document.rb +5 -3
  17. data/lib/ruby_lsp/erb_document.rb +29 -10
  18. data/lib/ruby_lsp/global_state.rb +4 -3
  19. data/lib/ruby_lsp/internal.rb +40 -2
  20. data/lib/ruby_lsp/listeners/code_lens.rb +34 -5
  21. data/lib/ruby_lsp/listeners/completion.rb +20 -6
  22. data/lib/ruby_lsp/listeners/inlay_hints.rb +1 -16
  23. data/lib/ruby_lsp/listeners/signature_help.rb +55 -24
  24. data/lib/ruby_lsp/rbs_document.rb +5 -4
  25. data/lib/ruby_lsp/requests/code_action_resolve.rb +0 -15
  26. data/lib/ruby_lsp/requests/code_actions.rb +0 -10
  27. data/lib/ruby_lsp/requests/code_lens.rb +1 -11
  28. data/lib/ruby_lsp/requests/completion.rb +3 -20
  29. data/lib/ruby_lsp/requests/completion_resolve.rb +2 -10
  30. data/lib/ruby_lsp/requests/definition.rb +6 -20
  31. data/lib/ruby_lsp/requests/diagnostics.rb +0 -10
  32. data/lib/ruby_lsp/requests/document_highlight.rb +7 -14
  33. data/lib/ruby_lsp/requests/document_link.rb +0 -10
  34. data/lib/ruby_lsp/requests/document_symbol.rb +0 -17
  35. data/lib/ruby_lsp/requests/folding_ranges.rb +0 -10
  36. data/lib/ruby_lsp/requests/formatting.rb +0 -16
  37. data/lib/ruby_lsp/requests/hover.rb +9 -9
  38. data/lib/ruby_lsp/requests/inlay_hints.rb +2 -35
  39. data/lib/ruby_lsp/requests/on_type_formatting.rb +0 -10
  40. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +0 -11
  41. data/lib/ruby_lsp/requests/request.rb +17 -1
  42. data/lib/ruby_lsp/requests/selection_ranges.rb +0 -10
  43. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -23
  44. data/lib/ruby_lsp/requests/show_syntax_tree.rb +0 -11
  45. data/lib/ruby_lsp/requests/signature_help.rb +5 -20
  46. data/lib/ruby_lsp/requests/support/common.rb +1 -1
  47. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -0
  48. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +2 -0
  49. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +0 -11
  50. data/lib/ruby_lsp/requests/workspace_symbol.rb +0 -12
  51. data/lib/ruby_lsp/ruby_document.rb +4 -3
  52. data/lib/ruby_lsp/server.rb +45 -11
  53. data/lib/ruby_lsp/setup_bundler.rb +33 -15
  54. data/lib/ruby_lsp/type_inferrer.rb +8 -10
  55. data/lib/ruby_lsp/utils.rb +11 -1
  56. metadata +3 -6
  57. data/lib/ruby_lsp/check_docs.rb +0 -130
  58. data/lib/ruby_lsp/requests.rb +0 -64
  59. data/lib/ruby_lsp/response_builders.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4b4c9204834ddf87c84009003c5eef5872211eaef6ed64022a93a2749e1849e
4
- data.tar.gz: 0d770cf2f67f90fb974a9fca08d9fb4dea1a73d668984ff96e5cc5785d7c5766
3
+ metadata.gz: 399abe97e6c7a69238a01464936c1c8e9365462e142a1175fdccc37b8ee32882
4
+ data.tar.gz: 97fe2193dd4569a78e1e2a55911adfd833ba7c791955953385152d0fd930b685
5
5
  SHA512:
6
- metadata.gz: 3534227d67761a6738c5e2ae4a06a08964a0836c4a30c9d306917efdd93aae1285d6ed03992e88486a44e5d3f7e7271997e56bbb12ab908599687c6a1e622cb4
7
- data.tar.gz: 5345bcf362b6206a0be392eebe5db2175fe9d06b1a38f113fe11d979e5fdcc48851cb47e5f016ae49726ffc73432e81ef801522588cee79ebb36fe545e02d965
6
+ metadata.gz: dd5960a18a96e3f5c40aa768f47977097e74c68b470adba70903436ffeef6e4e0038e4ea6bcd8d19f9dc1d4b23e0c36b11d9f5b4fd61e75346dda28b98582620
7
+ data.tar.gz: d021ddfd3850a4a5378232ecc6788f5c335d56d6093211ce4fb968738ce38c8d53bddca6f0a820af13e7a240d7c157bf07263960593128d7e2c48e5a798b5cfc
data/README.md CHANGED
@@ -15,119 +15,13 @@ experience to Ruby developers using modern standards for cross-editor features,
15
15
  Want to discuss Ruby developer experience? Consider joining the public
16
16
  [Ruby DX Slack workspace](https://join.slack.com/t/ruby-dx/shared_invite/zt-2c8zjlir6-uUDJl8oIwcen_FS_aA~b6Q).
17
17
 
18
- ## Features
18
+ ## Getting Started
19
19
 
20
- ![Ruby LSP demo](vscode/extras/ruby_lsp_demo.gif)
20
+ For VS Code users, you can start by installing the [Ruby LSP extension](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp) from the VS Code marketplace.
21
21
 
22
- The Ruby LSP features include
22
+ For other editors, please refer to the [EDITORS](https://shopify.github.io/ruby-lsp/editors.html) guide.
23
23
 
24
- - Semantic highlighting
25
- - Symbol search and code outline
26
- - RuboCop errors and warnings (diagnostics)
27
- - Format on save (with RuboCop or Syntax Tree)
28
- - Format on type
29
- - Debugging support
30
- - Running and debugging tests through VS Code's UI
31
- - Go to definition for classes, modules, constants and required files
32
- - Showing documentation on hover for classes, modules and constants
33
- - Completion for classes, modules, constants and require paths
34
- - Fuzzy search classes, modules and constants anywhere in the project and its dependencies (workspace symbol)
35
-
36
- As of July 2024, Ruby LSP has received significant enhancements to its code navigation features. For an in-depth look at these improvements, including video demonstrations, check out this [article](https://railsatscale.com/2024-07-18-mastering-ruby-code-navigation-major-enhancements-in-ruby-lsp-2024/). Despite these advancements, we plan to continue enhancing its code navigation support even further. You can follow our progress on this [GitHub issue](https://github.com/Shopify/ruby-lsp/issues/899).
37
-
38
- See complete information about features [here](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
39
-
40
- If you experience issues, please see the [troubleshooting
41
- guide](https://github.com/Shopify/ruby-lsp/blob/main/TROUBLESHOOTING.md).
42
-
43
- ## Usage
44
-
45
- ### With VS Code
46
-
47
- If using VS Code, all you have to do is install the [Ruby LSP
48
- extension](https://marketplace.visualstudio.com/items?itemName=Shopify.ruby-lsp) to get the extra features in the
49
- editor. Do not install the `ruby-lsp` gem manually.
50
-
51
- For more information on using and configuring the extension, see [vscode/README.md](vscode/README.md).
52
-
53
- ### With other editors
54
-
55
- See [editors](EDITORS.md) for community instructions on setting up the Ruby LSP, which current includes Emacs, Neovim, Sublime Text, and Zed.
56
-
57
- The gem can be installed by doing
58
- ```shell
59
- gem install ruby-lsp
60
- ```
61
-
62
- and the language server can be launched running `ruby-lsp` (without bundle exec in order to properly hook into your
63
- project's dependencies).
64
-
65
- ### Documentation
66
-
67
- See the [documentation](https://shopify.github.io/ruby-lsp) for more in-depth details about the
68
- [supported features](https://shopify.github.io/ruby-lsp/RubyLsp/Requests.html).
69
-
70
- For creating rich themes for Ruby using the semantic highlighting information, see the [semantic highlighting
71
- documentation](SEMANTIC_HIGHLIGHTING.md).
72
-
73
- ### Configuring code indexing
74
-
75
- By default, the Ruby LSP indexes all Ruby files defined in the current project and all of its dependencies, including
76
- default gems, except for
77
-
78
- - Gems that only appear under the `:development` group
79
- - All Ruby files under `test/**/*.rb`
80
-
81
- This behaviour can be overridden and tuned. Learn how to configure it [for VS Code](vscode/README.md#Indexing-Configuration) or [for other editors](EDITORS.md#Indexing-Configuration).
82
-
83
- Note that indexing-dependent behavior, such as definition, hover, completion or workspace symbol will be impacted by
84
- the configuration changes.
85
-
86
- The older approach of using a `.index.yml` file has been deprecated and will be removed in a future release.
87
-
88
- ```yaml
89
- # Exclude files based on a given pattern. Often used to exclude test files or fixtures
90
- excluded_patterns:
91
- - "**/spec/**/*.rb"
92
-
93
- # Include files based on a given pattern. Can be used to index Ruby files that use different extensions
94
- included_patterns:
95
- - "**/bin/*"
96
-
97
- # Exclude gems by name. If a gem is never referenced in the project's code and is only used as a tool, excluding it will
98
- # speed up indexing and reduce the amount of results in features like definition or completion
99
- excluded_gems:
100
- - rubocop
101
- - pathname
102
-
103
- # Include gems by name. Normally used to include development gems that are excluded by default
104
- included_gems:
105
- - prism
106
- ```
107
-
108
- ### Addons
109
-
110
- The Ruby LSP provides an addon system that allows other gems to enhance the base functionality with more editor
111
- features. This is the mechanism that powers addons like
112
-
113
- - [Ruby LSP Rails](https://github.com/Shopify/ruby-lsp-rails)
114
- - [Ruby LSP RSpec](https://github.com/st0012/ruby-lsp-rspec)
115
- - [Ruby LSP rubyfmt](https://github.com/jscharf/ruby-lsp-rubyfmt)
116
-
117
- Additionally, some tools may include a Ruby LSP addon directly, like
118
-
119
- - [Standard Ruby (from v1.39.1)](https://github.com/standardrb/standard/wiki/IDE:-vscode#using-ruby-lsp)
120
-
121
- Other community driven addons can be found in [rubygems](https://rubygems.org/search?query=name%3A+ruby-lsp) by
122
- searching for the `ruby-lsp` prefix.
123
-
124
- For instructions on how to create addons, see the [addons documentation](ADDONS.md).
125
-
126
- ## Learn More
127
-
128
- * [RubyConf 2022: Improving the development experience with language servers](https://www.youtube.com/watch?v=kEfXPTm1aCI) ([Vinicius Stock](https://github.com/vinistock))
129
- * [Remote Ruby: Ruby Language Server with Vinicius Stock](https://remoteruby.com/221)
130
- * [RubyKaigi 2023: Code indexing - How language servers understand our code](https://www.youtube.com/watch?v=ks3tQojSJLU) ([Vinicius Stock](https://github.com/vinistock))
24
+ To learn more about Ruby LSP, please refer to the official [documentation](https://shopify.github.io/ruby-lsp) for [supported features](https://shopify.github.io/ruby-lsp#features).
131
25
 
132
26
  ## Contributing
133
27
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.17
1
+ 0.18.3
data/exe/ruby-lsp CHANGED
@@ -29,13 +29,6 @@ parser = OptionParser.new do |opts|
29
29
  options[:branch] = branch
30
30
  end
31
31
 
32
- opts.on(
33
- "--experimental",
34
- "Run pre-release versions of the Ruby LSP",
35
- ) do
36
- options[:experimental] = true
37
- end
38
-
39
32
  opts.on("--doctor", "Run troubleshooting steps") do
40
33
  options[:doctor] = true
41
34
  end
@@ -64,15 +57,12 @@ if ENV["BUNDLE_GEMFILE"].nil?
64
57
  require_relative "../lib/ruby_lsp/setup_bundler"
65
58
 
66
59
  begin
67
- bundle_gemfile, bundle_path, bundle_app_config = RubyLsp::SetupBundler.new(Dir.pwd, **options).setup!
60
+ env = RubyLsp::SetupBundler.new(Dir.pwd, **options).setup!
68
61
  rescue RubyLsp::SetupBundler::BundleNotLocked
69
62
  warn("Project contains a Gemfile, but no Gemfile.lock. Run `bundle install` to lock gems and restart the server")
70
63
  exit(78)
71
64
  end
72
65
 
73
- env = { "BUNDLE_GEMFILE" => bundle_gemfile }
74
- env["BUNDLE_PATH"] = bundle_path if bundle_path
75
- env["BUNDLE_APP_CONFIG"] = bundle_app_config if bundle_app_config
76
66
  exit exec(env, "bundle exec ruby-lsp #{original_args.join(" ")}")
77
67
  end
78
68
 
@@ -135,4 +125,8 @@ if options[:doctor]
135
125
  return
136
126
  end
137
127
 
128
+ # Ensure all output goes out stderr by default to allow puts/p/pp to work
129
+ # without specifying output device.
130
+ $> = $stderr
131
+
138
132
  RubyLsp::Server.new.start
@@ -17,10 +17,11 @@ module RubyIndexer
17
17
  dispatcher: Prism::Dispatcher,
18
18
  parse_result: Prism::ParseResult,
19
19
  file_path: String,
20
+ collect_comments: T::Boolean,
20
21
  enhancements: T::Array[Enhancement],
21
22
  ).void
22
23
  end
23
- def initialize(index, dispatcher, parse_result, file_path, enhancements: [])
24
+ def initialize(index, dispatcher, parse_result, file_path, collect_comments: false, enhancements: [])
24
25
  @index = index
25
26
  @file_path = file_path
26
27
  @enhancements = enhancements
@@ -40,6 +41,7 @@ module RubyIndexer
40
41
  # A stack of namespace entries that represent where we currently are. Used to properly assign methods to an owner
41
42
  @owner_stack = T.let([], T::Array[Entry::Namespace])
42
43
  @indexing_errors = T.let([], T::Array[String])
44
+ @collect_comments = collect_comments
43
45
 
44
46
  dispatcher.register(
45
47
  self,
@@ -540,9 +542,11 @@ module RubyIndexer
540
542
  )
541
543
  end
542
544
 
543
- sig { params(node: Prism::Node).returns(T::Array[String]) }
545
+ sig { params(node: Prism::Node).returns(T.nilable(String)) }
544
546
  def collect_comments(node)
545
- comments = []
547
+ return unless @collect_comments
548
+
549
+ comments = +""
546
550
 
547
551
  start_line = node.location.start_line - 1
548
552
  start_line -= 1 unless @comments_by_line.key?(start_line)
@@ -551,7 +555,7 @@ module RubyIndexer
551
555
  comment = @comments_by_line[line]
552
556
  break unless comment
553
557
 
554
- comment_content = comment.location.slice.chomp
558
+ comment_content = comment.location.slice
555
559
 
556
560
  # invalid encodings would raise an "invalid byte sequence" exception
557
561
  if !comment_content.valid_encoding? || comment_content.match?(@index.configuration.magic_comment_regex)
@@ -560,9 +564,10 @@ module RubyIndexer
560
564
 
561
565
  comment_content.delete_prefix!("#")
562
566
  comment_content.delete_prefix!(" ")
563
- comments.prepend(comment_content)
567
+ comments.prepend("#{comment_content}\n")
564
568
  end
565
569
 
570
+ comments.chomp!
566
571
  comments
567
572
  end
568
573
 
@@ -685,9 +690,12 @@ module RubyIndexer
685
690
 
686
691
  keyword_rest = parameters_node.keyword_rest
687
692
 
688
- if keyword_rest.is_a?(Prism::KeywordRestParameterNode)
693
+ case keyword_rest
694
+ when Prism::KeywordRestParameterNode
689
695
  keyword_rest_name = parameter_name(keyword_rest) || Entry::KeywordRestParameter::DEFAULT_NAME
690
696
  parameters << Entry::KeywordRestParameter.new(name: keyword_rest_name)
697
+ when Prism::ForwardingParameterNode
698
+ parameters << Entry::ForwardingParameter.new
691
699
  end
692
700
 
693
701
  parameters_node.posts.each do |post|
@@ -24,9 +24,6 @@ module RubyIndexer
24
24
 
25
25
  alias_method :name_location, :location
26
26
 
27
- sig { returns(T::Array[String]) }
28
- attr_reader :comments
29
-
30
27
  sig { returns(Visibility) }
31
28
  attr_accessor :visibility
32
29
 
@@ -35,7 +32,7 @@ module RubyIndexer
35
32
  name: String,
36
33
  file_path: String,
37
34
  location: T.any(Prism::Location, RubyIndexer::Location),
38
- comments: T::Array[String],
35
+ comments: T.nilable(String),
39
36
  ).void
40
37
  end
41
38
  def initialize(name, file_path, location, comments)
@@ -79,6 +76,42 @@ module RubyIndexer
79
76
  File.basename(@file_path)
80
77
  end
81
78
 
79
+ sig { returns(String) }
80
+ def comments
81
+ @comments ||= begin
82
+ # Parse only the comments based on the file path, which is much faster than parsing the entire file
83
+ parsed_comments = Prism.parse_file_comments(@file_path)
84
+
85
+ # Group comments based on whether they belong to a single block of comments
86
+ grouped = parsed_comments.slice_when do |left, right|
87
+ left.location.start_line + 1 != right.location.start_line
88
+ end
89
+
90
+ # Find the group that is either immediately or two lines above the current entry
91
+ correct_group = grouped.find do |group|
92
+ comment_end_line = group.last.location.start_line
93
+ (comment_end_line - 1..comment_end_line).cover?(@location.start_line - 1)
94
+ end
95
+
96
+ # If we found something, we join the comments together. Otherwise, the entry has no documentation and we don't
97
+ # want to accidentally re-parse it, so we set it to an empty string. If an entry is updated, the entire entry
98
+ # object is dropped, so this will not prevent updates
99
+ if correct_group
100
+ correct_group.filter_map do |comment|
101
+ content = comment.slice.chomp
102
+
103
+ if content.valid_encoding?
104
+ content.delete_prefix!("#")
105
+ content.delete_prefix!(" ")
106
+ content
107
+ end
108
+ end.join("\n")
109
+ else
110
+ ""
111
+ end
112
+ end
113
+ end
114
+
82
115
  class ModuleOperation
83
116
  extend T::Sig
84
117
  extend T::Helpers
@@ -116,7 +149,7 @@ module RubyIndexer
116
149
  file_path: String,
117
150
  location: T.any(Prism::Location, RubyIndexer::Location),
118
151
  name_location: T.any(Prism::Location, Location),
119
- comments: T::Array[String],
152
+ comments: T.nilable(String),
120
153
  ).void
121
154
  end
122
155
  def initialize(nesting, file_path, location, name_location, comments)
@@ -177,7 +210,7 @@ module RubyIndexer
177
210
  file_path: String,
178
211
  location: T.any(Prism::Location, RubyIndexer::Location),
179
212
  name_location: T.any(Prism::Location, Location),
180
- comments: T::Array[String],
213
+ comments: T.nilable(String),
181
214
  parent_class: T.nilable(String),
182
215
  ).void
183
216
  end
@@ -195,7 +228,7 @@ module RubyIndexer
195
228
  class SingletonClass < Class
196
229
  extend T::Sig
197
230
 
198
- sig { params(location: Prism::Location, name_location: Prism::Location, comments: T::Array[String]).void }
231
+ sig { params(location: Prism::Location, name_location: Prism::Location, comments: T.nilable(String)).void }
199
232
  def update_singleton_information(location, name_location, comments)
200
233
  # Create a new RubyIndexer::Location object from the Prism location
201
234
  @location = Location.new(
@@ -210,7 +243,7 @@ module RubyIndexer
210
243
  name_location.start_column,
211
244
  name_location.end_column,
212
245
  )
213
- @comments.concat(comments)
246
+ (@comments ||= +"") << comments if comments
214
247
  end
215
248
  end
216
249
 
@@ -302,6 +335,17 @@ module RubyIndexer
302
335
  end
303
336
  end
304
337
 
338
+ # A forwarding method parameter, e.g. `def foo(...)`
339
+ class ForwardingParameter < Parameter
340
+ extend T::Sig
341
+
342
+ sig { void }
343
+ def initialize
344
+ # You can't name a forwarding parameter, it's always called `...`
345
+ super(name: :"...")
346
+ end
347
+ end
348
+
305
349
  class Member < Entry
306
350
  extend T::Sig
307
351
  extend T::Helpers
@@ -311,17 +355,12 @@ module RubyIndexer
311
355
  sig { returns(T.nilable(Entry::Namespace)) }
312
356
  attr_reader :owner
313
357
 
314
- sig { returns(T::Array[RubyIndexer::Entry::Parameter]) }
315
- def parameters
316
- T.must(signatures.first).parameters
317
- end
318
-
319
358
  sig do
320
359
  params(
321
360
  name: String,
322
361
  file_path: String,
323
362
  location: T.any(Prism::Location, RubyIndexer::Location),
324
- comments: T::Array[String],
363
+ comments: T.nilable(String),
325
364
  visibility: Visibility,
326
365
  owner: T.nilable(Entry::Namespace),
327
366
  ).void
@@ -389,7 +428,7 @@ module RubyIndexer
389
428
  file_path: String,
390
429
  location: T.any(Prism::Location, RubyIndexer::Location),
391
430
  name_location: T.any(Prism::Location, Location),
392
- comments: T::Array[String],
431
+ comments: T.nilable(String),
393
432
  signatures: T::Array[Signature],
394
433
  visibility: Visibility,
395
434
  owner: T.nilable(Entry::Namespace),
@@ -440,7 +479,7 @@ module RubyIndexer
440
479
  name: String,
441
480
  file_path: String,
442
481
  location: T.any(Prism::Location, RubyIndexer::Location),
443
- comments: T::Array[String],
482
+ comments: T.nilable(String),
444
483
  ).void
445
484
  end
446
485
  def initialize(target, nesting, name, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
@@ -477,7 +516,7 @@ module RubyIndexer
477
516
  name: String,
478
517
  file_path: String,
479
518
  location: T.any(Prism::Location, RubyIndexer::Location),
480
- comments: T::Array[String],
519
+ comments: T.nilable(String),
481
520
  owner: T.nilable(Entry::Namespace),
482
521
  ).void
483
522
  end
@@ -506,7 +545,7 @@ module RubyIndexer
506
545
  owner: T.nilable(Entry::Namespace),
507
546
  file_path: String,
508
547
  location: T.any(Prism::Location, RubyIndexer::Location),
509
- comments: T::Array[String],
548
+ comments: T.nilable(String),
510
549
  ).void
511
550
  end
512
551
  def initialize(new_name, old_name, owner, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
@@ -530,10 +569,9 @@ module RubyIndexer
530
569
 
531
570
  sig { params(target: T.any(Member, MethodAlias), unresolved_alias: UnresolvedMethodAlias).void }
532
571
  def initialize(target, unresolved_alias)
533
- full_comments = ["Alias for #{target.name}\n"]
534
- full_comments.concat(unresolved_alias.comments)
535
- full_comments << "\n"
536
- full_comments.concat(target.comments)
572
+ full_comments = +"Alias for #{target.name}\n"
573
+ full_comments << "#{unresolved_alias.comments}\n"
574
+ full_comments << target.comments
537
575
 
538
576
  super(
539
577
  unresolved_alias.new_name,
@@ -546,11 +584,6 @@ module RubyIndexer
546
584
  @owner = T.let(unresolved_alias.owner, T.nilable(Entry::Namespace))
547
585
  end
548
586
 
549
- sig { returns(T::Array[Parameter]) }
550
- def parameters
551
- @target.parameters
552
- end
553
-
554
587
  sig { returns(String) }
555
588
  def decorated_parameters
556
589
  @target.decorated_parameters
@@ -586,6 +619,108 @@ module RubyIndexer
586
619
  def format
587
620
  @parameters.map(&:decorated_name).join(", ")
588
621
  end
622
+
623
+ # Returns `true` if the given call node arguments array matches this method signature. This method will prefer
624
+ # returning `true` for situations that cannot be analyzed statically, like the presence of splats, keyword splats
625
+ # or forwarding arguments.
626
+ #
627
+ # Since this method is used to detect which overload should be displayed in signature help, it will also return
628
+ # `true` if there are missing arguments since the user may not be done typing yet. For example:
629
+ #
630
+ # ```ruby
631
+ # def foo(a, b); end
632
+ # # All of the following are considered matches because the user might be in the middle of typing and we have to
633
+ # # show them the signature
634
+ # foo
635
+ # foo(1)
636
+ # foo(1, 2)
637
+ # ```
638
+ sig { params(arguments: T::Array[Prism::Node]).returns(T::Boolean) }
639
+ def matches?(arguments)
640
+ min_pos = 0
641
+ max_pos = T.let(0, T.any(Integer, Float))
642
+ names = []
643
+ has_forward = T.let(false, T::Boolean)
644
+ has_keyword_rest = T.let(false, T::Boolean)
645
+
646
+ @parameters.each do |param|
647
+ case param
648
+ when RequiredParameter
649
+ min_pos += 1
650
+ max_pos += 1
651
+ when OptionalParameter
652
+ max_pos += 1
653
+ when RestParameter
654
+ max_pos = Float::INFINITY
655
+ when ForwardingParameter
656
+ max_pos = Float::INFINITY
657
+ has_forward = true
658
+ when KeywordParameter, OptionalKeywordParameter
659
+ names << param.name
660
+ when KeywordRestParameter
661
+ has_keyword_rest = true
662
+ end
663
+ end
664
+
665
+ keyword_hash_nodes, positional_args = arguments.partition { |arg| arg.is_a?(Prism::KeywordHashNode) }
666
+ keyword_args = T.cast(keyword_hash_nodes.first, T.nilable(Prism::KeywordHashNode))&.elements
667
+ forwarding_arguments, positionals = positional_args.partition do |arg|
668
+ arg.is_a?(Prism::ForwardingArgumentsNode)
669
+ end
670
+
671
+ return true if has_forward && min_pos == 0
672
+
673
+ # If the only argument passed is a forwarding argument, then anything will match
674
+ (positionals.empty? && forwarding_arguments.any?) ||
675
+ (
676
+ # Check if positional arguments match. This includes required, optional, rest arguments. We also need to
677
+ # verify if there's a trailing forwading argument, like `def foo(a, ...); end`
678
+ positional_arguments_match?(positionals, forwarding_arguments, keyword_args, min_pos, max_pos) &&
679
+ # If the positional arguments match, we move on to checking keyword, optional keyword and keyword rest
680
+ # arguments. If there's a forward argument, then it will always match. If the method accepts a keyword rest
681
+ # (**kwargs), then we can't analyze statically because the user could be passing a hash and we don't know
682
+ # what the runtime values inside the hash are.
683
+ #
684
+ # If none of those match, then we verify if the user is passing the expect names for the keyword arguments
685
+ (has_forward || has_keyword_rest || keyword_arguments_match?(keyword_args, names))
686
+ )
687
+ end
688
+
689
+ sig do
690
+ params(
691
+ positional_args: T::Array[Prism::Node],
692
+ forwarding_arguments: T::Array[Prism::Node],
693
+ keyword_args: T.nilable(T::Array[Prism::Node]),
694
+ min_pos: Integer,
695
+ max_pos: T.any(Integer, Float),
696
+ ).returns(T::Boolean)
697
+ end
698
+ def positional_arguments_match?(positional_args, forwarding_arguments, keyword_args, min_pos, max_pos)
699
+ # If the method accepts at least one positional argument and a splat has been passed
700
+ (min_pos > 0 && positional_args.any? { |arg| arg.is_a?(Prism::SplatNode) }) ||
701
+ # If there's at least one positional argument unaccounted for and a keyword splat has been passed
702
+ (min_pos - positional_args.length > 0 && keyword_args&.any? { |arg| arg.is_a?(Prism::AssocSplatNode) }) ||
703
+ # If there's at least one positional argument unaccounted for and a forwarding argument has been passed
704
+ (min_pos - positional_args.length > 0 && forwarding_arguments.any?) ||
705
+ # If the number of positional arguments is within the expected range
706
+ (min_pos > 0 && positional_args.length <= max_pos) ||
707
+ (min_pos == 0 && positional_args.empty?)
708
+ end
709
+
710
+ sig { params(args: T.nilable(T::Array[Prism::Node]), names: T::Array[Symbol]).returns(T::Boolean) }
711
+ def keyword_arguments_match?(args, names)
712
+ return true unless args
713
+ return true if args.any? { |arg| arg.is_a?(Prism::AssocSplatNode) }
714
+
715
+ arg_names = args.filter_map do |arg|
716
+ next unless arg.is_a?(Prism::AssocNode)
717
+
718
+ key = arg.key
719
+ key.value&.to_sym if key.is_a?(Prism::SymbolNode)
720
+ end
721
+
722
+ (arg_names - names).empty?
723
+ end
589
724
  end
590
725
  end
591
726
  end