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.
- checksums.yaml +4 -4
- data/README.md +4 -110
- data/VERSION +1 -1
- data/exe/ruby-lsp +5 -11
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +14 -6
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +162 -27
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +110 -8
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +24 -10
- data/lib/ruby_indexer/test/constant_test.rb +4 -4
- data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
- data/lib/ruby_indexer/test/index_test.rb +68 -0
- data/lib/ruby_indexer/test/method_test.rb +257 -2
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_lsp/base_server.rb +21 -1
- data/lib/ruby_lsp/document.rb +5 -3
- data/lib/ruby_lsp/erb_document.rb +29 -10
- data/lib/ruby_lsp/global_state.rb +4 -3
- data/lib/ruby_lsp/internal.rb +40 -2
- data/lib/ruby_lsp/listeners/code_lens.rb +34 -5
- data/lib/ruby_lsp/listeners/completion.rb +20 -6
- data/lib/ruby_lsp/listeners/inlay_hints.rb +1 -16
- data/lib/ruby_lsp/listeners/signature_help.rb +55 -24
- data/lib/ruby_lsp/rbs_document.rb +5 -4
- data/lib/ruby_lsp/requests/code_action_resolve.rb +0 -15
- data/lib/ruby_lsp/requests/code_actions.rb +0 -10
- data/lib/ruby_lsp/requests/code_lens.rb +1 -11
- data/lib/ruby_lsp/requests/completion.rb +3 -20
- data/lib/ruby_lsp/requests/completion_resolve.rb +2 -10
- data/lib/ruby_lsp/requests/definition.rb +6 -20
- data/lib/ruby_lsp/requests/diagnostics.rb +0 -10
- data/lib/ruby_lsp/requests/document_highlight.rb +7 -14
- data/lib/ruby_lsp/requests/document_link.rb +0 -10
- data/lib/ruby_lsp/requests/document_symbol.rb +0 -17
- data/lib/ruby_lsp/requests/folding_ranges.rb +0 -10
- data/lib/ruby_lsp/requests/formatting.rb +0 -16
- data/lib/ruby_lsp/requests/hover.rb +9 -9
- data/lib/ruby_lsp/requests/inlay_hints.rb +2 -35
- data/lib/ruby_lsp/requests/on_type_formatting.rb +0 -10
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +0 -11
- data/lib/ruby_lsp/requests/request.rb +17 -1
- data/lib/ruby_lsp/requests/selection_ranges.rb +0 -10
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -23
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +0 -11
- data/lib/ruby_lsp/requests/signature_help.rb +5 -20
- data/lib/ruby_lsp/requests/support/common.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -0
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +2 -0
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +0 -11
- data/lib/ruby_lsp/requests/workspace_symbol.rb +0 -12
- data/lib/ruby_lsp/ruby_document.rb +4 -3
- data/lib/ruby_lsp/server.rb +45 -11
- data/lib/ruby_lsp/setup_bundler.rb +33 -15
- data/lib/ruby_lsp/type_inferrer.rb +8 -10
- data/lib/ruby_lsp/utils.rb +11 -1
- metadata +3 -6
- data/lib/ruby_lsp/check_docs.rb +0 -130
- data/lib/ruby_lsp/requests.rb +0 -64
- data/lib/ruby_lsp/response_builders.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 399abe97e6c7a69238a01464936c1c8e9365462e142a1175fdccc37b8ee32882
|
4
|
+
data.tar.gz: 97fe2193dd4569a78e1e2a55911adfd833ba7c791955953385152d0fd930b685
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
##
|
18
|
+
## Getting Started
|
19
19
|
|
20
|
-
|
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
|
-
|
22
|
+
For other editors, please refer to the [EDITORS](https://shopify.github.io/ruby-lsp/editors.html) guide.
|
23
23
|
|
24
|
-
-
|
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.
|
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
|
-
|
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
|
545
|
+
sig { params(node: Prism::Node).returns(T.nilable(String)) }
|
544
546
|
def collect_comments(node)
|
545
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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 =
|
534
|
-
full_comments
|
535
|
-
full_comments <<
|
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
|