ruby-lsp 0.17.17 → 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 +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
|
|