ruby-lsp 0.0.4 → 0.2.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +11 -1
  3. data/CHANGELOG.md +35 -0
  4. data/Gemfile +4 -3
  5. data/Gemfile.lock +26 -24
  6. data/README.md +3 -2
  7. data/Rakefile +8 -1
  8. data/VERSION +1 -1
  9. data/bin/console +19 -0
  10. data/exe/ruby-lsp +1 -3
  11. data/lib/ruby_lsp/document.rb +17 -4
  12. data/lib/ruby_lsp/handler.rb +54 -141
  13. data/lib/{internal.rb → ruby_lsp/internal.rb} +4 -2
  14. data/lib/ruby_lsp/requests/base_request.rb +9 -7
  15. data/lib/ruby_lsp/requests/code_actions.rb +20 -9
  16. data/lib/ruby_lsp/requests/diagnostics.rb +25 -8
  17. data/lib/ruby_lsp/requests/document_highlight.rb +32 -32
  18. data/lib/ruby_lsp/requests/document_symbol.rb +59 -10
  19. data/lib/ruby_lsp/requests/folding_ranges.rb +73 -34
  20. data/lib/ruby_lsp/requests/formatting.rb +25 -15
  21. data/lib/ruby_lsp/requests/selection_ranges.rb +18 -5
  22. data/lib/ruby_lsp/requests/semantic_highlighting.rb +179 -36
  23. data/lib/ruby_lsp/requests/support/highlight_target.rb +87 -0
  24. data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +16 -4
  25. data/lib/ruby_lsp/requests/support/rubocop_diagnostics_runner.rb +61 -0
  26. data/lib/ruby_lsp/requests/support/rubocop_formatting_runner.rb +50 -0
  27. data/lib/ruby_lsp/requests/support/selection_range.rb +4 -1
  28. data/lib/ruby_lsp/requests/support/semantic_token_encoder.rb +13 -3
  29. data/lib/ruby_lsp/requests/support/syntax_error_diagnostic.rb +6 -1
  30. data/lib/ruby_lsp/requests.rb +13 -2
  31. data/lib/ruby_lsp/server.rb +160 -0
  32. data/lib/ruby_lsp/store.rb +17 -9
  33. data/rakelib/check_docs.rake +30 -5
  34. data/ruby-lsp.gemspec +6 -5
  35. data/sorbet/tapioca/require.rb +1 -1
  36. metadata +14 -26
  37. data/lib/ruby_lsp/cli.rb +0 -88
  38. data/lib/ruby_lsp/requests/rubocop_request.rb +0 -50
  39. data/shipit.production.yml +0 -1
@@ -1,8 +1,12 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/rubocop_diagnostics_runner"
5
+
4
6
  module RubyLsp
5
7
  module Requests
8
+ # ![Diagnostics demo](../../misc/diagnostics.gif)
9
+ #
6
10
  # The
7
11
  # [diagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
8
12
  # request informs the editor of RuboCop offenses for a given file.
@@ -14,21 +18,34 @@ module RubyLsp
14
18
  # puts "Hello" # --> diagnostics: incorrect indentantion
15
19
  # end
16
20
  # ```
17
- class Diagnostics < RuboCopRequest
18
- def run
19
- return syntax_error_diagnostics if @document.syntax_errors?
21
+ class Diagnostics < BaseRequest
22
+ extend T::Sig
20
23
 
21
- super
24
+ sig { params(uri: String, document: Document).void }
25
+ def initialize(uri, document)
26
+ super(document)
22
27
 
23
- @diagnostics
28
+ @uri = uri
24
29
  end
25
30
 
26
- def file_finished(_file, offenses)
27
- @diagnostics = offenses.map { |offense| Support::RuboCopDiagnostic.new(offense, @uri) }
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
39
+ def run
40
+ return syntax_error_diagnostics if @document.syntax_errors?
41
+ return [] unless defined?(Support::RuboCopDiagnosticsRunner)
42
+
43
+ Support::RuboCopDiagnosticsRunner.instance.run(@uri, @document)
28
44
  end
