ruby-lsp 0.17.16 → 0.18.0
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 +10 -8
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +14 -6
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +157 -27
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +31 -12
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +10 -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 +41 -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/addon.rb +3 -2
- 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 +15 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +34 -5
- data/lib/ruby_lsp/listeners/folding_ranges.rb +1 -1
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +28 -0
- 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 +0 -8
- 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 +3 -17
- data/lib/ruby_lsp/requests/hover.rb +9 -9
- data/lib/ruby_lsp/requests/inlay_hints.rb +0 -30
- 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_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 +23 -8
- data/lib/ruby_lsp/setup_bundler.rb +31 -13
- data/lib/ruby_lsp/type_inferrer.rb +6 -2
- data/lib/ruby_lsp/utils.rb +11 -1
- metadata +7 -14
- data/lib/ruby_lsp/check_docs.rb +0 -130
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 407c26b9ad43a3992fc5b007456dba7c370f4ba6bf398a68bebdfaaafc58ba81
|
4
|
+
data.tar.gz: b890b6762b8d1b4fe43632d6d74e3b0fc1e83168f8d51ccbd2b659df389bff42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75a2a425311f1b07969fea0849b7b214babd8b455d1a93a4c1883a75df7f26ec9d7c7e2748a7499a404313eb93ec3391a19d22102bc0658d9eb296f4ceaf48a4
|
7
|
+
data.tar.gz: 536fd4d95c63b493f83a1620307d21f8aa2c96b71bc3782975f55173bc5c7027be5399928a19508bb2ed08f222ae86d075d721e30b334dae7fa097caf71fb558
|
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.0
|
data/exe/ruby-lsp
CHANGED
@@ -64,15 +64,12 @@ if ENV["BUNDLE_GEMFILE"].nil?
|
|
64
64
|
require_relative "../lib/ruby_lsp/setup_bundler"
|
65
65
|
|
66
66
|
begin
|
67
|
-
|
67
|
+
env = RubyLsp::SetupBundler.new(Dir.pwd, **options).setup!
|
68
68
|
rescue RubyLsp::SetupBundler::BundleNotLocked
|
69
69
|
warn("Project contains a Gemfile, but no Gemfile.lock. Run `bundle install` to lock gems and restart the server")
|
70
70
|
exit(78)
|
71
71
|
end
|
72
72
|
|
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
73
|
exit exec(env, "bundle exec ruby-lsp #{original_args.join(" ")}")
|
77
74
|
end
|
78
75
|
|
@@ -98,16 +95,17 @@ if options[:debug]
|
|
98
95
|
end
|
99
96
|
|
100
97
|
if options[:time_index]
|
101
|
-
require "benchmark"
|
102
|
-
|
103
98
|
index = RubyIndexer::Index.new
|
104
99
|
|
105
|
-
|
100
|
+
time_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
101
|
+
index.index_all
|
102
|
+
elapsed_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - time_start
|
103
|
+
|
106
104
|
entries = index.instance_variable_get(:@entries)
|
107
105
|
entries_by_entry_type = entries.values.flatten.group_by(&:class)
|
108
106
|
|
109
107
|
puts <<~MSG
|
110
|
-
Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{
|
108
|
+
Ruby LSP v#{RubyLsp::VERSION}: Indexing took #{elapsed_time.round(5)} seconds and generated:
|
111
109
|
- #{entries_by_entry_type.sort_by { |k, _| k.to_s }.map { |k, v| "#{k.name.split("::").last}: #{v.size}" }.join("\n- ")}
|
112
110
|
MSG
|
113
111
|
return
|
@@ -134,4 +132,8 @@ if options[:doctor]
|
|
134
132
|
return
|
135
133
|
end
|
136
134
|
|
135
|
+
# Ensure all output goes out stderr by default to allow puts/p/pp to work
|
136
|
+
# without specifying output device.
|
137
|
+
$> = $stderr
|
138
|
+
|
137
139
|
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,37 @@ 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
|
102
|
+
content if content.valid_encoding?
|
103
|
+
end.join("\n")
|
104
|
+
else
|
105
|
+
""
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
82
110
|
class ModuleOperation
|
83
111
|
extend T::Sig
|
84
112
|
extend T::Helpers
|
@@ -116,7 +144,7 @@ module RubyIndexer
|
|
116
144
|
file_path: String,
|
117
145
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
118
146
|
name_location: T.any(Prism::Location, Location),
|
119
|
-
comments: T
|
147
|
+
comments: T.nilable(String),
|
120
148
|
).void
|
121
149
|
end
|
122
150
|
def initialize(nesting, file_path, location, name_location, comments)
|
@@ -177,7 +205,7 @@ module RubyIndexer
|
|
177
205
|
file_path: String,
|
178
206
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
179
207
|
name_location: T.any(Prism::Location, Location),
|
180
|
-
comments: T
|
208
|
+
comments: T.nilable(String),
|
181
209
|
parent_class: T.nilable(String),
|
182
210
|
).void
|
183
211
|
end
|
@@ -195,7 +223,7 @@ module RubyIndexer
|
|
195
223
|
class SingletonClass < Class
|
196
224
|
extend T::Sig
|
197
225
|
|
198
|
-
sig { params(location: Prism::Location, name_location: Prism::Location, comments: T
|
226
|
+
sig { params(location: Prism::Location, name_location: Prism::Location, comments: T.nilable(String)).void }
|
199
227
|
def update_singleton_information(location, name_location, comments)
|
200
228
|
# Create a new RubyIndexer::Location object from the Prism location
|
201
229
|
@location = Location.new(
|
@@ -210,7 +238,7 @@ module RubyIndexer
|
|
210
238
|
name_location.start_column,
|
211
239
|
name_location.end_column,
|
212
240
|
)
|
213
|
-
@comments
|
241
|
+
(@comments ||= +"") << comments if comments
|
214
242
|
end
|
215
243
|
end
|
216
244
|
|
@@ -302,6 +330,17 @@ module RubyIndexer
|
|
302
330
|
end
|
303
331
|
end
|
304
332
|
|
333
|
+
# A forwarding method parameter, e.g. `def foo(...)`
|
334
|
+
class ForwardingParameter < Parameter
|
335
|
+
extend T::Sig
|
336
|
+
|
337
|
+
sig { void }
|
338
|
+
def initialize
|
339
|
+
# You can't name a forwarding parameter, it's always called `...`
|
340
|
+
super(name: :"...")
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
305
344
|
class Member < Entry
|
306
345
|
extend T::Sig
|
307
346
|
extend T::Helpers
|
@@ -311,17 +350,12 @@ module RubyIndexer
|
|
311
350
|
sig { returns(T.nilable(Entry::Namespace)) }
|
312
351
|
attr_reader :owner
|
313
352
|
|
314
|
-
sig { returns(T::Array[RubyIndexer::Entry::Parameter]) }
|
315
|
-
def parameters
|
316
|
-
T.must(signatures.first).parameters
|
317
|
-
end
|
318
|
-
|
319
353
|
sig do
|
320
354
|
params(
|
321
355
|
name: String,
|
322
356
|
file_path: String,
|
323
357
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
324
|
-
comments: T
|
358
|
+
comments: T.nilable(String),
|
325
359
|
visibility: Visibility,
|
326
360
|
owner: T.nilable(Entry::Namespace),
|
327
361
|
).void
|
@@ -389,7 +423,7 @@ module RubyIndexer
|
|
389
423
|
file_path: String,
|
390
424
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
391
425
|
name_location: T.any(Prism::Location, Location),
|
392
|
-
comments: T
|
426
|
+
comments: T.nilable(String),
|
393
427
|
signatures: T::Array[Signature],
|
394
428
|
visibility: Visibility,
|
395
429
|
owner: T.nilable(Entry::Namespace),
|
@@ -440,7 +474,7 @@ module RubyIndexer
|
|
440
474
|
name: String,
|
441
475
|
file_path: String,
|
442
476
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
443
|
-
comments: T
|
477
|
+
comments: T.nilable(String),
|
444
478
|
).void
|
445
479
|
end
|
446
480
|
def initialize(target, nesting, name, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
|
@@ -477,7 +511,7 @@ module RubyIndexer
|
|
477
511
|
name: String,
|
478
512
|
file_path: String,
|
479
513
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
480
|
-
comments: T
|
514
|
+
comments: T.nilable(String),
|
481
515
|
owner: T.nilable(Entry::Namespace),
|
482
516
|
).void
|
483
517
|
end
|
@@ -506,7 +540,7 @@ module RubyIndexer
|
|
506
540
|
owner: T.nilable(Entry::Namespace),
|
507
541
|
file_path: String,
|
508
542
|
location: T.any(Prism::Location, RubyIndexer::Location),
|
509
|
-
comments: T
|
543
|
+
comments: T.nilable(String),
|
510
544
|
).void
|
511
545
|
end
|
512
546
|
def initialize(new_name, old_name, owner, file_path, location, comments) # rubocop:disable Metrics/ParameterLists
|
@@ -530,10 +564,9 @@ module RubyIndexer
|
|
530
564
|
|
531
565
|
sig { params(target: T.any(Member, MethodAlias), unresolved_alias: UnresolvedMethodAlias).void }
|
532
566
|
def initialize(target, unresolved_alias)
|
533
|
-
full_comments =
|
534
|
-
full_comments
|
535
|
-
full_comments <<
|
536
|
-
full_comments.concat(target.comments)
|
567
|
+
full_comments = +"Alias for #{target.name}\n"
|
568
|
+
full_comments << "#{unresolved_alias.comments}\n"
|
569
|
+
full_comments << target.comments
|
537
570
|
|
538
571
|
super(
|
539
572
|
unresolved_alias.new_name,
|
@@ -546,11 +579,6 @@ module RubyIndexer
|
|
546
579
|
@owner = T.let(unresolved_alias.owner, T.nilable(Entry::Namespace))
|
547
580
|
end
|
548
581
|
|
549
|
-
sig { returns(T::Array[Parameter]) }
|
550
|
-
def parameters
|
551
|
-
@target.parameters
|
552
|
-
end
|
553
|
-
|
554
582
|
sig { returns(String) }
|
555
583
|
def decorated_parameters
|
556
584
|
@target.decorated_parameters
|
@@ -586,6 +614,108 @@ module RubyIndexer
|
|
586
614
|
def format
|
587
615
|
@parameters.map(&:decorated_name).join(", ")
|
588
616
|
end
|
617
|
+
|
618
|
+
# Returns `true` if the given call node arguments array matches this method signature. This method will prefer
|
619
|
+
# returning `true` for situations that cannot be analyzed statically, like the presence of splats, keyword splats
|
620
|
+
# or forwarding arguments.
|
621
|
+
#
|
622
|
+
# Since this method is used to detect which overload should be displayed in signature help, it will also return
|
623
|
+
# `true` if there are missing arguments since the user may not be done typing yet. For example:
|
624
|
+
#
|
625
|
+
# ```ruby
|
626
|
+
# def foo(a, b); end
|
627
|
+
# # All of the following are considered matches because the user might be in the middle of typing and we have to
|
628
|
+
# # show them the signature
|
629
|
+
# foo
|
630
|
+
# foo(1)
|
631
|
+
# foo(1, 2)
|
632
|
+
# ```
|
633
|
+
sig { params(arguments: T::Array[Prism::Node]).returns(T::Boolean) }
|
634
|
+
def matches?(arguments)
|
635
|
+
min_pos = 0
|
636
|
+
max_pos = T.let(0, T.any(Integer, Float))
|
637
|
+
names = []
|
638
|
+
has_forward = T.let(false, T::Boolean)
|
639
|
+
has_keyword_rest = T.let(false, T::Boolean)
|
640
|
+
|
641
|
+
@parameters.each do |param|
|
642
|
+
case param
|
643
|
+
when RequiredParameter
|
644
|
+
min_pos += 1
|
645
|
+
max_pos += 1
|
646
|
+
when OptionalParameter
|
647
|
+
max_pos += 1
|
648
|
+
when RestParameter
|
649
|
+
max_pos = Float::INFINITY
|
650
|
+
when ForwardingParameter
|
651
|
+
max_pos = Float::INFINITY
|
652
|
+
has_forward = true
|
653
|
+
when KeywordParameter, OptionalKeywordParameter
|
654
|
+
names << param.name
|
655
|
+
when KeywordRestParameter
|
656
|
+
has_keyword_rest = true
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
keyword_hash_nodes, positional_args = arguments.partition { |arg| arg.is_a?(Prism::KeywordHashNode) }
|
661
|
+
keyword_args = T.cast(keyword_hash_nodes.first, T.nilable(Prism::KeywordHashNode))&.elements
|
662
|
+
forwarding_arguments, positionals = positional_args.partition do |arg|
|
663
|
+
arg.is_a?(Prism::ForwardingArgumentsNode)
|
664
|
+
end
|
665
|
+
|
666
|
+
return true if has_forward && min_pos == 0
|
667
|
+
|
668
|
+
# If the only argument passed is a forwarding argument, then anything will match
|
669
|
+
(positionals.empty? && forwarding_arguments.any?) ||
|
670
|
+
(
|
671
|
+
# Check if positional arguments match. This includes required, optional, rest arguments. We also need to
|
672
|
+
# verify if there's a trailing forwading argument, like `def foo(a, ...); end`
|
673
|
+
positional_arguments_match?(positionals, forwarding_arguments, keyword_args, min_pos, max_pos) &&
|
674
|
+
# If the positional arguments match, we move on to checking keyword, optional keyword and keyword rest
|
675
|
+
# arguments. If there's a forward argument, then it will always match. If the method accepts a keyword rest
|
676
|
+
# (**kwargs), then we can't analyze statically because the user could be passing a hash and we don't know
|
677
|
+
# what the runtime values inside the hash are.
|
678
|
+
#
|
679
|
+
# If none of those match, then we verify if the user is passing the expect names for the keyword arguments
|
680
|
+
(has_forward || has_keyword_rest || keyword_arguments_match?(keyword_args, names))
|
681
|
+
)
|
682
|
+
end
|
683
|
+
|
684
|
+
sig do
|
685
|
+
params(
|
686
|
+
positional_args: T::Array[Prism::Node],
|
687
|
+
forwarding_arguments: T::Array[Prism::Node],
|
688
|
+
keyword_args: T.nilable(T::Array[Prism::Node]),
|
689
|
+
min_pos: Integer,
|
690
|
+
max_pos: T.any(Integer, Float),
|
691
|
+
).returns(T::Boolean)
|
692
|
+
end
|
693
|
+
def positional_arguments_match?(positional_args, forwarding_arguments, keyword_args, min_pos, max_pos)
|
694
|
+
# If the method accepts at least one positional argument and a splat has been passed
|
695
|
+
(min_pos > 0 && positional_args.any? { |arg| arg.is_a?(Prism::SplatNode) }) ||
|
696
|
+
# If there's at least one positional argument unaccounted for and a keyword splat has been passed
|
697
|
+
(min_pos - positional_args.length > 0 && keyword_args&.any? { |arg| arg.is_a?(Prism::AssocSplatNode) }) ||
|
698
|
+
# If there's at least one positional argument unaccounted for and a forwarding argument has been passed
|
699
|
+
(min_pos - positional_args.length > 0 && forwarding_arguments.any?) ||
|
700
|
+
# If the number of positional arguments is within the expected range
|
701
|
+
(min_pos > 0 && positional_args.length <= max_pos) ||
|
702
|
+
(min_pos == 0 && positional_args.empty?)
|
703
|
+
end
|
704
|
+
|
705
|
+
sig { params(args: T.nilable(T::Array[Prism::Node]), names: T::Array[Symbol]).returns(T::Boolean) }
|
706
|
+
def keyword_arguments_match?(args, names)
|
707
|
+
return true unless args
|
708
|
+
return true if args.any? { |arg| arg.is_a?(Prism::AssocSplatNode) }
|
709
|
+
|
710
|
+
arg_names = args.filter_map do |arg|
|
711
|
+
next unless arg.is_a?(Prism::AssocNode)
|
712
|
+
|
713
|
+
key = arg.key
|
714
|
+
key.value&.to_sym if key.is_a?(Prism::SymbolNode)
|
715
|
+
end
|
716
|
+
|
717
|
+
(arg_names - names).empty?
|
718
|
+
end
|
589
719
|
end
|
590
720
|
end
|
591
721
|
end
|
@@ -232,7 +232,7 @@ module RubyIndexer
|
|
232
232
|
next unless ancestor_index && (!existing_entry || ancestor_index < existing_entry_index)
|
233
233
|
|
234
234
|
if entry.is_a?(Entry::UnresolvedMethodAlias)
|
235
|
-
resolved_alias = resolve_method_alias(entry, receiver_name)
|
235
|
+
resolved_alias = resolve_method_alias(entry, receiver_name, [])
|
236
236
|
hash[entry_name] = [resolved_alias, ancestor_index] if resolved_alias.is_a?(Entry::MethodAlias)
|
237
237
|
else
|
238
238
|
hash[entry_name] = [entry, ancestor_index]
|
@@ -312,12 +312,12 @@ module RubyIndexer
|
|
312
312
|
break unless block.call(progress)
|
313
313
|
end
|
314
314
|
|
315
|
-
index_single(path)
|
315
|
+
index_single(path, collect_comments: false)
|
316
316
|
end
|
317
317
|
end
|
318
318
|
|
319
|
-
sig { params(indexable_path: IndexablePath, source: T.nilable(String)).void }
|
320
|
-
def index_single(indexable_path, source = nil)
|
319
|
+
sig { params(indexable_path: IndexablePath, source: T.nilable(String), collect_comments: T::Boolean).void }
|
320
|
+
def index_single(indexable_path, source = nil, collect_comments: true)
|
321
321
|
content = source || File.read(indexable_path.full_path)
|
322
322
|
dispatcher = Prism::Dispatcher.new
|
323
323
|
|
@@ -327,6 +327,7 @@ module RubyIndexer
|
|
327
327
|
dispatcher,
|
328
328
|
result,
|
329
329
|
indexable_path.full_path,
|
330
|
+
collect_comments: collect_comments,
|
330
331
|
enhancements: @enhancements,
|
331
332
|
)
|
332
333
|
dispatcher.dispatch(result.value)
|
@@ -394,16 +395,18 @@ module RubyIndexer
|
|
394
395
|
real_parts.join("::")
|
395
396
|
end
|
396
397
|
|
397
|
-
# Attempts to find methods for a resolved fully qualified receiver name.
|
398
|
+
# Attempts to find methods for a resolved fully qualified receiver name. Do not provide the `seen_names` parameter
|
399
|
+
# as it is used only internally to prevent infinite loops when resolving circular aliases
|
398
400
|
# Returns `nil` if the method does not exist on that receiver
|
399
401
|
sig do
|
400
402
|
params(
|
401
403
|
method_name: String,
|
402
404
|
receiver_name: String,
|
405
|
+
seen_names: T::Array[String],
|
403
406
|
inherited_only: T::Boolean,
|
404
407
|
).returns(T.nilable(T::Array[T.any(Entry::Member, Entry::MethodAlias)]))
|
405
408
|
end
|
406
|
-
def resolve_method(method_name, receiver_name, inherited_only: false)
|
409
|
+
def resolve_method(method_name, receiver_name, seen_names = [], inherited_only: false)
|
407
410
|
method_entries = self[method_name]
|
408
411
|
return unless method_entries
|
409
412
|
|
@@ -418,7 +421,7 @@ module RubyIndexer
|
|
418
421
|
when Entry::UnresolvedMethodAlias
|
419
422
|
# Resolve aliases lazily as we find them
|
420
423
|
if entry.owner&.name == ancestor
|
421
|
-
resolved_alias = resolve_method_alias(entry, receiver_name)
|
424
|
+
resolved_alias = resolve_method_alias(entry, receiver_name, seen_names)
|
422
425
|
resolved_alias if resolved_alias.is_a?(Entry::MethodAlias)
|
423
426
|
end
|
424
427
|
end
|
@@ -605,7 +608,7 @@ module RubyIndexer
|
|
605
608
|
attached_ancestor.file_path,
|
606
609
|
attached_ancestor.location,
|
607
610
|
attached_ancestor.name_location,
|
608
|
-
|
611
|
+
nil,
|
609
612
|
nil,
|
610
613
|
)
|
611
614
|
add(singleton, skip_prefix_tree: true)
|
@@ -614,6 +617,17 @@ module RubyIndexer
|
|
614
617
|
singleton
|
615
618
|
end
|
616
619
|
|
620
|
+
sig do
|
621
|
+
type_parameters(:T).params(
|
622
|
+
path: String,
|
623
|
+
type: T::Class[T.all(T.type_parameter(:T), Entry)],
|
624
|
+
).returns(T.nilable(T::Array[T.type_parameter(:T)]))
|
625
|
+
end
|
626
|
+
def entries_for(path, type)
|
627
|
+
entries = @files_to_entries[path]
|
628
|
+
entries&.grep(type)
|
629
|
+
end
|
630
|
+
|
617
631
|
private
|
618
632
|
|
619
633
|
# Runs the registered included hooks
|
@@ -919,16 +933,21 @@ module RubyIndexer
|
|
919
933
|
params(
|
920
934
|
entry: Entry::UnresolvedMethodAlias,
|
921
935
|
receiver_name: String,
|
936
|
+
seen_names: T::Array[String],
|
922
937
|
).returns(T.any(Entry::MethodAlias, Entry::UnresolvedMethodAlias))
|
923
938
|
end
|
924
|
-
def resolve_method_alias(entry, receiver_name)
|
925
|
-
|
939
|
+
def resolve_method_alias(entry, receiver_name, seen_names)
|
940
|
+
new_name = entry.new_name
|
941
|
+
return entry if new_name == entry.old_name
|
942
|
+
return entry if seen_names.include?(new_name)
|
943
|
+
|
944
|
+
seen_names << new_name
|
926
945
|
|
927
|
-
target_method_entries = resolve_method(entry.old_name, receiver_name)
|
946
|
+
target_method_entries = resolve_method(entry.old_name, receiver_name, seen_names)
|
928
947
|
return entry unless target_method_entries
|
929
948
|
|
930
949
|
resolved_alias = Entry::MethodAlias.new(T.must(target_method_entries.first), entry)
|
931
|
-
original_entries = T.must(@entries[
|
950
|
+
original_entries = T.must(@entries[new_name])
|
932
951
|
original_entries.delete(entry)
|
933
952
|
original_entries << resolved_alias
|
934
953
|
resolved_alias
|
@@ -272,10 +272,10 @@ module RubyIndexer
|
|
272
272
|
RBS::AST::Declarations::Constant,
|
273
273
|
RBS::AST::Members::MethodDefinition,
|
274
274
|
RBS::AST::Members::Alias,
|
275
|
-
)).returns(T
|
275
|
+
)).returns(T.nilable(String))
|
276
276
|
end
|
277
277
|
def comments_to_string(declaration)
|
278
|
-
|
278
|
+
declaration.comment&.string
|
279
279
|
end
|
280
280
|
end
|
281
281
|
end
|