ruby-lsp 0.20.1 → 0.22.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +19 -4
- data/exe/ruby-lsp-launcher +124 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +6 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +233 -59
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +34 -16
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +15 -15
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +4 -4
- data/lib/ruby_indexer/test/configuration_test.rb +10 -0
- data/lib/ruby_indexer/test/constant_test.rb +8 -8
- data/lib/ruby_indexer/test/enhancements_test.rb +169 -41
- data/lib/ruby_indexer/test/index_test.rb +41 -2
- data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
- data/lib/ruby_indexer/test/method_test.rb +139 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_lsp/addon.rb +9 -2
- data/lib/ruby_lsp/base_server.rb +14 -5
- data/lib/ruby_lsp/client_capabilities.rb +67 -0
- data/lib/ruby_lsp/document.rb +1 -1
- data/lib/ruby_lsp/global_state.rb +33 -20
- data/lib/ruby_lsp/internal.rb +3 -0
- data/lib/ruby_lsp/listeners/completion.rb +62 -0
- data/lib/ruby_lsp/listeners/definition.rb +48 -13
- data/lib/ruby_lsp/listeners/document_highlight.rb +91 -4
- data/lib/ruby_lsp/listeners/document_symbol.rb +37 -4
- data/lib/ruby_lsp/listeners/hover.rb +52 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/completion.rb +7 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +28 -11
- data/lib/ruby_lsp/requests/document_highlight.rb +7 -1
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
- data/lib/ruby_lsp/requests/hover.rb +26 -6
- data/lib/ruby_lsp/requests/rename.rb +1 -1
- data/lib/ruby_lsp/requests/request.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +12 -1
- data/lib/ruby_lsp/scripts/compose_bundle.rb +20 -0
- data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +8 -0
- data/lib/ruby_lsp/server.rb +85 -55
- data/lib/ruby_lsp/setup_bundler.rb +154 -47
- data/lib/ruby_lsp/store.rb +0 -4
- data/lib/ruby_lsp/utils.rb +63 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efd5671eae595026a17c3114a00de1053865ac828726dd7ac5a66548f6b1ad56
|
4
|
+
data.tar.gz: ccf5b7af12a6e1e327cc2d458be11f39a9ac7319ab9049863ed5a74e333e8a6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fbcdfaac508788a17fb3b7e939ce290e33049a0080ff6260abfaeb609ee76a033234ccc2efa5724587e9c4cbf4552e2cd5d2cb0af1186971b8ed06fec290cb3
|
7
|
+
data.tar.gz: 3e37acf250618e60df2addbce7f9acf86df4bf3e771b7b49c4cd8f4ad326ea82e3d41ad80c5ce71e3b0429dab98385df51f4645824cd419e32dac5ea13778760
|
data/README.md
CHANGED
@@ -29,8 +29,8 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Shopif
|
|
29
29
|
be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor
|
30
30
|
Covenant](CODE_OF_CONDUCT.md) code of conduct.
|
31
31
|
|
32
|
-
If you wish to contribute, see [
|
33
|
-
[roadmap
|
32
|
+
If you wish to contribute, see [Contributing](https://shopify.github.io/ruby-lsp/contributing.html) for development instructions and check out our
|
33
|
+
[Design and roadmap](https://shopify.github.io/ruby-lsp/design-and-roadmap.html) for a list of tasks to get started.
|
34
34
|
|
35
35
|
## License
|
36
36
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.22.1
|
data/exe/ruby-lsp
CHANGED
@@ -33,6 +33,10 @@ parser = OptionParser.new do |opts|
|
|
33
33
|
options[:doctor] = true
|
34
34
|
end
|
35
35
|
|
36
|
+
opts.on("--use-launcher", "[EXPERIMENTAL] Use launcher mechanism to handle missing dependencies gracefully") do
|
37
|
+
options[:launcher] = true
|
38
|
+
end
|
39
|
+
|
36
40
|
opts.on("-h", "--help", "Print this help") do
|
37
41
|
puts opts.help
|
38
42
|
puts
|
@@ -50,10 +54,21 @@ rescue OptionParser::InvalidOption => e
|
|
50
54
|
exit(1)
|
51
55
|
end
|
52
56
|
|
53
|
-
# When we're running without bundler, then we need to make sure the
|
57
|
+
# When we're running without bundler, then we need to make sure the composed bundle is fully configured and re-execute
|
54
58
|
# using `BUNDLE_GEMFILE=.ruby-lsp/Gemfile bundle exec ruby-lsp` so that we have access to the gems that are a part of
|
55
59
|
# the application's bundle
|
56
60
|
if ENV["BUNDLE_GEMFILE"].nil?
|
61
|
+
# Substitute the current process by the launcher. RubyGems activates all dependencies of a gem's executable eagerly,
|
62
|
+
# but we can't have that happen because we want to invoke Bundler.setup ourselves with the composed bundle and avoid
|
63
|
+
# duplicate spec activation errors. Replacing the process with the launcher executable will clear the activated specs,
|
64
|
+
# which gives us the opportunity to control which specs are activated and enter degraded mode if any gems failed to
|
65
|
+
# install rather than failing to boot the server completely
|
66
|
+
if options[:launcher]
|
67
|
+
command = +File.expand_path("ruby-lsp-launcher", __dir__)
|
68
|
+
command << " --debug" if options[:debug]
|
69
|
+
exit exec(command)
|
70
|
+
end
|
71
|
+
|
57
72
|
require_relative "../lib/ruby_lsp/setup_bundler"
|
58
73
|
|
59
74
|
begin
|
@@ -69,12 +84,12 @@ if ENV["BUNDLE_GEMFILE"].nil?
|
|
69
84
|
"bundle"
|
70
85
|
end
|
71
86
|
|
72
|
-
exit exec(env, "#{base_bundle} exec ruby-lsp #{original_args.join(" ")}")
|
87
|
+
exit exec(env, "#{base_bundle} exec ruby-lsp #{original_args.join(" ")}".strip)
|
73
88
|
end
|
74
89
|
|
75
|
-
require "ruby_lsp/load_sorbet"
|
76
|
-
|
77
90
|
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
91
|
+
|
92
|
+
require "ruby_lsp/load_sorbet"
|
78
93
|
require "ruby_lsp/internal"
|
79
94
|
|
80
95
|
T::Utils.run_all_sig_blocks
|
@@ -0,0 +1,124 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# !!!!!!!
|
5
|
+
# No gems can be required in this file until we invoke bundler setup except inside the forked process that sets up the
|
6
|
+
# composed bundle
|
7
|
+
# !!!!!!!
|
8
|
+
|
9
|
+
setup_error = nil
|
10
|
+
|
11
|
+
# Read the initialize request before even starting the server. We need to do this to figure out the workspace URI.
|
12
|
+
# Editors are not required to spawn the language server process on the same directory as the workspace URI, so we need
|
13
|
+
# to ensure that we're setting up the bundle in the right place
|
14
|
+
$stdin.binmode
|
15
|
+
headers = $stdin.gets("\r\n\r\n")
|
16
|
+
content_length = headers[/Content-Length: (\d+)/i, 1].to_i
|
17
|
+
raw_initialize = $stdin.read(content_length)
|
18
|
+
|
19
|
+
# Compose the Ruby LSP bundle in a forked process so that we can require gems without polluting the main process
|
20
|
+
# `$LOAD_PATH` and `Gem.loaded_specs`. Windows doesn't support forking, so we need a separate path to support it
|
21
|
+
pid = if Gem.win_platform?
|
22
|
+
# Since we can't fork on Windows and spawn won't carry over the existing load paths, we need to explicitly pass that
|
23
|
+
# down to the child process or else requiring gems during composing the bundle will fail
|
24
|
+
load_path = $LOAD_PATH.flat_map do |path|
|
25
|
+
["-I", File.expand_path(path)]
|
26
|
+
end
|
27
|
+
|
28
|
+
Process.spawn(
|
29
|
+
Gem.ruby,
|
30
|
+
*load_path,
|
31
|
+
File.expand_path("../lib/ruby_lsp/scripts/compose_bundle_windows.rb", __dir__),
|
32
|
+
raw_initialize,
|
33
|
+
)
|
34
|
+
else
|
35
|
+
fork do
|
36
|
+
require_relative "../lib/ruby_lsp/scripts/compose_bundle"
|
37
|
+
compose(raw_initialize)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
# Wait until the composed Bundle is finished
|
43
|
+
Process.wait(pid)
|
44
|
+
rescue Errno::ECHILD
|
45
|
+
# In theory, the child process can finish before we even get to the wait call, but that is not an error
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
bundle_env_path = File.join(".ruby-lsp", "bundle_env")
|
50
|
+
# We can't require `bundler/setup` because that file prematurely exits the process if setup fails. However, we can't
|
51
|
+
# simply require bundler either because the version required might conflict with the one locked in the composed
|
52
|
+
# bundle. We need the composed bundle sub-process to inform us of the locked Bundler version, so that we can then
|
53
|
+
# activate the right spec and require the exact Bundler version required by the app
|
54
|
+
if File.exist?(bundle_env_path)
|
55
|
+
env = File.readlines(bundle_env_path).to_h { |line| line.chomp.split("=", 2) }
|
56
|
+
ENV.merge!(env)
|
57
|
+
|
58
|
+
if env["BUNDLER_VERSION"]
|
59
|
+
Gem::Specification.find_by_name("bundler", env["BUNDLER_VERSION"]).activate
|
60
|
+
end
|
61
|
+
|
62
|
+
require "bundler"
|
63
|
+
Bundler.ui.level = :silent
|
64
|
+
Bundler.setup
|
65
|
+
$stderr.puts("Composed Bundle set up successfully")
|
66
|
+
end
|
67
|
+
rescue StandardError => e
|
68
|
+
# If installing gems failed for any reason, we don't want to exit the process prematurely. We can still provide most
|
69
|
+
# features in a degraded mode. We simply save the error so that we can report to the user that certain gems might be
|
70
|
+
# missing, but we respect the LSP life cycle
|
71
|
+
setup_error = e
|
72
|
+
$stderr.puts("Failed to set up composed Bundle\n#{e.full_message}")
|
73
|
+
|
74
|
+
# If Bundler.setup fails, we need to restore the original $LOAD_PATH so that we can still require the Ruby LSP server
|
75
|
+
# in degraded mode
|
76
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
77
|
+
end
|
78
|
+
|
79
|
+
error_path = File.join(".ruby-lsp", "install_error")
|
80
|
+
|
81
|
+
install_error = if File.exist?(error_path)
|
82
|
+
Marshal.load(File.read(error_path))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
|
86
|
+
# configured the load path using the version of the Ruby LSP present in the composed bundle. Do not push any Ruby LSP
|
87
|
+
# paths into the load path manually or we may end up requiring the wrong version of the gem
|
88
|
+
require "ruby_lsp/load_sorbet"
|
89
|
+
require "ruby_lsp/internal"
|
90
|
+
|
91
|
+
T::Utils.run_all_sig_blocks
|
92
|
+
|
93
|
+
if ARGV.include?("--debug")
|
94
|
+
if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
|
95
|
+
$stderr.puts "Debugging is not supported on Windows"
|
96
|
+
else
|
97
|
+
begin
|
98
|
+
ENV.delete("RUBY_DEBUG_IRB_CONSOLE")
|
99
|
+
require "debug/open_nonstop"
|
100
|
+
rescue LoadError
|
101
|
+
$stderr.puts("You need to install the debug gem to use the --debug flag")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
|
107
|
+
$> = $stderr
|
108
|
+
|
109
|
+
initialize_request = JSON.parse(raw_initialize, symbolize_names: true) if raw_initialize
|
110
|
+
|
111
|
+
begin
|
112
|
+
RubyLsp::Server.new(
|
113
|
+
install_error: install_error,
|
114
|
+
setup_error: setup_error,
|
115
|
+
initialize_request: initialize_request,
|
116
|
+
).start
|
117
|
+
rescue ArgumentError
|
118
|
+
# If the launcher is booting an outdated version of the server, then the initializer doesn't accept a keyword splat
|
119
|
+
# and we already read the initialize request from the stdin pipe. In this case, we need to process the initialize
|
120
|
+
# request manually and then start the main loop
|
121
|
+
server = RubyLsp::Server.new
|
122
|
+
server.process_message(initialize_request)
|
123
|
+
server.start
|
124
|
+
end
|
@@ -109,6 +109,12 @@ module RubyIndexer
|
|
109
109
|
|
110
110
|
indexables = T.let([], T::Array[IndexablePath])
|
111
111
|
|
112
|
+
# Handle top level files separately. The path below is an optimization to prevent descending down directories that
|
113
|
+
# are going to be excluded anyway, so we need to handle top level scripts separately
|
114
|
+
Dir.glob(File.join(@workspace_path, "*.rb"), flags).each do |path|
|
115
|
+
indexables << IndexablePath.new(nil, path)
|
116
|
+
end
|
117
|
+
|
112
118
|
# Add user specified patterns
|
113
119
|
@included_patterns.each do |pattern|
|
114
120
|
load_path_entry = T.let(nil, T.nilable(String))
|
@@ -18,13 +18,12 @@ module RubyIndexer
|
|
18
18
|
parse_result: Prism::ParseResult,
|
19
19
|
file_path: String,
|
20
20
|
collect_comments: T::Boolean,
|
21
|
-
enhancements: T::Array[Enhancement],
|
22
21
|
).void
|
23
22
|
end
|
24
|
-
def initialize(index, dispatcher, parse_result, file_path, collect_comments: false
|
23
|
+
def initialize(index, dispatcher, parse_result, file_path, collect_comments: false)
|
25
24
|
@index = index
|
26
25
|
@file_path = file_path
|
27
|
-
@enhancements =
|
26
|
+
@enhancements = T.let(Enhancement.all(self), T::Array[Enhancement])
|
28
27
|
@visibility_stack = T.let([Entry::Visibility::PUBLIC], T::Array[Entry::Visibility])
|
29
28
|
@comments_by_line = T.let(
|
30
29
|
parse_result.comments.to_h do |c|
|
@@ -37,6 +36,7 @@ module RubyIndexer
|
|
37
36
|
parse_result.code_units_cache(@index.configuration.encoding),
|
38
37
|
T.any(T.proc.params(arg0: Integer).returns(Integer), Prism::CodeUnitsCache),
|
39
38
|
)
|
39
|
+
@source_lines = T.let(parse_result.source.lines, T::Array[String])
|
40
40
|
|
41
41
|
# The nesting stack we're currently inside. Used to determine the fully qualified name of constants, but only
|
42
42
|
# stored by unresolved aliases which need the original nesting to be lazily resolved
|
@@ -85,15 +85,9 @@ module RubyIndexer
|
|
85
85
|
|
86
86
|
sig { params(node: Prism::ClassNode).void }
|
87
87
|
def on_class_node_enter(node)
|
88
|
-
@visibility_stack.push(Entry::Visibility::PUBLIC)
|
89
88
|
constant_path = node.constant_path
|
90
|
-
name = constant_path.slice
|
91
|
-
|
92
|
-
comments = collect_comments(node)
|
93
|
-
|
94
89
|
superclass = node.superclass
|
95
|
-
|
96
|
-
nesting = actual_nesting(name)
|
90
|
+
nesting = actual_nesting(constant_path.slice)
|
97
91
|
|
98
92
|
parent_class = case superclass
|
99
93
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
@@ -112,53 +106,29 @@ module RubyIndexer
|
|
112
106
|
end
|
113
107
|
end
|
114
108
|
|
115
|
-
|
109
|
+
add_class(
|
116
110
|
nesting,
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
comments,
|
121
|
-
parent_class,
|
111
|
+
node.location,
|
112
|
+
constant_path.location,
|
113
|
+
parent_class_name: parent_class,
|
114
|
+
comments: collect_comments(node),
|
122
115
|
)
|
123
|
-
|
124
|
-
@owner_stack << entry
|
125
|
-
@index.add(entry)
|
126
|
-
@stack << name
|
127
116
|
end
|
128
117
|
|
129
118
|
sig { params(node: Prism::ClassNode).void }
|
130
119
|
def on_class_node_leave(node)
|
131
|
-
|
132
|
-
@owner_stack.pop
|
133
|
-
@visibility_stack.pop
|
120
|
+
pop_namespace_stack
|
134
121
|
end
|
135
122
|
|
136
123
|
sig { params(node: Prism::ModuleNode).void }
|
137
124
|
def on_module_node_enter(node)
|
138
|
-
@visibility_stack.push(Entry::Visibility::PUBLIC)
|
139
125
|
constant_path = node.constant_path
|
140
|
-
|
141
|
-
|
142
|
-
comments = collect_comments(node)
|
143
|
-
|
144
|
-
entry = Entry::Module.new(
|
145
|
-
actual_nesting(name),
|
146
|
-
@file_path,
|
147
|
-
Location.from_prism_location(node.location, @code_units_cache),
|
148
|
-
Location.from_prism_location(constant_path.location, @code_units_cache),
|
149
|
-
comments,
|
150
|
-
)
|
151
|
-
|
152
|
-
@owner_stack << entry
|
153
|
-
@index.add(entry)
|
154
|
-
@stack << name
|
126
|
+
add_module(constant_path.slice, node.location, constant_path.location, comments: collect_comments(node))
|
155
127
|
end
|
156
128
|
|
157
129
|
sig { params(node: Prism::ModuleNode).void }
|
158
130
|
def on_module_node_leave(node)
|
159
|
-
|
160
|
-
@owner_stack.pop
|
161
|
-
@visibility_stack.pop
|
131
|
+
pop_namespace_stack
|
162
132
|
end
|
163
133
|
|
164
134
|
sig { params(node: Prism::SingletonClassNode).void }
|
@@ -200,9 +170,7 @@ module RubyIndexer
|
|
200
170
|
|
201
171
|
sig { params(node: Prism::SingletonClassNode).void }
|
202
172
|
def on_singleton_class_node_leave(node)
|
203
|
-
|
204
|
-
@owner_stack.pop
|
205
|
-
@visibility_stack.pop
|
173
|
+
pop_namespace_stack
|
206
174
|
end
|
207
175
|
|
208
176
|
sig { params(node: Prism::MultiWriteNode).void }
|
@@ -312,12 +280,19 @@ module RubyIndexer
|
|
312
280
|
@visibility_stack.push(Entry::Visibility::PROTECTED)
|
313
281
|
when :private
|
314
282
|
@visibility_stack.push(Entry::Visibility::PRIVATE)
|
283
|
+
when :module_function
|
284
|
+
handle_module_function(node)
|
285
|
+
when :private_class_method
|
286
|
+
@visibility_stack.push(Entry::Visibility::PRIVATE)
|
287
|
+
handle_private_class_method(node)
|
315
288
|
end
|
316
289
|
|
317
290
|
@enhancements.each do |enhancement|
|
318
|
-
enhancement.
|
291
|
+
enhancement.on_call_node_enter(node)
|
319
292
|
rescue StandardError => e
|
320
|
-
@indexing_errors <<
|
293
|
+
@indexing_errors << <<~MSG
|
294
|
+
Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node enter enhancement: #{e.message}
|
295
|
+
MSG
|
321
296
|
end
|
322
297
|
end
|
323
298
|
|
@@ -325,13 +300,21 @@ module RubyIndexer
|
|
325
300
|
def on_call_node_leave(node)
|
326
301
|
message = node.name
|
327
302
|
case message
|
328
|
-
when :public, :protected, :private
|
303
|
+
when :public, :protected, :private, :private_class_method
|
329
304
|
# We want to restore the visibility stack when we leave a method definition with a visibility modifier
|
330
305
|
# e.g. `private def foo; end`
|
331
306
|
if node.arguments&.arguments&.first&.is_a?(Prism::DefNode)
|
332
307
|
@visibility_stack.pop
|
333
308
|
end
|
334
309
|
end
|
310
|
+
|
311
|
+
@enhancements.each do |enhancement|
|
312
|
+
enhancement.on_call_node_leave(node)
|
313
|
+
rescue StandardError => e
|
314
|
+
@indexing_errors << <<~MSG
|
315
|
+
Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node leave enhancement: #{e.message}
|
316
|
+
MSG
|
317
|
+
end
|
335
318
|
end
|
336
319
|
|
337
320
|
sig { params(node: Prism::DefNode).void }
|
@@ -451,6 +434,98 @@ module RubyIndexer
|
|
451
434
|
)
|
452
435
|
end
|
453
436
|
|
437
|
+
sig do
|
438
|
+
params(
|
439
|
+
name: String,
|
440
|
+
node_location: Prism::Location,
|
441
|
+
signatures: T::Array[Entry::Signature],
|
442
|
+
visibility: Entry::Visibility,
|
443
|
+
comments: T.nilable(String),
|
444
|
+
).void
|
445
|
+
end
|
446
|
+
def add_method(name, node_location, signatures, visibility: Entry::Visibility::PUBLIC, comments: nil)
|
447
|
+
location = Location.from_prism_location(node_location, @code_units_cache)
|
448
|
+
|
449
|
+
@index.add(Entry::Method.new(
|
450
|
+
name,
|
451
|
+
@file_path,
|
452
|
+
location,
|
453
|
+
location,
|
454
|
+
comments,
|
455
|
+
signatures,
|
456
|
+
visibility,
|
457
|
+
@owner_stack.last,
|
458
|
+
))
|
459
|
+
end
|
460
|
+
|
461
|
+
sig do
|
462
|
+
params(
|
463
|
+
name: String,
|
464
|
+
full_location: Prism::Location,
|
465
|
+
name_location: Prism::Location,
|
466
|
+
comments: T.nilable(String),
|
467
|
+
).void
|
468
|
+
end
|
469
|
+
def add_module(name, full_location, name_location, comments: nil)
|
470
|
+
location = Location.from_prism_location(full_location, @code_units_cache)
|
471
|
+
name_loc = Location.from_prism_location(name_location, @code_units_cache)
|
472
|
+
|
473
|
+
entry = Entry::Module.new(
|
474
|
+
actual_nesting(name),
|
475
|
+
@file_path,
|
476
|
+
location,
|
477
|
+
name_loc,
|
478
|
+
comments,
|
479
|
+
)
|
480
|
+
|
481
|
+
advance_namespace_stack(name, entry)
|
482
|
+
end
|
483
|
+
|
484
|
+
sig do
|
485
|
+
params(
|
486
|
+
name_or_nesting: T.any(String, T::Array[String]),
|
487
|
+
full_location: Prism::Location,
|
488
|
+
name_location: Prism::Location,
|
489
|
+
parent_class_name: T.nilable(String),
|
490
|
+
comments: T.nilable(String),
|
491
|
+
).void
|
492
|
+
end
|
493
|
+
def add_class(name_or_nesting, full_location, name_location, parent_class_name: nil, comments: nil)
|
494
|
+
nesting = name_or_nesting.is_a?(Array) ? name_or_nesting : actual_nesting(name_or_nesting)
|
495
|
+
entry = Entry::Class.new(
|
496
|
+
nesting,
|
497
|
+
@file_path,
|
498
|
+
Location.from_prism_location(full_location, @code_units_cache),
|
499
|
+
Location.from_prism_location(name_location, @code_units_cache),
|
500
|
+
comments,
|
501
|
+
parent_class_name,
|
502
|
+
)
|
503
|
+
|
504
|
+
advance_namespace_stack(T.must(nesting.last), entry)
|
505
|
+
end
|
506
|
+
|
507
|
+
sig { params(block: T.proc.params(index: Index, base: Entry::Namespace).void).void }
|
508
|
+
def register_included_hook(&block)
|
509
|
+
owner = @owner_stack.last
|
510
|
+
return unless owner
|
511
|
+
|
512
|
+
@index.register_included_hook(owner.name) do |index, base|
|
513
|
+
block.call(index, base)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
sig { void }
|
518
|
+
def pop_namespace_stack
|
519
|
+
@stack.pop
|
520
|
+
@owner_stack.pop
|
521
|
+
@visibility_stack.pop
|
522
|
+
end
|
523
|
+
|
524
|
+
sig { returns(T.nilable(Entry::Namespace)) }
|
525
|
+
def current_owner
|
526
|
+
@owner_stack.last
|
527
|
+
end
|
528
|
+
|
454
529
|
private
|
455
530
|
|
456
531
|
sig do
|
@@ -649,8 +724,7 @@ module RubyIndexer
|
|
649
724
|
comments = +""
|
650
725
|
|
651
726
|
start_line = node.location.start_line - 1
|
652
|
-
start_line -= 1 unless
|
653
|
-
|
727
|
+
start_line -= 1 unless comment_exists_at?(start_line)
|
654
728
|
start_line.downto(1) do |line|
|
655
729
|
comment = @comments_by_line[line]
|
656
730
|
break unless comment
|
@@ -671,6 +745,11 @@ module RubyIndexer
|
|
671
745
|
comments
|
672
746
|
end
|
673
747
|
|
748
|
+
sig { params(line: Integer).returns(T::Boolean) }
|
749
|
+
def comment_exists_at?(line)
|
750
|
+
@comments_by_line.key?(line) || !@source_lines[line - 1].to_s.strip.empty?
|
751
|
+
end
|
752
|
+
|
674
753
|
sig { params(name: String).returns(String) }
|
675
754
|
def fully_qualify_name(name)
|
676
755
|
if @stack.empty? || name.start_with?("::")
|
@@ -734,16 +813,22 @@ module RubyIndexer
|
|
734
813
|
return unless arguments
|
735
814
|
|
736
815
|
arguments.each do |node|
|
737
|
-
next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
owner.mixin_operations << Entry::Include.new(node.full_name)
|
742
|
-
when :prepend
|
743
|
-
owner.mixin_operations << Entry::Prepend.new(node.full_name)
|
744
|
-
when :extend
|
816
|
+
next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode) ||
|
817
|
+
(node.is_a?(Prism::SelfNode) && operation == :extend)
|
818
|
+
|
819
|
+
if node.is_a?(Prism::SelfNode)
|
745
820
|
singleton = @index.existing_or_new_singleton_class(owner.name)
|
746
|
-
singleton.mixin_operations << Entry::Include.new(
|
821
|
+
singleton.mixin_operations << Entry::Include.new(owner.name)
|
822
|
+
else
|
823
|
+
case operation
|
824
|
+
when :include
|
825
|
+
owner.mixin_operations << Entry::Include.new(node.full_name)
|
826
|
+
when :prepend
|
827
|
+
owner.mixin_operations << Entry::Prepend.new(node.full_name)
|
828
|
+
when :extend
|
829
|
+
singleton = @index.existing_or_new_singleton_class(owner.name)
|
830
|
+
singleton.mixin_operations << Entry::Include.new(node.full_name)
|
831
|
+
end
|
747
832
|
end
|
748
833
|
rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
|
749
834
|
Prism::ConstantPathNode::MissingNodesInConstantPathError
|
@@ -751,6 +836,87 @@ module RubyIndexer
|
|
751
836
|
end
|
752
837
|
end
|
753
838
|
|
839
|
+
sig { params(node: Prism::CallNode).void }
|
840
|
+
def handle_module_function(node)
|
841
|
+
arguments_node = node.arguments
|
842
|
+
return unless arguments_node
|
843
|
+
|
844
|
+
owner_name = @owner_stack.last&.name
|
845
|
+
return unless owner_name
|
846
|
+
|
847
|
+
arguments_node.arguments.each do |argument|
|
848
|
+
method_name = case argument
|
849
|
+
when Prism::StringNode
|
850
|
+
argument.content
|
851
|
+
when Prism::SymbolNode
|
852
|
+
argument.value
|
853
|
+
end
|
854
|
+
next unless method_name
|
855
|
+
|
856
|
+
entries = @index.resolve_method(method_name, owner_name)
|
857
|
+
next unless entries
|
858
|
+
|
859
|
+
entries.each do |entry|
|
860
|
+
entry_owner_name = entry.owner&.name
|
861
|
+
next unless entry_owner_name
|
862
|
+
|
863
|
+
entry.visibility = Entry::Visibility::PRIVATE
|
864
|
+
|
865
|
+
singleton = @index.existing_or_new_singleton_class(entry_owner_name)
|
866
|
+
location = Location.from_prism_location(argument.location, @code_units_cache)
|
867
|
+
@index.add(Entry::Method.new(
|
868
|
+
method_name,
|
869
|
+
@file_path,
|
870
|
+
location,
|
871
|
+
location,
|
872
|
+
collect_comments(node)&.concat(entry.comments),
|
873
|
+
entry.signatures,
|
874
|
+
Entry::Visibility::PUBLIC,
|
875
|
+
singleton,
|
876
|
+
))
|
877
|
+
end
|
878
|
+
end
|
879
|
+
end
|
880
|
+
|
881
|
+
sig { params(node: Prism::CallNode).void }
|
882
|
+
def handle_private_class_method(node)
|
883
|
+
node.arguments&.arguments&.each do |argument|
|
884
|
+
string_or_symbol_nodes = case argument
|
885
|
+
when Prism::StringNode, Prism::SymbolNode
|
886
|
+
[argument]
|
887
|
+
when Prism::ArrayNode
|
888
|
+
argument.elements
|
889
|
+
else
|
890
|
+
[]
|
891
|
+
end
|
892
|
+
|
893
|
+
unless string_or_symbol_nodes.empty?
|
894
|
+
# pop the visibility off since there isn't a method definition following `private_class_method`
|
895
|
+
@visibility_stack.pop
|
896
|
+
end
|
897
|
+
|
898
|
+
string_or_symbol_nodes.each do |string_or_symbol_node|
|
899
|
+
method_name = case string_or_symbol_node
|
900
|
+
when Prism::StringNode
|
901
|
+
string_or_symbol_node.content
|
902
|
+
when Prism::SymbolNode
|
903
|
+
string_or_symbol_node.value
|
904
|
+
end
|
905
|
+
next unless method_name
|
906
|
+
|
907
|
+
owner_name = @owner_stack.last&.name
|
908
|
+
next unless owner_name
|
909
|
+
|
910
|
+
entries = @index.resolve_method(method_name, @index.existing_or_new_singleton_class(owner_name).name)
|
911
|
+
next unless entries
|
912
|
+
|
913
|
+
entries.each do |entry|
|
914
|
+
entry.visibility = Entry::Visibility::PRIVATE
|
915
|
+
end
|
916
|
+
end
|
917
|
+
end
|
918
|
+
end
|
919
|
+
|
754
920
|
sig { returns(Entry::Visibility) }
|
755
921
|
def current_visibility
|
756
922
|
T.must(@visibility_stack.last)
|
@@ -856,5 +1022,13 @@ module RubyIndexer
|
|
856
1022
|
|
857
1023
|
corrected_nesting
|
858
1024
|
end
|
1025
|
+
|
1026
|
+
sig { params(short_name: String, entry: Entry::Namespace).void }
|
1027
|
+
def advance_namespace_stack(short_name, entry)
|
1028
|
+
@visibility_stack.push(Entry::Visibility::PUBLIC)
|
1029
|
+
@owner_stack << entry
|
1030
|
+
@index.add(entry)
|
1031
|
+
@stack << short_name
|
1032
|
+
end
|
859
1033
|
end
|
860
1034
|
end
|
@@ -2,29 +2,47 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module RubyIndexer
|
5
|
-
|
5
|
+
class Enhancement
|
6
6
|
extend T::Sig
|
7
7
|
extend T::Helpers
|
8
8
|
|
9
|
-
|
9
|
+
abstract!
|
10
10
|
|
11
|
-
|
11
|
+
@enhancements = T.let([], T::Array[T::Class[Enhancement]])
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { params(child: T::Class[Enhancement]).void }
|
17
|
+
def inherited(child)
|
18
|
+
@enhancements << child
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(listener: DeclarationListener).returns(T::Array[Enhancement]) }
|
23
|
+
def all(listener)
|
24
|
+
@enhancements.map { |enhancement| enhancement.new(listener) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Only available for testing purposes
|
28
|
+
sig { void }
|
29
|
+
def clear
|
30
|
+
@enhancements.clear
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { params(listener: DeclarationListener).void }
|
35
|
+
def initialize(listener)
|
36
|
+
@listener = listener
|
37
|
+
end
|
12
38
|
|
13
39
|
# The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
|
14
40
|
# register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
|
15
41
|
# `ClassMethods` modules
|
16
|
-
sig
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
file_path: String,
|
22
|
-
code_units_cache: T.any(
|
23
|
-
T.proc.params(arg0: Integer).returns(Integer),
|
24
|
-
Prism::CodeUnitsCache,
|
25
|
-
),
|
26
|
-
).void
|
27
|
-
end
|
28
|
-
def on_call_node(index, owner, node, file_path, code_units_cache); end
|
42
|
+
sig { overridable.params(node: Prism::CallNode).void }
|
43
|
+
def on_call_node_enter(node); end # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
44
|
+
|
45
|
+
sig { overridable.params(node: Prism::CallNode).void }
|
46
|
+
def on_call_node_leave(node); end # rubocop:disable RubyLsp/UseRegisterWithHandlerMethod
|
29
47
|
end
|
30
48
|
end
|