rucoa 0.3.0 → 0.4.0

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +2 -2
  4. data/lib/rucoa/configuration.rb +8 -0
  5. data/lib/rucoa/handler_concerns/diagnostics_publishable.rb +140 -0
  6. data/lib/rucoa/handlers/initialize_handler.rb +1 -0
  7. data/lib/rucoa/handlers/text_document_code_action_handler.rb +78 -3
  8. data/lib/rucoa/handlers/text_document_document_symbol_handler.rb +241 -0
  9. data/lib/rucoa/handlers/text_document_formatting_handler.rb +36 -5
  10. data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +65 -13
  11. data/lib/rucoa/handlers/text_document_selection_range_handler.rb +124 -8
  12. data/lib/rucoa/handlers.rb +1 -0
  13. data/lib/rucoa/node_concerns/name_full_qualifiable.rb +20 -0
  14. data/lib/rucoa/node_concerns.rb +7 -0
  15. data/lib/rucoa/nodes/base.rb +13 -0
  16. data/lib/rucoa/nodes/casgn_node.rb +14 -0
  17. data/lib/rucoa/nodes/class_node.rb +8 -0
  18. data/lib/rucoa/nodes/const_node.rb +12 -0
  19. data/lib/rucoa/nodes/def_node.rb +12 -0
  20. data/lib/rucoa/nodes/defs_node.rb +12 -0
  21. data/lib/rucoa/nodes/module_node.rb +21 -0
  22. data/lib/rucoa/nodes/sclass_node.rb +8 -0
  23. data/lib/rucoa/nodes/send_node.rb +17 -0
  24. data/lib/rucoa/nodes/str_node.rb +4 -0
  25. data/lib/rucoa/nodes/sym_node.rb +12 -0
  26. data/lib/rucoa/nodes.rb +9 -0
  27. data/lib/rucoa/parser_builder.rb +19 -10
  28. data/lib/rucoa/server.rb +1 -0
  29. data/lib/rucoa/source.rb +2 -2
  30. data/lib/rucoa/version.rb +1 -1
  31. data/lib/rucoa.rb +1 -5
  32. metadata +14 -7
  33. data/lib/rucoa/code_action_provider.rb +0 -102
  34. data/lib/rucoa/diagnostic_provider.rb +0 -143
  35. data/lib/rucoa/formatting_provider.rb +0 -54
  36. data/lib/rucoa/range_formatting_provider.rb +0 -67
  37. data/lib/rucoa/selection_range_provider.rb +0 -99
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7206c3901e7ceea282b2de34f31ab90cc5ffa7fa48c06e58ae64bb718f48cd52
4
- data.tar.gz: 6188a2047317b98160565599de3dc08663e732d7b54a546a8bc3e610379cb39a
3
+ metadata.gz: 1656b3603d72a44829a30fd9bd0d0d876ab9daa3af05cad16ad78298fa6fce7d
4
+ data.tar.gz: ff2fffdb7e90ed0f23cf782bc5c25db77dabb327c3e5b1d0a92aad8d2a5dc201
5
5
  SHA512:
6
- metadata.gz: afa9c37dae4a5c460eb1f5505ca511138c20f5b8c483a5ba68f798d1f9b18a5f5c4a481ee3ec743eb8585328538b5daec613b9f69a9599e5337106c57b1c0ea8
7
- data.tar.gz: 539517254cff781a99b6ec463aed13bd04cbbcf3da3a9ff15cf72dac809b12c53ef9ff53c51828a163975bbdbe60a174767a472d2b4476f03f1813b5f513c62d
6
+ metadata.gz: 49421faba6f35b3cf0bd33c12ae521cf55125fc8c8b044f5a4d353b4ee9413150b0ca4082884e786329b1cefa3cb5eb5ab513b9723e638cb9786ab768d2f8dd0
7
+ data.tar.gz: 8ef7dfaf46a49d70eb9404558191aadb3e1778f761a4642c432b53c9e3b1f208ff57f64a5b43ec7aef527dc4b573baaed10f4d859043574e86452b0af60aecb4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rucoa (0.3.0)
4
+ rucoa (0.4.0)
5
5
  parser