29
45
 
30
46
  private
31
47
 
48
+ sig { returns(T::Array[Support::SyntaxErrorDiagnostic]) }
32
49
  def syntax_error_diagnostics
33
50
  @document.syntax_error_edits.map { |e| Support::SyntaxErrorDiagnostic.new(e) }
34
51
  end
@@ -1,8 +1,10 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Document highlight demo](../../misc/document_highlight.gif)
7
+ #
6
8
  # The [document highlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
7
9
  # informs the editor all relevant elements of the currently pointed item for highlighting. For example, when
8
10
  # the cursor is on the `F` of the constant `FOO`, the editor should identify other occurences of `FOO`
@@ -21,18 +23,18 @@ module RubyLsp
21
23
  # end
22
24
  # ```
23
25
  class DocumentHighlight < BaseRequest
24
- def self.run(document, position)
25
- new(document, position).run
26
- end
26
+ extend T::Sig
27
27
 
28
+ sig { params(document: Document, position: Document::PositionShape).void }
28
29
  def initialize(document, position)
29
- @highlights = []
30
+ @highlights = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentHighlight])
30
31
  position = Document::Scanner.new(document.source).find_position(position)
31
- @target = find(document.tree, position)
32
+ @target = T.let(find(T.must(document.tree), position), T.nilable(Support::HighlightTarget))
32
33
 
33
34
  super(document)
34
35
  end
35
36
 
37
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentHighlight], Object)) }
36
38
  def run
37
39
  # no @target means the target is not highlightable
38
40
  return [] unless @target
@@ -41,30 +43,24 @@ module RubyLsp
41
43
  @highlights
42
44
  end
43
45
 
44
- def visit_var_field(node)
45
- if matches_target?(node.value)
46
- add_highlight(
47
- node.value,
48
- LanguageServer::Protocol::Constant::DocumentHighlightKind::WRITE
49
- )
50
- end
51
-
52
- super
53
- end
46
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
47
+ def visit(node)
48
+ return if node.nil?
54
49
 
55
- def visit_var_ref(node)
56
- if matches_target?(node.value)
57
- add_highlight(
58
- node.value,
59
- LanguageServer::Protocol::Constant::DocumentHighlightKind::READ
60
- )
61
- end
50
+ match = T.must(@target).highlight_type(node)
51
+ add_highlight(match) if match
62
52
 
63
53
  super
64
54
  end
65
55
 
66
56
  private
67
57
 
58
+ sig do
59
+ params(
60
+ node: SyntaxTree::Node,
61
+ position: Integer,
62
+ ).returns(T.nilable(Support::HighlightTarget))
63
+ end
68
64
  def find(node, position)
69
65
  matched =
70
66
  node.child_nodes.compact.bsearch do |child|
@@ -76,20 +72,24 @@ module RubyLsp
76
72
  end
77
73
 
78
74
  case matched
79
- when SyntaxTree::GVar, SyntaxTree::Ident, SyntaxTree::IVar, SyntaxTree::Const, SyntaxTree::CVar
80
- matched
75
+ when SyntaxTree::GVar,
76
+ SyntaxTree::IVar,
77
+ SyntaxTree::Const,
78
+ SyntaxTree::CVar,
79
+ SyntaxTree::VarField
80
+ Support::HighlightTarget.new(matched)
81
+ when SyntaxTree::Ident
82
+ relevant_node = node.is_a?(SyntaxTree::Params) ? matched : node
83
+ Support::HighlightTarget.new(relevant_node)
81
84
  when SyntaxTree::Node
82
85
  find(matched, position)
83
86
  end
84
87
  end
85
88
 
86
- def matches_target?(node)
87
- node.is_a?(@target.class) && node.value == @target.value
88
- end
89
-
90
- def add_highlight(node, kind)
91
- range = range_from_syntax_tree_node(node)
92
- @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: kind)
89
+ sig { params(match: Support::HighlightTarget::HighlightMatch).void }
90
+ def add_highlight(match)
91
+ range = range_from_syntax_tree_node(match.node)
92
+ @highlights << LanguageServer::Protocol::Interface::DocumentHighlight.new(range: range, kind: match.type)
93
93
  end
