ruby-lsp 0.3.8 → 0.4.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/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
|
+
# 
|
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
|
|