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.
@@ -23,25 +23,55 @@ module RubyLsp
23
23
  params(
24
24
  uri: String,
25
25
  document: Document,
26
- range: T::Range[Integer],
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[LanguageServer::Protocol::Interface::CodeAction], Object)) }
38
+ sig { override.returns(T.nilable(T.all(T::Array[Interface::CodeAction], Object))) }
37
39
  def run
38
- diagnostics = @document.cache_fetch(:diagnostics) { Diagnostics.new(@uri, @document).run }
39
- corrections = diagnostics.select do |diagnostic|
40
- diagnostic.correctable? && T.cast(diagnostic, Support::RuboCopDiagnostic).in_range?(@range)
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
- T.cast(corrections, T::Array[Support::RuboCopDiagnostic]).map!(&:to_lsp_code_action)
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 indentantion
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 do
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 [] if @document.syntax_error?
41
- return [] unless defined?(Support::RuboCopDiagnosticsRunner)
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[LanguageServer::Protocol::Interface::TextEdit], Object))) }
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
- LanguageServer::Protocol::Interface::TextEdit.new(
45
- range: LanguageServer::Protocol::Interface::Range.new(
46
- start: LanguageServer::Protocol::Interface::Position.new(line: 0, character: 0),
47
- end: LanguageServer::Protocol::Interface::Position.new(line: size, character: size),
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..line_end])
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.nilable(T.all(T::Array[Interface::TextEdit], Object))) }
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
- add_edit_with_text(" \n#{indents}end")
88
- move_cursor_to(@position[:line], @indentation + 2)
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
- position = Interface::Position.new(
100
- line: @position[:line],
101
- character: @position[: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::WithEnvironment
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 < T::Struct
82
- const :location, SyntaxTree::Location
83
- const :length, Integer
84
- const :type, Integer
85
- const :modifier, T::Array[Integer]
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
- unless message == :call || special_method?(message.value)
130
- add_token(message.location, :method)
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
- add_token(node.message.location, :method) unless special_method?(node.message.value)
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
- add_token(node.value.location, :method) unless special_method?(node.value.value)
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 = current_environment.find_local(value.value)
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 < T::Struct
14
- const :type, Integer
15
- const :node, SyntaxTree::Node
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: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
13
- info: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
14
- refactor: LanguageServer::Protocol::Constant::DiagnosticSeverity::INFORMATION,
15
- warning: LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING,
16
- error: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
17
- fatal: LanguageServer::Protocol::Constant::DiagnosticSeverity::ERROR,
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 { params(range: T::Range[Integer]).returns(T::Boolean) }
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
- LanguageServer::Protocol::Interface::CodeAction.new(
30
+ Interface::CodeAction.new(
48
31
  title: "Autocorrect #{@offense.cop_name}",
49
- kind: LanguageServer::Protocol::Constant::CodeActionKind::QUICK_FIX,
50
- edit: LanguageServer::Protocol::Interface::WorkspaceEdit.new(
32
+ kind: Constant::CodeActionKind::QUICK_FIX,
33
+ edit: Interface::WorkspaceEdit.new(
51
34
  document_changes: [
52
- LanguageServer::Protocol::Interface::TextDocumentEdit.new(
53
- text_document: LanguageServer::Protocol::Interface::OptionalVersionedTextDocumentIdentifier.new(
35
+ Interface::TextDocumentEdit.new(
36
+ text_document: Interface::OptionalVersionedTextDocumentIdentifier.new(
54
37
  uri: @uri,
55
38
  version: nil,
56
39
  ),
57
- edits: @replacements,
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(LanguageServer::Protocol::Interface::Diagnostic) }
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 = LanguageServer::Protocol::Constant::DiagnosticSeverity::WARNING
54
+ severity = Constant::DiagnosticSeverity::WARNING
72
55
  message = "#{@offense.message}\n\nThis offense is not auto-correctable.\n"
73
56
  end
74
- LanguageServer::Protocol::Interface::Diagnostic.new(
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: LanguageServer::Protocol::Interface::Range.new(
80
- start: LanguageServer::Protocol::Interface::Position.new(
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: LanguageServer::Protocol::Interface::Position.new(
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[LanguageServer::Protocol::Interface::TextEdit]) }
82
+ sig { returns(T::Array[Interface::TextEdit]) }
95
83
  def offense_replacements
96
84
  @offense.corrector.as_replacements.map do |range, replacement|
97
- LanguageServer::Protocol::Interface::TextEdit.new(
98
- range: LanguageServer::Protocol::Interface::Range.new(
99
- start: LanguageServer::Protocol::Interface::Position.new(line: range.line - 1, character: range.column),
100
- end: LanguageServer::Protocol::Interface::Position.new(
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(T.nilable(String)) }
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