ruby-lsp 0.13.2 → 0.13.3
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 +30 -0
- data/VERSION +1 -1
- data/lib/ruby_lsp/check_docs.rb +3 -3
- data/lib/ruby_lsp/document.rb +12 -0
- data/lib/ruby_lsp/executor.rb +77 -266
- data/lib/ruby_lsp/listener.rb +1 -50
- data/lib/ruby_lsp/listeners/code_lens.rb +233 -0
- data/lib/ruby_lsp/listeners/completion.rb +275 -0
- data/lib/ruby_lsp/listeners/definition.rb +158 -0
- data/lib/ruby_lsp/listeners/document_highlight.rb +556 -0
- data/lib/ruby_lsp/listeners/document_link.rb +162 -0
- data/lib/ruby_lsp/listeners/document_symbol.rb +223 -0
- data/lib/ruby_lsp/listeners/folding_ranges.rb +271 -0
- data/lib/ruby_lsp/listeners/hover.rb +152 -0
- data/lib/ruby_lsp/listeners/inlay_hints.rb +80 -0
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +430 -0
- data/lib/ruby_lsp/listeners/signature_help.rb +74 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +4 -4
- data/lib/ruby_lsp/requests/code_actions.rb +13 -4
- data/lib/ruby_lsp/requests/code_lens.rb +21 -221
- data/lib/ruby_lsp/requests/completion.rb +64 -244
- data/lib/ruby_lsp/requests/definition.rb +34 -147
- data/lib/ruby_lsp/requests/diagnostics.rb +17 -5
- data/lib/ruby_lsp/requests/document_highlight.rb +12 -536
- data/lib/ruby_lsp/requests/document_link.rb +11 -132
- data/lib/ruby_lsp/requests/document_symbol.rb +23 -210
- data/lib/ruby_lsp/requests/folding_ranges.rb +16 -252
- data/lib/ruby_lsp/requests/formatting.rb +4 -4
- data/lib/ruby_lsp/requests/hover.rb +48 -92
- data/lib/ruby_lsp/requests/inlay_hints.rb +23 -56
- data/lib/ruby_lsp/requests/on_type_formatting.rb +16 -4
- data/lib/ruby_lsp/requests/request.rb +17 -0
- data/lib/ruby_lsp/requests/selection_ranges.rb +4 -3
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +21 -408
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +4 -4
- data/lib/ruby_lsp/requests/signature_help.rb +43 -51
- data/lib/ruby_lsp/requests/support/common.rb +3 -2
- data/lib/ruby_lsp/requests/support/dependency_detector.rb +2 -0
- data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +2 -2
- data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
- data/lib/ruby_lsp/requests.rb +1 -1
- data/lib/ruby_lsp/utils.rb +8 -0
- metadata +15 -4
- data/lib/ruby_lsp/requests/base_request.rb +0 -24
@@ -1,6 +1,8 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "ruby_lsp/listeners/completion"
|
5
|
+
|
4
6
|
module RubyLsp
|
5
7
|
module Requests
|
6
8
|
# 
|
@@ -22,269 +24,87 @@ module RubyLsp
|
|
22
24
|
#
|
23
25
|
# RubyLsp::Requests:: # --> completion: suggests `Completion`, `Hover`, ...
|
24
26
|
# ```
|
25
|
-
class Completion <
|
27
|
+
class Completion < Request
|
26
28
|
extend T::Sig
|
27
29
|
extend T::Generic
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
def initialize(index, nesting, dispatcher)
|
42
|
-
super(dispatcher)
|
43
|
-
@_response = T.let([], ResponseType)
|
44
|
-
@index = index
|
45
|
-
@nesting = nesting
|
46
|
-
|
47
|
-
dispatcher.register(
|
48
|
-
self,
|
49
|
-
:on_string_node_enter,
|
50
|
-
:on_constant_path_node_enter,
|
51
|
-
:on_constant_read_node_enter,
|
52
|
-
:on_call_node_enter,
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
sig { params(node: Prism::StringNode).void }
|
57
|
-
def on_string_node_enter(node)
|
58
|
-
@index.search_require_paths(node.content).map!(&:require_path).sort!.each do |path|
|
59
|
-
@_response << build_completion(T.must(path), node)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Handle completion on regular constant references (e.g. `Bar`)
|
64
|
-
sig { params(node: Prism::ConstantReadNode).void }
|
65
|
-
def on_constant_read_node_enter(node)
|
66
|
-
return if DependencyDetector.instance.typechecker
|
67
|
-
|
68
|
-
name = node.slice
|
69
|
-
candidates = @index.prefix_search(name, @nesting)
|
70
|
-
candidates.each do |entries|
|
71
|
-
complete_name = T.must(entries.first).name
|
72
|
-
@_response << build_entry_completion(
|
73
|
-
complete_name,
|
74
|
-
name,
|
75
|
-
node,
|
76
|
-
entries,
|
77
|
-
top_level?(complete_name),
|
78
|
-
)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
83
|
-
sig { params(node: Prism::ConstantPathNode).void }
|
84
|
-
def on_constant_path_node_enter(node)
|
85
|
-
return if DependencyDetector.instance.typechecker
|
86
|
-
|
87
|
-
name = node.slice
|
88
|
-
|
89
|
-
top_level_reference = if name.start_with?("::")
|
90
|
-
name = name.delete_prefix("::")
|
91
|
-
true
|
92
|
-
else
|
93
|
-
false
|
94
|
-
end
|
95
|
-
|
96
|
-
# If we're trying to provide completion for an aliased namespace, we need to first discover it's real name in
|
97
|
-
# order to find which possible constants match the desired search
|
98
|
-
*namespace, incomplete_name = name.split("::")
|
99
|
-
aliased_namespace = T.must(namespace).join("::")
|
100
|
-
namespace_entries = @index.resolve(aliased_namespace, @nesting)
|
101
|
-
return unless namespace_entries
|
102
|
-
|
103
|
-
real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
|
104
|
-
|
105
|
-
candidates = @index.prefix_search("#{real_namespace}::#{incomplete_name}", top_level_reference ? [] : @nesting)
|
106
|
-
candidates.each do |entries|
|
107
|
-
# The only time we may have a private constant reference from outside of the namespace is if we're dealing
|
108
|
-
# with ConstantPath and the entry name doesn't start with the current nesting
|
109
|
-
first_entry = T.must(entries.first)
|
110
|
-
next if first_entry.visibility == :private && !first_entry.name.start_with?("#{@nesting}::")
|
111
|
-
|
112
|
-
constant_name = T.must(first_entry.name.split("::").last)
|
113
|
-
|
114
|
-
full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
|
115
|
-
|
116
|
-
@_response << build_entry_completion(
|
117
|
-
full_name,
|
118
|
-
name,
|
119
|
-
node,
|
120
|
-
entries,
|
121
|
-
top_level_reference || top_level?(T.must(entries.first).name),
|
31
|
+
class << self
|
32
|
+
extend T::Sig
|
33
|
+
|
34
|
+
sig { returns(Interface::CompletionOptions) }
|
35
|
+
def provider
|
36
|
+
Interface::CompletionOptions.new(
|
37
|
+
resolve_provider: false,
|
38
|
+
trigger_characters: ["/"],
|
39
|
+
completion_item: {
|
40
|
+
labelDetailsSupport: true,
|
41
|
+
},
|
122
42
|
)
|
123
43
|
end
|
124
44
|
end
|
125
45
|
|
126
|
-
|
127
|
-
def on_call_node_enter(node)
|
128
|
-
return if DependencyDetector.instance.typechecker
|
129
|
-
return unless self_receiver?(node)
|
130
|
-
|
131
|
-
name = node.message
|
132
|
-
return unless name
|
133
|
-
|
134
|
-
receiver_entries = @index[@nesting.join("::")]
|
135
|
-
return unless receiver_entries
|
136
|
-
|
137
|
-
receiver = T.must(receiver_entries.first)
|
138
|
-
|
139
|
-
@index.prefix_search(name).each do |entries|
|
140
|
-
entry = entries.find { |e| e.is_a?(RubyIndexer::Entry::Member) && e.owner&.name == receiver.name }
|
141
|
-
next unless entry
|
142
|
-
|
143
|
-
@_response << build_method_completion(T.cast(entry, RubyIndexer::Entry::Member), node)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
46
|
+
ResponseType = type_member { { fixed: T::Array[Interface::CompletionItem] } }
|
148
47
|
|
149
48
|
sig do
|
150
49
|
params(
|
151
|
-
|
152
|
-
|
153
|
-
|
50
|
+
document: Document,
|
51
|
+
index: RubyIndexer::Index,
|
52
|
+
position: T::Hash[Symbol, T.untyped],
|
53
|
+
typechecker_enabled: T::Boolean,
|
54
|
+
dispatcher: Prism::Dispatcher,
|
55
|
+
).void
|
154
56
|
end
|
155
|
-
def
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
documentation: markdown_from_index_entries(name, entry),
|
57
|
+
def initialize(document, index, position, typechecker_enabled, dispatcher)
|
58
|
+
super()
|
59
|
+
@target = T.let(nil, T.nilable(Prism::Node))
|
60
|
+
@dispatcher = dispatcher
|
61
|
+
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
62
|
+
# back by 1, so that we find the right node
|
63
|
+
char_position = document.create_scanner.find_char_position(position) - 1
|
64
|
+
matched, parent, nesting = document.locate(
|
65
|
+
document.tree,
|
66
|
+
char_position,
|
67
|
+
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
|
167
68
|
)
|
168
|
-
end
|
169
69
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
loc = node.content_loc
|
174
|
-
|
175
|
-
Interface::CompletionItem.new(
|
176
|
-
label: label,
|
177
|
-
text_edit: Interface::TextEdit.new(
|
178
|
-
range: range_from_location(loc),
|
179
|
-
new_text: label,
|
180
|
-
),
|
181
|
-
kind: Constant::CompletionItemKind::FILE,
|
70
|
+
@listener = T.let(
|
71
|
+
Listeners::Completion.new(index, nesting, typechecker_enabled, dispatcher),
|
72
|
+
Listener[ResponseType],
|
182
73
|
)
|
183
|
-
end
|
184
74
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
# If we have two entries with the same name inside the current namespace and the user selects the top level
|
211
|
-
# option, we have to ensure it's prefixed with `::` or else we're completing the wrong constant. For example:
|
212
|
-
# If we have the index with ["Foo::Bar", "Bar"], and we're providing suggestions for `B` inside a `Foo` module,
|
213
|
-
# then selecting the `Foo::Bar` option needs to complete to `Bar` and selecting the top level `Bar` option needs
|
214
|
-
# to complete to `::Bar`.
|
215
|
-
if top_level
|
216
|
-
insertion_text.prepend("::")
|
217
|
-
filter_text.prepend("::")
|
218
|
-
end
|
219
|
-
|
220
|
-
# If the user is searching for a constant inside the current namespace, then we prefer completing the short name
|
221
|
-
# of that constant. E.g.:
|
222
|
-
#
|
223
|
-
# module Foo
|
224
|
-
# class Bar
|
225
|
-
# end
|
226
|
-
#
|
227
|
-
# Foo::B # --> completion inserts `Bar` instead of `Foo::Bar`
|
228
|
-
# end
|
229
|
-
@nesting.each do |namespace|
|
230
|
-
prefix = "#{namespace}::"
|
231
|
-
shortened_name = insertion_text.delete_prefix(prefix)
|
232
|
-
|
233
|
-
# If a different entry exists for the shortened name, then there's a conflict and we should not shorten it
|
234
|
-
conflict_name = "#{@nesting.join("::")}::#{shortened_name}"
|
235
|
-
break if real_name != conflict_name && @index[conflict_name]
|
236
|
-
|
237
|
-
insertion_text = shortened_name
|
238
|
-
|
239
|
-
# If the user is typing a fully qualified name `Foo::Bar::Baz`, then we should not use the short name (e.g.:
|
240
|
-
# `Baz`) as filtering. So we only shorten the filter text if the user is not including the namespaces in their
|
241
|
-
# typing
|
242
|
-
filter_text.delete_prefix!(prefix) unless incomplete_name.start_with?(prefix)
|
75
|
+
return unless matched && parent
|
76
|
+
|
77
|
+
@target = case matched
|
78
|
+
when Prism::CallNode
|
79
|
+
message = matched.message
|
80
|
+
|
81
|
+
if message == "require"
|
82
|
+
args = matched.arguments&.arguments
|
83
|
+
return if args.nil? || args.is_a?(Prism::ForwardingArgumentsNode)
|
84
|
+
|
85
|
+
argument = args.first
|
86
|
+
return unless argument.is_a?(Prism::StringNode)
|
87
|
+
return unless (argument.location.start_offset..argument.location.end_offset).cover?(char_position)
|
88
|
+
|
89
|
+
argument
|
90
|
+
else
|
91
|
+
matched
|
92
|
+
end
|
93
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
94
|
+
if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
|
95
|
+
parent
|
96
|
+
else
|
97
|
+
matched
|
98
|
+
end
|
243
99
|
end
|
244
|
-
|
245
|
-
# When using a top level constant reference (e.g.: `::Bar`), the editor includes the `::` as part of the filter.
|
246
|
-
# For these top level references, we need to include the `::` as part of the filter text or else it won't match
|
247
|
-
# the right entries in the index
|
248
|
-
Interface::CompletionItem.new(
|
249
|
-
label: real_name,
|
250
|
-
filter_text: filter_text,
|
251
|
-
text_edit: Interface::TextEdit.new(
|
252
|
-
range: range_from_node(node),
|
253
|
-
new_text: insertion_text,
|
254
|
-
),
|
255
|
-
kind: kind,
|
256
|
-
label_details: Interface::CompletionItemLabelDetails.new(
|
257
|
-
description: entries.map(&:file_name).join(","),
|
258
|
-
),
|
259
|
-
documentation: markdown_from_index_entries(real_name, entries),
|
260
|
-
)
|
261
100
|
end
|
262
101
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
# ```ruby
|
267
|
-
# class Bar; end
|
268
|
-
#
|
269
|
-
# module Foo
|
270
|
-
# class Bar; end
|
271
|
-
#
|
272
|
-
# # in this case, the completion for `Bar` conflicts with `Foo::Bar`, so we can't suggest `Bar` as the
|
273
|
-
# # completion, but instead need to suggest `::Bar`
|
274
|
-
# B
|
275
|
-
# end
|
276
|
-
# ```
|
277
|
-
sig { params(entry_name: String).returns(T::Boolean) }
|
278
|
-
def top_level?(entry_name)
|
279
|
-
@nesting.length.downto(0).each do |i|
|
280
|
-
prefix = T.must(@nesting[0...i]).join("::")
|
281
|
-
full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
|
282
|
-
next if full_name == entry_name
|
283
|
-
|
284
|
-
return true if @index[full_name]
|
285
|
-
end
|
102
|
+
sig { override.returns(ResponseType) }
|
103
|
+
def perform
|
104
|
+
return [] unless @target
|
286
105
|
|
287
|
-
|
106
|
+
@dispatcher.dispatch_once(@target)
|
107
|
+
@listener.response
|
288
108
|
end
|
289
109
|
end
|
290
110
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require "ruby_lsp/listeners/definition"
|
5
|
+
|
4
6
|
module RubyLsp
|
5
7
|
module Requests
|
6
8
|
# 
|
@@ -22,174 +24,59 @@ module RubyLsp
|
|
22
24
|
# require "some_gem/file" # <- Request go to definition on this string will take you to the file
|
23
25
|
# Product.new # <- Request go to definition on this class name will take you to its declaration.
|
24
26
|
# ```
|
25
|
-
class Definition <
|
27
|
+
class Definition < Request
|
26
28
|
extend T::Sig
|
27
29
|
extend T::Generic
|
28
30
|
|
29
31
|
ResponseType = type_member { { fixed: T.nilable(T.any(T::Array[Interface::Location], Interface::Location)) } }
|
30
32
|
|
31
|
-
sig { override.returns(ResponseType) }
|
32
|
-
attr_reader :_response
|
33
|
-
|
34
33
|
sig do
|
35
34
|
params(
|
36
|
-
|
37
|
-
nesting: T::Array[String],
|
35
|
+
document: Document,
|
38
36
|
index: RubyIndexer::Index,
|
37
|
+
position: T::Hash[Symbol, T.untyped],
|
39
38
|
dispatcher: Prism::Dispatcher,
|
39
|
+
typechecker_enabled: T::Boolean,
|
40
40
|
).void
|
41
41
|
end
|
42
|
-
def initialize(
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
super(dispatcher)
|
49
|
-
|
50
|
-
dispatcher.register(
|
51
|
-
self,
|
52
|
-
:on_call_node_enter,
|
53
|
-
:on_constant_read_node_enter,
|
54
|
-
:on_constant_path_node_enter,
|
42
|
+
def initialize(document, index, position, dispatcher, typechecker_enabled)
|
43
|
+
super()
|
44
|
+
target, parent, nesting = document.locate_node(
|
45
|
+
position,
|
46
|
+
node_types: [Prism::CallNode, Prism::ConstantReadNode, Prism::ConstantPathNode],
|
55
47
|
)
|
56
|
-
end
|
57
|
-
|
58
|
-
sig { override.params(addon: Addon).returns(T.nilable(RubyLsp::Listener[ResponseType])) }
|
59
|
-
def initialize_external_listener(addon)
|
60
|
-
addon.create_definition_listener(@uri, @nesting, @index, @dispatcher)
|
61
|
-
end
|
62
48
|
|
63
|
-
|
64
|
-
def merge_response!(other)
|
65
|
-
other_response = other._response
|
66
|
-
|
67
|
-
case @_response
|
68
|
-
when Interface::Location
|
69
|
-
@_response = [@_response, *other_response]
|
70
|
-
when Array
|
71
|
-
@_response.concat(Array(other_response))
|
72
|
-
when nil
|
73
|
-
@_response = other_response
|
74
|
-
end
|
49
|
+
target = parent if target.is_a?(Prism::ConstantReadNode) && parent.is_a?(Prism::ConstantPathNode)
|
75
50
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
if message == :require || message == :require_relative
|
84
|
-
handle_require_definition(node)
|
85
|
-
else
|
86
|
-
handle_method_definition(node)
|
51
|
+
@listeners = T.let(
|
52
|
+
[Listeners::Definition.new(document.uri, nesting, index, dispatcher, typechecker_enabled)],
|
53
|
+
T::Array[Listener[T.nilable(T.any(T::Array[Interface::Location], Interface::Location))]],
|
54
|
+
)
|
55
|
+
Addon.addons.each do |addon|
|
56
|
+
addon_listener = addon.create_definition_listener(document.uri, nesting, index, dispatcher)
|
57
|
+
@listeners << addon_listener if addon_listener
|
87
58
|
end
|
88
|
-
end
|
89
59
|
|
90
|
-
|
91
|
-
|
92
|
-
find_in_index(node.slice)
|
60
|
+
@target = T.let(target, T.nilable(Prism::Node))
|
61
|
+
@dispatcher = dispatcher
|
93
62
|
end
|
94
63
|
|
95
|
-
sig {
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
return unless message
|
108
|
-
|
109
|
-
target_method = @index.resolve_method(message, @nesting.join("::"))
|
110
|
-
return unless target_method
|
111
|
-
|
112
|
-
location = target_method.location
|
113
|
-
file_path = target_method.file_path
|
114
|
-
return if defined_in_gem?(file_path)
|
115
|
-
|
116
|
-
@_response = Interface::Location.new(
|
117
|
-
uri: URI::Generic.from_path(path: file_path).to_s,
|
118
|
-
range: Interface::Range.new(
|
119
|
-
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
120
|
-
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
121
|
-
),
|
122
|
-
)
|
123
|
-
end
|
124
|
-
|
125
|
-
sig { params(node: Prism::CallNode).void }
|
126
|
-
def handle_require_definition(node)
|
127
|
-
message = node.name
|
128
|
-
arguments = node.arguments
|
129
|
-
return unless arguments
|
130
|
-
|
131
|
-
argument = arguments.arguments.first
|
132
|
-
return unless argument.is_a?(Prism::StringNode)
|
133
|
-
|
134
|
-
case message
|
135
|
-
when :require
|
136
|
-
entry = @index.search_require_paths(argument.content).find do |indexable_path|
|
137
|
-
indexable_path.require_path == argument.content
|
138
|
-
end
|
139
|
-
|
140
|
-
if entry
|
141
|
-
candidate = entry.full_path
|
142
|
-
|
143
|
-
@_response = Interface::Location.new(
|
144
|
-
uri: URI::Generic.from_path(path: candidate).to_s,
|
145
|
-
range: Interface::Range.new(
|
146
|
-
start: Interface::Position.new(line: 0, character: 0),
|
147
|
-
end: Interface::Position.new(line: 0, character: 0),
|
148
|
-
),
|
149
|
-
)
|
64
|
+
sig { override.returns(ResponseType) }
|
65
|
+
def perform
|
66
|
+
@dispatcher.dispatch_once(@target)
|
67
|
+
result = []
|
68
|
+
|
69
|
+
@listeners.each do |listener|
|
70
|
+
res = listener.response
|
71
|
+
case res
|
72
|
+
when Interface::Location
|
73
|
+
result << res
|
74
|
+
when Array
|
75
|
+
result.concat(res)
|
150
76
|
end
|
151
|
-
when :require_relative
|
152
|
-
required_file = "#{argument.content}.rb"
|
153
|
-
path = @uri.to_standardized_path
|
154
|
-
current_folder = path ? Pathname.new(CGI.unescape(path)).dirname : Dir.pwd
|
155
|
-
candidate = File.expand_path(File.join(current_folder, required_file))
|
156
|
-
|
157
|
-
@_response = Interface::Location.new(
|
158
|
-
uri: URI::Generic.from_path(path: candidate).to_s,
|
159
|
-
range: Interface::Range.new(
|
160
|
-
start: Interface::Position.new(line: 0, character: 0),
|
161
|
-
end: Interface::Position.new(line: 0, character: 0),
|
162
|
-
),
|
163
|
-
)
|
164
77
|
end
|
165
|
-
end
|
166
78
|
|
167
|
-
|
168
|
-
def find_in_index(value)
|
169
|
-
entries = @index.resolve(value, @nesting)
|
170
|
-
return unless entries
|
171
|
-
|
172
|
-
# We should only allow jumping to the definition of private constants if the constant is defined in the same
|
173
|
-
# namespace as the reference
|
174
|
-
first_entry = T.must(entries.first)
|
175
|
-
return if first_entry.visibility == :private && first_entry.name != "#{@nesting.join("::")}::#{value}"
|
176
|
-
|
177
|
-
@_response = entries.filter_map do |entry|
|
178
|
-
location = entry.location
|
179
|
-
# If the project has Sorbet, then we only want to handle go to definition for constants defined in gems, as an
|
180
|
-
# additional behavior on top of jumping to RBIs. Sorbet can already handle go to definition for all constants
|
181
|
-
# in the project, even if the files are typed false
|
182
|
-
file_path = entry.file_path
|
183
|
-
next if defined_in_gem?(file_path)
|
184
|
-
|
185
|
-
Interface::Location.new(
|
186
|
-
uri: URI::Generic.from_path(path: file_path).to_s,
|
187
|
-
range: Interface::Range.new(
|
188
|
-
start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
|
189
|
-
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
|
190
|
-
),
|
191
|
-
)
|
192
|
-
end
|
79
|
+
result if result.any?
|
193
80
|
end
|
194
81
|
end
|
195
82
|
end
|
@@ -18,21 +18,33 @@ module RubyLsp
|
|
18
18
|
# puts "Hello" # --> diagnostics: incorrect indentation
|
19
19
|
# end
|
20
20
|
# ```
|
21
|
-
class Diagnostics <
|
21
|
+
class Diagnostics < Request
|
22
22
|
extend T::Sig
|
23
23
|
|
24
|
+
class << self
|
25
|
+
extend T::Sig
|
26
|
+
|
27
|
+
sig { returns(T::Hash[Symbol, T::Boolean]) }
|
28
|
+
def provider
|
29
|
+
{
|
30
|
+
interFileDependencies: false,
|
31
|
+
workspaceDiagnostics: false,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
24
36
|
sig { params(document: Document).void }
|
25
37
|
def initialize(document)
|
26
|
-
super(
|
27
|
-
|
38
|
+
super()
|
39
|
+
@document = document
|
28
40
|
@uri = T.let(document.uri, URI::Generic)
|
29
41
|
end
|
30
42
|
|
31
43
|
sig { override.returns(T.nilable(T.all(T::Array[Interface::Diagnostic], Object))) }
|
32
|
-
def
|
44
|
+
def perform
|
33
45
|
# Running RuboCop is slow, so to avoid excessive runs we only do so if the file is syntactically valid
|
34
46
|
return syntax_error_diagnostics if @document.syntax_error?
|
35
|
-
return unless defined?(Support::RuboCopDiagnosticsRunner)
|
47
|
+
return [] unless defined?(Support::RuboCopDiagnosticsRunner)
|
36
48
|
|
37
49
|
Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document).map!(&:to_lsp_diagnostic)
|
38
50
|
end
|