ruby-lsp 0.17.17 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -110
- data/VERSION +1 -1
- data/exe/ruby-lsp +5 -4
- 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 +5 -4
- 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/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 +3 -1
- data/lib/ruby_lsp/listeners/code_lens.rb +34 -5
- 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 +0 -16
- 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 +3 -4
- 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
|
|
@@ -135,4 +132,8 @@ if options[:doctor]
|
|
135
132
|
return
|
136
133
|
end
|
137
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
|
+
|
138
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
|
@@ -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)
|
@@ -607,7 +608,7 @@ module RubyIndexer
|
|
607
608
|
attached_ancestor.file_path,
|
608
609
|
attached_ancestor.location,
|
609
610
|
attached_ancestor.name_location,
|
610
|
-
|
611
|
+
nil,
|
611
612
|
nil,
|
612
613
|
)
|
613
614
|
add(singleton, skip_prefix_tree: true)
|
@@ -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
|
@@ -213,10 +213,10 @@ module RubyIndexer
|
|
213
213
|
RUBY
|
214
214
|
|
215
215
|
foo_entry = @index["Foo"].first
|
216
|
-
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments
|
216
|
+
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments)
|
217
217
|
|
218
218
|
bar_entry = @index["Bar"].first
|
219
|
-
assert_equal("This Bar comment has 1 line padding", bar_entry.comments
|
219
|
+
assert_equal("This Bar comment has 1 line padding", bar_entry.comments)
|
220
220
|
end
|
221
221
|
|
222
222
|
def test_skips_comments_containing_invalid_encodings
|
@@ -239,10 +239,10 @@ module RubyIndexer
|
|
239
239
|
RUBY
|
240
240
|
|
241
241
|
foo_entry = @index["Foo"].first
|
242
|
-
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments
|
242
|
+
assert_equal("This is a Foo comment\nThis is another Foo comment", foo_entry.comments)
|
243
243
|
|
244
244
|
bar_entry = @index["Foo::Bar"].first
|
245
|
-
assert_equal("This is a Bar comment", bar_entry.comments
|
245
|
+
assert_equal("This is a Bar comment", bar_entry.comments)
|
246
246
|
end
|
247
247
|
|
248
248
|
def test_comments_can_be_attached_to_a_reopened_class
|
@@ -255,10 +255,10 @@ module RubyIndexer
|
|
255
255
|
RUBY
|
256
256
|
|
257
257
|
first_foo_entry = @index["Foo"][0]
|
258
|
-
assert_equal("This is a Foo comment", first_foo_entry.comments
|
258
|
+
assert_equal("This is a Foo comment", first_foo_entry.comments)
|
259
259
|
|
260
260
|
second_foo_entry = @index["Foo"][1]
|
261
|
-
assert_equal("This is another Foo comment", second_foo_entry.comments
|
261
|
+
assert_equal("This is another Foo comment", second_foo_entry.comments)
|
262
262
|
end
|
263
263
|
|
264
264
|
def test_comments_removes_the_leading_pound_and_space
|
@@ -271,10 +271,10 @@ module RubyIndexer
|
|
271
271
|
RUBY
|
272
272
|
|
273
273
|
first_foo_entry = @index["Foo"][0]
|
274
|
-
assert_equal("This is a Foo comment", first_foo_entry.comments
|
274
|
+
assert_equal("This is a Foo comment", first_foo_entry.comments)
|
275
275
|
|
276
276
|
second_foo_entry = @index["Bar"][0]
|
277
|
-
assert_equal("This is a Bar comment", second_foo_entry.comments
|
277
|
+
assert_equal("This is a Bar comment", second_foo_entry.comments)
|
278
278
|
end
|
279
279
|
|
280
280
|
def test_private_class_and_module_indexing
|
@@ -483,7 +483,7 @@ module RubyIndexer
|
|
483
483
|
|
484
484
|
foo = T.must(@index["Foo::<Class:Foo>"].first)
|
485
485
|
assert_equal(4, foo.location.start_line)
|
486
|
-
assert_equal("Some extra comments", foo.comments
|
486
|
+
assert_equal("Some extra comments", foo.comments)
|
487
487
|
end
|
488
488
|
|
489
489
|
def test_dynamic_singleton_class_blocks
|
@@ -501,7 +501,7 @@ module RubyIndexer
|
|
501
501
|
# That pattern cannot be properly analyzed statically and assuming that it's always a regular singleton simplifies
|
502
502
|
# the implementation considerably.
|
503
503
|
assert_equal(3, singleton.location.start_line)
|
504
|
-
assert_equal("Some extra comments", singleton.comments
|
504
|
+
assert_equal("Some extra comments", singleton.comments)
|
505
505
|
end
|
506
506
|
|
507
507
|
def test_namespaces_inside_singleton_blocks
|
@@ -86,16 +86,16 @@ module RubyIndexer
|
|
86
86
|
A::BAZ = 1
|
87
87
|
RUBY
|
88
88
|
|
89
|
-
foo_comment = @index["FOO"].first.comments
|
89
|
+
foo_comment = @index["FOO"].first.comments
|
90
90
|
assert_equal("FOO comment", foo_comment)
|
91
91
|
|
92
|
-
a_foo_comment = @index["A::FOO"].first.comments
|
92
|
+
a_foo_comment = @index["A::FOO"].first.comments
|
93
93
|
assert_equal("A::FOO comment", a_foo_comment)
|
94
94
|
|
95
|
-
bar_comment = @index["BAR"].first.comments
|
95
|
+
bar_comment = @index["BAR"].first.comments
|
96
96
|
assert_equal("::BAR comment", bar_comment)
|
97
97
|
|
98
|
-
a_baz_comment = @index["A::BAZ"].first.comments
|
98
|
+
a_baz_comment = @index["A::BAZ"].first.comments
|
99
99
|
assert_equal("A::BAZ comment", a_baz_comment)
|
100
100
|
end
|
101
101
|
|