ruby-lsp 0.17.4 → 0.17.13
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 +11 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +26 -1
- data/exe/ruby-lsp-check +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +74 -43
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +26 -0
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +147 -29
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +383 -79
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +195 -61
- data/lib/ruby_indexer/ruby_indexer.rb +1 -8
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +71 -3
- data/lib/ruby_indexer/test/configuration_test.rb +1 -1
- data/lib/ruby_indexer/test/constant_test.rb +17 -17
- data/lib/ruby_indexer/test/enhancements_test.rb +197 -0
- data/lib/ruby_indexer/test/index_test.rb +367 -17
- data/lib/ruby_indexer/test/method_test.rb +58 -25
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +297 -0
- data/lib/ruby_indexer/test/test_case.rb +1 -5
- data/lib/ruby_lsp/addon.rb +22 -5
- data/lib/ruby_lsp/base_server.rb +8 -3
- data/lib/ruby_lsp/document.rb +27 -46
- data/lib/ruby_lsp/erb_document.rb +125 -0
- data/lib/ruby_lsp/global_state.rb +47 -19
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +161 -57
- data/lib/ruby_lsp/listeners/definition.rb +91 -27
- data/lib/ruby_lsp/listeners/document_highlight.rb +5 -1
- data/lib/ruby_lsp/listeners/hover.rb +61 -19
- data/lib/ruby_lsp/listeners/signature_help.rb +13 -6
- data/lib/ruby_lsp/node_context.rb +65 -5
- data/lib/ruby_lsp/requests/code_action_resolve.rb +107 -9
- data/lib/ruby_lsp/requests/code_actions.rb +11 -2
- data/lib/ruby_lsp/requests/completion.rb +4 -4
- data/lib/ruby_lsp/requests/completion_resolve.rb +14 -9
- data/lib/ruby_lsp/requests/definition.rb +18 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +6 -5
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -7
- data/lib/ruby_lsp/requests/folding_ranges.rb +6 -2
- data/lib/ruby_lsp/requests/formatting.rb +15 -0
- data/lib/ruby_lsp/requests/hover.rb +5 -5
- data/lib/ruby_lsp/requests/on_type_formatting.rb +6 -4
- data/lib/ruby_lsp/requests/selection_ranges.rb +1 -1
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +3 -2
- data/lib/ruby_lsp/requests/signature_help.rb +3 -3
- data/lib/ruby_lsp/requests/support/common.rb +11 -2
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +2 -6
- data/lib/ruby_lsp/ruby_document.rb +74 -0
- data/lib/ruby_lsp/server.rb +129 -54
- data/lib/ruby_lsp/store.rb +33 -9
- data/lib/ruby_lsp/test_helper.rb +3 -1
- data/lib/ruby_lsp/type_inferrer.rb +61 -25
- data/lib/ruby_lsp/utils.rb +13 -0
- metadata +9 -8
- data/exe/ruby-lsp-doctor +0 -23
@@ -21,6 +21,8 @@ module RubyLsp
|
|
21
21
|
Prism::InstanceVariableWriteNode,
|
22
22
|
Prism::SymbolNode,
|
23
23
|
Prism::StringNode,
|
24
|
+
Prism::SuperNode,
|
25
|
+
Prism::ForwardingSuperNode,
|
24
26
|
],
|
25
27
|
T::Array[T.class_of(Prism::Node)],
|
26
28
|
)
|
@@ -40,17 +42,17 @@ module RubyLsp
|
|
40
42
|
uri: URI::Generic,
|
41
43
|
node_context: NodeContext,
|
42
44
|
dispatcher: Prism::Dispatcher,
|
43
|
-
|
45
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
44
46
|
).void
|
45
47
|
end
|
46
|
-
def initialize(response_builder, global_state, uri, node_context, dispatcher,
|
48
|
+
def initialize(response_builder, global_state, uri, node_context, dispatcher, sorbet_level) # rubocop:disable Metrics/ParameterLists
|
47
49
|
@response_builder = response_builder
|
48
50
|
@global_state = global_state
|
49
51
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
50
52
|
@type_inferrer = T.let(global_state.type_inferrer, TypeInferrer)
|
51
53
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
52
54
|
@node_context = node_context
|
53
|
-
@
|
55
|
+
@sorbet_level = sorbet_level
|
54
56
|
|
55
57
|
dispatcher.register(
|
56
58
|
self,
|
@@ -64,12 +66,14 @@ module RubyLsp
|
|
64
66
|
:on_instance_variable_operator_write_node_enter,
|
65
67
|
:on_instance_variable_or_write_node_enter,
|
66
68
|
:on_instance_variable_target_node_enter,
|
69
|
+
:on_super_node_enter,
|
70
|
+
:on_forwarding_super_node_enter,
|
67
71
|
)
|
68
72
|
end
|
69
73
|
|
70
74
|
sig { params(node: Prism::ConstantReadNode).void }
|
71
75
|
def on_constant_read_node_enter(node)
|
72
|
-
return if @
|
76
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
73
77
|
|
74
78
|
name = constant_name(node)
|
75
79
|
return if name.nil?
|
@@ -79,14 +83,14 @@ module RubyLsp
|
|
79
83
|
|
80
84
|
sig { params(node: Prism::ConstantWriteNode).void }
|
81
85
|
def on_constant_write_node_enter(node)
|
82
|
-
return if @
|
86
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
83
87
|
|
84
88
|
generate_hover(node.name.to_s, node.name_loc)
|
85
89
|
end
|
86
90
|
|
87
91
|
sig { params(node: Prism::ConstantPathNode).void }
|
88
92
|
def on_constant_path_node_enter(node)
|
89
|
-
return if @
|
93
|
+
return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
|
90
94
|
|
91
95
|
name = constant_name(node)
|
92
96
|
return if name.nil?
|
@@ -101,22 +105,12 @@ module RubyLsp
|
|
101
105
|
return
|
102
106
|
end
|
103
107
|
|
104
|
-
return if @
|
108
|
+
return if sorbet_level_true_or_higher?(@sorbet_level) && self_receiver?(node)
|
105
109
|
|
106
110
|
message = node.message
|
107
111
|
return unless message
|
108
112
|
|
109
|
-
|
110
|
-
return unless type
|
111
|
-
|
112
|
-
methods = @index.resolve_method(message, type)
|
113
|
-
return unless methods
|
114
|
-
|
115
|
-
title = "#{message}#{T.must(methods.first).decorated_parameters}"
|
116
|
-
|
117
|
-
categorized_markdown_from_index_entries(title, methods).each do |category, content|
|
118
|
-
@response_builder.push(content, category: category)
|
119
|
-
end
|
113
|
+
handle_method_hover(message)
|
120
114
|
end
|
121
115
|
|
122
116
|
sig { params(node: Prism::InstanceVariableReadNode).void }
|
@@ -149,14 +143,62 @@ module RubyLsp
|
|
149
143
|
handle_instance_variable_hover(node.name.to_s)
|
150
144
|
end
|
151
145
|
|
146
|
+
sig { params(node: Prism::SuperNode).void }
|
147
|
+
def on_super_node_enter(node)
|
148
|
+
handle_super_node_hover
|
149
|
+
end
|
150
|
+
|
151
|
+
sig { params(node: Prism::ForwardingSuperNode).void }
|
152
|
+
def on_forwarding_super_node_enter(node)
|
153
|
+
handle_super_node_hover
|
154
|
+
end
|
155
|
+
|
152
156
|
private
|
153
157
|
|
158
|
+
sig { void }
|
159
|
+
def handle_super_node_hover
|
160
|
+
# Sorbet can handle super hover on typed true or higher
|
161
|
+
return if sorbet_level_true_or_higher?(@sorbet_level)
|
162
|
+
|
163
|
+
surrounding_method = @node_context.surrounding_method
|
164
|
+
return unless surrounding_method
|
165
|
+
|
166
|
+
handle_method_hover(surrounding_method, inherited_only: true)
|
167
|
+
end
|
168
|
+
|
169
|
+
sig { params(message: String, inherited_only: T::Boolean).void }
|
170
|
+
def handle_method_hover(message, inherited_only: false)
|
171
|
+
type = @type_inferrer.infer_receiver_type(@node_context)
|
172
|
+
return unless type
|
173
|
+
|
174
|
+
methods = @index.resolve_method(message, type.name, inherited_only: inherited_only)
|
175
|
+
return unless methods
|
176
|
+
|
177
|
+
first_method = T.must(methods.first)
|
178
|
+
|
179
|
+
title = "#{message}#{first_method.decorated_parameters}"
|
180
|
+
title << first_method.formatted_signatures
|
181
|
+
|
182
|
+
if type.is_a?(TypeInferrer::GuessedType)
|
183
|
+
title << "\n\nGuessed receiver: #{type.name}"
|
184
|
+
@response_builder.push("[Learn more about guessed types](#{GUESSED_TYPES_URL})\n", category: :links)
|
185
|
+
end
|
186
|
+
|
187
|
+
categorized_markdown_from_index_entries(title, methods).each do |category, content|
|
188
|
+
@response_builder.push(content, category: category)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
154
192
|
sig { params(name: String).void }
|
155
193
|
def handle_instance_variable_hover(name)
|
194
|
+
# Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
|
195
|
+
# to provide all features for them
|
196
|
+
return if @sorbet_level == RubyDocument::SorbetLevel::Strict
|
197
|
+
|
156
198
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
157
199
|
return unless type
|
158
200
|
|
159
|
-
entries = @index.resolve_instance_variable(name, type)
|
201
|
+
entries = @index.resolve_instance_variable(name, type.name)
|
160
202
|
return unless entries
|
161
203
|
|
162
204
|
categorized_markdown_from_index_entries(name, entries).each do |category, content|
|
@@ -13,11 +13,11 @@ module RubyLsp
|
|
13
13
|
global_state: GlobalState,
|
14
14
|
node_context: NodeContext,
|
15
15
|
dispatcher: Prism::Dispatcher,
|
16
|
-
|
16
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
17
17
|
).void
|
18
18
|
end
|
19
|
-
def initialize(response_builder, global_state, node_context, dispatcher,
|
20
|
-
@
|
19
|
+
def initialize(response_builder, global_state, node_context, dispatcher, sorbet_level)
|
20
|
+
@sorbet_level = sorbet_level
|
21
21
|
@response_builder = response_builder
|
22
22
|
@global_state = global_state
|
23
23
|
@index = T.let(global_state.index, RubyIndexer::Index)
|
@@ -28,7 +28,7 @@ module RubyLsp
|
|
28
28
|
|
29
29
|
sig { params(node: Prism::CallNode).void }
|
30
30
|
def on_call_node_enter(node)
|
31
|
-
return if @
|
31
|
+
return if sorbet_level_true_or_higher?(@sorbet_level)
|
32
32
|
|
33
33
|
message = node.message
|
34
34
|
return unless message
|
@@ -36,7 +36,7 @@ module RubyLsp
|
|
36
36
|
type = @type_inferrer.infer_receiver_type(@node_context)
|
37
37
|
return unless type
|
38
38
|
|
39
|
-
methods = @index.resolve_method(message, type)
|
39
|
+
methods = @index.resolve_method(message, type.name)
|
40
40
|
return unless methods
|
41
41
|
|
42
42
|
target_method = methods.first
|
@@ -61,6 +61,13 @@ module RubyLsp
|
|
61
61
|
active_parameter += 1
|
62
62
|
end
|
63
63
|
|
64
|
+
title = +""
|
65
|
+
|
66
|
+
extra_links = if type.is_a?(TypeInferrer::GuessedType)
|
67
|
+
title << "\n\nGuessed receiver: #{type.name}"
|
68
|
+
"[Learn more about guessed types](#{GUESSED_TYPES_URL})"
|
69
|
+
end
|
70
|
+
|
64
71
|
signature_help = Interface::SignatureHelp.new(
|
65
72
|
signatures: [
|
66
73
|
Interface::SignatureInformation.new(
|
@@ -68,7 +75,7 @@ module RubyLsp
|
|
68
75
|
parameters: parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
|
69
76
|
documentation: Interface::MarkupContent.new(
|
70
77
|
kind: "markdown",
|
71
|
-
value: markdown_from_index_entries(
|
78
|
+
value: markdown_from_index_entries(title, methods, extra_links: extra_links),
|
72
79
|
),
|
73
80
|
),
|
74
81
|
],
|
@@ -23,22 +23,82 @@ module RubyLsp
|
|
23
23
|
params(
|
24
24
|
node: T.nilable(Prism::Node),
|
25
25
|
parent: T.nilable(Prism::Node),
|
26
|
-
|
26
|
+
nesting_nodes: T::Array[T.any(
|
27
|
+
Prism::ClassNode,
|
28
|
+
Prism::ModuleNode,
|
29
|
+
Prism::SingletonClassNode,
|
30
|
+
Prism::DefNode,
|
31
|
+
Prism::BlockNode,
|
32
|
+
Prism::LambdaNode,
|
33
|
+
Prism::ProgramNode,
|
34
|
+
)],
|
27
35
|
call_node: T.nilable(Prism::CallNode),
|
28
|
-
surrounding_method: T.nilable(String),
|
29
36
|
).void
|
30
37
|
end
|
31
|
-
def initialize(node, parent,
|
38
|
+
def initialize(node, parent, nesting_nodes, call_node)
|
32
39
|
@node = node
|
33
40
|
@parent = parent
|
34
|
-
@
|
41
|
+
@nesting_nodes = nesting_nodes
|
35
42
|
@call_node = call_node
|
36
|
-
|
43
|
+
|
44
|
+
nesting, surrounding_method = handle_nesting_nodes(nesting_nodes)
|
45
|
+
@nesting = T.let(nesting, T::Array[String])
|
46
|
+
@surrounding_method = T.let(surrounding_method, T.nilable(String))
|
37
47
|
end
|
38
48
|
|
39
49
|
sig { returns(String) }
|
40
50
|
def fully_qualified_name
|
41
51
|
@fully_qualified_name ||= T.let(@nesting.join("::"), T.nilable(String))
|
42
52
|
end
|
53
|
+
|
54
|
+
sig { returns(T::Array[Symbol]) }
|
55
|
+
def locals_for_scope
|
56
|
+
locals = []
|
57
|
+
|
58
|
+
@nesting_nodes.each do |node|
|
59
|
+
if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) || node.is_a?(Prism::SingletonClassNode) ||
|
60
|
+
node.is_a?(Prism::DefNode)
|
61
|
+
locals.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
locals.concat(node.locals)
|
65
|
+
end
|
66
|
+
|
67
|
+
locals
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
sig do
|
73
|
+
params(nodes: T::Array[T.any(
|
74
|
+
Prism::ClassNode,
|
75
|
+
Prism::ModuleNode,
|
76
|
+
Prism::SingletonClassNode,
|
77
|
+
Prism::DefNode,
|
78
|
+
Prism::BlockNode,
|
79
|
+
Prism::LambdaNode,
|
80
|
+
Prism::ProgramNode,
|
81
|
+
)]).returns([T::Array[String], T.nilable(String)])
|
82
|
+
end
|
83
|
+
def handle_nesting_nodes(nodes)
|
84
|
+
nesting = []
|
85
|
+
surrounding_method = T.let(nil, T.nilable(String))
|
86
|
+
|
87
|
+
@nesting_nodes.each do |node|
|
88
|
+
case node
|
89
|
+
when Prism::ClassNode, Prism::ModuleNode
|
90
|
+
nesting << node.constant_path.slice
|
91
|
+
when Prism::SingletonClassNode
|
92
|
+
nesting << "<Class:#{nesting.flat_map { |n| n.split("::") }.last}>"
|
93
|
+
when Prism::DefNode
|
94
|
+
surrounding_method = node.name.to_s
|
95
|
+
next unless node.receiver.is_a?(Prism::SelfNode)
|
96
|
+
|
97
|
+
nesting << "<Class:#{nesting.flat_map { |n| n.split("::") }.last}>"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
[nesting, surrounding_method]
|
102
|
+
end
|
43
103
|
end
|
44
104
|
end
|
@@ -23,6 +23,8 @@ module RubyLsp
|
|
23
23
|
#
|
24
24
|
class CodeActionResolve < Request
|
25
25
|
extend T::Sig
|
26
|
+
include Support::Common
|
27
|
+
|
26
28
|
NEW_VARIABLE_NAME = "new_variable"
|
27
29
|
NEW_METHOD_NAME = "new_method"
|
28
30
|
|
@@ -36,7 +38,7 @@ module RubyLsp
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
sig { params(document:
|
41
|
+
sig { params(document: RubyDocument, code_action: T::Hash[Symbol, T.untyped]).void }
|
40
42
|
def initialize(document, code_action)
|
41
43
|
super()
|
42
44
|
@document = document
|
@@ -45,20 +47,62 @@ module RubyLsp
|
|
45
47
|
|
46
48
|
sig { override.returns(T.any(Interface::CodeAction, Error)) }
|
47
49
|
def perform
|
50
|
+
return Error::EmptySelection if @document.source.empty?
|
51
|
+
|
48
52
|
case @code_action[:title]
|
49
53
|
when CodeActions::EXTRACT_TO_VARIABLE_TITLE
|
50
54
|
refactor_variable
|
51
55
|
when CodeActions::EXTRACT_TO_METHOD_TITLE
|
52
56
|
refactor_method
|
57
|
+
when CodeActions::TOGGLE_BLOCK_STYLE_TITLE
|
58
|
+
switch_block_style
|
53
59
|
else
|
54
60
|
Error::UnknownCodeAction
|
55
61
|
end
|
56
62
|
end
|
57
63
|
|
64
|
+
private
|
65
|
+
|
58
66
|
sig { returns(T.any(Interface::CodeAction, Error)) }
|
59
|
-
def
|
60
|
-
|
67
|
+
def switch_block_style
|
68
|
+
source_range = @code_action.dig(:data, :range)
|
69
|
+
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
70
|
+
|
71
|
+
target = @document.locate_first_within_range(
|
72
|
+
@code_action.dig(:data, :range),
|
73
|
+
node_types: [Prism::CallNode],
|
74
|
+
)
|
75
|
+
|
76
|
+
return Error::InvalidTargetRange unless target.is_a?(Prism::CallNode)
|
77
|
+
|
78
|
+
node = target.block
|
79
|
+
return Error::InvalidTargetRange unless node.is_a?(Prism::BlockNode)
|
61
80
|
|
81
|
+
indentation = " " * target.location.start_column unless node.opening_loc.slice == "do"
|
82
|
+
|
83
|
+
Interface::CodeAction.new(
|
84
|
+
title: CodeActions::TOGGLE_BLOCK_STYLE_TITLE,
|
85
|
+
edit: Interface::WorkspaceEdit.new(
|
86
|
+
document_changes: [
|
87
|
+
Interface::TextDocumentEdit.new(
|
88
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
89
|
+
uri: @code_action.dig(:data, :uri),
|
90
|
+
version: nil,
|
91
|
+
),
|
92
|
+
edits: [
|
93
|
+
Interface::TextEdit.new(
|
94
|
+
range: range_from_location(node.location),
|
95
|
+
new_text: recursively_switch_nested_block_styles(node, indentation),
|
96
|
+
),
|
97
|
+
],
|
98
|
+
),
|
99
|
+
],
|
100
|
+
),
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
sig { returns(T.any(Interface::CodeAction, Error)) }
|
105
|
+
def refactor_variable
|
62
106
|
source_range = @code_action.dig(:data, :range)
|
63
107
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
64
108
|
|
@@ -69,7 +113,7 @@ module RubyLsp
|
|
69
113
|
|
70
114
|
# Find the closest statements node, so that we place the refactor in a valid position
|
71
115
|
node_context = @document
|
72
|
-
.locate(@document.
|
116
|
+
.locate(@document.parse_result.value, start_index, node_types: [Prism::StatementsNode, Prism::BlockNode])
|
73
117
|
|
74
118
|
closest_statements = node_context.node
|
75
119
|
parent_statements = node_context.parent
|
@@ -153,8 +197,6 @@ module RubyLsp
|
|
153
197
|
|
154
198
|
sig { returns(T.any(Interface::CodeAction, Error)) }
|
155
199
|
def refactor_method
|
156
|
-
return Error::EmptySelection if @document.source.empty?
|
157
|
-
|
158
200
|
source_range = @code_action.dig(:data, :range)
|
159
201
|
return Error::EmptySelection if source_range[:start] == source_range[:end]
|
160
202
|
|
@@ -164,7 +206,7 @@ module RubyLsp
|
|
164
206
|
extracted_source = T.must(@document.source[start_index...end_index])
|
165
207
|
|
166
208
|
# Find the closest method declaration node, so that we place the refactor in a valid position
|
167
|
-
node_context = @document.locate(@document.
|
209
|
+
node_context = @document.locate(@document.parse_result.value, start_index, node_types: [Prism::DefNode])
|
168
210
|
closest_def = T.cast(node_context.node, Prism::DefNode)
|
169
211
|
return Error::InvalidTargetRange if closest_def.nil?
|
170
212
|
|
@@ -206,8 +248,6 @@ module RubyLsp
|
|
206
248
|
)
|
207
249
|
end
|
208
250
|
|
209
|
-
private
|
210
|
-
|
211
251
|
sig { params(range: T::Hash[Symbol, T.untyped], new_text: String).returns(Interface::TextEdit) }
|
212
252
|
def create_text_edit(range, new_text)
|
213
253
|
Interface::TextEdit.new(
|
@@ -218,6 +258,64 @@ module RubyLsp
|
|
218
258
|
new_text: new_text,
|
219
259
|
)
|
220
260
|
end
|
261
|
+
|
262
|
+
sig { params(node: Prism::BlockNode, indentation: T.nilable(String)).returns(String) }
|
263
|
+
def recursively_switch_nested_block_styles(node, indentation)
|
264
|
+
parameters = node.parameters
|
265
|
+
body = node.body
|
266
|
+
|
267
|
+
# We use the indentation to differentiate between do...end and brace style blocks because only the do...end
|
268
|
+
# style requires the indentation to build the edit.
|
269
|
+
#
|
270
|
+
# If the block is using `do...end` style, we change it to a single line brace block. Newlines are turned into
|
271
|
+
# semi colons, so that the result is valid Ruby code and still a one liner. If the block is using brace style,
|
272
|
+
# we do the opposite and turn it into a `do...end` block, making all semi colons into newlines.
|
273
|
+
source = +""
|
274
|
+
|
275
|
+
if indentation
|
276
|
+
source << "do"
|
277
|
+
source << " #{parameters.slice}" if parameters
|
278
|
+
source << "\n#{indentation} "
|
279
|
+
source << switch_block_body(body, indentation) if body
|
280
|
+
source << "\n#{indentation}end"
|
281
|
+
else
|
282
|
+
source << "{ "
|
283
|
+
source << "#{parameters.slice} " if parameters
|
284
|
+
source << switch_block_body(body, nil) if body
|
285
|
+
source << "}"
|
286
|
+
end
|
287
|
+
|
288
|
+
source
|
289
|
+
end
|
290
|
+
|
291
|
+
sig { params(body: Prism::Node, indentation: T.nilable(String)).returns(String) }
|
292
|
+
def switch_block_body(body, indentation)
|
293
|
+
# Check if there are any nested blocks inside of the current block
|
294
|
+
body_loc = body.location
|
295
|
+
nested_block = @document.locate_first_within_range(
|
296
|
+
{
|
297
|
+
start: { line: body_loc.start_line - 1, character: body_loc.start_column },
|
298
|
+
end: { line: body_loc.end_line - 1, character: body_loc.end_column },
|
299
|
+
},
|
300
|
+
node_types: [Prism::BlockNode],
|
301
|
+
)
|
302
|
+
|
303
|
+
body_content = body.slice.dup
|
304
|
+
|
305
|
+
# If there are nested blocks, then we change their style too and we have to mutate the string using the
|
306
|
+
# relative position in respect to the beginning of the body
|
307
|
+
if nested_block.is_a?(Prism::BlockNode)
|
308
|
+
location = nested_block.location
|
309
|
+
correction_start = location.start_offset - body_loc.start_offset
|
310
|
+
correction_end = location.end_offset - body_loc.start_offset
|
311
|
+
next_indentation = indentation ? "#{indentation} " : nil
|
312
|
+
|
313
|
+
body_content[correction_start...correction_end] =
|
314
|
+
recursively_switch_nested_block_styles(nested_block, next_indentation)
|
315
|
+
end
|
316
|
+
|
317
|
+
indentation ? body_content.gsub(";", "\n") : "#{body_content.gsub("\n", ";")} "
|
318
|
+
end
|
221
319
|
end
|
222
320
|
end
|
223
321
|
end
|
@@ -21,13 +21,17 @@ module RubyLsp
|
|
21
21
|
|
22
22
|
EXTRACT_TO_VARIABLE_TITLE = "Refactor: Extract Variable"
|
23
23
|
EXTRACT_TO_METHOD_TITLE = "Refactor: Extract Method"
|
24
|
+
TOGGLE_BLOCK_STYLE_TITLE = "Refactor: Toggle block style"
|
24
25
|
|
25
26
|
class << self
|
26
27
|
extend T::Sig
|
27
28
|
|
28
|
-
sig { returns(Interface::
|
29
|
+
sig { returns(Interface::CodeActionRegistrationOptions) }
|
29
30
|
def provider
|
30
|
-
Interface::
|
31
|
+
Interface::CodeActionRegistrationOptions.new(
|
32
|
+
document_selector: [Interface::DocumentFilter.new(language: "ruby")],
|
33
|
+
resolve_provider: true,
|
34
|
+
)
|
31
35
|
end
|
32
36
|
end
|
33
37
|
|
@@ -66,6 +70,11 @@ module RubyLsp
|
|
66
70
|
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
|
67
71
|
data: { range: @range, uri: @uri.to_s },
|
68
72
|
)
|
73
|
+
code_actions << Interface::CodeAction.new(
|
74
|
+
title: TOGGLE_BLOCK_STYLE_TITLE,
|
75
|
+
kind: Constant::CodeActionKind::REFACTOR_REWRITE,
|
76
|
+
data: { range: @range, uri: @uri.to_s },
|
77
|
+
)
|
69
78
|
end
|
70
79
|
|
71
80
|
code_actions
|
@@ -49,11 +49,11 @@ module RubyLsp
|
|
49
49
|
document: Document,
|
50
50
|
global_state: GlobalState,
|
51
51
|
params: T::Hash[Symbol, T.untyped],
|
52
|
-
|
52
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
53
53
|
dispatcher: Prism::Dispatcher,
|
54
54
|
).void
|
55
55
|
end
|
56
|
-
def initialize(document, global_state, params,
|
56
|
+
def initialize(document, global_state, params, sorbet_level, dispatcher)
|
57
57
|
super()
|
58
58
|
@target = T.let(nil, T.nilable(Prism::Node))
|
59
59
|
@dispatcher = dispatcher
|
@@ -61,7 +61,7 @@ module RubyLsp
|
|
61
61
|
# back by 1, so that we find the right node
|
62
62
|
char_position = document.create_scanner.find_char_position(params[:position]) - 1
|
63
63
|
node_context = document.locate(
|
64
|
-
document.
|
64
|
+
document.parse_result.value,
|
65
65
|
char_position,
|
66
66
|
node_types: [
|
67
67
|
Prism::CallNode,
|
@@ -84,7 +84,7 @@ module RubyLsp
|
|
84
84
|
@response_builder,
|
85
85
|
global_state,
|
86
86
|
node_context,
|
87
|
-
|
87
|
+
sorbet_level,
|
88
88
|
dispatcher,
|
89
89
|
document.uri,
|
90
90
|
params.dig(:context, :triggerCharacter),
|
@@ -38,13 +38,16 @@ module RubyLsp
|
|
38
38
|
|
39
39
|
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
40
40
|
def perform
|
41
|
+
return @item if @item.dig(:data, :skip_resolve)
|
42
|
+
|
41
43
|
# Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
|
42
44
|
# a completion resolve request must always return the original completion item without modifying ANY fields
|
43
|
-
# other than
|
45
|
+
# other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
|
46
|
+
# be broken.
|
44
47
|
#
|
45
48
|
# For example, forgetting to return the `insertText` included in the original item will make the editor use the
|
46
49
|
# `label` for the text edit instead
|
47
|
-
label = @item[:label]
|
50
|
+
label = @item[:label].dup
|
48
51
|
entries = @index[label] || []
|
49
52
|
|
50
53
|
owner_name = @item.dig(:data, :owner_name)
|
@@ -59,18 +62,20 @@ module RubyLsp
|
|
59
62
|
first_entry = T.must(entries.first)
|
60
63
|
|
61
64
|
if first_entry.is_a?(RubyIndexer::Entry::Member)
|
62
|
-
|
63
|
-
label
|
65
|
+
label = +"#{label}#{first_entry.decorated_parameters}"
|
66
|
+
label << first_entry.formatted_signatures
|
64
67
|
end
|
65
68
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
guessed_type = @item.dig(:data, :guessed_type)
|
70
|
+
|
71
|
+
extra_links = if guessed_type
|
72
|
+
label << "\n\nGuessed receiver: #{guessed_type}"
|
73
|
+
"[Learn more about guessed types](#{GUESSED_TYPES_URL})"
|
74
|
+
end
|
70
75
|
|
71
76
|
@item[:documentation] = Interface::MarkupContent.new(
|
72
77
|
kind: "markdown",
|
73
|
-
value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
|
78
|
+
value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES, extra_links: extra_links),
|
74
79
|
)
|
75
80
|
|
76
81
|
@item
|
@@ -30,20 +30,26 @@ module RubyLsp
|
|
30
30
|
extend T::Sig
|
31
31
|
extend T::Generic
|
32
32
|
|
33
|
+
SPECIAL_METHOD_CALLS = [
|
34
|
+
:require,
|
35
|
+
:require_relative,
|
36
|
+
:autoload,
|
37
|
+
].freeze
|
38
|
+
|
33
39
|
sig do
|
34
40
|
params(
|
35
41
|
document: Document,
|
36
42
|
global_state: GlobalState,
|
37
43
|
position: T::Hash[Symbol, T.untyped],
|
38
44
|
dispatcher: Prism::Dispatcher,
|
39
|
-
|
45
|
+
sorbet_level: RubyDocument::SorbetLevel,
|
40
46
|
).void
|
41
47
|
end
|
42
|
-
def initialize(document, global_state, position, dispatcher,
|
48
|
+
def initialize(document, global_state, position, dispatcher, sorbet_level)
|
43
49
|
super()
|
44
50
|
@response_builder = T.let(
|
45
|
-
ResponseBuilders::CollectionResponseBuilder[Interface::Location].new,
|
46
|
-
ResponseBuilders::CollectionResponseBuilder[Interface::Location],
|
51
|
+
ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)].new,
|
52
|
+
ResponseBuilders::CollectionResponseBuilder[T.any(Interface::Location, Interface::LocationLink)],
|
47
53
|
)
|
48
54
|
@dispatcher = dispatcher
|
49
55
|
|
@@ -62,6 +68,8 @@ module RubyLsp
|
|
62
68
|
Prism::InstanceVariableWriteNode,
|
63
69
|
Prism::SymbolNode,
|
64
70
|
Prism::StringNode,
|
71
|
+
Prism::SuperNode,
|
72
|
+
Prism::ForwardingSuperNode,
|
65
73
|
],
|
66
74
|
)
|
67
75
|
|
@@ -76,8 +84,9 @@ module RubyLsp
|
|
76
84
|
parent,
|
77
85
|
position,
|
78
86
|
)
|
79
|
-
elsif target.is_a?(Prism::CallNode) && target.
|
80
|
-
|
87
|
+
elsif target.is_a?(Prism::CallNode) && !SPECIAL_METHOD_CALLS.include?(target.message) && !covers_position?(
|
88
|
+
target.message_loc, position
|
89
|
+
)
|
81
90
|
# If the target is a method call, we need to ensure that the requested position is exactly on top of the
|
82
91
|
# method identifier. Otherwise, we risk showing definitions for unrelated things
|
83
92
|
target = nil
|
@@ -90,10 +99,11 @@ module RubyLsp
|
|
90
99
|
Listeners::Definition.new(
|
91
100
|
@response_builder,
|
92
101
|
global_state,
|
102
|
+
document.language_id,
|
93
103
|
document.uri,
|
94
104
|
node_context,
|
95
105
|
dispatcher,
|
96
|
-
|
106
|
+
sorbet_level,
|
97
107
|
)
|
98
108
|
|
99
109
|
Addon.addons.each do |addon|
|
@@ -104,7 +114,7 @@ module RubyLsp
|
|
104
114
|
@target = T.let(target, T.nilable(Prism::Node))
|
105
115
|
end
|
106
116
|
|
107
|
-
sig { override.returns(T::Array[Interface::Location]) }
|
117
|
+
sig { override.returns(T::Array[T.any(Interface::Location, Interface::LocationLink)]) }
|
108
118
|
def perform
|
109
119
|
@dispatcher.dispatch_once(@target) if @target
|
110
120
|
@response_builder.response
|
@@ -22,12 +22,13 @@ module RubyLsp
|
|
22
22
|
class << self
|
23
23
|
extend T::Sig
|
24
24
|
|
25
|
-
sig { returns(
|
25
|
+
sig { returns(Interface::DiagnosticRegistrationOptions) }
|
26
26
|
def provider
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
Interface::DiagnosticRegistrationOptions.new(
|
28
|
+
document_selector: [Interface::DocumentFilter.new(language: "ruby")],
|
29
|
+
inter_file_dependencies: false,
|
30
|
+
workspace_diagnostics: false,
|
31
|
+
)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|