6
6
  rubocop
7
7
 
data/README.md CHANGED
@@ -6,7 +6,7 @@ Language server for Ruby.
6
6
 
7
7
  ## Usage
8
8
 
9
- Install both rucoa gem and [vscode-rucoa](https://github.com/r7kamura/vscode-rucoa) extension, then open your Ruby project in VSCode.
9
+ Install both rucoa gem and [vscode-rucoa](https://marketplace.visualstudio.com/items?itemName=r7kamura.vscode-rucoa) extension, then open your Ruby project in VSCode.
10
10
 
11
11
  To install rucoa gem, if your project is managed by bundler, add to your Gemfile:
12
12
 
@@ -36,6 +36,7 @@ gem install rucoa
36
36
  - Formatting
37
37
  - Quick Fix
38
38
  - Selection Range
39
+ - Symbol
39
40
 
40
41
  ### Coming soon
41
42
 
@@ -44,4 +45,3 @@ gem install rucoa
44
45
  - Go to Definition
45
46
  - Highlight
46
47
  - Semantic Tokens
47
- - Symbol
@@ -14,6 +14,9 @@ module Rucoa
14
14
  'diagnostics' => {
15
15
  'enable' => true
16
16
  },
17
+ 'documentSymbol' => {
18
+ 'enable' => true
19
+ },
17
20
  'formatting' => {
18
21
  'enable' => true
19
22
  },
@@ -37,6 +40,11 @@ module Rucoa
37
40
  @settings.dig('feature', 'diagnostics', 'enable')
38
41
  end
39
42
 
43
+ # @return [Boolean]
44
+ def enables_document_symbol?
45
+ @settings.dig('feature', 'documentSymbol', 'enable')
46
+ end
47
+
40
48
  # @return [Boolean]
41
49
  def enables_formatting?
42
50
  @settings.dig('feature', 'formatting', 'enable')
@@ -27,6 +27,146 @@ module Rucoa
27
27
  }
28
28
  )
29
29
  end