94
94
  end
95
95
  end
@@ -1,8 +1,10 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Document symbol demo](../../misc/document_symbol.gif)
7
+ #
6
8
  # The [document
7
9
  # symbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) request
8
10
  # informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
@@ -25,7 +27,9 @@ module RubyLsp
25
27
  # end
26
28
  # ```
27
29
  class DocumentSymbol < BaseRequest
28
- SYMBOL_KIND = {
30
+ extend T::Sig
31
+
32
+ SYMBOL_KIND = T.let({
29
33
  file: 1,
30
34
  module: 2,
31
35
  namespace: 3,
@@ -52,33 +56,43 @@ module RubyLsp
52
56
  event: 24,
53
57
  operator: 25,
54
58
  typeparameter: 26,
55
- }.freeze
59
+ }.freeze, T::Hash[Symbol, Integer])
56
60
 
57
- ATTR_ACCESSORS = ["attr_reader", "attr_writer", "attr_accessor"].freeze
61
+ ATTR_ACCESSORS = T.let(["attr_reader", "attr_writer", "attr_accessor"].freeze, T::Array[String])
58
62
 
59
63
  class SymbolHierarchyRoot
64
+ extend T::Sig
65
+
66
+ sig { returns(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol]) }
60
67
  attr_reader :children
61
68
 
69
+ sig { void }
62
70
  def initialize
63
- @children = []
71
+ @children = T.let([], T::Array[LanguageServer::Protocol::Interface::DocumentSymbol])
64
72
  end
65
73
  end
66
74
 
75
+ sig { params(document: Document).void }
67
76
  def initialize(document)
68
77
  super
69
78
 
70
- @root = SymbolHierarchyRoot.new
71
- @stack = [@root]
79
+ @root = T.let(SymbolHierarchyRoot.new, SymbolHierarchyRoot)
80
+ @stack = T.let(
81
+ [@root],
82
+ T::Array[T.any(SymbolHierarchyRoot, LanguageServer::Protocol::Interface::DocumentSymbol)]
83
+ )
72
84
  end
73
85
 
86
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::DocumentSymbol], Object)) }
74
87
  def run
75
88
  visit(@document.tree)
76
89
  @root.children
77
90
  end
78
91
 
92
+ sig { params(node: SyntaxTree::ClassDeclaration).void }
79
93
  def visit_class(node)
