ruby-lsp 0.20.1 → 0.21.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 +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 +25 -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 +111 -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: c270ba4b6a7348ccb821e1bae776f4bd9ac704973b251791d2104d79861878c1
|
4
|
+
data.tar.gz: 5d27d76eca727dca7e439aa177db04fb3ec17e1169c19a267ba2cffc8f9e19a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8502d09966be4e7ec79c276b4d078edc0f4e150c86ff24bb6e6f5b9c28e4f89393760983b7344c108e356a5bc79daa314806c69b81441338465b00f8bb693cc1
|
7
|
+
data.tar.gz: 0d84c86826022c8e71311bef8070f8b5364cd45f10f242fcb5b8cf96f9dd391a9a45a034b264ee8b2cec236ce69257a1a102d9e769bbdf3b8c0dc9baff8650e9
|
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.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
|
@@ -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(
|