ruby-lsp 0.10.1 → 0.11.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 +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +40 -5
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +141 -5
- data/lib/ruby_indexer/lib/ruby_indexer/visitor.rb +66 -18
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +23 -0
- data/lib/ruby_indexer/test/configuration_test.rb +2 -0
- data/lib/ruby_indexer/test/constant_test.rb +202 -0
- data/lib/ruby_indexer/test/index_test.rb +20 -0
- data/lib/ruby_lsp/{extension.rb → addon.rb} +27 -25
- data/lib/ruby_lsp/check_docs.rb +7 -8
- data/lib/ruby_lsp/document.rb +35 -38
- data/lib/ruby_lsp/event_emitter.rb +239 -77
- data/lib/ruby_lsp/executor.rb +45 -55
- data/lib/ruby_lsp/internal.rb +2 -3
- data/lib/ruby_lsp/listener.rb +8 -7
- data/lib/ruby_lsp/parameter_scope.rb +33 -0
- data/lib/ruby_lsp/requests/base_request.rb +3 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +14 -14
- data/lib/ruby_lsp/requests/code_lens.rb +39 -63
- data/lib/ruby_lsp/requests/completion.rb +54 -32
- data/lib/ruby_lsp/requests/definition.rb +30 -27
- data/lib/ruby_lsp/requests/diagnostics.rb +26 -3
- data/lib/ruby_lsp/requests/document_highlight.rb +18 -19
- data/lib/ruby_lsp/requests/document_link.rb +50 -9
- data/lib/ruby_lsp/requests/document_symbol.rb +82 -75
- data/lib/ruby_lsp/requests/folding_ranges.rb +199 -222
- data/lib/ruby_lsp/requests/formatting.rb +5 -6
- data/lib/ruby_lsp/requests/hover.rb +33 -22
- data/lib/ruby_lsp/requests/inlay_hints.rb +2 -3
- data/lib/ruby_lsp/requests/selection_ranges.rb +65 -40
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +187 -145
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -4
- data/lib/ruby_lsp/requests/support/annotation.rb +18 -17
- data/lib/ruby_lsp/requests/support/common.rb +17 -26
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +67 -42
- data/lib/ruby_lsp/requests/support/highlight_target.rb +64 -45
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -4
- data/lib/ruby_lsp/requests/support/selection_range.rb +5 -4
- data/lib/ruby_lsp/requests/support/sorbet.rb +2 -57
- data/lib/ruby_lsp/requests/support/syntax_tree_formatting_runner.rb +7 -1
- data/lib/ruby_lsp/requests/workspace_symbol.rb +4 -1
- data/lib/ruby_lsp/server.rb +6 -44
- data/lib/ruby_lsp/utils.rb +2 -12
- metadata +11 -30
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
5
|
-
require "syntax_tree"
|
6
5
|
require "yarp"
|
7
6
|
require "language_server-protocol"
|
8
|
-
require "benchmark"
|
9
7
|
require "bundler"
|
10
8
|
require "uri"
|
11
9
|
require "cgi"
|
@@ -14,11 +12,12 @@ require "ruby-lsp"
|
|
14
12
|
require "ruby_indexer/ruby_indexer"
|
15
13
|
require "core_ext/uri"
|
16
14
|
require "ruby_lsp/utils"
|
15
|
+
require "ruby_lsp/parameter_scope"
|
17
16
|
require "ruby_lsp/server"
|
18
17
|
require "ruby_lsp/executor"
|
19
18
|
require "ruby_lsp/event_emitter"
|
20
19
|
require "ruby_lsp/requests"
|
21
20
|
require "ruby_lsp/listener"
|
22
21
|
require "ruby_lsp/store"
|
23
|
-
require "ruby_lsp/
|
22
|
+
require "ruby_lsp/addon"
|
24
23
|
require "ruby_lsp/requests/support/rubocop_runner"
|
data/lib/ruby_lsp/listener.rb
CHANGED
@@ -31,7 +31,7 @@ module RubyLsp
|
|
31
31
|
def _response; end
|
32
32
|
end
|
33
33
|
|
34
|
-
# ExtensibleListener is an abstract class to be used by requests that accept
|
34
|
+
# ExtensibleListener is an abstract class to be used by requests that accept addons.
|
35
35
|
class ExtensibleListener < Listener
|
36
36
|
extend T::Sig
|
37
37
|
extend T::Generic
|
@@ -48,7 +48,7 @@ module RubyLsp
|
|
48
48
|
super
|
49
49
|
@response_merged = T.let(false, T::Boolean)
|
50
50
|
@external_listeners = T.let(
|
51
|
-
|
51
|
+
Addon.addons.filter_map do |ext|
|
52
52
|
initialize_external_listener(ext)
|
53
53
|
end,
|
54
54
|
T::Array[RubyLsp::Listener[ResponseType]],
|
@@ -56,7 +56,7 @@ module RubyLsp
|
|
56
56
|
end
|
57
57
|
|
58
58
|
# Merge responses from all external listeners into the base listener's response. We do this to return a single
|
59
|
-
# response to the editor including the results of all
|
59
|
+
# response to the editor including the results of all addons
|
60
60
|
sig { void }
|
61
61
|
def merge_external_listeners_responses!
|
62
62
|
@external_listeners.each { |l| merge_response!(l) }
|
@@ -69,14 +69,15 @@ module RubyLsp
|
|
69
69
|
end
|
70
70
|
|
71
71
|
sig do
|
72
|
-
abstract.params(
|
72
|
+
abstract.params(addon: RubyLsp::Addon).returns(T.nilable(RubyLsp::Listener[ResponseType]))
|
73
73
|
end
|
74
|
-
def initialize_external_listener(
|
74
|
+
def initialize_external_listener(addon); end
|
75
75
|
|
76
|
-
# Does nothing by default. Requests that accept
|
77
|
-
#
|
76
|
+
# Does nothing by default. Requests that accept addons should override this method to define how to merge responses
|
77
|
+
# coming from external listeners
|
78
78
|
sig { abstract.params(other: Listener[T.untyped]).returns(T.self_type) }
|
79
79
|
def merge_response!(other)
|
80
80
|
end
|
81
81
|
end
|
82
|
+
private_constant(:ExtensibleListener)
|
82
83
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
class ParameterScope
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns(T.nilable(ParameterScope)) }
|
9
|
+
attr_reader :parent
|
10
|
+
|
11
|
+
sig { params(parent: T.nilable(ParameterScope)).void }
|
12
|
+
def initialize(parent = nil)
|
13
|
+
@parent = parent
|
14
|
+
@parameters = T.let(Set.new, T::Set[Symbol])
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(name: T.any(String, Symbol)).void }
|
18
|
+
def <<(name)
|
19
|
+
@parameters << name.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(name: T.any(Symbol, String)).returns(Symbol) }
|
23
|
+
def type_for(name)
|
24
|
+
parameter?(name) ? :parameter : :variable
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(name: T.any(Symbol, String)).returns(T::Boolean) }
|
28
|
+
def parameter?(name)
|
29
|
+
sym = name.to_sym
|
30
|
+
@parameters.include?(sym) || (!@parent.nil? && @parent.parameter?(sym))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
module RubyLsp
|
5
5
|
module Requests
|
6
6
|
# :nodoc:
|
7
|
-
class BaseRequest <
|
7
|
+
class BaseRequest < YARP::Visitor
|
8
8
|
extend T::Sig
|
9
9
|
extend T::Helpers
|
10
10
|
include Support::Common
|
@@ -20,10 +20,10 @@ module RubyLsp
|
|
20
20
|
sig { abstract.returns(Object) }
|
21
21
|
def run; end
|
22
22
|
|
23
|
-
#
|
23
|
+
# YARP implements `visit_all` using `map` instead of `each` for users who want to use the pattern
|
24
24
|
# `result = visitor.visit(tree)`. However, we don't use that pattern and should avoid producing a new array for
|
25
25
|
# every single node visited
|
26
|
-
sig { params(nodes: T::Array[T.nilable(
|
26
|
+
sig { params(nodes: T::Array[T.nilable(YARP::Node)]).void }
|
27
27
|
def visit_all(nodes)
|
28
28
|
nodes.each { |node| visit(node) }
|
29
29
|
end
|
@@ -43,11 +43,11 @@ module RubyLsp
|
|
43
43
|
|
44
44
|
sig { override.returns(T.any(Interface::CodeAction, Error)) }
|
45
45
|
def run
|
46
|
+
return Error::EmptySelection if @document.source.empty?
|
47
|
+
|
46
48
|
source_range = @code_action.dig(:data, :range)
|
47
49
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
48
50
|
|
49
|
-
return Error::InvalidTargetRange if @document.syntax_error?
|
50
|
-
|
51
51
|
scanner = @document.create_scanner
|
52
52
|
start_index = scanner.find_char_position(source_range[:start])
|
53
53
|
end_index = scanner.find_char_position(source_range[:end])
|
@@ -55,26 +55,23 @@ module RubyLsp
|
|
55
55
|
|
56
56
|
# Find the closest statements node, so that we place the refactor in a valid position
|
57
57
|
closest_statements, parent_statements = @document
|
58
|
-
.locate(
|
59
|
-
|
58
|
+
.locate(@document.tree, start_index, node_types: [YARP::StatementsNode, YARP::BlockNode])
|
59
|
+
|
60
|
+
return Error::InvalidTargetRange if closest_statements.nil? || closest_statements.child_nodes.compact.empty?
|
60
61
|
|
61
62
|
# Find the node with the end line closest to the requested position, so that we can place the refactor
|
62
63
|
# immediately after that closest node
|
63
|
-
closest_node = closest_statements.child_nodes.compact.min_by do |node|
|
64
|
+
closest_node = T.must(closest_statements.child_nodes.compact.min_by do |node|
|
64
65
|
distance = source_range.dig(:start, :line) - (node.location.end_line - 1)
|
65
66
|
distance <= 0 ? Float::INFINITY : distance
|
66
|
-
end
|
67
|
+
end)
|
67
68
|
|
68
|
-
|
69
|
-
parent_expression = parent_statements.child_nodes.compact.find do |node|
|
70
|
-
loc = node.location
|
71
|
-
loc.start_line - 1 <= source_range.dig(:start, :line) && loc.end_line - 1 >= source_range.dig(:end, :line)
|
72
|
-
end if parent_statements
|
69
|
+
return Error::InvalidTargetRange if closest_node.is_a?(YARP::MissingNode)
|
73
70
|
|
74
71
|
closest_node_loc = closest_node.location
|
75
72
|
# If the parent expression is a single line block, then we have to extract it inside of the oneline block
|
76
|
-
if
|
77
|
-
|
73
|
+
if parent_statements.is_a?(YARP::BlockNode) &&
|
74
|
+
parent_statements.location.start_line == parent_statements.location.end_line
|
78
75
|
|
79
76
|
variable_source = " #{NEW_VARIABLE_NAME} = #{extracted_source};"
|
80
77
|
character = source_range.dig(:start, :character) - 1
|
@@ -105,7 +102,10 @@ module RubyLsp
|
|
105
102
|
end: { line: target_line, character: indentation },
|
106
103
|
}
|
107
104
|
|
108
|
-
|
105
|
+
line = lines[target_line]
|
106
|
+
return Error::InvalidTargetRange unless line
|
107
|
+
|
108
|
+
variable_source = if line.strip.empty?
|
109
109
|
"\n#{" " * indentation}#{NEW_VARIABLE_NAME} = #{extracted_source}"
|
110
110
|
else
|
111
111
|
"#{NEW_VARIABLE_NAME} = #{extracted_source}\n#{" " * indentation}"
|
@@ -31,10 +31,9 @@ module RubyLsp
|
|
31
31
|
sig { override.returns(ResponseType) }
|
32
32
|
attr_reader :_response
|
33
33
|
|
34
|
-
sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue
|
35
|
-
def initialize(uri, emitter, message_queue
|
34
|
+
sig { params(uri: URI::Generic, emitter: EventEmitter, message_queue: Thread::Queue).void }
|
35
|
+
def initialize(uri, emitter, message_queue)
|
36
36
|
@uri = T.let(uri, URI::Generic)
|
37
|
-
@test_library = T.let(test_library, String)
|
38
37
|
@_response = T.let([], ResponseType)
|
39
38
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
40
39
|
# visibility_stack is a stack of [current_visibility, previous_visibility]
|
@@ -48,18 +47,15 @@ module RubyLsp
|
|
48
47
|
:on_class,
|
49
48
|
:after_class,
|
50
49
|
:on_def,
|
51
|
-
:on_command,
|
52
|
-
:after_command,
|
53
50
|
:on_call,
|
54
51
|
:after_call,
|
55
|
-
:on_vcall,
|
56
52
|
)
|
57
53
|
end
|
58
54
|
|
59
|
-
sig { params(node:
|
55
|
+
sig { params(node: YARP::ClassNode).void }
|
60
56
|
def on_class(node)
|
61
57
|
@visibility_stack.push(["public", "public"])
|
62
|
-
class_name = node.
|
58
|
+
class_name = node.constant_path.slice
|
63
59
|
@class_stack.push(class_name)
|
64
60
|
|
65
61
|
if @path && class_name.end_with?("Test")
|
@@ -72,20 +68,20 @@ module RubyLsp
|
|
72
68
|
end
|
73
69
|
end
|
74
70
|
|
75
|
-
sig { params(node:
|
71
|
+
sig { params(node: YARP::ClassNode).void }
|
76
72
|
def after_class(node)
|
77
73
|
@visibility_stack.pop
|
78
74
|
@class_stack.pop
|
79
75
|
end
|
80
76
|
|
81
|
-
sig { params(node:
|
77
|
+
sig { params(node: YARP::DefNode).void }
|
82
78
|
def on_def(node)
|
83
79
|
class_name = @class_stack.last
|
84
80
|
return unless class_name&.end_with?("Test")
|
85
81
|
|
86
82
|
visibility, _ = @visibility_stack.last
|
87
83
|
if visibility == "public"
|
88
|
-
method_name = node.name.
|
84
|
+
method_name = node.name.to_s
|
89
85
|
if @path && method_name.start_with?("test_")
|
90
86
|
add_test_code_lens(
|
91
87
|
node,
|
@@ -97,58 +93,44 @@ module RubyLsp
|
|
97
93
|
end
|
98
94
|
end
|
99
95
|
|
100
|
-
sig { params(node:
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
96
|
+
sig { params(node: YARP::CallNode).void }
|
97
|
+
def on_call(node)
|
98
|
+
name = node.name
|
99
|
+
arguments = node.arguments
|
100
|
+
|
101
|
+
# If we found `private` by itself or `private def foo`
|
102
|
+
if ACCESS_MODIFIERS.include?(name)
|
103
|
+
if arguments.nil?
|
104
|
+
@visibility_stack.pop
|
105
|
+
@visibility_stack.push([name, name])
|
106
|
+
elsif arguments.arguments.first.is_a?(YARP::DefNode)
|
107
|
+
visibility, _ = @visibility_stack.pop
|
108
|
+
@visibility_stack.push([name, visibility])
|
109
|
+
end
|
109
110
|
|
110
|
-
|
111
|
+
return
|
111
112
|
end
|
112
|
-
end
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
@visibility_stack.push([prev_visibility, prev_visibility])
|
118
|
-
end
|
114
|
+
if @path&.include?("Gemfile") && name == "gem" && arguments
|
115
|
+
first_argument = arguments.arguments.first
|
116
|
+
return unless first_argument.is_a?(YARP::StringNode)
|
119
117
|
|
120
|
-
|
121
|
-
|
122
|
-
ident = node.message if node.message.is_a?(SyntaxTree::Ident)
|
118
|
+
remote = resolve_gem_remote(first_argument)
|
119
|
+
return unless remote
|
123
120
|
|
124
|
-
|
125
|
-
ident_value = T.cast(ident, SyntaxTree::Ident).value
|
126
|
-
if ACCESS_MODIFIERS.include?(ident_value)
|
127
|
-
visibility, _ = @visibility_stack.pop
|
128
|
-
@visibility_stack.push([ident_value, visibility])
|
129
|
-
end
|
121
|
+
add_open_gem_remote_code_lens(node, remote)
|
130
122
|
end
|
131
123
|
end
|
132
124
|
|
133
|
-
sig { params(node:
|
125
|
+
sig { params(node: YARP::CallNode).void }
|
134
126
|
def after_call(node)
|
135
127
|
_, prev_visibility = @visibility_stack.pop
|
136
128
|
@visibility_stack.push([prev_visibility, prev_visibility])
|
137
129
|
end
|
138
130
|
|
139
|
-
sig { params(
|
140
|
-
def
|
141
|
-
|
142
|
-
|
143
|
-
if ACCESS_MODIFIERS.include?(vcall_value)
|
144
|
-
@visibility_stack.pop
|
145
|
-
@visibility_stack.push([vcall_value, vcall_value])
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
sig { override.params(extension: RubyLsp::Extension).returns(T.nilable(Listener[ResponseType])) }
|
150
|
-
def initialize_external_listener(extension)
|
151
|
-
extension.create_code_lens_listener(@uri, @emitter, @message_queue)
|
131
|
+
sig { override.params(addon: Addon).returns(T.nilable(Listener[ResponseType])) }
|
132
|
+
def initialize_external_listener(addon)
|
133
|
+
addon.create_code_lens_listener(@uri, @emitter, @message_queue)
|
152
134
|
end
|
153
135
|
|
154
136
|
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
@@ -159,10 +141,10 @@ module RubyLsp
|
|
159
141
|
|
160
142
|
private
|
161
143
|
|
162
|
-
sig { params(node:
|
144
|
+
sig { params(node: YARP::Node, name: String, command: String, kind: Symbol).void }
|
163
145
|
def add_test_code_lens(node, name:, command:, kind:)
|
164
146
|
# don't add code lenses if the test library is not supported or unknown
|
165
|
-
return unless SUPPORTED_TEST_LIBRARIES.include?(
|
147
|
+
return unless SUPPORTED_TEST_LIBRARIES.include?(DependencyDetector.instance.detected_test_library) && @path
|
166
148
|
|
167
149
|
arguments = [
|
168
150
|
@path,
|
@@ -201,15 +183,9 @@ module RubyLsp
|
|
201
183
|
)
|
202
184
|
end
|
203
185
|
|
204
|
-
sig { params(
|
205
|
-
def resolve_gem_remote(
|
206
|
-
|
207
|
-
return unless gem_statement.is_a?(SyntaxTree::StringLiteral)
|
208
|
-
|
209
|
-
gem_name = gem_statement.parts.first
|
210
|
-
return unless gem_name.is_a?(SyntaxTree::TStringContent)
|
211
|
-
|
212
|
-
spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name.value }&.to_spec
|
186
|
+
sig { params(gem_name: YARP::StringNode).returns(T.nilable(String)) }
|
187
|
+
def resolve_gem_remote(gem_name)
|
188
|
+
spec = Gem::Specification.stubs.find { |gem| gem.name == gem_name.content }&.to_spec
|
213
189
|
return if spec.nil?
|
214
190
|
|
215
191
|
[spec.homepage, spec.metadata["source_code_uri"]].compact.find do |page|
|
@@ -221,7 +197,7 @@ module RubyLsp
|
|
221
197
|
def generate_test_command(class_name:, method_name: nil)
|
222
198
|
command = BASE_COMMAND + T.must(@path)
|
223
199
|
|
224
|
-
case
|
200
|
+
case DependencyDetector.instance.detected_test_library
|
225
201
|
when "minitest"
|
226
202
|
command += if method_name
|
227
203
|
" --name " + "/#{Shellwords.escape(class_name + "#" + method_name)}/"
|
@@ -239,7 +215,7 @@ module RubyLsp
|
|
239
215
|
command
|
240
216
|
end
|
241
217
|
|
242
|
-
sig { params(node:
|
218
|
+
sig { params(node: YARP::CallNode, remote: String).void }
|
243
219
|
def add_open_gem_remote_code_lens(node, remote)
|
244
220
|
@_response << create_code_lens(
|
245
221
|
node,
|
@@ -40,58 +40,80 @@ module RubyLsp
|
|
40
40
|
@index = index
|
41
41
|
@nesting = nesting
|
42
42
|
|
43
|
-
emitter.register(self, :
|
43
|
+
emitter.register(self, :on_string, :on_constant_path, :on_constant_read)
|
44
44
|
end
|
45
45
|
|
46
|
-
sig { params(node:
|
47
|
-
def
|
48
|
-
@index.search_require_paths(node.
|
46
|
+
sig { params(node: YARP::StringNode).void }
|
47
|
+
def on_string(node)
|
48
|
+
@index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
|
49
49
|
@_response << build_completion(T.must(path), node)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
53
|
# Handle completion on regular constant references (e.g. `Bar`)
|
54
|
-
sig { params(node:
|
55
|
-
def
|
56
|
-
return if DependencyDetector
|
54
|
+
sig { params(node: YARP::ConstantReadNode).void }
|
55
|
+
def on_constant_read(node)
|
56
|
+
return if DependencyDetector.instance.typechecker
|
57
57
|
|
58
|
-
name = node.
|
58
|
+
name = node.slice
|
59
59
|
candidates = @index.prefix_search(name, @nesting)
|
60
60
|
candidates.each do |entries|
|
61
|
-
|
61
|
+
complete_name = T.must(entries.first).name
|
62
|
+
@_response << build_entry_completion(complete_name, node, entries, top_level?(complete_name, candidates))
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
65
66
|
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
66
|
-
sig { params(node:
|
67
|
-
def
|
68
|
-
return if DependencyDetector
|
67
|
+
sig { params(node: YARP::ConstantPathNode).void }
|
68
|
+
def on_constant_path(node)
|
69
|
+
return if DependencyDetector.instance.typechecker
|
69
70
|
|
70
|
-
name =
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
name = node.slice
|
72
|
+
|
73
|
+
top_level_reference = if name.start_with?("::")
|
74
|
+
name = name.delete_prefix("::")
|
75
|
+
true
|
76
|
+
else
|
77
|
+
false
|
74
78
|
end
|
75
|
-
end
|
76
79
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
80
|
+
# If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
|
81
|
+
# order to find which possible constants match the desired search
|
82
|
+
*namespace, incomplete_name = name.split("::")
|
83
|
+
aliased_namespace = namespace.join("::")
|
84
|
+
namespace_entries = @index.resolve(aliased_namespace, @nesting)
|
85
|
+
return unless namespace_entries
|
86
|
+
|
87
|
+
real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
|
81
88
|
|
82
|
-
|
83
|
-
candidates
|
84
|
-
|
89
|
+
candidates = @index.prefix_search("#{real_namespace}::#{incomplete_name}", top_level_reference ? [] : @nesting)
|
90
|
+
candidates.each do |entries|
|
91
|
+
# The only time we may have a private constant reference from outside of the namespace is if we're dealing
|
92
|
+
# with ConstantPath and the entry name doesn't start with the current nesting
|
93
|
+
first_entry = T.must(entries.first)
|
94
|
+
next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
|
95
|
+
|
96
|
+
constant_name = T.must(first_entry.name.split("::").last)
|
97
|
+
|
98
|
+
full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
|
99
|
+
|
100
|
+
@_response << build_entry_completion(
|
101
|
+
full_name,
|
102
|
+
node,
|
103
|
+
entries,
|
104
|
+
top_level_reference || top_level?(T.must(entries.first).name, candidates),
|
105
|
+
)
|
106
|
+
end
|
85
107
|
end
|
86
108
|
|
87
109
|
private
|
88
110
|
|
89
|
-
sig { params(label: String, node:
|
111
|
+
sig { params(label: String, node: YARP::StringNode).returns(Interface::CompletionItem) }
|
90
112
|
def build_completion(label, node)
|
91
113
|
Interface::CompletionItem.new(
|
92
114
|
label: label,
|
93
115
|
text_edit: Interface::TextEdit.new(
|
94
|
-
range:
|
116
|
+
range: range_from_node(node),
|
95
117
|
new_text: label,
|
96
118
|
),
|
97
119
|
kind: Constant::CompletionItemKind::REFERENCE,
|
@@ -101,7 +123,7 @@ module RubyLsp
|
|
101
123
|
sig do
|
102
124
|
params(
|
103
125
|
name: String,
|
104
|
-
node:
|
126
|
+
node: YARP::Node,
|
105
127
|
entries: T::Array[RubyIndexer::Index::Entry],
|
106
128
|
top_level: T::Boolean,
|
107
129
|
).returns(Interface::CompletionItem)
|
@@ -119,7 +141,7 @@ module RubyLsp
|
|
119
141
|
Constant::CompletionItemKind::REFERENCE
|
120
142
|
end
|
121
143
|
|
122
|
-
insertion_text =
|
144
|
+
insertion_text = name.dup
|
123
145
|
|
124
146
|
# If we have two entries with the same name inside the current namespace and the user selects the top level
|
125
147
|
# option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
|
@@ -143,17 +165,17 @@ module RubyLsp
|
|
143
165
|
# For these top level references, we need to include the `::` as part of the filter text or else it won't match
|
144
166
|
# the right entries in the index
|
145
167
|
Interface::CompletionItem.new(
|
146
|
-
label:
|
147
|
-
filter_text: top_level ? "::#{
|
168
|
+
label: name,
|
169
|
+
filter_text: top_level ? "::#{name}" : name,
|
148
170
|
text_edit: Interface::TextEdit.new(
|
149
|
-
range:
|
171
|
+
range: range_from_node(node),
|
150
172
|
new_text: insertion_text,
|
151
173
|
),
|
152
174
|
kind: kind,
|
153
175
|
label_details: Interface::CompletionItemLabelDetails.new(
|
154
176
|
description: entries.map(&:file_name).join(","),
|
155
177
|
),
|
156
|
-
documentation: markdown_from_index_entries(
|
178
|
+
documentation: markdown_from_index_entries(name, entries),
|
157
179
|
)
|
158
180
|
end
|
159
181
|
|
@@ -43,12 +43,11 @@ module RubyLsp
|
|
43
43
|
|
44
44
|
super(emitter, message_queue)
|
45
45
|
|
46
|
-
emitter.register(self, :
|
46
|
+
emitter.register(self, :on_call, :on_constant_read, :on_constant_path)
|
47
47
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
ext.create_definition_listener(@uri, @nesting, @index, @emitter, @message_queue)
|
48
|
+
sig { override.params(addon: Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
|
49
|
+
def initialize_external_listener(addon)
|
50
|
+
addon.create_definition_listener(@uri, @nesting, @index, @emitter, @message_queue)
|
52
51
|
end
|
53
52
|
|
54
53
|
sig { override.params(other: Listener[ResponseType]).returns(T.self_type) }
|
@@ -67,32 +66,21 @@ module RubyLsp
|
|
67
66
|
self
|
68
67
|
end
|
69
68
|
|
70
|
-
sig { params(node:
|
71
|
-
def
|
72
|
-
|
73
|
-
find_in_index(name)
|
74
|
-
end
|
75
|
-
|
76
|
-
sig { params(node: SyntaxTree::Const).void }
|
77
|
-
def on_const(node)
|
78
|
-
find_in_index(node.value)
|
79
|
-
end
|
80
|
-
|
81
|
-
sig { params(node: SyntaxTree::Command).void }
|
82
|
-
def on_command(node)
|
83
|
-
message = node.message.value
|
69
|
+
sig { params(node: YARP::CallNode).void }
|
70
|
+
def on_call(node)
|
71
|
+
message = node.name
|
84
72
|
return unless message == "require" || message == "require_relative"
|
85
73
|
|
86
|
-
|
87
|
-
return unless
|
74
|
+
arguments = node.arguments
|
75
|
+
return unless arguments
|
88
76
|
|
89
|
-
|
90
|
-
return unless
|
77
|
+
argument = arguments.arguments.first
|
78
|
+
return unless argument.is_a?(YARP::StringNode)
|
91
79
|
|
92
80
|
case message
|
93
81
|
when "require"
|
94
|
-
entry = @index.search_require_paths(
|
95
|
-
indexable_path.require_path ==
|
82
|
+
entry = @index.search_require_paths(argument.content).find do |indexable_path|
|
83
|
+
indexable_path.require_path == argument.content
|
96
84
|
end
|
97
85
|
|
98
86
|
if entry
|
@@ -107,7 +95,7 @@ module RubyLsp
|
|
107
95
|
)
|
108
96
|
end
|
109
97
|
when "require_relative"
|
110
|
-
required_file = "#{
|
98
|
+
required_file = "#{argument.content}.rb"
|
111
99
|
path = @uri.to_standardized_path
|
112
100
|
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
|
113
101
|
candidate = File.expand_path(File.join(current_folder, required_file))
|
@@ -122,6 +110,16 @@ module RubyLsp
|
|
122
110
|
end
|
123
111
|
end
|
124
112
|
|
113
|
+
sig { params(node: YARP::ConstantPathNode).void }
|
114
|
+
def on_constant_path(node)
|
115
|
+
find_in_index(node.slice)
|
116
|
+
end
|
117
|
+
|
118
|
+
sig { params(node: YARP::ConstantReadNode).void }
|
119
|
+
def on_constant_read(node)
|
120
|
+
find_in_index(node.slice)
|
121
|
+
end
|
122
|
+
|
125
123
|
private
|
126
124
|
|
127
125
|
sig { params(value: String).void }
|
@@ -129,6 +127,11 @@ module RubyLsp
|
|
129
127
|
entries = @index.resolve(value, @nesting)
|
130
128
|
return unless entries
|
131
129
|
|
130
|
+
# We should only allow jumping to the definition of private constants if the constant is defined in the same
|
131
|
+
# namespace as the reference
|
132
|
+
first_entry = T.must(entries.first)
|
133
|
+
return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
|
134
|
+
|
132
135
|
bundle_path = begin
|
133
136
|
Bundler.bundle_path.to_s
|
134
137
|
rescue Bundler::GemfileNotFound
|
@@ -141,7 +144,7 @@ module RubyLsp
|
|
141
144
|
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
142
145
|
# in the project, even if the files are typed false
|
143
146
|
file_path = entry.file_path
|
144
|
-
if DependencyDetector
|
147
|
+
if DependencyDetector.instance.typechecker && bundle_path && !file_path.start_with?(bundle_path) &&
|
145
148
|
!file_path.start_with?(RbConfig::CONFIG["rubylibdir"])
|
146
149
|
|
147
150
|
next
|