ruby-lsp 0.17.2 → 0.17.4
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 -0
- data/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +280 -74
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +102 -102
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +234 -56
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +147 -0
- data/lib/ruby_indexer/ruby_indexer.rb +1 -0
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +49 -2
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/constant_test.rb +1 -1
- data/lib/ruby_indexer/test/index_test.rb +702 -71
- data/lib/ruby_indexer/test/instance_variables_test.rb +84 -7
- data/lib/ruby_indexer/test/method_test.rb +74 -24
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +67 -0
- data/lib/ruby_indexer/test/test_case.rb +7 -0
- data/lib/ruby_lsp/document.rb +37 -8
- data/lib/ruby_lsp/global_state.rb +43 -18
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/code_lens.rb +2 -2
- data/lib/ruby_lsp/listeners/completion.rb +53 -14
- data/lib/ruby_lsp/listeners/definition.rb +11 -7
- data/lib/ruby_lsp/listeners/hover.rb +14 -7
- data/lib/ruby_lsp/listeners/signature_help.rb +5 -2
- data/lib/ruby_lsp/node_context.rb +6 -1
- data/lib/ruby_lsp/requests/completion.rb +5 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +8 -0
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +88 -0
- data/lib/ruby_lsp/requests/support/common.rb +19 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +12 -4
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +91 -0
- data/lib/ruby_lsp/requests/workspace_symbol.rb +1 -21
- data/lib/ruby_lsp/requests.rb +2 -0
- data/lib/ruby_lsp/server.rb +54 -4
- data/lib/ruby_lsp/test_helper.rb +1 -1
- data/lib/ruby_lsp/type_inferrer.rb +86 -0
- metadata +29 -4
@@ -15,15 +15,26 @@ module RubyLsp
|
|
15
15
|
typechecker_enabled: T::Boolean,
|
16
16
|
dispatcher: Prism::Dispatcher,
|
17
17
|
uri: URI::Generic,
|
18
|
+
trigger_character: T.nilable(String),
|
18
19
|
).void
|
19
20
|
end
|
20
|
-
def initialize(
|
21
|
+
def initialize( # rubocop:disable Metrics/ParameterLists
|
22
|
+
response_builder,
|
23
|
+
global_state,
|
24
|
+
node_context,
|
25
|
+
typechecker_enabled,
|
26
|
+
dispatcher,
|
27
|
+
uri,
|
28
|
+
trigger_character
|
29
|
+
)
|
21
30
|
@response_builder = response_builder
|
22
31
|
@global_state = global_state
|
23
32
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
33
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
24
34
|
@node_context = node_context
|
25
35
|
@typechecker_enabled = typechecker_enabled
|
26
36
|
@uri = uri
|
37
|
+
@trigger_character = trigger_character
|
27
38
|
|
28
39
|
dispatcher.register(
|
29
40
|
self,
|
@@ -42,7 +53,7 @@ module RubyLsp
|
|
42
53
|
# Handle completion on regular constant references (e.g. `Bar`)
|
43
54
|
sig { params(node: Prism::ConstantReadNode).void }
|
44
55
|
def on_constant_read_node_enter(node)
|
45
|
-
return if @global_state.
|
56
|
+
return if @global_state.has_type_checker
|
46
57
|
|
47
58
|
name = constant_name(node)
|
48
59
|
return if name.nil?
|
@@ -63,7 +74,7 @@ module RubyLsp
|
|
63
74
|
# Handle completion on namespaced constant references (e.g. `Foo::Bar`)
|
64
75
|
sig { params(node: Prism::ConstantPathNode).void }
|
65
76
|
def on_constant_path_node_enter(node)
|
66
|
-
return if @global_state.
|
77
|
+
return if @global_state.has_type_checker
|
67
78
|
|
68
79
|
name = constant_name(node)
|
69
80
|
return if name.nil?
|
@@ -107,7 +118,7 @@ module RubyLsp
|
|
107
118
|
when "require_relative"
|
108
119
|
complete_require_relative(node)
|
109
120
|
else
|
110
|
-
|
121
|
+
complete_methods(node, name) unless @typechecker_enabled
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
@@ -158,7 +169,7 @@ module RubyLsp
|
|
158
169
|
name.delete_suffix("::")
|
159
170
|
else
|
160
171
|
*namespace, incomplete_name = name.split("::")
|
161
|
-
|
172
|
+
namespace.join("::")
|
162
173
|
end
|
163
174
|
|
164
175
|
nesting = @node_context.nesting
|
@@ -192,7 +203,10 @@ module RubyLsp
|
|
192
203
|
|
193
204
|
sig { params(name: String, location: Prism::Location).void }
|
194
205
|
def handle_instance_variable_completion(name, location)
|
195
|
-
@
|
206
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
207
|
+
return unless type
|
208
|
+
|
209
|
+
@index.instance_variable_completion_candidates(name, type).each do |entry|
|
196
210
|
variable_name = entry.name
|
197
211
|
|
198
212
|
@response_builder << Interface::CompletionItem.new(
|
@@ -257,20 +271,45 @@ module RubyLsp
|
|
257
271
|
end
|
258
272
|
|
259
273
|
sig { params(node: Prism::CallNode, name: String).void }
|
260
|
-
def
|
261
|
-
|
262
|
-
return unless
|
274
|
+
def complete_methods(node, name)
|
275
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
276
|
+
return unless type
|
277
|
+
|
278
|
+
# When the trigger character is a dot, Prism matches the name of the call node to whatever is next in the source
|
279
|
+
# code, leading to us searching for the wrong name. What we want to do instead is show every available method
|
280
|
+
# when dot is pressed
|
281
|
+
method_name = @trigger_character == "." ? nil : name
|
282
|
+
|
283
|
+
range = if method_name
|
284
|
+
range_from_location(T.must(node.message_loc))
|
285
|
+
else
|
286
|
+
loc = T.must(node.call_operator_loc)
|
287
|
+
Interface::Range.new(
|
288
|
+
start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
289
|
+
end: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column + 1),
|
290
|
+
)
|
291
|
+
end
|
263
292
|
|
264
|
-
|
293
|
+
@index.method_completion_candidates(method_name, type).each do |entry|
|
294
|
+
entry_name = entry.name
|
265
295
|
|
266
|
-
|
267
|
-
|
296
|
+
@response_builder << Interface::CompletionItem.new(
|
297
|
+
label: entry_name,
|
298
|
+
filter_text: entry_name,
|
299
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
|
300
|
+
kind: Constant::CompletionItemKind::METHOD,
|
301
|
+
data: {
|
302
|
+
owner_name: entry.owner&.name,
|
303
|
+
},
|
304
|
+
)
|
268
305
|
end
|
306
|
+
rescue RubyIndexer::Index::NonExistingNamespaceError
|
307
|
+
# We have not indexed this namespace, so we can't provide any completions
|
269
308
|
end
|
270
309
|
|
271
310
|
sig do
|
272
311
|
params(
|
273
|
-
entry: RubyIndexer::Entry::Member,
|
312
|
+
entry: T.any(RubyIndexer::Entry::Member, RubyIndexer::Entry::MethodAlias),
|
274
313
|
node: Prism::CallNode,
|
275
314
|
).returns(Interface::CompletionItem)
|
276
315
|
end
|
@@ -283,7 +322,7 @@ module RubyLsp
|
|
283
322
|
text_edit: Interface::TextEdit.new(range: range_from_location(T.must(node.message_loc)), new_text: name),
|
284
323
|
kind: Constant::CompletionItemKind::METHOD,
|
285
324
|
label_details: Interface::CompletionItemLabelDetails.new(
|
286
|
-
detail:
|
325
|
+
detail: entry.decorated_parameters,
|
287
326
|
description: entry.file_name,
|
288
327
|
),
|
289
328
|
documentation: Interface::MarkupContent.new(
|
@@ -23,6 +23,7 @@ module RubyLsp
|
|
23
23
|
@response_builder = response_builder
|
24
24
|
@global_state = global_state
|
25
25
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
26
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
26
27
|
@uri = uri
|
27
28
|
@node_context = node_context
|
28
29
|
@typechecker_enabled = typechecker_enabled
|
@@ -48,7 +49,7 @@ module RubyLsp
|
|
48
49
|
message = node.message
|
49
50
|
return unless message
|
50
51
|
|
51
|
-
handle_method_definition(message,
|
52
|
+
handle_method_definition(message, @type_inferrer.infer_receiver_type(@node_context))
|
52
53
|
end
|
53
54
|
|
54
55
|
sig { params(node: Prism::StringNode).void }
|
@@ -70,7 +71,7 @@ module RubyLsp
|
|
70
71
|
value = expression.value
|
71
72
|
return unless value
|
72
73
|
|
73
|
-
handle_method_definition(value,
|
74
|
+
handle_method_definition(value, nil)
|
74
75
|
end
|
75
76
|
|
76
77
|
sig { params(node: Prism::ConstantPathNode).void }
|
@@ -123,7 +124,10 @@ module RubyLsp
|
|
123
124
|
|
124
125
|
sig { params(name: String).void }
|
125
126
|
def handle_instance_variable_definition(name)
|
126
|
-
|
127
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
128
|
+
return unless type
|
129
|
+
|
130
|
+
entries = @index.resolve_instance_variable(name, type)
|
127
131
|
return unless entries
|
128
132
|
|
129
133
|
entries.each do |entry|
|
@@ -141,10 +145,10 @@ module RubyLsp
|
|
141
145
|
# If by any chance we haven't indexed the owner, then there's no way to find the right declaration
|
142
146
|
end
|
143
147
|
|
144
|
-
sig { params(message: String,
|
145
|
-
def handle_method_definition(message,
|
146
|
-
methods = if
|
147
|
-
@index.resolve_method(message,
|
148
|
+
sig { params(message: String, receiver_type: T.nilable(String)).void }
|
149
|
+
def handle_method_definition(message, receiver_type)
|
150
|
+
methods = if receiver_type
|
151
|
+
@index.resolve_method(message, receiver_type)
|
148
152
|
else
|
149
153
|
# If the method doesn't have a receiver, then we provide a few candidates to jump to
|
150
154
|
# But we don't want to provide too many candidates, as it can be overwhelming
|
@@ -47,6 +47,7 @@ module RubyLsp
|
|
47
47
|
@response_builder = response_builder
|
48
48
|
@global_state = global_state
|
49
49
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
50
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
50
51
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
51
52
|
@node_context = node_context
|
52
53
|
@typechecker_enabled = typechecker_enabled
|
@@ -78,14 +79,14 @@ module RubyLsp
|
|
78
79
|
|
79
80
|
sig { params(node: Prism::ConstantWriteNode).void }
|
80
81
|
def on_constant_write_node_enter(node)
|
81
|
-
return if @global_state.
|
82
|
+
return if @global_state.has_type_checker
|
82
83
|
|
83
84
|
generate_hover(node.name.to_s, node.name_loc)
|
84
85
|
end
|
85
86
|
|
86
87
|
sig { params(node: Prism::ConstantPathNode).void }
|
87
88
|
def on_constant_path_node_enter(node)
|
88
|
-
return if @global_state.
|
89
|
+
return if @global_state.has_type_checker
|
89
90
|
|
90
91
|
name = constant_name(node)
|
91
92
|
return if name.nil?
|
@@ -95,8 +96,6 @@ module RubyLsp
|
|
95
96
|
|
96
97
|
sig { params(node: Prism::CallNode).void }
|
97
98
|
def on_call_node_enter(node)
|
98
|
-
return unless self_receiver?(node)
|
99
|
-
|
100
99
|
if @path && File.basename(@path) == GEMFILE_NAME && node.name == :gem
|
101
100
|
generate_gem_hover(node)
|
102
101
|
return
|
@@ -107,10 +106,15 @@ module RubyLsp
|
|
107
106
|
message = node.message
|
108
107
|
return unless message
|
109
108
|
|
110
|
-
|
109
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
110
|
+
return unless type
|
111
|
+
|
112
|
+
methods = @index.resolve_method(message, type)
|
111
113
|
return unless methods
|
112
114
|
|
113
|
-
|
115
|
+
title = "#{message}#{T.must(methods.first).decorated_parameters}"
|
116
|
+
|
117
|
+
categorized_markdown_from_index_entries(title, methods).each do |category, content|
|
114
118
|
@response_builder.push(content, category: category)
|
115
119
|
end
|
116
120
|
end
|
@@ -149,7 +153,10 @@ module RubyLsp
|
|
149
153
|
|
150
154
|
sig { params(name: String).void }
|
151
155
|
def handle_instance_variable_hover(name)
|
152
|
-
|
156
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
157
|
+
return unless type
|
158
|
+
|
159
|
+
entries = @index.resolve_instance_variable(name, type)
|
153
160
|
return unless entries
|
154
161
|
|
155
162
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
@@ -21,6 +21,7 @@ module RubyLsp
|
|
21
21
|
@response_builder = response_builder
|
22
22
|
@global_state = global_state
|
23
23
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
24
|
+
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
24
25
|
@node_context = node_context
|
25
26
|
dispatcher.register(self, :on_call_node_enter)
|
26
27
|
end
|
@@ -28,12 +29,14 @@ module RubyLsp
|
|
28
29
|
sig { params(node: Prism::CallNode).void }
|
29
30
|
def on_call_node_enter(node)
|
30
31
|
return if @typechecker_enabled
|
31
|
-
return unless self_receiver?(node)
|
32
32
|
|
33
33
|
message = node.message
|
34
34
|
return unless message
|
35
35
|
|
36
|
-
|
36
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
37
|
+
return unless type
|
38
|
+
|
39
|
+
methods = @index.resolve_method(message, type)
|
37
40
|
return unless methods
|
38
41
|
|
39
42
|
target_method = methods.first
|
@@ -16,19 +16,24 @@ module RubyLsp
|
|
16
16
|
sig { returns(T.nilable(Prism::CallNode)) }
|
17
17
|
attr_reader :call_node
|
18
18
|
|
19
|
+
sig { returns(T.nilable(String)) }
|
20
|
+
attr_reader :surrounding_method
|
21
|
+
|
19
22
|
sig do
|
20
23
|
params(
|
21
24
|
node: T.nilable(Prism::Node),
|
22
25
|
parent: T.nilable(Prism::Node),
|
23
26
|
nesting: T::Array[String],
|
24
27
|
call_node: T.nilable(Prism::CallNode),
|
28
|
+
surrounding_method: T.nilable(String),
|
25
29
|
).void
|
26
30
|
end
|
27
|
-
def initialize(node, parent, nesting, call_node)
|
31
|
+
def initialize(node, parent, nesting, call_node, surrounding_method)
|
28
32
|
@node = node
|
29
33
|
@parent = parent
|
30
34
|
@nesting = nesting
|
31
35
|
@call_node = call_node
|
36
|
+
@surrounding_method = surrounding_method
|
32
37
|
end
|
33
38
|
|
34
39
|
sig { returns(String) }
|
@@ -36,7 +36,7 @@ module RubyLsp
|
|
36
36
|
def provider
|
37
37
|
Interface::CompletionOptions.new(
|
38
38
|
resolve_provider: true,
|
39
|
-
trigger_characters: ["/", "\"", "'", ":", "@"],
|
39
|
+
trigger_characters: ["/", "\"", "'", ":", "@", "."],
|
40
40
|
completion_item: {
|
41
41
|
labelDetailsSupport: true,
|
42
42
|
},
|
@@ -48,18 +48,18 @@ module RubyLsp
|
|
48
48
|
params(
|
49
49
|
document: Document,
|
50
50
|
global_state: GlobalState,
|
51
|
-
|
51
|
+
params: T::Hash[Symbol, T.untyped],
|
52
52
|
typechecker_enabled: T::Boolean,
|
53
53
|
dispatcher: Prism::Dispatcher,
|
54
54
|
).void
|
55
55
|
end
|
56
|
-
def initialize(document, global_state,
|
56
|
+
def initialize(document, global_state, params, typechecker_enabled, dispatcher)
|
57
57
|
super()
|
58
58
|
@target = T.let(nil, T.nilable(Prism::Node))
|
59
59
|
@dispatcher = dispatcher
|
60
60
|
# Completion always receives the position immediately after the character that was just typed. Here we adjust it
|
61
61
|
# back by 1, so that we find the right node
|
62
|
-
char_position = document.create_scanner.find_char_position(position) - 1
|
62
|
+
char_position = document.create_scanner.find_char_position(params[:position]) - 1
|
63
63
|
node_context = document.locate(
|
64
64
|
document.tree,
|
65
65
|
char_position,
|
@@ -87,6 +87,7 @@ module RubyLsp
|
|
87
87
|
typechecker_enabled,
|
88
88
|
dispatcher,
|
89
89
|
document.uri,
|
90
|
+
params.dig(:context, :triggerCharacter),
|
90
91
|
)
|
91
92
|
|
92
93
|
Addon.addons.each do |addon|
|
@@ -56,8 +56,16 @@ module RubyLsp
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
first_entry = T.must(entries.first)
|
60
|
+
|
61
|
+
if first_entry.is_a?(RubyIndexer::Entry::Member)
|
62
|
+
detail = first_entry.decorated_parameters
|
63
|
+
label = "#{label}#{first_entry.decorated_parameters}"
|
64
|
+
end
|
65
|
+
|
59
66
|
@item[:labelDetails] = Interface::CompletionItemLabelDetails.new(
|
60
67
|
description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
|
68
|
+
detail: detail,
|
61
69
|
)
|
62
70
|
|
63
71
|
@item[:documentation] = Interface::MarkupContent.new(
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# ![Prepare type hierarchy demo](../../prepare_type_hierarchy.gif)
|
7
|
+
#
|
8
|
+
# The [prepare type hierarchy
|
9
|
+
# request](https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareTypeHierarchy)
|
10
|
+
# displays the list of ancestors (supertypes) and descendants (subtypes) for the selected type.
|
11
|
+
#
|
12
|
+
# Currently only supports supertypes due to a limitation of the index.
|
13
|
+
#
|
14
|
+
# # Example
|
15
|
+
#
|
16
|
+
# ```ruby
|
17
|
+
# class Foo; end
|
18
|
+
# class Bar < Foo; end
|
19
|
+
#
|
20
|
+
# puts Bar # <-- right click on `Bar` and select "Show Type Hierarchy"
|
21
|
+
# ```
|
22
|
+
class PrepareTypeHierarchy < Request
|
23
|
+
extend T::Sig
|
24
|
+
|
25
|
+
include Support::Common
|
26
|
+
|
27
|
+
class << self
|
28
|
+
extend T::Sig
|
29
|
+
|
30
|
+
sig { returns(Interface::TypeHierarchyOptions) }
|
31
|
+
def provider
|
32
|
+
Interface::TypeHierarchyOptions.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
sig do
|
37
|
+
params(
|
38
|
+
document: Document,
|
39
|
+
index: RubyIndexer::Index,
|
40
|
+
position: T::Hash[Symbol, T.untyped],
|
41
|
+
).void
|
42
|
+
end
|
43
|
+
def initialize(document, index, position)
|
44
|
+
super()
|
45
|
+
|
46
|
+
@document = document
|
47
|
+
@index = index
|
48
|
+
@position = position
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { override.returns(T.nilable(T::Array[Interface::TypeHierarchyItem])) }
|
52
|
+
def perform
|
53
|
+
context = @document.locate_node(
|
54
|
+
@position,
|
55
|
+
node_types: [
|
56
|
+
Prism::ConstantReadNode,
|
57
|
+
Prism::ConstantWriteNode,
|
58
|
+
Prism::ConstantPathNode,
|
59
|
+
],
|
60
|
+
)
|
61
|
+
|
62
|
+
node = context.node
|
63
|
+
parent = context.parent
|
64
|
+
return unless node && parent
|
65
|
+
|
66
|
+
target = determine_target(node, parent, @position)
|
67
|
+
entries = @index.resolve(target.slice, context.nesting)
|
68
|
+
return unless entries
|
69
|
+
|
70
|
+
# While the spec allows for multiple entries, VSCode seems to only support one
|
71
|
+
# We'll just return the first one for now
|
72
|
+
first_entry = T.must(entries.first)
|
73
|
+
|
74
|
+
range = range_from_location(first_entry.location)
|
75
|
+
|
76
|
+
[
|
77
|
+
Interface::TypeHierarchyItem.new(
|
78
|
+
name: first_entry.name,
|
79
|
+
kind: kind_for_entry(first_entry),
|
80
|
+
uri: URI::Generic.from_path(path: first_entry.file_path).to_s,
|
81
|
+
range: range,
|
82
|
+
selection_range: range,
|
83
|
+
),
|
84
|
+
]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -26,7 +26,7 @@ module RubyLsp
|
|
26
26
|
)
|
27
27
|
end
|
28
28
|
|
29
|
-
sig { params(location: Prism::Location).returns(Interface::Range) }
|
29
|
+
sig { params(location: T.any(Prism::Location, RubyIndexer::Location)).returns(Interface::Range) }
|
30
30
|
def range_from_location(location)
|
31
31
|
Interface::Range.new(
|
32
32
|
start: Interface::Position.new(
|
@@ -186,6 +186,24 @@ module RubyLsp
|
|
186
186
|
current = current.parent
|
187
187
|
end
|
188
188
|
end
|
189
|
+
|
190
|
+
sig { params(entry: RubyIndexer::Entry).returns(T.nilable(Integer)) }
|
191
|
+
def kind_for_entry(entry)
|
192
|
+
case entry
|
193
|
+
when RubyIndexer::Entry::Class
|
194
|
+
Constant::SymbolKind::CLASS
|
195
|
+
when RubyIndexer::Entry::Module
|
196
|
+
Constant::SymbolKind::NAMESPACE
|
197
|
+
when RubyIndexer::Entry::Constant
|
198
|
+
Constant::SymbolKind::CONSTANT
|
199
|
+
when RubyIndexer::Entry::Method
|
200
|
+
entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
|
201
|
+
when RubyIndexer::Entry::Accessor
|
202
|
+
Constant::SymbolKind::PROPERTY
|
203
|
+
when RubyIndexer::Entry::InstanceVariable
|
204
|
+
Constant::SymbolKind::FIELD
|
205
|
+
end
|
206
|
+
end
|
189
207
|
end
|
190
208
|
end
|
191
209
|
end
|
@@ -42,7 +42,7 @@ module RubyLsp
|
|
42
42
|
def to_lsp_code_actions
|
43
43
|
code_actions = []
|
44
44
|
|
45
|
-
code_actions << autocorrect_action if
|
45
|
+
code_actions << autocorrect_action if correctable?
|
46
46
|
code_actions << disable_line_action
|
47
47
|
|
48
48
|
code_actions
|
@@ -70,7 +70,7 @@ module RubyLsp
|
|
70
70
|
),
|
71
71
|
),
|
72
72
|
data: {
|
73
|
-
correctable:
|
73
|
+
correctable: correctable?,
|
74
74
|
code_actions: to_lsp_code_actions,
|
75
75
|
},
|
76
76
|
)
|
@@ -81,7 +81,7 @@ module RubyLsp
|
|
81
81
|
sig { returns(String) }
|
82
82
|
def message
|
83
83
|
message = @offense.message
|
84
|
-
message += "\n\nThis offense is not auto-correctable.\n" unless
|
84
|
+
message += "\n\nThis offense is not auto-correctable.\n" unless correctable?
|
85
85
|
message
|
86
86
|
end
|
87
87
|
|
@@ -115,7 +115,7 @@ module RubyLsp
|
|
115
115
|
uri: @uri.to_s,
|
116
116
|
version: nil,
|
117
117
|
),
|
118
|
-
edits:
|
118
|
+
edits: correctable? ? offense_replacements : [],
|
119
119
|
),
|
120
120
|
],
|
121
121
|
),
|
@@ -193,6 +193,14 @@ module RubyLsp
|
|
193
193
|
line.length
|
194
194
|
end
|
195
195
|
end
|
196
|
+
|
197
|
+
# When `RuboCop::LSP.enable` is called, contextual autocorrect will not offer itself
|
198
|
+
# as `correctable?` to prevent annoying changes while typing. Instead check if
|
199
|
+
# a corrector is present. If it is, then that means some code transformation can be applied.
|
200
|
+
sig { returns(T::Boolean) }
|
201
|
+
def correctable?
|
202
|
+
!@offense.corrector.nil?
|
203
|
+
end
|
196
204
|
end
|
197
205
|
end
|
198
206
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# ![Type hierarchy supertypes demo](../../type_hierarchy_supertypes.gif)
|
7
|
+
#
|
8
|
+
# The [type hierarchy supertypes
|
9
|
+
# request](https://microsoft.github.io/language-server-protocol/specification#typeHierarchy_supertypes)
|
10
|
+
# displays the list of ancestors (supertypes) for the selected type.
|
11
|
+
#
|
12
|
+
# # Example
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# class Foo; end
|
16
|
+
# class Bar < Foo; end
|
17
|
+
#
|
18
|
+
# puts Bar # <-- right click on `Bar` and select "Show Type Hierarchy"
|
19
|
+
# ```
|
20
|
+
class TypeHierarchySupertypes < Request
|
21
|
+
extend T::Sig
|
22
|
+
|
23
|
+
include Support::Common
|
24
|
+
|
25
|
+
sig { params(index: RubyIndexer::Index, item: T::Hash[Symbol, T.untyped]).void }
|
26
|
+
def initialize(index, item)
|
27
|
+
super()
|
28
|
+
|
29
|
+
@index = index
|
30
|
+
@item = item
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { override.returns(T.nilable(T::Array[Interface::TypeHierarchyItem])) }
|
34
|
+
def perform
|
35
|
+
name = @item[:name]
|
36
|
+
entries = @index[name]
|
37
|
+
|
38
|
+
parents = T.let(Set.new, T::Set[RubyIndexer::Entry::Namespace])
|
39
|
+
return unless entries&.any?
|
40
|
+
|
41
|
+
entries.each do |entry|
|
42
|
+
next unless entry.is_a?(RubyIndexer::Entry::Namespace)
|
43
|
+
|
44
|
+
if entry.is_a?(RubyIndexer::Entry::Class)
|
45
|
+
parent_class_name = entry.parent_class
|
46
|
+
if parent_class_name
|
47
|
+
resolved_parent_entries = @index.resolve(parent_class_name, entry.nesting)
|
48
|
+
resolved_parent_entries&.each do |entry|
|
49
|
+
next unless entry.is_a?(RubyIndexer::Entry::Class)
|
50
|
+
|
51
|
+
parents << entry
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
entry.mixin_operations.each do |mixin_operation|
|
57
|
+
next if mixin_operation.is_a?(RubyIndexer::Entry::Extend)
|
58
|
+
|
59
|
+
mixin_name = mixin_operation.module_name
|
60
|
+
resolved_mixin_entries = @index.resolve(mixin_name, entry.nesting)
|
61
|
+
next unless resolved_mixin_entries
|
62
|
+
|
63
|
+
resolved_mixin_entries.each do |mixin_entry|
|
64
|
+
next unless mixin_entry.is_a?(RubyIndexer::Entry::Module)
|
65
|
+
|
66
|
+
parents << mixin_entry
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
parents.map { |entry| hierarchy_item(entry) }
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
sig { params(entry: RubyIndexer::Entry).returns(Interface::TypeHierarchyItem) }
|
77
|
+
def hierarchy_item(entry)
|
78
|
+
range = range_from_location(entry.location)
|
79
|
+
|
80
|
+
Interface::TypeHierarchyItem.new(
|
81
|
+
name: entry.name,
|
82
|
+
kind: kind_for_entry(entry),
|
83
|
+
uri: URI::Generic.from_path(path: entry.file_path).to_s,
|
84
|
+
range: range,
|
85
|
+
selection_range: range,
|
86
|
+
detail: entry.file_name,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -52,7 +52,7 @@ module RubyLsp
|
|
52
52
|
|
53
53
|
Interface::WorkspaceSymbol.new(
|
54
54
|
name: entry.name,
|
55
|
-
container_name:
|
55
|
+
container_name: container.join("::"),
|
56
56
|
kind: kind,
|
57
57
|
location: Interface::Location.new(
|
58
58
|
uri: URI::Generic.from_path(path: file_path).to_s,
|
@@ -64,26 +64,6 @@ module RubyLsp
|
|
64
64
|
)
|
65
65
|
end
|
66
66
|
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
sig { params(entry: RubyIndexer::Entry).returns(T.nilable(Integer)) }
|
71
|
-
def kind_for_entry(entry)
|
72
|
-
case entry
|
73
|
-
when RubyIndexer::Entry::Class
|
74
|
-
Constant::SymbolKind::CLASS
|
75
|
-
when RubyIndexer::Entry::Module
|
76
|
-
Constant::SymbolKind::NAMESPACE
|
77
|
-
when RubyIndexer::Entry::Constant
|
78
|
-
Constant::SymbolKind::CONSTANT
|
79
|
-
when RubyIndexer::Entry::Method
|
80
|
-
entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
|
81
|
-
when RubyIndexer::Entry::Accessor
|
82
|
-
Constant::SymbolKind::PROPERTY
|
83
|
-
when RubyIndexer::Entry::InstanceVariable
|
84
|
-
Constant::SymbolKind::FIELD
|
85
|
-
end
|
86
|
-
end
|
87
67
|
end
|
88
68
|
end
|
89
69
|
end
|
data/lib/ruby_lsp/requests.rb
CHANGED
@@ -47,6 +47,8 @@ module RubyLsp
|
|
47
47
|
autoload :ShowSyntaxTree, "ruby_lsp/requests/show_syntax_tree"
|
48
48
|
autoload :WorkspaceSymbol, "ruby_lsp/requests/workspace_symbol"
|
49
49
|
autoload :SignatureHelp, "ruby_lsp/requests/signature_help"
|
50
|
+
autoload :PrepareTypeHierarchy, "ruby_lsp/requests/prepare_type_hierarchy"
|
51
|
+
autoload :TypeHierarchySupertypes, "ruby_lsp/requests/type_hierarchy_supertypes"
|
50
52
|
|
51
53
|
# :nodoc:
|
52
54
|
module Support
|