30
+
31
+ class DiagnosticProvider
32
+ # @param source [Rucoa::Source]
33
+ # @param uri [String]
34
+ # @return [Array<Hash>]
35
+ def self.call(source:, uri:)
36
+ new(
37
+ source: source,
38
+ uri: uri
39
+ ).call
40
+ end
41
+
42
+ # @param source [Rucoa::Source]
43
+ # @param uri [String]
44
+ def initialize(source:, uri:)
45
+ @source = source
46
+ @uri = uri
47
+ end
48
+
49
+ # @return [Array<Hash>]
50
+ def call
51
+ return [] unless RubocopConfigurationChecker.call
52
+
53
+ offenses.map do |offense|
54
+ OffenseToDiagnosticMapper.call(
55
+ offense,
56
+ source: @source,
57
+ uri: @uri
58
+ )
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # @return [Array<RuboCop::Cop::Offense>]
65
+ def offenses
66
+ RubocopInvestigator.call(source: @source)
67
+ end
68
+
69
+ class OffenseToDiagnosticMapper
70
+ LSP_SEVERITY_NAME_TO_VALUE_MAP = {
71
+ error: 1,
72
+ hint: 4,
73
+ information: 3,
74
+ warning: 2
75
+ }.freeze
76
+ private_constant :LSP_SEVERITY_NAME_TO_VALUE_MAP
77
+
78
+ RUBOCOP_SEVERITY_NAME_TO_LSP_SEVERITY_MAP = {
79
+ convention: LSP_SEVERITY_NAME_TO_VALUE_MAP[:information],
80
+ error: LSP_SEVERITY_NAME_TO_VALUE_MAP[:error],
81
+ fatal: LSP_SEVERITY_NAME_TO_VALUE_MAP[:error],
82
+ info: LSP_SEVERITY_NAME_TO_VALUE_MAP[:information],
83
+ refactor: LSP_SEVERITY_NAME_TO_VALUE_MAP[:hint],
84
+ warning: LSP_SEVERITY_NAME_TO_VALUE_MAP[:warning]
85
+ }.freeze
86
+ private_constant :RUBOCOP_SEVERITY_NAME_TO_LSP_SEVERITY_MAP
87
+
88
+ class << self
89
+ # @param offense [RuboCop::Cop::Offense]
90
+ # @param source [Rucoa::Source]
91
+ # @param uri [String]
92
+ # @return [Hash]
93
+ def call(offense, source:, uri:)
94
+ new(
95
+ offense,
96
+ source: source,
97
+ uri: uri
98
+ ).call
99
+ end
100
+ end
101
+
102
+ # @param offense [RuboCop::Cop::Offense]
103
+ # @param source [Rucoa::Source]
104
+ # @param uri [String]
105
+ def initialize(offense, source:, uri:)
106
+ @offense = offense
107
+ @source = source
108
+ @uri = uri
109
+ end
110
+
111
+ # @return [Hash]
112
+ def call
113
+ {
114
+ code: code,
115
+ data: data,
116
+ message: message,
117
+ range: range,
118
+ severity: severity,
119
+ source: source
120
+ }
121
+ end
122
+
123
+ private
124
+
125
+ # @return [String]
126
+ def code
127
+ @offense.cop_name
128
+ end
129
+
130
+ # @return [Hash]
131
+ def data
132
+ {
133
+ cop_name: @offense.cop_name,
134
+ edits: edits,
135
+ uri: @uri
136
+ }
137
+ end
138
+
139
+ # @return [Array<Hash>, nil]
140
+ def edits
141
+ @offense.corrector&.as_replacements&.map do |range, replacement|
142
+ {
143
+ newText: replacement,
144
+ range: Range.from_parser_range(range).to_vscode_range
145
+ }
146
+ end
147
+ end
148
+
149
+ # @return [String]
150
+ def message
151
+ @offense.message.delete_prefix("#{@offense.cop_name}: ")
152
+ end
153
+
154
+ # @return [Hash]
155
+ def range
156
+ Range.from_parser_range(@offense.location).to_vscode_range
157
+ end
158
+
159
+ # @return [Integer]
160
+ def severity
161
+ RUBOCOP_SEVERITY_NAME_TO_LSP_SEVERITY_MAP.fetch(@offense.severity.name, 1)
162
+ end
163
+
164
+ # @return [String]
165
+ def source
166
+ 'RuboCop'
167
+ end
168
+ end
169
+ end
30
170
  end
31
171
  end
32
172
  end
@@ -9,6 +9,7 @@ module Rucoa
9
9
  codeActionProvider: true,
10
10
  documentFormattingProvider: true,
11
11
  documentRangeFormattingProvider: true,
12
+ documentSymbolProvider: true,
12
13
  selectionRangeProvider: true,