80
94
  symbol = create_document_symbol(
81
- name: node.constant.constant.value,
95
+ name: fully_qualified_name(node),
82
96
  kind: :class,
83
97
  range_node: node,
84
98
  selection_range_node: node.constant
@@ -89,6 +103,7 @@ module RubyLsp
89
103
  @stack.pop
90
104
  end
91
105
 
106
+ sig { params(node: SyntaxTree::Command).void }
92
107
  def visit_command(node)
93
108
  return unless ATTR_ACCESSORS.include?(node.message.value)
94
109
 
@@ -104,6 +119,7 @@ module RubyLsp
104
119
  end
105
120
  end
106
121
 
122
+ sig { params(node: SyntaxTree::ConstPathField).void }
107
123
  def visit_const_path_field(node)
108
124
  create_document_symbol(
109
125
  name: node.constant.value,
@@ -113,6 +129,7 @@ module RubyLsp
113
129
  )
114
130
  end
115
131
 
132
+ sig { params(node: SyntaxTree::Def).void }
116
133
  def visit_def(node)
117
134
  name = node.name.value
118
135
 
@@ -128,6 +145,7 @@ module RubyLsp
128
145
  @stack.pop
129
146
  end
130
147
 
148
+ sig { params(node: SyntaxTree::DefEndless).void }
131
149
  def visit_def_endless(node)
132
150
  name = node.name.value
133
151
 
@@ -143,6 +161,7 @@ module RubyLsp
143
161
  @stack.pop
144
162
  end
145
163
 
164
+ sig { params(node: SyntaxTree::Defs).void }
146
165
  def visit_defs(node)
147
166
  symbol = create_document_symbol(
148
167
  name: "self.#{node.name.value}",
@@ -156,9 +175,10 @@ module RubyLsp
156
175
  @stack.pop
157
176
  end
158
177
 
178
+ sig { params(node: SyntaxTree::ModuleDeclaration).void }
159
179
  def visit_module(node)
160
180
  symbol = create_document_symbol(
161
- name: node.constant.constant.value,
181
+ name: fully_qualified_name(node),
162
182
  kind: :module,
163
183
  range_node: node,
164
184
  selection_range_node: node.constant
@@ -169,6 +189,7 @@ module RubyLsp
169
189
  @stack.pop
170
190
  end
171
191
 
192
+ sig { params(node: SyntaxTree::TopConstField).void }
172
193
  def visit_top_const_field(node)
173
194
  create_document_symbol(
174
195
  name: node.constant.value,
@@ -178,6 +199,7 @@ module RubyLsp
178
199
  )
179
200
  end
180
201
 
202
+ sig { params(node: SyntaxTree::VarField).void }
181
203
  def visit_var_field(node)
182
204
  kind = case node.value
183
205
  when SyntaxTree::Const
@@ -198,6 +220,14 @@ module RubyLsp
198
220
 
199
221
  private
200
222
 
223
+ sig do
224
+ params(
225
+ name: String,
226
+ kind: Symbol,
227
+ range_node: SyntaxTree::Node,
228
+ selection_range_node: SyntaxTree::Node
229
+ ).returns(LanguageServer::Protocol::Interface::DocumentSymbol)
230
+ end
201
231
  def create_document_symbol(name:, kind:, range_node:, selection_range_node:)
202
232
  symbol = LanguageServer::Protocol::Interface::DocumentSymbol.new(
203
233
  name: name,
@@ -207,10 +237,29 @@ module RubyLsp
207
237
  children: [],
208
238
  )
209
239
 
210
- @stack.last.children << symbol
240
+ T.must(@stack.last).children << symbol
211
241
 
212
242
  symbol
213
243
  end
244
+
245
+ sig { params(node: T.any(SyntaxTree::ClassDeclaration, SyntaxTree::ModuleDeclaration)).returns(String) }
246
+ def fully_qualified_name(node)
247
+ constant = T.let(node.constant, SyntaxTree::Node)
248
+ name = +node.constant.constant.value
249
+
250
+ while constant.is_a?(SyntaxTree::ConstPathRef)
251
+ constant = constant.parent
252
+
253
+ case constant
254
+ when SyntaxTree::ConstPathRef
255
+ name.prepend("#{constant.constant.value}::")
256
+ when SyntaxTree::VarRef
257
+ name.prepend("#{constant.value.value}::")
258
+ end
259
+ end
260
+
261
+ name
262
+ end
214
263
  end
215
264
  end
216
265
  end
@@ -1,19 +1,24 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
+ # ![Folding ranges demo](../../misc/folding_ranges.gif)
7
+ #
6
8
  # The [folding ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange)
7
- # request informs the editor of the ranges where code can be folded.
9
+ # request informs the editor of the ranges where and how code can be folded.
8
10
  #
9
11
  # # Example
12
+ #
10
13
  # ```ruby
11
14
  # def say_hello # <-- folding range start
12
15
  # puts "Hello"
13
16
  # end # <-- folding range end
14
17
  # ```
15
18
  class FoldingRanges < BaseRequest
16
- SIMPLE_FOLDABLES = [
19
+ extend T::Sig
20
+
21
+ SIMPLE_FOLDABLES = T.let([
17
22
  SyntaxTree::ArrayLiteral,
18
23
  SyntaxTree::BraceBlock,
19
24
  SyntaxTree::Case,
@@ -30,24 +35,36 @@ module RubyLsp
30
35
  SyntaxTree::Unless,
31
36
  SyntaxTree::Until,
32
37
  SyntaxTree::While,
33
- ].freeze
34
-
35
- NODES_WITH_STATEMENTS = [
36
38
  SyntaxTree::Else,
37
- SyntaxTree::Elsif,
38
39
  SyntaxTree::Ensure,
40
+ SyntaxTree::Begin,
41
+ ].freeze, T::Array[T.class_of(SyntaxTree::Node)])
42
+
43
+ NODES_WITH_STATEMENTS = T.let([
44
+ SyntaxTree::Elsif,
39
45
  SyntaxTree::In,
40
46
  SyntaxTree::Rescue,
41
47
  SyntaxTree::When,
42
- ].freeze
48
+ ].freeze, T::Array[T.class_of(SyntaxTree::Node)])
49
+
50
+ StatementNode = T.type_alias do
51
+ T.any(
52
+ SyntaxTree::Elsif,
53
+ SyntaxTree::In,
54
+ SyntaxTree::Rescue,
55
+ SyntaxTree::When,
56
+ )
57
+ end
43
58
 
59
+ sig { params(document: Document).void }
44
60
  def initialize(document)
45
61
  super
46
62
 
47
- @ranges = []
48
- @partial_range = nil
63
+ @ranges = T.let([], T::Array[LanguageServer::Protocol::Interface::FoldingRange])
64
+ @partial_range = T.let(nil, T.nilable(PartialRange))
49
65
  end
50
66
 
67
+ sig { override.returns(T.all(T::Array[LanguageServer::Protocol::Interface::FoldingRange], Object)) }
51
68
  def run
52
69
  visit(@document.tree)
53
70
  emit_partial_range
@@ -56,16 +73,16 @@ module RubyLsp
56
73
 
57
74
  private
58
75
 
76
+ sig { params(node: T.nilable(SyntaxTree::Node)).void }
59
77
  def visit(node)
60
78
  return unless handle_partial_range(node)
61
79
 
62
80
  case node
63
81
  when *SIMPLE_FOLDABLES
64
- add_node_range(node)
82
+ location = T.must(node).location
83
+ add_lines_range(location.start_line, location.end_line - 1)
65
84
  when *NODES_WITH_STATEMENTS
66
- add_statements_range(node, node.statements)
67
- when SyntaxTree::Begin
68
- add_statements_range(node, node.bodystmt.statements)
85
+ add_statements_range(T.must(node), T.cast(node, StatementNode).statements)
69
86
  when SyntaxTree::Call, SyntaxTree::CommandCall
70
87
  add_call_range(node)
71
88
  return
@@ -80,27 +97,38 @@ module RubyLsp
80
97
  end
81
98
 
82
99
  class PartialRange
83
- attr_reader :kind, :end_line
100
+ extend T::Sig
84
101
 
102
+ sig { returns(String) }
103
+ attr_reader :kind
104
+
105
+ sig { returns(Integer) }
106
+ attr_reader :end_line
107
+
108
+ sig { params(node: SyntaxTree::Node, kind: String).returns(PartialRange) }
85
109
  def self.from(node, kind)
86
110
  new(node.location.start_line - 1, node.location.end_line - 1, kind)
87
111
  end
88
112
 
113
+ sig { params(start_line: Integer, end_line: Integer, kind: String).void }
89
114
  def initialize(start_line, end_line, kind)
90
115
  @start_line = start_line
91
116
  @end_line = end_line
92
117
  @kind = kind
93
118
  end
94
119
 
120
+ sig { params(node: SyntaxTree::Node).returns(PartialRange) }
95
121
  def extend_to(node)
96
122
  @end_line = node.location.end_line - 1
97
123
  self
98
124
  end
99
125
 
126
+ sig { params(node: SyntaxTree::Node).returns(T::Boolean) }
100
127
  def new_section?(node)
101
128
  node.is_a?(SyntaxTree::Comment) && @end_line + 1 != node.location.start_line - 1
102
129
  end
103
130
 
131
+ sig { returns(LanguageServer::Protocol::Interface::FoldingRange) }
104
132
  def to_range
105
133
  LanguageServer::Protocol::Interface::FoldingRange.new(
106
134
  start_line: @start_line,
@@ -108,8 +136,14 @@ module RubyLsp
108
136
  kind: @kind
109
137
  )
110
138
  end
139
+
140
+ sig { returns(T::Boolean) }
141
+ def multiline?
142
+ @end_line > @start_line
143
+ end
111
144
  end
112
145
 
146
+ sig { params(node: T.nilable(SyntaxTree::Node)).returns(T::Boolean) }
113
147
  def handle_partial_range(node)
114
148
  kind = partial_range_kind(node)
115
149
 
@@ -118,18 +152,20 @@ module RubyLsp
118
152
  return true
119
153
  end
120
154
 
155
+ target_node = T.must(node)
121
156
  @partial_range = if @partial_range.nil?
122
- PartialRange.from(node, kind)
123
- elsif @partial_range.kind != kind || @partial_range.new_section?(node)
157
+ PartialRange.from(target_node, kind)
158
+ elsif @partial_range.kind != kind || @partial_range.new_section?(target_node)
124
159
  emit_partial_range
125
- PartialRange.from(node, kind)
160
+ PartialRange.from(target_node, kind)
126
161
  else
127
- @partial_range.extend_to(node)
162
+ @partial_range.extend_to(target_node)
128
163
  end
129
164
 
130
165
  false
131
166
  end
132
167
 
168
+ sig { params(node: T.nilable(SyntaxTree::Node)).returns(T.nilable(String)) }
133
169
  def partial_range_kind(node)
134
170
  case node
135
171
  when SyntaxTree::Comment
@@ -141,13 +177,15 @@ module RubyLsp
141
177
  end
142
178
  end
143
179
 
180
+ sig { void }
144
181
  def emit_partial_range
145
182
  return if @partial_range.nil?
146
183
 
147
- @ranges << @partial_range.to_range
184
+ @ranges << @partial_range.to_range if @partial_range.multiline?
148
185
  @partial_range = nil
149
186
  end
150
187
 
188
+ sig { params(node: T.any(SyntaxTree::Call, SyntaxTree::CommandCall)).void }
151
189
  def add_call_range(node)
152
190
  receiver = T.let(node.receiver, SyntaxTree::Node)
153
191
  loop do
@@ -157,48 +195,49 @@ module RubyLsp
157
195
  receiver = receiver.receiver
158
196
  when SyntaxTree::MethodAddBlock
159
197
  visit(receiver.block)
160
- receiver = receiver.call.receiver
198
+ receiver = receiver.call
199
+
200
+ if receiver.is_a?(SyntaxTree::Call) || receiver.is_a?(SyntaxTree::CommandCall)
201
+ receiver = receiver.receiver
202
+ end
161
203
  else
162
204
  break
163
205
  end
164
206
  end
165
207
 
166
- add_lines_range(receiver.location.start_line, node.location.end_line)
208
+ add_lines_range(receiver.location.start_line, node.location.end_line - 1)
167
209
 
168
210
  visit(node.arguments)
169
211
  end
170
212
 
213
+ sig { params(node: T.any(SyntaxTree::Def, SyntaxTree::Defs)).void }
171
214
  def add_def_range(node)
172
215
  params_location = node.params.location
173
216
 
174
217
  if params_location.start_line < params_location.end_line
175
- add_lines_range(params_location.end_line, node.location.end_line)
218
+ add_lines_range(params_location.end_line, node.location.end_line - 1)
176
219
  else
177
- add_node_range(node)
220
+ location = node.location
221
+ add_lines_range(location.start_line, location.end_line - 1)
178
222
  end
179
223
 
180
224
  visit(node.bodystmt.statements)
181
225
  end
182
226
 
227
+ sig { params(node: SyntaxTree::Node, statements: SyntaxTree::Statements).void }
183
228
  def add_statements_range(node, statements)
184
- add_lines_range(node.location.start_line, statements.location.end_line) unless statements.empty?
229
+ add_lines_range(node.location.start_line, statements.body.last.location.end_line) unless statements.empty?
185
230
  end
186
231
 
232
+ sig { params(node: SyntaxTree::StringConcat).void }
187
233
  def add_string_concat(node)
188
234
  left = T.let(node.left, SyntaxTree::Node)
189
235
  left = left.left while left.is_a?(SyntaxTree::StringConcat)
190
236
 
191
- add_lines_range(left.location.start_line, node.right.location.end_line)
192
- end
193
-
194
- def add_node_range(node)
195
- add_location_range(node.location)
196
- end
197
-
198
- def add_location_range(location)
199
- add_lines_range(location.start_line, location.end_line)
237
+ add_lines_range(left.location.start_line, node.right.location.end_line - 1)
200
238
  end
201
239
 
240
+ sig { params(start_line: Integer, end_line: Integer).void }
202
241
  def add_lines_range(start_line, end_line)
203
242
  return if start_line >= end_line
204
243
 
@@ -1,8 +1,12 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "ruby_lsp/requests/support/rubocop_formatting_runner"
5
+
4
6
  module RubyLsp
5
7
  module Requests
8
+ # ![Formatting symbol demo](../../misc/formatting.gif)
9
+ #
6
10
  # The [formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting)
7
11
  # request uses RuboCop to fix auto-correctable offenses in the document. This requires enabling format on save and
8
12
  # registering the ruby-lsp as the Ruby formatter.
@@ -14,38 +18,44 @@ module RubyLsp
14
18
  # puts "Hello" # --> formatting: fixes the indentation on save
15
19
  # end
16
20
  # ```
17
- class Formatting < RuboCopRequest
18
- RUBOCOP_FLAGS = (COMMON_RUBOCOP_FLAGS + ["--autocorrect"]).freeze
21
+ class Formatting < BaseRequest
22
+ extend T::Sig
19
23
 
24
+ sig { params(uri: String, document: Document).void }
20
25
  def initialize(uri, document)
21
- super
22
- @formatted_text = nil
26
+ super(document)
27
+
28
+ @uri = uri
23
29
  end
24
30
 
31
+ sig { override.returns(T.nilable(T.all(T::Array[LanguageServer::Protocol::Interface::TextEdit], Object))) }
25
32
  def run
26
- super
33
+ formatted_text = formatted_file
34
+ return unless formatted_text
27
35
 
28
- @formatted_text = @options[:stdin] # Rubocop applies the corrections on stdin
29
- return unless @formatted_text
36
+ size = @document.source.size
37
+ return if formatted_text.size == size && formatted_text == @document.source
30
38
 
31
39
  [
32
40
  LanguageServer::Protocol::Interface::TextEdit.new(
33
41
  range: LanguageServer::Protocol::Interface::Range.new(
34
42
  start: LanguageServer::Protocol::Interface::Position.new(line: 0, character: 0),
35
- end: LanguageServer::Protocol::Interface::Position.new(
36
- line: text.size,
37
- character: text.size
38
- )
43
+ end: LanguageServer::Protocol::Interface::Position.new(line: size, character: size)
39
44
  ),
40
- new_text: @formatted_text
45
+ new_text: formatted_text
41
46
  ),
42
47
  ]
43
48
  end
44
49
 
45
50
  private
46
51
 
47
- def rubocop_flags
48
- RUBOCOP_FLAGS
52
+ sig { returns(T.nilable(String)) }
53
+ def formatted_file
54
+ if defined?(Support::RuboCopFormattingRunner)
55
+ Support::RuboCopFormattingRunner.instance.run(@uri, @document)
56
+ else
57
+ SyntaxTree.format(@document.source)
58
+ end
49
59
  end
50
60
  end
51
61
  end