ruby-lsp 0.3.8 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +39 -1
- data/VERSION +1 -1
- data/exe/ruby-lsp +2 -1
- data/lib/ruby_lsp/document.rb +5 -3
- data/lib/ruby_lsp/executor.rb +390 -0
- data/lib/ruby_lsp/internal.rb +6 -1
- data/lib/ruby_lsp/requests/base_request.rb +10 -3
- data/lib/ruby_lsp/requests/code_action_resolve.rb +100 -0
- data/lib/ruby_lsp/requests/code_actions.rb +38 -8
- data/lib/ruby_lsp/requests/diagnostics.rb +7 -11
- data/lib/ruby_lsp/requests/formatting.rb +10 -5
- data/lib/ruby_lsp/requests/on_type_formatting.rb +33 -14
- data/lib/ruby_lsp/requests/path_completion.rb +95 -0
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +35 -11
- data/lib/ruby_lsp/requests/support/annotation.rb +46 -0
- data/lib/ruby_lsp/requests/support/highlight_target.rb +14 -3
- data/lib/ruby_lsp/requests/support/prefix_tree.rb +80 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +29 -44
- data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +27 -1
- data/lib/ruby_lsp/requests/support/sorbet.rb +120 -0
- data/lib/ruby_lsp/requests.rb +7 -1
- data/lib/ruby_lsp/server.rb +129 -221
- data/lib/ruby_lsp/utils.rb +78 -0
- metadata +13 -9
- data/lib/ruby_lsp/handler.rb +0 -118
- data/lib/ruby_lsp/queue.rb +0 -182
- data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +0 -32
@@ -23,25 +23,55 @@ module RubyLsp
|
|
23
23
|
params(
|
24
24
|
uri: String,
|
25
25
|
document: Document,
|
26
|
-
range:
|
26
|
+
range: Document::RangeShape,
|
27
|
+
context: T::Hash[Symbol, T.untyped],
|
27
28
|
).void
|
28
29
|
end
|
29
|
-
def initialize(uri, document, range)
|
30
|
+
def initialize(uri, document, range, context)
|
30
31
|
super(document)
|
31
32
|
|
32
33
|
@uri = uri
|
33
34
|
@range = range
|
35
|
+
@context = context
|
34
36
|
end
|
35
37
|
|
36
|
-
sig { override.returns(T.all(T::Array[
|
38
|
+
sig { override.returns(T.nilable(T.all(T::Array[Interface::CodeAction], Object))) }
|
37
39
|
def run
|
38
|
-
diagnostics = @
|
39
|
-
|
40
|
-
|
40
|
+
diagnostics = @context[:diagnostics]
|
41
|
+
|
42
|
+
code_actions = diagnostics.filter_map do |diagnostic|
|
43
|
+
code_action = diagnostic.dig(:data, :code_action)
|
44
|
+
next if code_action.nil?
|
45
|
+
|
46
|
+
# We want to return only code actions that are within range or that do not have any edits, such as refactor
|
47
|
+
# code actions
|
48
|
+
range = code_action.dig(:edit, :documentChanges, 0, :edits, 0, :range)
|
49
|
+
code_action if diagnostic.dig(:data, :correctable) && cover?(range)
|
41
50
|
end
|
42
|
-
return [] if corrections.empty?
|
43
51
|
|
44
|
-
|
52
|
+
code_actions << refactor_code_action(@range, @uri)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
sig { params(range: T.nilable(Document::RangeShape)).returns(T::Boolean) }
|
58
|
+
def cover?(range)
|
59
|
+
range.nil? ||
|
60
|
+
((@range.dig(:start, :line))..(@range.dig(:end, :line))).cover?(
|
61
|
+
(range.dig(:start, :line))..(range.dig(:end, :line)),
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { params(range: Document::RangeShape, uri: String).returns(Interface::CodeAction) }
|
66
|
+
def refactor_code_action(range, uri)
|
67
|
+
Interface::CodeAction.new(
|
68
|
+
title: "Refactor: Extract Variable",
|
69
|
+
kind: Constant::CodeActionKind::REFACTOR_EXTRACT,
|
70
|
+
data: {
|
71
|
+
range: range,
|
72
|
+
uri: uri,
|
73
|
+
},
|
74
|
+
)
|
45
75
|
end
|
46
76
|
end
|
47
77
|
end
|
@@ -15,7 +15,7 @@ module RubyLsp
|
|
15
15
|
#
|
16
16
|
# ```ruby
|
17
17
|
# def say_hello
|
18
|
-
# puts "Hello" # --> diagnostics: incorrect
|
18
|
+
# puts "Hello" # --> diagnostics: incorrect indentation
|
19
19
|
# end
|
20
20
|
# ```
|
21
21
|
class Diagnostics < BaseRequest
|
@@ -28,17 +28,13 @@ module RubyLsp
|
|
28
28
|
@uri = uri
|
29
29
|
end
|
30
30
|
|
31
|
-
sig
|
32
|
-
override.returns(
|
33
|
-
T.any(
|
34
|
-
T.all(T::Array[Support::RuboCopDiagnostic], Object),
|
35
|
-
T.all(T::Array[Support::SyntaxErrorDiagnostic], Object),
|
36
|
-
),
|
37
|
-
)
|
38
|
-
end
|
31
|
+
sig { override.returns(T.nilable(T.all(T::Array[Support::RuboCopDiagnostic], Object))) }
|
39
32
|
def run
|
40
|
-
return
|
41
|
-
return
|
33
|
+
return if @document.syntax_error?
|
34
|
+
return unless defined?(Support::RuboCopDiagnosticsRunner)
|
35
|
+
|
36
|
+
# Don't try to run RuboCop diagnostics for files outside the current working directory
|
37
|
+
return unless @uri.sub("file://", "").start_with?(Dir.pwd)
|
42
38
|
|
43
39
|
Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
|
44
40
|
end
|
@@ -32,8 +32,13 @@ module RubyLsp
|
|
32
32
|
@uri = uri
|
33
33
|
end
|
34
34
|
|
35
|
-
sig { override.returns(T.nilable(T.all(T::Array[
|
35
|
+
sig { override.returns(T.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
|
36
36
|
def run
|
37
|
+
# Don't try to format files outside the current working directory
|
38
|
+
return unless @uri.sub("file://", "").start_with?(Dir.pwd)
|
39
|
+
|
40
|
+
return if @document.syntax_error?
|
41
|
+
|
37
42
|
formatted_text = formatted_file
|
38
43
|
return unless formatted_text
|
39
44
|
|
@@ -41,10 +46,10 @@ module RubyLsp
|
|
41
46
|
return if formatted_text.size == size && formatted_text == @document.source
|
42
47
|
|
43
48
|
[
|
44
|
-
|
45
|
-
range:
|
46
|
-
start:
|
47
|
-
end:
|
49
|
+
Interface::TextEdit.new(
|
50
|
+
range: Interface::Range.new(
|
51
|
+
start: Interface::Position.new(line: 0, character: 0),
|
52
|
+
end: Interface::Position.new(line: size, character: size),
|
48
53
|
),
|
49
54
|
new_text: formatted_text,
|
50
55
|
),
|
@@ -32,8 +32,8 @@ module RubyLsp
|
|
32
32
|
|
33
33
|
scanner = document.create_scanner
|
34
34
|
line_begin = position[:line] == 0 ? 0 : scanner.find_char_position({ line: position[:line] - 1, character: 0 })
|
35
|
-
line_end = scanner.find_char_position(position)
|
36
|
-
line = T.must(@document.source[line_begin
|
35
|
+
@line_end = T.let(scanner.find_char_position(position), Integer)
|
36
|
+
line = T.must(@document.source[line_begin..@line_end])
|
37
37
|
|
38
38
|
@indentation = T.let(find_indentation(line), Integer)
|
39
39
|
@previous_line = T.let(line.strip.chomp, String)
|
@@ -42,7 +42,7 @@ module RubyLsp
|
|
42
42
|
@trigger_character = trigger_character
|
43
43
|
end
|
44
44
|
|
45
|
-
sig { override.returns(T.
|
45
|
+
sig { override.returns(T.all(T::Array[Interface::TextEdit], Object)) }
|
46
46
|
def run
|
47
47
|
case @trigger_character
|
48
48
|
when "{"
|
@@ -84,8 +84,30 @@ module RubyLsp
|
|
84
84
|
|
85
85
|
indents = " " * @indentation
|
86
86
|
|
87
|
-
|
88
|
-
|
87
|
+
if @previous_line.include?("\n")
|
88
|
+
# If the previous line has a line break, then it means there's content after the line break that triggered
|
89
|
+
# this completion. For these cases, we want to add the `end` after the content and move the cursor back to the
|
90
|
+
# keyword that triggered the completion
|
91
|
+
|
92
|
+
line = @position[:line]
|
93
|
+
|
94
|
+
# If there are enough lines in the document, we want to add the `end` token on the line below the extra
|
95
|
+
# content. Otherwise, we want to insert and extra line break ourselves
|
96
|
+
correction = if T.must(@document.source[@line_end..-1]).count("\n") >= 2
|
97
|
+
line -= 1
|
98
|
+
"#{indents}end"
|
99
|
+
else
|
100
|
+
"#{indents}\nend"
|
101
|
+
end
|
102
|
+
|
103
|
+
add_edit_with_text(correction, { line: @position[:line] + 1, character: @position[:character] })
|
104
|
+
move_cursor_to(line, @indentation + 3)
|
105
|
+
else
|
106
|
+
# If there's nothing after the new line break that triggered the completion, then we want to add the `end` and
|
107
|
+
# move the cursor to the body of the statement
|
108
|
+
add_edit_with_text(" \n#{indents}end")
|
109
|
+
move_cursor_to(@position[:line], @indentation + 2)
|
110
|
+
end
|
89
111
|
end
|
90
112
|
|
91
113
|
sig { params(spaces: String).void }
|
@@ -94,18 +116,15 @@ module RubyLsp
|
|
94
116
|
move_cursor_to(@position[:line], @indentation + spaces.size + 1)
|
95
117
|
end
|
96
118
|
|
97
|
-
sig { params(text: String).void }
|
98
|
-
def add_edit_with_text(text)
|
99
|
-
|
100
|
-
line:
|
101
|
-
character:
|
119
|
+
sig { params(text: String, position: Document::PositionShape).void }
|
120
|
+
def add_edit_with_text(text, position = @position)
|
121
|
+
pos = Interface::Position.new(
|
122
|
+
line: position[:line],
|
123
|
+
character: position[:character],
|
102
124
|
)
|
103
125
|
|
104
126
|
@edits << Interface::TextEdit.new(
|
105
|
-
range: Interface::Range.new(
|
106
|
-
start: position,
|
107
|
-
end: position,
|
108
|
-
),
|
127
|
+
range: Interface::Range.new(start: pos, end: pos),
|
109
128
|
new_text: text,
|
110
129
|
)
|
111
130
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
# ![Path completion demo](../../misc/path_completion.gif)
|
7
|
+
#
|
8
|
+
# The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
|
9
|
+
# request looks up Ruby files in the $LOAD_PATH to suggest path completion inside `require` statements.
|
10
|
+
#
|
11
|
+
# # Example
|
12
|
+
#
|
13
|
+
# ```ruby
|
14
|
+
# require "ruby_lsp/requests" # --> completion: suggests `base_request`, `code_actions`, ...
|
15
|
+
# ```
|
16
|
+
class PathCompletion < BaseRequest
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
sig { params(document: Document, position: Document::PositionShape).void }
|
20
|
+
def initialize(document, position)
|
21
|
+
super(document)
|
22
|
+
|
23
|
+
@tree = T.let(Support::PrefixTree.new(collect_load_path_files), Support::PrefixTree)
|
24
|
+
@position = position
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { override.returns(T.all(T::Array[Interface::CompletionItem], Object)) }
|
28
|
+
def run
|
29
|
+
# We can't verify if we're inside a require when there are syntax errors
|
30
|
+
return [] if @document.syntax_error?
|
31
|
+
|
32
|
+
char_position = @document.create_scanner.find_char_position(@position)
|
33
|
+
target = T.let(find(char_position), T.nilable(SyntaxTree::TStringContent))
|
34
|
+
# no target means the we are not inside a `require` call
|
35
|
+
return [] unless target
|
36
|
+
|
37
|
+
text = target.value
|
38
|
+
@tree.search(text).sort.map! do |path|
|
39
|
+
build_completion(path, path.delete_prefix(text))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
sig { returns(T::Array[String]) }
|
46
|
+
def collect_load_path_files
|
47
|
+
$LOAD_PATH.flat_map do |p|
|
48
|
+
Dir.glob("**/*.rb", base: p)
|
49
|
+
end.map! do |result|
|
50
|
+
result.delete_suffix!(".rb")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(position: Integer).returns(T.nilable(SyntaxTree::TStringContent)) }
|
55
|
+
def find(position)
|
56
|
+
matched, parent = locate(
|
57
|
+
T.must(@document.tree),
|
58
|
+
position,
|
59
|
+
node_types: [SyntaxTree::Command, SyntaxTree::CommandCall, SyntaxTree::CallNode],
|
60
|
+
)
|
61
|
+
|
62
|
+
return unless matched && parent
|
63
|
+
|
64
|
+
case matched
|
65
|
+
when SyntaxTree::Command, SyntaxTree::CallNode, SyntaxTree::CommandCall
|
66
|
+
return unless matched.message.value == "require"
|
67
|
+
|
68
|
+
args = matched.arguments
|
69
|
+
args = args.arguments if args.is_a?(SyntaxTree::ArgParen)
|
70
|
+
|
71
|
+
path_node = args.parts.first.parts.first
|
72
|
+
return unless path_node
|
73
|
+
return unless (path_node.location.start_char..path_node.location.end_char).cover?(position)
|
74
|
+
|
75
|
+
path_node
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
sig { params(label: String, insert_text: String).returns(Interface::CompletionItem) }
|
80
|
+
def build_completion(label, insert_text)
|
81
|
+
Interface::CompletionItem.new(
|
82
|
+
label: label,
|
83
|
+
text_edit: Interface::TextEdit.new(
|
84
|
+
range: Interface::Range.new(
|
85
|
+
start: @position,
|
86
|
+
end: @position,
|
87
|
+
),
|
88
|
+
new_text: insert_text,
|
89
|
+
),
|
90
|
+
kind: Constant::CompletionItemKind::REFERENCE,
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -20,7 +20,7 @@ module RubyLsp
|
|
20
20
|
# ```
|
21
21
|
class SemanticHighlighting < BaseRequest
|
22
22
|
extend T::Sig
|
23
|
-
include SyntaxTree::
|
23
|
+
include SyntaxTree::WithScope
|
24
24
|
|
25
25
|
TOKEN_TYPES = T.let(
|
26
26
|
{
|
@@ -78,11 +78,28 @@ module RubyLsp
|
|
78
78
|
T::Array[String],
|
79
79
|
)
|
80
80
|
|
81
|
-
class SemanticToken
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
class SemanticToken
|
82
|
+
extend T::Sig
|
83
|
+
|
84
|
+
sig { returns(SyntaxTree::Location) }
|
85
|
+
attr_reader :location
|
86
|
+
|
87
|
+
sig { returns(Integer) }
|
88
|
+
attr_reader :length
|
89
|
+
|
90
|
+
sig { returns(Integer) }
|
91
|
+
attr_reader :type
|
92
|
+
|
93
|
+
sig { returns(T::Array[Integer]) }
|
94
|
+
attr_reader :modifier
|
95
|
+
|
96
|
+
sig { params(location: SyntaxTree::Location, length: Integer, type: Integer, modifier: T::Array[Integer]).void }
|
97
|
+
def initialize(location:, length:, type:, modifier:)
|
98
|
+
@location = location
|
99
|
+
@length = length
|
100
|
+
@type = type
|
101
|
+
@modifier = modifier
|
102
|
+
end
|
86
103
|
end
|
87
104
|
|
88
105
|
sig do
|
@@ -126,8 +143,10 @@ module RubyLsp
|
|
126
143
|
visit(node.receiver)
|
127
144
|
|
128
145
|
message = node.message
|
129
|
-
|
130
|
-
|
146
|
+
if message != :call && !special_method?(message.value)
|
147
|
+
type = Support::Sorbet.annotation?(node) ? :type : :method
|
148
|
+
|
149
|
+
add_token(message.location, type)
|
131
150
|
end
|
132
151
|
|
133
152
|
visit(node.arguments)
|
@@ -137,7 +156,9 @@ module RubyLsp
|
|
137
156
|
def visit_command(node)
|
138
157
|
return super unless visible?(node, @range)
|
139
158
|
|
140
|
-
|
159
|
+
unless special_method?(node.message.value)
|
160
|
+
add_token(node.message.location, :method)
|
161
|
+
end
|
141
162
|
visit(node.arguments)
|
142
163
|
end
|
143
164
|
|
@@ -243,7 +264,10 @@ module RubyLsp
|
|
243
264
|
def visit_vcall(node)
|
244
265
|
return super unless visible?(node, @range)
|
245
266
|
|
246
|
-
|
267
|
+
return if special_method?(node.value.value)
|
268
|
+
|
269
|
+
type = Support::Sorbet.annotation?(node) ? :type : :method
|
270
|
+
add_token(node.value.location, type)
|
247
271
|
end
|
248
272
|
|
249
273
|
sig { override.params(node: SyntaxTree::ClassDeclaration).void }
|
@@ -305,7 +329,7 @@ module RubyLsp
|
|
305
329
|
|
306
330
|
sig { params(value: SyntaxTree::Ident).returns(Symbol) }
|
307
331
|
def type_for_local(value)
|
308
|
-
local =
|
332
|
+
local = current_scope.find_local(value.value)
|
309
333
|
|
310
334
|
if local.nil? || local.type == :variable
|
311
335
|
:variable
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
module Support
|
7
|
+
class Annotation
|
8
|
+
extend T::Sig
|
9
|
+
sig do
|
10
|
+
params(
|
11
|
+
arity: T.any(Integer, T::Range[Integer]),
|
12
|
+
receiver: T::Boolean,
|
13
|
+
).void
|
14
|
+
end
|
15
|
+
def initialize(arity:, receiver: false)
|
16
|
+
@arity = arity
|
17
|
+
@receiver = receiver
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { returns(T.any(Integer, T::Range[Integer])) }
|
21
|
+
attr_reader :arity
|
22
|
+
|
23
|
+
sig { returns(T::Boolean) }
|
24
|
+
attr_reader :receiver
|
25
|
+
|
26
|
+
sig { params(arity: T.any(T::Range[Integer], Integer)).returns(T::Boolean) }
|
27
|
+
def supports_arity?(arity)
|
28
|
+
if @arity.is_a?(Integer)
|
29
|
+
@arity == arity
|
30
|
+
elsif @arity.is_a?(Range)
|
31
|
+
@arity.cover?(arity)
|
32
|
+
else
|
33
|
+
T.absurd(@arity)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { params(receiver: T.nilable(String)).returns(T::Boolean) }
|
38
|
+
def supports_receiver?(receiver)
|
39
|
+
return receiver.nil? || receiver.empty? if @receiver == false
|
40
|
+
|
41
|
+
receiver == "T"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -10,9 +10,20 @@ module RubyLsp
|
|
10
10
|
READ = LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
|
11
11
|
WRITE = LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
|
12
12
|
|
13
|
-
class HighlightMatch
|
14
|
-
|
15
|
-
|
13
|
+
class HighlightMatch
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { returns(Integer) }
|
17
|
+
attr_reader :type
|
18
|
+
|
19
|
+
sig { returns(SyntaxTree::Node) }
|
20
|
+
attr_reader :node
|
21
|
+
|
22
|
+
sig { params(type: Integer, node: SyntaxTree::Node).void }
|
23
|
+
def initialize(type:, node:)
|
24
|
+
@type = type
|
25
|
+
@node = node
|
26
|
+
end
|
16
27
|
end
|
17
28
|
|
18
29
|
sig { params(node: SyntaxTree::Node).void }
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module RubyLsp
|
5
|
+
module Requests
|
6
|
+
module Support
|
7
|
+
class PrefixTree
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(items: T::Array[String]).void }
|
11
|
+
def initialize(items)
|
12
|
+
@root = T.let(Node.new(""), Node)
|
13
|
+
|
14
|
+
items.each do |item|
|
15
|
+
insert(item)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { params(prefix: String).returns(T::Array[String]) }
|
20
|
+
def search(prefix)
|
21
|
+
node = T.let(@root, Node)
|
22
|
+
|
23
|
+
prefix.each_char do |char|
|
24
|
+
snode = node.children[char]
|
25
|
+
return [] unless snode
|
26
|
+
|
27
|
+
node = snode
|
28
|
+
end
|
29
|
+
|
30
|
+
node.collect
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
sig { params(item: String).void }
|
36
|
+
def insert(item)
|
37
|
+
node = T.let(@root, Node)
|
38
|
+
|
39
|
+
item.each_char do |char|
|
40
|
+
node = node.children[char] ||= Node.new(node.value + char)
|
41
|
+
end
|
42
|
+
|
43
|
+
node.leaf = true
|
44
|
+
end
|
45
|
+
|
46
|
+
class Node
|
47
|
+
extend T::Sig
|
48
|
+
|
49
|
+
sig { returns(T::Hash[String, Node]) }
|
50
|
+
attr_reader :children
|
51
|
+
|
52
|
+
sig { returns(String) }
|
53
|
+
attr_reader :value
|
54
|
+
|
55
|
+
sig { returns(T::Boolean) }
|
56
|
+
attr_accessor :leaf
|
57
|
+
|
58
|
+
sig { params(value: String).void }
|
59
|
+
def initialize(value)
|
60
|
+
@children = T.let({}, T::Hash[String, Node])
|
61
|
+
@value = T.let(value, String)
|
62
|
+
@leaf = T.let(false, T::Boolean)
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { returns(T::Array[String]) }
|
66
|
+
def collect
|
67
|
+
result = T.let([], T::Array[String])
|
68
|
+
result << value if leaf
|
69
|
+
|
70
|
+
children.each_value do |node|
|
71
|
+
result.concat(node.collect)
|
72
|
+
end
|
73
|
+
|
74
|
+
result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -9,52 +9,35 @@ module RubyLsp
|
|
9
9
|
|
10
10
|
RUBOCOP_TO_LSP_SEVERITY = T.let(
|
11
11
|
{
|
12
|
-
convention:
|
13
|
-
info:
|
14
|
-
refactor:
|
15
|
-
warning:
|
16
|
-
error:
|
17
|
-
fatal:
|
12
|
+
convention: Constant::DiagnosticSeverity::INFORMATION,
|
13
|
+
info: Constant::DiagnosticSeverity::INFORMATION,
|
14
|
+
refactor: Constant::DiagnosticSeverity::INFORMATION,
|
15
|
+
warning: Constant::DiagnosticSeverity::WARNING,
|
16
|
+
error: Constant::DiagnosticSeverity::ERROR,
|
17
|
+
fatal: Constant::DiagnosticSeverity::ERROR,
|
18
18
|
}.freeze,
|
19
19
|
T::Hash[Symbol, Integer],
|
20
20
|
)
|
21
21
|
|
22
|
-
sig { returns(T::Array[LanguageServer::Protocol::Interface::TextEdit]) }
|
23
|
-
attr_reader :replacements
|
24
|
-
|
25
22
|
sig { params(offense: RuboCop::Cop::Offense, uri: String).void }
|
26
23
|
def initialize(offense, uri)
|
27
24
|
@offense = offense
|
28
25
|
@uri = uri
|
29
|
-
@replacements = T.let(
|
30
|
-
offense.correctable? ? offense_replacements : [],
|
31
|
-
T::Array[LanguageServer::Protocol::Interface::TextEdit],
|
32
|
-
)
|
33
|
-
end
|
34
|
-
|
35
|
-
sig { returns(T::Boolean) }
|
36
|
-
def correctable?
|
37
|
-
@offense.correctable?
|
38
26
|
end
|
39
27
|
|
40
|
-
sig {
|
41
|
-
def in_range?(range)
|
42
|
-
range.cover?(@offense.line - 1)
|
43
|
-
end
|
44
|
-
|
45
|
-
sig { returns(LanguageServer::Protocol::Interface::CodeAction) }
|
28
|
+
sig { returns(Interface::CodeAction) }
|
46
29
|
def to_lsp_code_action
|
47
|
-
|
30
|
+
Interface::CodeAction.new(
|
48
31
|
title: "Autocorrect #{@offense.cop_name}",
|
49
|
-
kind:
|
50
|
-
edit:
|
32
|
+
kind: Constant::CodeActionKind::QUICK_FIX,
|
33
|
+
edit: Interface::WorkspaceEdit.new(
|
51
34
|
document_changes: [
|
52
|
-
|
53
|
-
text_document:
|
35
|
+
Interface::TextDocumentEdit.new(
|
36
|
+
text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
|
54
37
|
uri: @uri,
|
55
38
|
version: nil,
|
56
39
|
),
|
57
|
-
edits: @
|
40
|
+
edits: @offense.correctable? ? offense_replacements : [],
|
58
41
|
),
|
59
42
|
],
|
60
43
|
),
|
@@ -62,45 +45,47 @@ module RubyLsp
|
|
62
45
|
)
|
63
46
|
end
|
64
47
|
|
65
|
-
sig { returns(
|
48
|
+
sig { returns(Interface::Diagnostic) }
|
66
49
|
def to_lsp_diagnostic
|
67
50
|
if @offense.correctable?
|
68
51
|
severity = RUBOCOP_TO_LSP_SEVERITY[@offense.severity.name]
|
69
52
|
message = @offense.message
|
70
53
|
else
|
71
|
-
severity =
|
54
|
+
severity = Constant::DiagnosticSeverity::WARNING
|
72
55
|
message = "#{@offense.message}\n\nThis offense is not auto-correctable.\n"
|
73
56
|
end
|
74
|
-
|
57
|
+
|
58
|
+
Interface::Diagnostic.new(
|
75
59
|
message: message,
|
76
60
|
source: "RuboCop",
|
77
61
|
code: @offense.cop_name,
|
78
62
|
severity: severity,
|
79
|
-
range:
|
80
|
-
start:
|
63
|
+
range: Interface::Range.new(
|
64
|
+
start: Interface::Position.new(
|
81
65
|
line: @offense.line - 1,
|
82
66
|
character: @offense.column,
|
83
67
|
),
|
84
|
-
end:
|
68
|
+
end: Interface::Position.new(
|
85
69
|
line: @offense.last_line - 1,
|
86
70
|
character: @offense.last_column,
|
87
71
|
),
|
88
72
|
),
|
73
|
+
data: {
|
74
|
+
correctable: @offense.correctable?,
|
75
|
+
code_action: to_lsp_code_action,
|
76
|
+
},
|
89
77
|
)
|
90
78
|
end
|
91
79
|
|
92
80
|
private
|
93
81
|
|
94
|
-
sig { returns(T::Array[
|
82
|
+
sig { returns(T::Array[Interface::TextEdit]) }
|
95
83
|
def offense_replacements
|
96
84
|
@offense.corrector.as_replacements.map do |range, replacement|
|
97
|
-
|
98
|
-
range:
|
99
|
-
start:
|
100
|
-
end:
|
101
|
-
line: range.last_line - 1,
|
102
|
-
character: range.last_column,
|
103
|
-
),
|
85
|
+
Interface::TextEdit.new(
|
86
|
+
range: Interface::Range.new(
|
87
|
+
start: Interface::Position.new(line: range.line - 1, character: range.column),
|
88
|
+
end: Interface::Position.new(line: range.last_line - 1, character: range.last_column),
|
104
89
|
),
|
105
90
|
new_text: replacement,
|
106
91
|
)
|
@@ -21,7 +21,7 @@ module RubyLsp
|
|
21
21
|
@runner = T.let(RuboCopRunner.new("-a"), RuboCopRunner)
|
22
22
|
end
|
23
23
|
|
24
|
-
sig { params(uri: String, document: Document).returns(
|
24
|
+
sig { params(uri: String, document: Document).returns(String) }
|
25
25
|
def run(uri, document)
|
26
26
|
filename = CGI.unescape(URI.parse(uri).path)
|
27
27
|
|