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