ruby-lsp 0.13.0 → 0.13.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/VERSION +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +51 -3
- data/lib/ruby_indexer/test/method_test.rb +136 -0
- data/lib/ruby_lsp/document.rb +3 -0
- data/lib/ruby_lsp/executor.rb +10 -3
- data/lib/ruby_lsp/requests/code_actions.rb +2 -16
- data/lib/ruby_lsp/requests/code_lens.rb +15 -2
- data/lib/ruby_lsp/requests/inlay_hints.rb +19 -2
- data/lib/ruby_lsp/requests/on_type_formatting.rb +29 -1
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +92 -21
- data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +1 -1
- data/lib/ruby_lsp/store.rb +17 -0
- data/lib/ruby_lsp/utils.rb +18 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78df9c8d35fc18cea6fc06a0f679b48b69e3daaaf504d762d2f5ae4df9d99ad0
|
4
|
+
data.tar.gz: 3c251eed7939e81c5aa115d303b2876d3769c509ef0487b0faa26d1a2de79c50
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af33b81309429318e7fe61dc4a2bd989328b8e9127097ab621eab162e61e1b2d93f1be1b192307d3b48f47659489343e093d92125e9c68895c40517803dba5ed
|
7
|
+
data.tar.gz: 6373eb77209eb865e29026fba603f4db8d3249949bcff7c0aee3f550569a7225c84b76d5872fd61a8798c0ef04da2d538b50b4d3045697ef40cc85336a2c715b
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.13.
|
1
|
+
0.13.1
|
@@ -106,6 +106,21 @@ module RubyIndexer
|
|
106
106
|
class OptionalKeywordParameter < Parameter
|
107
107
|
end
|
108
108
|
|
109
|
+
# A rest method parameter, e.g. `def foo(*a)`
|
110
|
+
class RestParameter < Parameter
|
111
|
+
DEFAULT_NAME = T.let(:"<anonymous splat>", Symbol)
|
112
|
+
end
|
113
|
+
|
114
|
+
# A keyword rest method parameter, e.g. `def foo(**a)`
|
115
|
+
class KeywordRestParameter < Parameter
|
116
|
+
DEFAULT_NAME = T.let(:"<anonymous keyword splat>", Symbol)
|
117
|
+
end
|
118
|
+
|
119
|
+
# A block method parameter, e.g. `def foo(&block)`
|
120
|
+
class BlockParameter < Parameter
|
121
|
+
DEFAULT_NAME = T.let(:"<anonymous block>", Symbol)
|
122
|
+
end
|
123
|
+
|
109
124
|
class Member < Entry
|
110
125
|
extend T::Sig
|
111
126
|
extend T::Helpers
|
@@ -203,17 +218,50 @@ module RubyIndexer
|
|
203
218
|
end
|
204
219
|
end
|
205
220
|
|
221
|
+
rest = parameters_node.rest
|
222
|
+
|
223
|
+
if rest
|
224
|
+
rest_name = rest.name || RestParameter::DEFAULT_NAME
|
225
|
+
parameters << RestParameter.new(name: rest_name)
|
226
|
+
end
|
227
|
+
|
228
|
+
keyword_rest = parameters_node.keyword_rest
|
229
|
+
|
230
|
+
if keyword_rest.is_a?(Prism::KeywordRestParameterNode)
|
231
|
+
keyword_rest_name = parameter_name(keyword_rest) || KeywordRestParameter::DEFAULT_NAME
|
232
|
+
parameters << KeywordRestParameter.new(name: keyword_rest_name)
|
233
|
+
end
|
234
|
+
|
235
|
+
parameters_node.posts.each do |post|
|
236
|
+
name = parameter_name(post)
|
237
|
+
next unless name
|
238
|
+
|
239
|
+
parameters << RequiredParameter.new(name: name)
|
240
|
+
end
|
241
|
+
|
242
|
+
block = parameters_node.block
|
243
|
+
parameters << BlockParameter.new(name: block.name || BlockParameter::DEFAULT_NAME) if block
|
244
|
+
|
206
245
|
parameters
|
207
246
|
end
|
208
247
|
|
209
|
-
sig { params(node: Prism::Node).returns(T.nilable(Symbol)) }
|
248
|
+
sig { params(node: T.nilable(Prism::Node)).returns(T.nilable(Symbol)) }
|
210
249
|
def parameter_name(node)
|
211
250
|
case node
|
212
251
|
when Prism::RequiredParameterNode, Prism::OptionalParameterNode,
|
213
|
-
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode
|
252
|
+
Prism::RequiredKeywordParameterNode, Prism::OptionalKeywordParameterNode,
|
253
|
+
Prism::RestParameterNode, Prism::KeywordRestParameterNode
|
214
254
|
node.name
|
215
255
|
when Prism::MultiTargetNode
|
216
|
-
names =
|
256
|
+
names = node.lefts.map { |parameter_node| parameter_name(parameter_node) }
|
257
|
+
|
258
|
+
rest = node.rest
|
259
|
+
if rest.is_a?(Prism::SplatNode)
|
260
|
+
name = rest.expression&.slice
|
261
|
+
names << (rest.operator == "*" ? "*#{name}".to_sym : name&.to_sym)
|
262
|
+
end
|
263
|
+
|
264
|
+
names.concat(node.rights.map { |parameter_node| parameter_name(parameter_node) })
|
217
265
|
|
218
266
|
names_with_commas = names.join(", ")
|
219
267
|
:"(#{names_with_commas})"
|
@@ -106,6 +106,142 @@ module RubyIndexer
|
|
106
106
|
assert_instance_of(Entry::OptionalKeywordParameter, b)
|
107
107
|
end
|
108
108
|
|
109
|
+
def test_method_with_rest_and_keyword_rest_parameters
|
110
|
+
index(<<~RUBY)
|
111
|
+
class Foo
|
112
|
+
def bar(*a, **b)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
RUBY
|
116
|
+
|
117
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
118
|
+
entry = T.must(@index["bar"].first)
|
119
|
+
assert_equal(2, entry.parameters.length)
|
120
|
+
a, b = entry.parameters
|
121
|
+
|
122
|
+
assert_equal(:a, a.name)
|
123
|
+
assert_instance_of(Entry::RestParameter, a)
|
124
|
+
|
125
|
+
assert_equal(:b, b.name)
|
126
|
+
assert_instance_of(Entry::KeywordRestParameter, b)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_method_with_post_parameters
|
130
|
+
index(<<~RUBY)
|
131
|
+
class Foo
|
132
|
+
def bar(*a, b)
|
133
|
+
end
|
134
|
+
|
135
|
+
def baz(**a, b)
|
136
|
+
end
|
137
|
+
|
138
|
+
def qux(*a, (b, c))
|
139
|
+
end
|
140
|
+
RUBY
|
141
|
+
|
142
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
143
|
+
entry = T.must(@index["bar"].first)
|
144
|
+
assert_equal(2, entry.parameters.length)
|
145
|
+
a, b = entry.parameters
|
146
|
+
|
147
|
+
assert_equal(:a, a.name)
|
148
|
+
assert_instance_of(Entry::RestParameter, a)
|
149
|
+
|
150
|
+
assert_equal(:b, b.name)
|
151
|
+
assert_instance_of(Entry::RequiredParameter, b)
|
152
|
+
|
153
|
+
entry = T.must(@index["baz"].first)
|
154
|
+
assert_equal(2, entry.parameters.length)
|
155
|
+
a, b = entry.parameters
|
156
|
+
|
157
|
+
assert_equal(:a, a.name)
|
158
|
+
assert_instance_of(Entry::KeywordRestParameter, a)
|
159
|
+
|
160
|
+
assert_equal(:b, b.name)
|
161
|
+
assert_instance_of(Entry::RequiredParameter, b)
|
162
|
+
|
163
|
+
entry = T.must(@index["qux"].first)
|
164
|
+
assert_equal(2, entry.parameters.length)
|
165
|
+
_a, second = entry.parameters
|
166
|
+
|
167
|
+
assert_equal(:"(b, c)", second.name)
|
168
|
+
assert_instance_of(Entry::RequiredParameter, second)
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_method_with_destructured_rest_parameters
|
172
|
+
index(<<~RUBY)
|
173
|
+
class Foo
|
174
|
+
def bar((a, *b))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
RUBY
|
178
|
+
|
179
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
180
|
+
entry = T.must(@index["bar"].first)
|
181
|
+
assert_equal(1, entry.parameters.length)
|
182
|
+
param = entry.parameters.first
|
183
|
+
|
184
|
+
assert_equal(:"(a, *b)", param.name)
|
185
|
+
assert_instance_of(Entry::RequiredParameter, param)
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_method_with_block_parameters
|
189
|
+
index(<<~RUBY)
|
190
|
+
class Foo
|
191
|
+
def bar(&block)
|
192
|
+
end
|
193
|
+
|
194
|
+
def baz(&)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
RUBY
|
198
|
+
|
199
|
+
entry = T.must(@index["bar"].first)
|
200
|
+
param = entry.parameters.first
|
201
|
+
assert_equal(:block, param.name)
|
202
|
+
assert_instance_of(Entry::BlockParameter, param)
|
203
|
+
|
204
|
+
entry = T.must(@index["baz"].first)
|
205
|
+
assert_equal(1, entry.parameters.length)
|
206
|
+
|
207
|
+
param = entry.parameters.first
|
208
|
+
assert_equal(Entry::BlockParameter::DEFAULT_NAME, param.name)
|
209
|
+
assert_instance_of(Entry::BlockParameter, param)
|
210
|
+
end
|
211
|
+
|
212
|
+
def test_method_with_anonymous_rest_parameters
|
213
|
+
index(<<~RUBY)
|
214
|
+
class Foo
|
215
|
+
def bar(*, **)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
RUBY
|
219
|
+
|
220
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
221
|
+
entry = T.must(@index["bar"].first)
|
222
|
+
assert_equal(2, entry.parameters.length)
|
223
|
+
first, second = entry.parameters
|
224
|
+
|
225
|
+
assert_equal(Entry::RestParameter::DEFAULT_NAME, first.name)
|
226
|
+
assert_instance_of(Entry::RestParameter, first)
|
227
|
+
|
228
|
+
assert_equal(Entry::KeywordRestParameter::DEFAULT_NAME, second.name)
|
229
|
+
assert_instance_of(Entry::KeywordRestParameter, second)
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_method_with_forbidden_keyword_splat_parameter
|
233
|
+
index(<<~RUBY)
|
234
|
+
class Foo
|
235
|
+
def bar(**nil)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
RUBY
|
239
|
+
|
240
|
+
assert_entry("bar", Entry::InstanceMethod, "/fake/path/foo.rb:1-2:2-5")
|
241
|
+
entry = T.must(@index["bar"].first)
|
242
|
+
assert_empty(entry.parameters)
|
243
|
+
end
|
244
|
+
|
109
245
|
def test_keeps_track_of_method_owner
|
110
246
|
index(<<~RUBY)
|
111
247
|
class Foo
|
data/lib/ruby_lsp/document.rb
CHANGED
@@ -24,6 +24,9 @@ module RubyLsp
|
|
24
24
|
sig { returns(URI::Generic) }
|
25
25
|
attr_reader :uri
|
26
26
|
|
27
|
+
sig { returns(String) }
|
28
|
+
attr_reader :encoding
|
29
|
+
|
27
30
|
sig { params(source: String, version: Integer, uri: URI::Generic, encoding: String).void }
|
28
31
|
def initialize(source:, version:, uri:, encoding: Constant::PositionEncodingKind::UTF8)
|
29
32
|
@cache = T.let({}, T::Hash[String, T.untyped])
|
data/lib/ruby_lsp/executor.rb
CHANGED
@@ -98,7 +98,8 @@ module RubyLsp
|
|
98
98
|
folding_range = Requests::FoldingRanges.new(document.parse_result.comments, dispatcher)
|
99
99
|
document_symbol = Requests::DocumentSymbol.new(dispatcher)
|
100
100
|
document_link = Requests::DocumentLink.new(uri, document.comments, dispatcher)
|
101
|
-
|
101
|
+
lenses_configuration = T.must(@store.features_configuration.dig(:codeLens))
|
102
|
+
code_lens = Requests::CodeLens.new(uri, lenses_configuration, dispatcher)
|
102
103
|
|
103
104
|
semantic_highlighting = Requests::SemanticHighlighting.new(dispatcher)
|
104
105
|
dispatcher.dispatch(document.tree)
|
@@ -392,7 +393,8 @@ module RubyLsp
|
|
392
393
|
end_line = range.dig(:end, :line)
|
393
394
|
|
394
395
|
dispatcher = Prism::Dispatcher.new
|
395
|
-
|
396
|
+
hints_configurations = T.must(@store.features_configuration.dig(:inlayHint))
|
397
|
+
listener = Requests::InlayHints.new(start_line..end_line, hints_configurations, dispatcher)
|
396
398
|
dispatcher.visit(document.tree)
|
397
399
|
listener.response
|
398
400
|
end
|
@@ -602,6 +604,11 @@ module RubyLsp
|
|
602
604
|
configured_features = options.dig(:initializationOptions, :enabledFeatures)
|
603
605
|
@store.experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
|
604
606
|
|
607
|
+
configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
|
608
|
+
configured_lenses = options.dig(:initializationOptions, :featuresConfiguration, :codeLens)
|
609
|
+
T.must(@store.features_configuration.dig(:inlayHint)).configuration.merge!(configured_hints) if configured_hints
|
610
|
+
T.must(@store.features_configuration.dig(:codeLens)).configuration.merge!(configured_lenses) if configured_lenses
|
611
|
+
|
605
612
|
enabled_features = case configured_features
|
606
613
|
when Array
|
607
614
|
# If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
|
@@ -663,7 +670,7 @@ module RubyLsp
|
|
663
670
|
on_type_formatting_provider = if enabled_features["onTypeFormatting"]
|
664
671
|
Interface::DocumentOnTypeFormattingOptions.new(
|
665
672
|
first_trigger_character: "{",
|
666
|
-
more_trigger_character: ["\n", "|"],
|
673
|
+
more_trigger_character: ["\n", "|", "d"],
|
667
674
|
)
|
668
675
|
end
|
669
676
|
|
@@ -38,14 +38,8 @@ module RubyLsp
|
|
38
38
|
def run
|
39
39
|
diagnostics = @context[:diagnostics]
|
40
40
|
|
41
|
-
code_actions = diagnostics.
|
42
|
-
|
43
|
-
next if code_action.nil?
|
44
|
-
|
45
|
-
# We want to return only code actions that are within range or that do not have any edits, such as refactor
|
46
|
-
# code actions
|
47
|
-
range = code_action.dig(:edit, :documentChanges, 0, :edits, 0, :range)
|
48
|
-
code_action if diagnostic.dig(:data, :correctable) && cover?(range)
|
41
|
+
code_actions = diagnostics.flat_map do |diagnostic|
|
42
|
+
diagnostic.dig(:data, :code_actions) || []
|
49
43
|
end
|
50
44
|
|
51
45
|
# Only add refactor actions if there's a non empty selection in the editor
|
@@ -55,14 +49,6 @@ module RubyLsp
|
|
55
49
|
|
56
50
|
private
|
57
51
|
|
58
|
-
sig { params(range: T.nilable(Document::RangeShape)).returns(T::Boolean) }
|
59
|
-
def cover?(range)
|
60
|
-
range.nil? ||
|
61
|
-
((@range.dig(:start, :line))..(@range.dig(:end, :line))).cover?(
|
62
|
-
(range.dig(:start, :line))..(range.dig(:end, :line)),
|
63
|
-
)
|
64
|
-
end
|
65
|
-
|
66
52
|
sig { params(range: Document::RangeShape, uri: URI::Generic).returns(Interface::CodeAction) }
|
67
53
|
def refactor_code_action(range, uri)
|
68
54
|
Interface::CodeAction.new(
|
@@ -11,6 +11,10 @@ module RubyLsp
|
|
11
11
|
# [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
|
12
12
|
# request informs the editor of runnable commands such as tests
|
13
13
|
#
|
14
|
+
# # Configuration
|
15
|
+
#
|
16
|
+
# To disable gem code lenses, set `rubyLsp.featuresConfiguration.codeLens.gemfileLinks` to `false`.
|
17
|
+
#
|
14
18
|
# # Example
|
15
19
|
#
|
16
20
|
# ```ruby
|
@@ -47,8 +51,14 @@ module RubyLsp
|
|
47
51
|
sig { override.returns(ResponseType) }
|
48
52
|
attr_reader :_response
|
49
53
|
|
50
|
-
sig
|
51
|
-
|
54
|
+
sig do
|
55
|
+
params(
|
56
|
+
uri: URI::Generic,
|
57
|
+
lenses_configuration: RequestConfig,
|
58
|
+
dispatcher: Prism::Dispatcher,
|
59
|
+
).void
|
60
|
+
end
|
61
|
+
def initialize(uri, lenses_configuration, dispatcher)
|
52
62
|
@uri = T.let(uri, URI::Generic)
|
53
63
|
@_response = T.let([], ResponseType)
|
54
64
|
@path = T.let(uri.to_standardized_path, T.nilable(String))
|
@@ -57,6 +67,7 @@ module RubyLsp
|
|
57
67
|
@class_stack = T.let([], T::Array[String])
|
58
68
|
@group_id = T.let(1, Integer)
|
59
69
|
@group_id_stack = T.let([], T::Array[Integer])
|
70
|
+
@lenses_configuration = lenses_configuration
|
60
71
|
|
61
72
|
super(dispatcher)
|
62
73
|
|
@@ -134,6 +145,8 @@ module RubyLsp
|
|
134
145
|
end
|
135
146
|
|
136
147
|
if @path&.include?(GEMFILE_NAME) && name == :gem && arguments
|
148
|
+
return unless @lenses_configuration.enabled?(:gemfileLinks)
|
149
|
+
|
137
150
|
first_argument = arguments.arguments.first
|
138
151
|
return unless first_argument.is_a?(Prism::StringNode)
|
139
152
|
|
@@ -9,6 +9,14 @@ module RubyLsp
|
|
9
9
|
# are labels added directly in the code that explicitly show the user something that might
|
10
10
|
# otherwise just be implied.
|
11
11
|
#
|
12
|
+
# # Configuration
|
13
|
+
#
|
14
|
+
# To enable rescue hints, set `rubyLsp.featuresConfiguration.inlayHint.implicitRescue` to `true`.
|
15
|
+
#
|
16
|
+
# To enable hash value hints, set `rubyLsp.featuresConfiguration.inlayHint.implicitHashValue` to `true`.
|
17
|
+
#
|
18
|
+
# To enable all hints, set `rubyLsp.featuresConfiguration.inlayHint.enableAll` to `true`.
|
19
|
+
#
|
12
20
|
# # Example
|
13
21
|
#
|
14
22
|
# ```ruby
|
@@ -39,18 +47,26 @@ module RubyLsp
|
|
39
47
|
sig { override.returns(ResponseType) }
|
40
48
|
attr_reader :_response
|
41
49
|
|
42
|
-
sig
|
43
|
-
|
50
|
+
sig do
|
51
|
+
params(
|
52
|
+
range: T::Range[Integer],
|
53
|
+
hints_configuration: RequestConfig,
|
54
|
+
dispatcher: Prism::Dispatcher,
|
55
|
+
).void
|
56
|
+
end
|
57
|
+
def initialize(range, hints_configuration, dispatcher)
|
44
58
|
super(dispatcher)
|
45
59
|
|
46
60
|
@_response = T.let([], ResponseType)
|
47
61
|
@range = range
|
62
|
+
@hints_configuration = hints_configuration
|
48
63
|
|
49
64
|
dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
|
50
65
|
end
|
51
66
|
|
52
67
|
sig { params(node: Prism::RescueNode).void }
|
53
68
|
def on_rescue_node_enter(node)
|
69
|
+
return unless @hints_configuration.enabled?(:implicitRescue)
|
54
70
|
return unless node.exceptions.empty?
|
55
71
|
|
56
72
|
loc = node.location
|
@@ -66,6 +82,7 @@ module RubyLsp
|
|
66
82
|
|
67
83
|
sig { params(node: Prism::ImplicitNode).void }
|
68
84
|
def on_implicit_node_enter(node)
|
85
|
+
return unless @hints_configuration.enabled?(:implicitHashValue)
|
69
86
|
return unless visible?(node, @range)
|
70
87
|
|
71
88
|
node_value = node.value
|
@@ -60,6 +60,8 @@ module RubyLsp
|
|
60
60
|
handle_statement_end
|
61
61
|
end
|
62
62
|
end
|
63
|
+
when "d"
|
64
|
+
auto_indent_after_end_keyword
|
63
65
|
end
|
64
66
|
|
65
67
|
@edits
|
@@ -118,7 +120,7 @@ module RubyLsp
|
|
118
120
|
current_line = @lines[@position[:line]]
|
119
121
|
next_line = @lines[@position[:line] + 1]
|
120
122
|
|
121
|
-
if current_line.nil? || current_line.strip.empty?
|
123
|
+
if current_line.nil? || current_line.strip.empty? || current_line.include?(")") || current_line.include?("]")
|
122
124
|
add_edit_with_text("\n")
|
123
125
|
add_edit_with_text("#{indents}end")
|
124
126
|
move_cursor_to(@position[:line], @indentation + 2)
|
@@ -185,6 +187,32 @@ module RubyLsp
|
|
185
187
|
|
186
188
|
count
|
187
189
|
end
|
190
|
+
|
191
|
+
sig { void }
|
192
|
+
def auto_indent_after_end_keyword
|
193
|
+
current_line = @lines[@position[:line]]
|
194
|
+
return unless current_line&.strip == "end"
|
195
|
+
|
196
|
+
target, _parent, _nesting = @document.locate_node({
|
197
|
+
line: @position[:line],
|
198
|
+
character: @position[:character] - 1,
|
199
|
+
})
|
200
|
+
|
201
|
+
statements = case target
|
202
|
+
when Prism::IfNode, Prism::UnlessNode, Prism::ForNode, Prism::WhileNode, Prism::UntilNode
|
203
|
+
target.statements
|
204
|
+
end
|
205
|
+
return unless statements
|
206
|
+
|
207
|
+
statements.body.each do |node|
|
208
|
+
loc = node.location
|
209
|
+
next unless loc.start_column == @indentation
|
210
|
+
|
211
|
+
add_edit_with_text(" ", { line: loc.start_line - 1, character: 0 })
|
212
|
+
end
|
213
|
+
|
214
|
+
move_cursor_to(@position[:line], @position[:character])
|
215
|
+
end
|
188
216
|
end
|
189
217
|
end
|
190
218
|
end
|
@@ -19,30 +19,23 @@ module RubyLsp
|
|
19
19
|
T::Hash[Symbol, Integer],
|
20
20
|
)
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
# TODO: avoid passing document once we have alternative ways to get at
|
23
|
+
# encoding and file source
|
24
|
+
sig { params(document: Document, offense: RuboCop::Cop::Offense, uri: URI::Generic).void }
|
25
|
+
def initialize(document, offense, uri)
|
26
|
+
@document = document
|
24
27
|
@offense = offense
|
25
28
|
@uri = uri
|
26
29
|
end
|
27
30
|
|
28
|
-
sig { returns(Interface::CodeAction) }
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
37
|
-
uri: @uri.to_s,
|
38
|
-
version: nil,
|
39
|
-
),
|
40
|
-
edits: @offense.correctable? ? offense_replacements : [],
|
41
|
-
),
|
42
|
-
],
|
43
|
-
),
|
44
|
-
is_preferred: true,
|
45
|
-
)
|
31
|
+
sig { returns(T::Array[Interface::CodeAction]) }
|
32
|
+
def to_lsp_code_actions
|
33
|
+
code_actions = []
|
34
|
+
|
35
|
+
code_actions << autocorrect_action if @offense.correctable?
|
36
|
+
code_actions << disable_line_action
|
37
|
+
|
38
|
+
code_actions
|
46
39
|
end
|
47
40
|
|
48
41
|
sig { returns(Interface::Diagnostic) }
|
@@ -65,7 +58,7 @@ module RubyLsp
|
|
65
58
|
),
|
66
59
|
data: {
|
67
60
|
correctable: @offense.correctable?,
|
68
|
-
|
61
|
+
code_actions: to_lsp_code_actions,
|
69
62
|
},
|
70
63
|
)
|
71
64
|
end
|
@@ -90,6 +83,26 @@ module RubyLsp
|
|
90
83
|
Interface::CodeDescription.new(href: doc_url) if doc_url
|
91
84
|
end
|
92
85
|
|
86
|
+
sig { returns(Interface::CodeAction) }
|
87
|
+
def autocorrect_action
|
88
|
+
Interface::CodeAction.new(
|
89
|
+
title: "Autocorrect #{@offense.cop_name}",
|
90
|
+
kind: Constant::CodeActionKind::QUICK_FIX,
|
91
|
+
edit: Interface::WorkspaceEdit.new(
|
92
|
+
document_changes: [
|
93
|
+
Interface::TextDocumentEdit.new(
|
94
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
95
|
+
uri: @uri.to_s,
|
96
|
+
version: nil,
|
97
|
+
),
|
98
|
+
edits: @offense.correctable? ? offense_replacements : [],
|
99
|
+
),
|
100
|
+
],
|
101
|
+
),
|
102
|
+
is_preferred: true,
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
93
106
|
sig { returns(T::Array[Interface::TextEdit]) }
|
94
107
|
def offense_replacements
|
95
108
|
@offense.corrector.as_replacements.map do |range, replacement|
|
@@ -102,6 +115,64 @@ module RubyLsp
|
|
102
115
|
)
|
103
116
|
end
|
104
117
|
end
|
118
|
+
|
119
|
+
sig { returns(Interface::CodeAction) }
|
120
|
+
def disable_line_action
|
121
|
+
Interface::CodeAction.new(
|
122
|
+
title: "Disable #{@offense.cop_name} for this line",
|
123
|
+
kind: Constant::CodeActionKind::QUICK_FIX,
|
124
|
+
edit: Interface::WorkspaceEdit.new(
|
125
|
+
document_changes: [
|
126
|
+
Interface::TextDocumentEdit.new(
|
127
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
128
|
+
uri: @uri.to_s,
|
129
|
+
version: nil,
|
130
|
+
),
|
131
|
+
edits: line_disable_comment,
|
132
|
+
),
|
133
|
+
],
|
134
|
+
),
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { returns(T::Array[Interface::TextEdit]) }
|
139
|
+
def line_disable_comment
|
140
|
+
new_text = if @offense.source_line.include?(" # rubocop:disable ")
|
141
|
+
",#{@offense.cop_name}"
|
142
|
+
else
|
143
|
+
" # rubocop:disable #{@offense.cop_name}"
|
144
|
+
end
|
145
|
+
|
146
|
+
eol = Interface::Position.new(
|
147
|
+
line: @offense.line - 1,
|
148
|
+
character: length_of_line(@offense.source_line),
|
149
|
+
)
|
150
|
+
|
151
|
+
# TODO: fails for multiline strings - may be preferable to use block
|
152
|
+
# comments to disable some offenses
|
153
|
+
inline_comment = Interface::TextEdit.new(
|
154
|
+
range: Interface::Range.new(start: eol, end: eol),
|
155
|
+
new_text: new_text,
|
156
|
+
)
|
157
|
+
|
158
|
+
[inline_comment]
|
159
|
+
end
|
160
|
+
|
161
|
+
sig { params(line: String).returns(Integer) }
|
162
|
+
def length_of_line(line)
|
163
|
+
if @document.encoding == Constant::PositionEncodingKind::UTF16
|
164
|
+
line_length = 0
|
165
|
+
line.codepoints.each do |codepoint|
|
166
|
+
line_length += 1
|
167
|
+
if codepoint > RubyLsp::Document::Scanner::SURROGATE_PAIR_START
|
168
|
+
line_length += 1
|
169
|
+
end
|
170
|
+
end
|
171
|
+
line_length
|
172
|
+
else
|
173
|
+
line.length
|
174
|
+
end
|
175
|
+
end
|
105
176
|
end
|
106
177
|
end
|
107
178
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
@@ -20,6 +20,9 @@ module RubyLsp
|
|
20
20
|
sig { returns(URI::Generic) }
|
21
21
|
attr_accessor :workspace_uri
|
22
22
|
|
23
|
+
sig { returns(T::Hash[Symbol, RequestConfig]) }
|
24
|
+
attr_accessor :features_configuration
|
25
|
+
|
23
26
|
sig { void }
|
24
27
|
def initialize
|
25
28
|
@state = T.let({}, T::Hash[String, Document])
|
@@ -28,6 +31,20 @@ module RubyLsp
|
|
28
31
|
@supports_progress = T.let(true, T::Boolean)
|
29
32
|
@experimental_features = T.let(false, T::Boolean)
|
30
33
|
@workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
|
34
|
+
@features_configuration = T.let(
|
35
|
+
{
|
36
|
+
codeLens: RequestConfig.new({
|
37
|
+
enableAll: false,
|
38
|
+
gemfileLinks: true,
|
39
|
+
}),
|
40
|
+
inlayHint: RequestConfig.new({
|
41
|
+
enableAll: false,
|
42
|
+
implicitRescue: false,
|
43
|
+
implicitHashValue: false,
|
44
|
+
}),
|
45
|
+
},
|
46
|
+
T::Hash[Symbol, RequestConfig],
|
47
|
+
)
|
31
48
|
end
|
32
49
|
|
33
50
|
sig { params(uri: URI::Generic).returns(Document) }
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -74,4 +74,22 @@ module RubyLsp
|
|
74
74
|
@cancelled = true
|
75
75
|
end
|
76
76
|
end
|
77
|
+
|
78
|
+
# A request configuration, to turn on/off features
|
79
|
+
class RequestConfig
|
80
|
+
extend T::Sig
|
81
|
+
|
82
|
+
sig { returns(T::Hash[Symbol, T::Boolean]) }
|
83
|
+
attr_accessor :configuration
|
84
|
+
|
85
|
+
sig { params(configuration: T::Hash[Symbol, T::Boolean]).void }
|
86
|
+
def initialize(configuration)
|
87
|
+
@configuration = configuration
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { params(feature: Symbol).returns(T.nilable(T::Boolean)) }
|
91
|
+
def enabled?(feature)
|
92
|
+
@configuration[:enableAll] || @configuration[feature]
|
93
|
+
end
|
94
|
+
end
|
77
95
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.13.
|
4
|
+
version: 0.13.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -157,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
157
|
- !ruby/object:Gem::Version
|
158
158
|
version: '0'
|
159
159
|
requirements: []
|
160
|
-
rubygems_version: 3.4.
|
160
|
+
rubygems_version: 3.4.22
|
161
161
|
signing_key:
|
162
162
|
specification_version: 4
|
163
163
|
summary: An opinionated language server for Ruby
|