ruby-lsp 0.20.1 → 0.21.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 +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +18 -3
- data/exe/ruby-lsp-launcher +127 -0
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +56 -2
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +21 -6
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +5 -5
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +2 -2
- data/lib/ruby_indexer/test/enhancements_test.rb +51 -19
- data/lib/ruby_indexer/test/index_test.rb +2 -2
- data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
- data/lib/ruby_indexer/test/method_test.rb +26 -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 +60 -0
- data/lib/ruby_lsp/document.rb +1 -1
- data/lib/ruby_lsp/global_state.rb +20 -19
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +62 -0
- data/lib/ruby_lsp/listeners/definition.rb +48 -13
- 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 +26 -11
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
- data/lib/ruby_lsp/requests/hover.rb +24 -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 +11 -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 +54 -16
- data/lib/ruby_lsp/setup_bundler.rb +108 -22
- data/lib/ruby_lsp/utils.rb +8 -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: a22b940469b6910a05ada8b39dbbc2ad918e26e41de0f2f287e79df46b8e46fe
|
4
|
+
data.tar.gz: a57e250627b82a42ba286fd9b14338c48fb04b5a2e76c889d28d93ea55f96f90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0661e457f068016e397436e76c9d48d8d4e2f718fb47268c6fe57797b1374e4669eb4c01b7a45ca26d8fe39dbc79f308797864e73aa31ba21e013ac3c2c97265
|
7
|
+
data.tar.gz: 709e136e94d223cb12fcb8a082d6193023fecc324aa1908f7a9dc2d9c3b612d6935cfe0362e7ba22fdd02ec3e6c1113a2a44e2a0f15230ff4698075a145d5e0d
|
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.21.0
|
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
|
@@ -54,6 +58,17 @@ end
|
|
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,127 @@
|
|
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
|
+
ensure
|
78
|
+
require "fileutils"
|
79
|
+
FileUtils.rm(bundle_env_path) if File.exist?(bundle_env_path)
|
80
|
+
end
|
81
|
+
|
82
|
+
error_path = File.join(".ruby-lsp", "install_error")
|
83
|
+
|
84
|
+
install_error = if File.exist?(error_path)
|
85
|
+
Marshal.load(File.read(error_path))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
|
89
|
+
# configured the load path using the version of the Ruby LSP present in the composed bundle. Do not push any Ruby LSP
|
90
|
+
# paths into the load path manually or we may end up requiring the wrong version of the gem
|
91
|
+
require "ruby_lsp/load_sorbet"
|
92
|
+
require "ruby_lsp/internal"
|
93
|
+
|
94
|
+
T::Utils.run_all_sig_blocks
|
95
|
+
|
96
|
+
if ARGV.include?("--debug")
|
97
|
+
if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
|
98
|
+
$stderr.puts "Debugging is not supported on Windows"
|
99
|
+
else
|
100
|
+
begin
|
101
|
+
ENV.delete("RUBY_DEBUG_IRB_CONSOLE")
|
102
|
+
require "debug/open_nonstop"
|
103
|
+
rescue LoadError
|
104
|
+
$stderr.puts("You need to install the debug gem to use the --debug flag")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
|
110
|
+
$> = $stderr
|
111
|
+
|
112
|
+
initialize_request = JSON.parse(raw_initialize, symbolize_names: true) if raw_initialize
|
113
|
+
|
114
|
+
begin
|
115
|
+
RubyLsp::Server.new(
|
116
|
+
install_error: install_error,
|
117
|
+
setup_error: setup_error,
|
118
|
+
initialize_request: initialize_request,
|
119
|
+
).start
|
120
|
+
rescue ArgumentError
|
121
|
+
# If the launcher is booting an outdated version of the server, then the initializer doesn't accept a keyword splat
|
122
|
+
# and we already read the initialize request from the stdin pipe. In this case, we need to process the initialize
|
123
|
+
# request manually and then start the main loop
|
124
|
+
server = RubyLsp::Server.new
|
125
|
+
server.process_message(initialize_request)
|
126
|
+
server.start
|
127
|
+
end
|
@@ -312,12 +312,16 @@ module RubyIndexer
|
|
312
312
|
@visibility_stack.push(Entry::Visibility::PROTECTED)
|
313
313
|
when :private
|
314
314
|
@visibility_stack.push(Entry::Visibility::PRIVATE)
|
315
|
+
when :module_function
|
316
|
+
handle_module_function(node)
|
315
317
|
end
|
316
318
|
|
317
319
|
@enhancements.each do |enhancement|
|
318
|
-
enhancement.
|
320
|
+
enhancement.on_call_node_enter(@owner_stack.last, node, @file_path, @code_units_cache)
|
319
321
|
rescue StandardError => e
|
320
|
-
@indexing_errors <<
|
322
|
+
@indexing_errors << <<~MSG
|
323
|
+
Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node enter enhancement: #{e.message}
|
324
|
+
MSG
|
321
325
|
end
|
322
326
|
end
|
323
327
|
|
@@ -332,6 +336,14 @@ module RubyIndexer
|
|
332
336
|
@visibility_stack.pop
|
333
337
|
end
|
334
338
|
end
|
339
|
+
|
340
|
+
@enhancements.each do |enhancement|
|
341
|
+
enhancement.on_call_node_leave(@owner_stack.last, node, @file_path, @code_units_cache)
|
342
|
+
rescue StandardError => e
|
343
|
+
@indexing_errors << <<~MSG
|
344
|
+
Indexing error in #{@file_path} with '#{enhancement.class.name}' on call node leave enhancement: #{e.message}
|
345
|
+
MSG
|
346
|
+
end
|
335
347
|
end
|
336
348
|
|
337
349
|
sig { params(node: Prism::DefNode).void }
|
@@ -751,6 +763,48 @@ module RubyIndexer
|
|
751
763
|
end
|
752
764
|
end
|
753
765
|
|
766
|
+
sig { params(node: Prism::CallNode).void }
|
767
|
+
def handle_module_function(node)
|
768
|
+
arguments_node = node.arguments
|
769
|
+
return unless arguments_node
|
770
|
+
|
771
|
+
owner_name = @owner_stack.last&.name
|
772
|
+
return unless owner_name
|
773
|
+
|
774
|
+
arguments_node.arguments.each do |argument|
|
775
|
+
method_name = case argument
|
776
|
+
when Prism::StringNode
|
777
|
+
argument.content
|
778
|
+
when Prism::SymbolNode
|
779
|
+
argument.value
|
780
|
+
end
|
781
|
+
next unless method_name
|
782
|
+
|
783
|
+
entries = @index.resolve_method(method_name, owner_name)
|
784
|
+
next unless entries
|
785
|
+
|
786
|
+
entries.each do |entry|
|
787
|
+
entry_owner_name = entry.owner&.name
|
788
|
+
next unless entry_owner_name
|
789
|
+
|
790
|
+
entry.visibility = Entry::Visibility::PRIVATE
|
791
|
+
|
792
|
+
singleton = @index.existing_or_new_singleton_class(entry_owner_name)
|
793
|
+
location = Location.from_prism_location(argument.location, @code_units_cache)
|
794
|
+
@index.add(Entry::Method.new(
|
795
|
+
method_name,
|
796
|
+
@file_path,
|
797
|
+
location,
|
798
|
+
location,
|
799
|
+
collect_comments(node)&.concat(entry.comments),
|
800
|
+
entry.signatures,
|
801
|
+
Entry::Visibility::PUBLIC,
|
802
|
+
singleton,
|
803
|
+
))
|
804
|
+
end
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
754
808
|
sig { returns(Entry::Visibility) }
|
755
809
|
def current_visibility
|
756
810
|
T.must(@visibility_stack.last)
|
@@ -2,20 +2,35 @@
|
|
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
|
+
sig { params(index: Index).void }
|
12
|
+
def initialize(index)
|
13
|
+
@index = index
|
14
|
+
end
|
12
15
|
|
13
16
|
# The `on_extend` indexing enhancement is invoked whenever an extend is encountered in the code. It can be used to
|
14
17
|
# register for an included callback, similar to what `ActiveSupport::Concern` does in order to auto-extend the
|
15
18
|
# `ClassMethods` modules
|
16
19
|
sig do
|
17
|
-
|
18
|
-
|
20
|
+
overridable.params(
|
21
|
+
owner: T.nilable(Entry::Namespace),
|
22
|
+
node: Prism::CallNode,
|
23
|
+
file_path: String,
|
24
|
+
code_units_cache: T.any(
|
25
|
+
T.proc.params(arg0: Integer).returns(Integer),
|
26
|
+
Prism::CodeUnitsCache,
|
27
|
+
),
|
28
|
+
).void
|
29
|
+
end
|
30
|
+
def on_call_node_enter(owner, node, file_path, code_units_cache); end
|
31
|
+
|
32
|
+
sig do
|
33
|
+
overridable.params(
|
19
34
|
owner: T.nilable(Entry::Namespace),
|
20
35
|
node: Prism::CallNode,
|
21
36
|
file_path: String,
|
@@ -25,6 +40,6 @@ module RubyIndexer
|
|
25
40
|
),
|
26
41
|
).void
|
27
42
|
end
|
28
|
-
def
|
43
|
+
def on_call_node_leave(owner, node, file_path, code_units_cache); end
|
29
44
|
end
|
30
45
|
end
|
@@ -646,7 +646,7 @@ module RubyIndexer
|
|
646
646
|
(positionals.empty? && forwarding_arguments.any?) ||
|
647
647
|
(
|
648
648
|
# Check if positional arguments match. This includes required, optional, rest arguments. We also need to
|
649
|
-
# verify if there's a trailing
|
649
|
+
# verify if there's a trailing forwarding argument, like `def foo(a, ...); end`
|
650
650
|
positional_arguments_match?(positionals, forwarding_arguments, keyword_args, min_pos, max_pos) &&
|
651
651
|
# If the positional arguments match, we move on to checking keyword, optional keyword and keyword rest
|
652
652
|
# arguments. If there's a forward argument, then it will always match. If the method accepts a keyword rest
|
@@ -784,7 +784,7 @@ module RubyIndexer
|
|
784
784
|
singleton_levels
|
785
785
|
)
|
786
786
|
# Find the first class entry that has a parent class. Notice that if the developer makes a mistake and inherits
|
787
|
-
# from two
|
787
|
+
# from two different classes in different files, we simply ignore it
|
788
788
|
superclass = T.cast(
|
789
789
|
if singleton_levels > 0
|
790
790
|
self[attached_class_name]&.find { |n| n.is_a?(Entry::Class) && n.parent_class }
|
@@ -974,10 +974,10 @@ module RubyIndexer
|
|
974
974
|
[]
|
975
975
|
end
|
976
976
|
|
977
|
-
# Removes
|
978
|
-
# of the ["A", "B"] nesting, then we should not concatenate the nesting with the name or else we'll end up
|
979
|
-
# `A::B::A::B::Foo`. This method will remove any redundant parts from the final name based on the reference and
|
980
|
-
# nesting
|
977
|
+
# Removes redundancy from a constant reference's full name. For example, if we find a reference to `A::B::Foo`
|
978
|
+
# inside of the ["A", "B"] nesting, then we should not concatenate the nesting with the name or else we'll end up
|
979
|
+
# with `A::B::A::B::Foo`. This method will remove any redundant parts from the final name based on the reference and
|
980
|
+
# the nesting
|
981
981
|
sig { params(name: String, nesting: T::Array[String]).returns(String) }
|
982
982
|
def build_non_redundant_full_name(name, nesting)
|
983
983
|
# If there's no nesting, then we can just return the name as is
|
@@ -72,7 +72,7 @@ module RubyIndexer
|
|
72
72
|
assert_entry("self::Bar", Entry::Class, "/fake/path/foo.rb:0-0:1-3")
|
73
73
|
end
|
74
74
|
|
75
|
-
def
|
75
|
+
def test_dynamically_namespaced_class_does_not_affect_other_classes
|
76
76
|
index(<<~RUBY)
|
77
77
|
class Foo
|
78
78
|
class self::Bar
|
@@ -143,7 +143,7 @@ module RubyIndexer
|
|
143
143
|
assert_entry("self::Bar", Entry::Module, "/fake/path/foo.rb:0-0:1-3")
|
144
144
|
end
|
145
145
|
|
146
|
-
def
|
146
|
+
def test_dynamically_namespaced_module_does_not_affect_other_modules
|
147
147
|
index(<<~RUBY)
|
148
148
|
module Foo
|
149
149
|
class self::Bar
|
@@ -6,10 +6,8 @@ require_relative "test_case"
|
|
6
6
|
module RubyIndexer
|
7
7
|
class EnhancementTest < TestCase
|
8
8
|
def test_enhancing_indexing_included_hook
|
9
|
-
enhancement_class = Class.new do
|
10
|
-
|
11
|
-
|
12
|
-
def on_call_node(index, owner, node, file_path, code_units_cache)
|
9
|
+
enhancement_class = Class.new(Enhancement) do
|
10
|
+
def on_call_node_enter(owner, node, file_path, code_units_cache)
|
13
11
|
return unless owner
|
14
12
|
return unless node.name == :extend
|
15
13
|
|
@@ -24,7 +22,7 @@ module RubyIndexer
|
|
24
22
|
module_name = node.full_name
|
25
23
|
next unless module_name == "ActiveSupport::Concern"
|
26
24
|
|
27
|
-
index.register_included_hook(owner.name) do |index, base|
|
25
|
+
@index.register_included_hook(owner.name) do |index, base|
|
28
26
|
class_methods_name = "#{owner.name}::ClassMethods"
|
29
27
|
|
30
28
|
if index.indexed?(class_methods_name)
|
@@ -33,7 +31,7 @@ module RubyIndexer
|
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
index.add(Entry::Method.new(
|
34
|
+
@index.add(Entry::Method.new(
|
37
35
|
"new_method",
|
38
36
|
file_path,
|
39
37
|
location,
|
@@ -50,7 +48,7 @@ module RubyIndexer
|
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
53
|
-
@index.register_enhancement(enhancement_class.new)
|
51
|
+
@index.register_enhancement(enhancement_class.new(@index))
|
54
52
|
index(<<~RUBY)
|
55
53
|
module ActiveSupport
|
56
54
|
module Concern
|
@@ -98,10 +96,8 @@ module RubyIndexer
|
|
98
96
|
end
|
99
97
|
|
100
98
|
def test_enhancing_indexing_configuration_dsl
|
101
|
-
enhancement_class = Class.new do
|
102
|
-
|
103
|
-
|
104
|
-
def on_call_node(index, owner, node, file_path, code_units_cache)
|
99
|
+
enhancement_class = Class.new(Enhancement) do
|
100
|
+
def on_call_node_enter(owner, node, file_path, code_units_cache)
|
105
101
|
return unless owner
|
106
102
|
|
107
103
|
name = node.name
|
@@ -115,7 +111,7 @@ module RubyIndexer
|
|
115
111
|
|
116
112
|
location = Location.from_prism_location(association_name.location, code_units_cache)
|
117
113
|
|
118
|
-
index.add(Entry::Method.new(
|
114
|
+
@index.add(Entry::Method.new(
|
119
115
|
T.must(association_name.value),
|
120
116
|
file_path,
|
121
117
|
location,
|
@@ -128,7 +124,7 @@ module RubyIndexer
|
|
128
124
|
end
|
129
125
|
end
|
130
126
|
|
131
|
-
@index.register_enhancement(enhancement_class.new)
|
127
|
+
@index.register_enhancement(enhancement_class.new(@index))
|
132
128
|
index(<<~RUBY)
|
133
129
|
module ActiveSupport
|
134
130
|
module Concern
|
@@ -160,11 +156,44 @@ module RubyIndexer
|
|
160
156
|
assert_entry("posts", Entry::Method, "/fake/path/foo.rb:23-11:23-17")
|
161
157
|
end
|
162
158
|
|
163
|
-
def
|
164
|
-
enhancement_class = Class.new do
|
165
|
-
|
159
|
+
def test_error_handling_in_on_call_node_enter_enhancement
|
160
|
+
enhancement_class = Class.new(Enhancement) do
|
161
|
+
def on_call_node_enter(owner, node, file_path, code_units_cache)
|
162
|
+
raise "Error"
|
163
|
+
end
|
164
|
+
|
165
|
+
class << self
|
166
|
+
def name
|
167
|
+
"TestEnhancement"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
@index.register_enhancement(enhancement_class.new(@index))
|
173
|
+
|
174
|
+
_stdout, stderr = capture_io do
|
175
|
+
index(<<~RUBY)
|
176
|
+
module ActiveSupport
|
177
|
+
module Concern
|
178
|
+
def self.extended(base)
|
179
|
+
base.class_eval("def new_method(a); end")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
RUBY
|
184
|
+
end
|
185
|
+
|
186
|
+
assert_match(
|
187
|
+
%r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' on call node enter enhancement},
|
188
|
+
stderr,
|
189
|
+
)
|
190
|
+
# The module should still be indexed
|
191
|
+
assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
|
192
|
+
end
|
166
193
|
|
167
|
-
|
194
|
+
def test_error_handling_in_on_call_node_leave_enhancement
|
195
|
+
enhancement_class = Class.new(Enhancement) do
|
196
|
+
def on_call_node_leave(owner, node, file_path, code_units_cache)
|
168
197
|
raise "Error"
|
169
198
|
end
|
170
199
|
|
@@ -175,7 +204,7 @@ module RubyIndexer
|
|
175
204
|
end
|
176
205
|
end
|
177
206
|
|
178
|
-
@index.register_enhancement(enhancement_class.new)
|
207
|
+
@index.register_enhancement(enhancement_class.new(@index))
|
179
208
|
|
180
209
|
_stdout, stderr = capture_io do
|
181
210
|
index(<<~RUBY)
|
@@ -189,7 +218,10 @@ module RubyIndexer
|
|
189
218
|
RUBY
|
190
219
|
end
|
191
220
|
|
192
|
-
assert_match(
|
221
|
+
assert_match(
|
222
|
+
%r{Indexing error in /fake/path/foo\.rb with 'TestEnhancement' on call node leave enhancement},
|
223
|
+
stderr,
|
224
|
+
)
|
193
225
|
# The module should still be indexed
|
194
226
|
assert_entry("ActiveSupport::Concern", Entry::Module, "/fake/path/foo.rb:1-2:5-5")
|
195
227
|
end
|
@@ -904,7 +904,7 @@ module RubyIndexer
|
|
904
904
|
assert_equal(14, entry.location.start_line)
|
905
905
|
end
|
906
906
|
|
907
|
-
def
|
907
|
+
def test_resolving_inherited_aliased_namespace
|
908
908
|
index(<<~RUBY)
|
909
909
|
module Bar
|
910
910
|
TARGET = 123
|
@@ -1490,7 +1490,7 @@ module RubyIndexer
|
|
1490
1490
|
assert_kind_of(Entry::UnresolvedMethodAlias, entry)
|
1491
1491
|
end
|
1492
1492
|
|
1493
|
-
def
|
1493
|
+
def test_unresolvable_method_aliases
|
1494
1494
|
index(<<~RUBY)
|
1495
1495
|
class Foo
|
1496
1496
|
alias bar baz
|
@@ -209,7 +209,7 @@ module RubyIndexer
|
|
209
209
|
end
|
210
210
|
RUBY
|
211
211
|
|
212
|
-
# If the surrounding method is
|
212
|
+
# If the surrounding method is being defined on any dynamic value that isn't `self`, then we attribute the
|
213
213
|
# instance variable to the wrong owner since there's no way to understand that statically
|
214
214
|
entry = T.must(@index["@a"]&.first)
|
215
215
|
owner = T.must(entry.owner)
|
@@ -123,6 +123,32 @@ module RubyIndexer
|
|
123
123
|
assert_entry("baz", Entry::Method, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
|
124
124
|
end
|
125
125
|
|
126
|
+
def test_visibility_tracking_with_module_function
|
127
|
+
index(<<~RUBY)
|
128
|
+
module Test
|
129
|
+
def foo; end
|
130
|
+
def bar; end
|
131
|
+
module_function :foo, "bar"
|
132
|
+
end
|
133
|
+
RUBY
|
134
|
+
|
135
|
+
["foo", "bar"].each do |keyword|
|
136
|
+
entries = T.must(@index[keyword])
|
137
|
+
# should receive two entries because module_function creates a singleton method
|
138
|
+
# for the Test module and a private method for classes include the Test module
|
139
|
+
assert_equal(entries.size, 2)
|
140
|
+
first_entry, second_entry = *entries
|
141
|
+
# The first entry points to the location of the module_function call
|
142
|
+
assert_equal("Test", first_entry.owner.name)
|
143
|
+
assert_instance_of(Entry::Module, first_entry.owner)
|
144
|
+
assert_equal(Entry::Visibility::PRIVATE, first_entry.visibility)
|
145
|
+
# The second entry points to the public singleton method
|
146
|
+
assert_equal("Test::<Class:Test>", second_entry.owner.name)
|
147
|
+
assert_instance_of(Entry::SingletonClass, second_entry.owner)
|
148
|
+
assert_equal(Entry::Visibility::PUBLIC, second_entry.visibility)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
126
152
|
def test_method_with_parameters
|
127
153
|
index(<<~RUBY)
|
128
154
|
class Foo
|
@@ -100,7 +100,7 @@ module RubyIndexer
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def test_location_and_name_location_are_the_same
|
103
|
-
# NOTE: RBS does not store the name location for classes, modules or methods. This
|
103
|
+
# NOTE: RBS does not store the name location for classes, modules or methods. This behavior is not exactly what
|
104
104
|
# we would like, but for now we assign the same location to both
|
105
105
|
|
106
106
|
entries = @index["Array"]
|
data/lib/ruby_lsp/addon.rb
CHANGED
@@ -46,7 +46,7 @@ module RubyLsp
|
|
46
46
|
sig { returns(T::Array[T.class_of(Addon)]) }
|
47
47
|
attr_reader :addon_classes
|
48
48
|
|
49
|
-
# Automatically track and instantiate
|
49
|
+
# Automatically track and instantiate add-on classes
|
50
50
|
sig { params(child_class: T.class_of(Addon)).void }
|
51
51
|
def inherited(child_class)
|
52
52
|
addon_classes << child_class
|
@@ -82,7 +82,7 @@ module RubyLsp
|
|
82
82
|
e
|
83
83
|
end
|
84
84
|
|
85
|
-
# Instantiate all discovered
|
85
|
+
# Instantiate all discovered add-on classes
|
86
86
|
self.addons = addon_classes.map(&:new)
|
87
87
|
self.file_watcher_addons = addons.select { |addon| addon.respond_to?(:workspace_did_change_watched_files) }
|
88
88
|
|
@@ -194,6 +194,13 @@ module RubyLsp
|
|
194
194
|
sig { abstract.returns(String) }
|
195
195
|
def version; end
|
196
196
|
|
197
|
+
# Handle a response from a window/showMessageRequest request. Add-ons must include the addon_name as part of the
|
198
|
+
# original request so that the response is delegated to the correct add-on and must override this method to handle
|
199
|
+
# the response
|
200
|
+
# https://microsoft.github.io/language-server-protocol/specification#window_showMessageRequest
|
201
|
+
sig { overridable.params(title: String).void }
|
202
|
+
def handle_window_show_message_response(title); end
|
203
|
+
|
197
204
|
# Creates a new CodeLens listener. This method is invoked on every CodeLens request
|
198
205
|
sig do
|
199
206
|
overridable.params(
|