13
14
  textDocumentSync: {
14
15
  change: 1, # Full
@@ -15,15 +15,90 @@ module Rucoa
15
15
  def code_actions
16
16
  return [] unless configuration.enables_code_action?
17
17
 
18
- CodeActionProvider.call(
19
- diagnostics: diagnostics
20
- )
18
+ correctable_diagnostics.map do |diagnostic|
19
+ DiagnosticToCodeActionMapper.call(diagnostic)
20
+ end
21
+ end
22
+
23
+ # @return [Array<Hash>]
24
+ def correctable_diagnostics
25
+ diagnostics.select do |diagnostic|
26
+ diagnostic.dig('data', 'edits')
27
+ end
21
28
  end
22
29
 
23
30
  # @return [Array<Hash>, nil]
24
31
  def diagnostics
25
32
  @diagnostics ||= request.dig('params', 'context', 'diagnostics')
26
33
  end
34
+
35
+ class DiagnosticToCodeActionMapper
36
+ class << self
37
+ # @param diagnostic [Hash]
38
+ # @return [Hash]
39
+ def call(diagnostic)
40
+ new(diagnostic).call
41
+ end
42
+ end
43
+
44
+ # @param diagnostic [Hash]
45
+ def initialize(diagnostic)
46
+ @diagnostic = diagnostic
47
+ end
48
+
49
+ # @return [Hash]
50
+ def call
51
+ {
52
+ diagnostics: diagnostics,
53
+ edit: edit,
54
+ isPreferred: preferred?,
55
+ kind: kind,
56
+ title: title
57
+ }
58
+ end
59
+
60
+ private
61
+
62
+ # @return [Hash]
63
+ def edit
64
+ {
65
+ documentChanges: [
66
+ {
67
+ edits: @diagnostic.dig('data', 'edits'),
68
+ textDocument: {
69
+ uri: @diagnostic.dig('data', 'uri'),
70
+ version: nil
71
+ }
72
+ }
73
+ ]
74
+ }
75
+ end
76
+
77
+ # @return [String]
78
+ def cop_name
79
+ @diagnostic.dig('data', 'cop_name')
80
+ end
81
+
82
+ # @return [Array]
83
+ def diagnostics
84
+ [@diagnostic]
85
+ end
86
+
87
+ # @return [Boolean]
88
+ def preferred?
89
+ true
90
+ end
91
+
92
+ # @return [String]
93
+ def kind
94
+ 'quickfix'
95
+ end
96
+
97
+ # @return [String]
98
+ def title
99
+ "Autocorrect #{cop_name}"
100
+ end
101
+ end
27
102
  end
28
103
  end
29
104
  end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Rucoa
6
+ module Handlers
7
+ class TextDocumentDocumentSymbolHandler < Base
8
+ DOCUMENT_SYMBOL_KIND_FOR_FILE = 1
9
+ DOCUMENT_SYMBOL_KIND_FOR_MODULE = 2
10
+ DOCUMENT_SYMBOL_KIND_FOR_NAMESPACE = 3
11
+ DOCUMENT_SYMBOL_KIND_FOR_PACKAGE = 4
12
+ DOCUMENT_SYMBOL_KIND_FOR_CLASS = 5
13
+ DOCUMENT_SYMBOL_KIND_FOR_METHOD = 6
14
+ DOCUMENT_SYMBOL_KIND_FOR_PROPERTY = 7
15
+ DOCUMENT_SYMBOL_KIND_FOR_FIELD = 8
16
+ DOCUMENT_SYMBOL_KIND_FOR_CONSTRUCTOR = 9
17
+ DOCUMENT_SYMBOL_KIND_FOR_ENUM = 10
18
+ DOCUMENT_SYMBOL_KIND_FOR_INTERFACE = 11
19
+ DOCUMENT_SYMBOL_KIND_FOR_FUNCTION = 12
20
+ DOCUMENT_SYMBOL_KIND_FOR_VARIABLE = 13
21
+ DOCUMENT_SYMBOL_KIND_FOR_CONSTANT = 14
22
+ DOCUMENT_SYMBOL_KIND_FOR_STRING = 15
23
+ DOCUMENT_SYMBOL_KIND_FOR_NUMBER = 16
24
+ DOCUMENT_SYMBOL_KIND_FOR_BOOLEAN = 17
25
+ DOCUMENT_SYMBOL_KIND_FOR_ARRAY = 18
26
+ DOCUMENT_SYMBOL_KIND_FOR_OBJECT = 19
27
+ DOCUMENT_SYMBOL_KIND_FOR_KEY = 20
28
+ DOCUMENT_SYMBOL_KIND_FOR_NULL = 21
29
+ DOCUMENT_SYMBOL_KIND_FOR_ENUMMEMBER = 22
30
+ DOCUMENT_SYMBOL_KIND_FOR_STRUCT = 23
31
+ DOCUMENT_SYMBOL_KIND_FOR_EVENT = 24
32
+ DOCUMENT_SYMBOL_KIND_FOR_OPERATOR = 25
33
+ DOCUMENT_SYMBOL_KIND_FOR_TYPEPARAMETER = 26
34
+
35
+ # @return [Set<String>]
36
+ ATTRIBUTE_METHOD_NAMES = ::Set[
37
+ 'attr_accessor',
38
+ 'attr_reader',
39
+ 'attr_writer',
40
+ ]
41
+
42
+ def call
43
+ return unless respondable?
44
+
45
+ respond(document_symbols)
46
+ end
47
+
48
+ private
49
+
50
+ # @return [Array<Hash>]
51
+ def document_symbols
52
+ visit(source.root_node)
53
+ document_symbol_stack.first[:children]
54
+ end
55
+
56
+ # @param node [Rucoa::Nodes::Base]
57
+ # @return [void]
58
+ def visit(node)
59
+ document_symbols = create_document_symbols_for(node)
60
+ document_symbol_stack.last[:children].push(*document_symbols)
61
+ with_document_symbol_stack(document_symbols.first) do
62
+ with_singleton_class_stack(node) do
63
+ node.each_child_node do |child_node|
64
+ visit(child_node)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # @param document_symbol [Hash, nil]
71
+ # @return [void]
72
+ def with_document_symbol_stack(document_symbol)
73
+ unless document_symbol
74
+ yield
75
+ return
76
+ end
77
+
78
+ document_symbol_stack.push(document_symbol)
79
+ yield
80
+ document_symbol_stack.pop
81
+ end
82
+
83
+ # @param node [Rucoa::Nodes::Base]
84
+ def with_singleton_class_stack(node)
85
+ unless node.is_a?(Rucoa::Nodes::SclassNode)
86
+ yield
87
+ return
88
+ end
89
+
90
+ singleton_class_stack.push(node)
91
+ yield
92
+ singleton_class_stack.pop
93
+ end
94
+
95
+ # @param node [Rucoa::Nodes::Base]
96
+ # @return [Array<Hash>]
97
+ def create_document_symbols_for(node)
98
+ case node
99
+ when Nodes::CasgnNode
100
+ create_document_symbols_for_casgn(node)
101
+ when Nodes::ClassNode
102
+ create_document_symbols_for_class(node)
103
+ when Nodes::DefNode
104
+ create_document_symbols_for_def(node)
105
+ when Nodes::DefsNode
106
+ create_document_symbols_for_defs(node)
107
+ when Nodes::ModuleNode
108
+ create_document_symbols_for_module(node)
109
+ when Nodes::SendNode
110
+ create_document_symbols_for_send(node)
111
+ else
112
+ []
113
+ end
114
+ end
115
+
116
+ # @param node [Rucoa::Nodes::CasgnNode]
117
+ # @return [Array<Hash>]
118
+ def create_document_symbols_for_casgn(node)
119
+ [
120
+ {
121
+ children: [],
122
+ kind: DOCUMENT_SYMBOL_KIND_FOR_CONSTANT,
123
+ name: node.name,
124
+ range: Range.from_parser_range(node.location.expression).to_vscode_range,
125
+ selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
126
+ }
127
+ ]
128
+ end
129
+
130
+ # @param node [Rucoa::Nodes::ClassNode]
131
+ # @return [Array<Hash>]
132
+ def create_document_symbols_for_class(node)
133
+ [
134
+ {
135
+ children: [],
136
+ kind: DOCUMENT_SYMBOL_KIND_FOR_CLASS,
137
+ name: node.name,
138
+ range: Range.from_parser_range(node.location.expression).to_vscode_range,
139
+ selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
140
+ }
141
+ ]
142
+ end
143
+
144
+ # @param node [Rucoa::Nodes::ModuleNode]
145
+ # @return [Array<Hash>]
146
+ def create_document_symbols_for_module(node)
147
+ [
148
+ {
149
+ children: [],
150
+ kind: DOCUMENT_SYMBOL_KIND_FOR_MODULE,
151
+ name: node.name,
152
+ range: Range.from_parser_range(node.location.expression).to_vscode_range,
153
+ selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
154
+ }
155
+ ]
156
+ end
157
+
158
+ # @param node [Rucoa::Nodes::DefNode]
159
+ # @return [Array<Hash>]
160
+ def create_document_symbols_for_def(node)
161
+ [
162
+ {
163
+ children: [],
164
+ kind: DOCUMENT_SYMBOL_KIND_FOR_METHOD,
165
+ name: singleton_class_stack.empty? ? "##{node.name}" : ".#{node.name}",
166
+ range: Range.from_parser_range(node.location.expression).to_vscode_range,
167
+ selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
168
+ }
169
+ ]
170
+ end
171
+
172
+ # @param node [Rucoa::Nodes::DefNode]
173
+ # @return [Array<Hash>]
174
+ def create_document_symbols_for_defs(node)
175
+ [
176
+ {
177
+ children: [],
178
+ kind: DOCUMENT_SYMBOL_KIND_FOR_METHOD,
179
+ name: ".#{node.name}",
180
+ range: Range.from_parser_range(node.location.expression).to_vscode_range,
181
+ selectionRange: Range.from_parser_range(node.location.name).to_vscode_range
182
+ }
183
+ ]
184
+ end
185
+
186
+ # @param node [Rucoa::Nodes::SendNode]
187
+ # @return [Array<Hash>]
188
+ def create_document_symbols_for_send(node)
189
+ return [] unless ATTRIBUTE_METHOD_NAMES.include?(node.name)
190
+
191
+ simple_types = ::Set[
192
+ :str,
193
+ :sym
194
+ ]
195
+ node.arguments.select do |argument|
196
+ simple_types.include?(argument.type)
197
+ end.map do |argument|
198
+ {
199
+ children: [],
200
+ kind: DOCUMENT_SYMBOL_KIND_FOR_FIELD,
201
+ name: argument.value.to_s,
202
+ range: Range.from_parser_range(argument.location.expression).to_vscode_range,
203
+ selectionRange: Range.from_parser_range(argument.location.expression).to_vscode_range
204
+ }
205
+ end
206
+ end
207
+
208
+ # @return [Boolean]
209
+ def respondable?
210
+ configuration.enables_document_symbol? && source
211
+ end
212
+
213
+ # @return [Rucoa::Source]
214
+ def source
215
+ @source ||= @server.source_store.get(uri)
216
+ end
217
+
218
+ # @return [String]
219
+ def uri
220
+ request.dig('params', 'textDocument', 'uri')
221
+ end
222
+
223
+ # @return [Arrah<Hash>]
224
+ def document_symbol_stack
225
+ @document_symbol_stack ||= [dummy_document_symbol]
226
+ end
227
+
228
+ # @return [Hash]
229
+ def dummy_document_symbol
230
+ {
231
+ children: []
232
+ }
233
+ end
234
+
235
+ # @return [Array<Rucoa::Nodes::SclassNode>]
236
+ def singleton_class_stack
237
+ @singleton_class_stack ||= []
238
+ end
239
+ end
240
+ end
241
+ end
@@ -9,14 +9,18 @@ module Rucoa
9
9
 
10
10
  private
11
11
 
12
+ # @return [Boolean]
13
+ def formattable?
14
+ configuration.enables_formatting? &&
15
+ source &&
16
+ RubocopConfigurationChecker.call
17
+ end
18
+
12
19
  # @return [Array<Hash>]
13
20
  def edits
14
- return unless configuration.enables_formatting?
15
- return unless source
21
+ return [] unless formattable?
16
22
 
17
- FormattingProvider.call(
18
- source: source
19
- )
23
+ [text_edit]
20
24
  end
21
25
 
22
26
  # @return [Rucoa::Source, nil]
@@ -28,6 +32,33 @@ module Rucoa
28
32
  def uri
29
33
  request.dig('params', 'textDocument', 'uri')
30
34
  end
35
+
36
+ # @return [Hash]
37
+ def text_edit
38
+ {
39
+ newText: new_text,
40
+ range: range
41
+ }
42
+ end
43
+
44
+ # @return [String]
45
+ def new_text
46
+ RubocopAutocorrector.call(source: source)
47
+ end
48
+
49
+ # @return [Hash]
50
+ def range
51
+ Range.new(
52
+ Position.new(
53
+ column: 0,
54
+ line: 1
55
+ ),
56
+ Position.new(
57
+ column: @source.content.lines.last.length,
58
+ line: @source.content.lines.count + 1
59
+ )
60
+ ).to_vscode_range
61
+ end
31
62
  end
32
63
  end
33
64
  end
@@ -4,21 +4,73 @@ module Rucoa
4
4
  module Handlers
5
5
  class TextDocumentRangeFormattingHandler < Base
6
6
  def call
7
- return unless configuration.enables_formatting?
8
-
9
- uri = request.dig('params', 'textDocument', 'uri')
10
- source = source_store.get(uri)
11
- return unless source
12
-
13
- respond(
14
- RangeFormattingProvider.call(
15
- range: Range.from_vscode_range(
16
- request.dig('params', 'range')
17
- ),
18
- source: source
19
- )
7
+ respond(edits)
8
+ end
9
+
10
+ private
11
+
12
+ # @return [Array<Hash>]
13
+ def edits
14
+ return [] unless formattable?
15
+
16
+ correctable_replacements.map do |replacement_range, replacement|
17
+ {
18
+ newText: replacement,
19
+ range: replacement_range.to_vscode_range
20
+ }
21
+ end
22
+ end
23
+
24
+ # @return [Boolean]
25
+ def formattable?
26
+ configuration.enables_formatting? &&
27
+ source &&
28
+ RubocopConfigurationChecker.call
29
+ end
30
+
31
+ # @return [Rucoa::Source]
32
+ def source
33
+ @source ||= source_store.get(uri)
34
+ end
35
+
36
+ # @return [String]
37
+ def uri
38
+ request.dig('params', 'textDocument', 'uri')
39
+ end
40
+
41
+ # @return [Rucoa::Range]
42
+ def range
43
+ @range ||= Range.from_vscode_range(
44
+ request.dig('params', 'range')
20
45
  )
21
46
  end
47
+
48
+ # @return [Array<RuboCop::Cop::Corrector>]
49
+ def correctable_offenses
50
+ offenses.select(&:corrector)
51
+ end
52
+
53
+ # @return [Array(Rucoa::Range, String)]
54
+ def correctable_replacements
55
+ replacements.select do |replacement_range, _|
56
+ range.contains?(replacement_range)
57
+ end
58
+ end
59
+
60
+ # @return [Array<RuboCop::Cop::Offense>]
61
+ def offenses
62
+ RubocopInvestigator.call(source: source)
63
+ end
64
+
65
+ # @return [Array(Rucoa::Range, String)]
66
+ def replacements
67
+ correctable_offenses.map(&:corrector).flat_map(&:as_replacements).map do |replacement_range, replacement|
68
+ [
69
+ Range.from_parser_range(replacement_range),
70
+ replacement
71
+ ]
72
+ end
73
+ end
22
74
  end
23
75
  end
24
76
  end