rucoa 0.2.0 → 0.5.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +11 -2
  5. data/README.md +39 -6
  6. data/data/definitions_ruby_3_1 +0 -0
  7. data/images/diagnostics.gif +0 -0
  8. data/images/document-formatting.gif +0 -0
  9. data/images/document-symbol.gif +0 -0
  10. data/images/selection-ranges.gif +0 -0
  11. data/lib/rucoa/cli.rb +2 -2
  12. data/lib/rucoa/configuration.rb +97 -0
  13. data/lib/rucoa/definition_archiver.rb +29 -0
  14. data/lib/rucoa/definition_builders/rbs_constant_definition_builder.rb +44 -0
  15. data/lib/rucoa/definition_builders/rbs_method_definition_builder.rb +106 -0
  16. data/lib/rucoa/definition_builders/yard_method_definition_builder.rb +191 -0
  17. data/lib/rucoa/definition_builders.rb +9 -0
  18. data/lib/rucoa/definition_store.rb +63 -0
  19. data/lib/rucoa/definitions/base.rb +12 -0
  20. data/lib/rucoa/definitions/constant_definition.rb +51 -0
  21. data/lib/rucoa/definitions/method_definition.rb +126 -0
  22. data/lib/rucoa/definitions/method_parameter_definition.rb +30 -0
  23. data/lib/rucoa/definitions.rb +9 -0
  24. data/lib/rucoa/handler_concerns/configuration_requestable.rb +35 -0
  25. data/lib/rucoa/handler_concerns/diagnostics_publishable.rb +172 -0
  26. data/lib/rucoa/handler_concerns.rb +8 -0
  27. data/lib/rucoa/handlers/base.rb +69 -0
  28. data/lib/rucoa/handlers/exit_handler.rb +11 -0
  29. data/lib/rucoa/handlers/initialize_handler.rb +29 -0
  30. data/lib/rucoa/handlers/initialized_handler.rb +23 -0
  31. data/lib/rucoa/handlers/shutdown_handler.rb +12 -0
  32. data/lib/rucoa/handlers/text_document_code_action_handler.rb +104 -0
  33. data/lib/rucoa/handlers/text_document_did_change_handler.rb +12 -0
  34. data/lib/rucoa/handlers/text_document_did_open_handler.rb +38 -0
  35. data/lib/rucoa/handlers/text_document_document_symbol_handler.rb +241 -0
  36. data/lib/rucoa/handlers/text_document_formatting_handler.rb +64 -0
  37. data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +76 -0
  38. data/lib/rucoa/handlers/text_document_selection_range_handler.rb +141 -0
  39. data/lib/rucoa/handlers/text_document_signature_help_handler.rb +68 -0
  40. data/lib/rucoa/handlers/workspace_did_change_configuration_handler.rb +13 -0
  41. data/lib/rucoa/handlers.rb +20 -0
  42. data/lib/rucoa/message_reader.rb +3 -1
  43. data/lib/rucoa/message_writer.rb +3 -0
  44. data/lib/rucoa/node_concerns/name_full_qualifiable.rb +20 -0
  45. data/lib/rucoa/node_concerns.rb +7 -0
  46. data/lib/rucoa/node_inspector.rb +109 -0
  47. data/lib/rucoa/nodes/base.rb +43 -0
  48. data/lib/rucoa/nodes/casgn_node.rb +14 -0
  49. data/lib/rucoa/nodes/class_node.rb +8 -0
  50. data/lib/rucoa/nodes/const_node.rb +12 -0
  51. data/lib/rucoa/nodes/def_node.rb +71 -0
  52. data/lib/rucoa/nodes/defs_node.rb +12 -0
  53. data/lib/rucoa/nodes/lvar_node.rb +25 -0
  54. data/lib/rucoa/nodes/module_node.rb +21 -0
  55. data/lib/rucoa/nodes/sclass_node.rb +8 -0
  56. data/lib/rucoa/nodes/send_node.rb +60 -0
  57. data/lib/rucoa/nodes/str_node.rb +4 -0
  58. data/lib/rucoa/nodes/sym_node.rb +12 -0
  59. data/lib/rucoa/nodes.rb +10 -0
  60. data/lib/rucoa/parser_builder.rb +20 -10
  61. data/lib/rucoa/range.rb +89 -13
  62. data/lib/rucoa/rbs_document_loader.rb +43 -0
  63. data/lib/rucoa/rubocop_autocorrector.rb +1 -1
  64. data/lib/rucoa/rubocop_configuration_checker.rb +42 -0
  65. data/lib/rucoa/rubocop_investigator.rb +1 -1
  66. data/lib/rucoa/server.rb +96 -122
  67. data/lib/rucoa/source.rb +56 -6
  68. data/lib/rucoa/source_store.rb +11 -9
  69. data/lib/rucoa/types/method_type.rb +23 -0
  70. data/lib/rucoa/types.rb +7 -0
  71. data/lib/rucoa/version.rb +1 -1
  72. data/lib/rucoa/yard_glob_document_loader.rb +47 -0
  73. data/lib/rucoa/yard_string_document_loader.rb +70 -0
  74. data/lib/rucoa.rb +14 -4
  75. data/rucoa.gemspec +1 -0
  76. metadata +70 -6
  77. data/lib/rucoa/code_action_provider.rb +0 -102
  78. data/lib/rucoa/diagnostic_provider.rb +0 -159
  79. data/lib/rucoa/formatting_provider.rb +0 -52
  80. data/lib/rucoa/selection_range_provider.rb +0 -97
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentCodeActionHandler < Base
6
+ def call
7
+ return unless diagnostics
8
+
9
+ respond(code_actions)
10
+ end
11
+
12
+ private
13
+
14
+ # @return [Array<Hash>]
15
+ def code_actions
16
+ return [] unless configuration.enables_code_action?
17
+
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
28
+ end
29
+
30
+ # @return [Array<Hash>, nil]
31
+ def diagnostics
32
+ @diagnostics ||= request.dig('params', 'context', 'diagnostics')
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
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentDidChangeHandler < TextDocumentDidOpenHandler
6
+ # @return [String]
7
+ def text
8
+ request.dig('params', 'contentChanges')[0]['text']
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentDidOpenHandler < Base
6
+ include HandlerConcerns::DiagnosticsPublishable
7
+
8
+ def call
9
+ source_store.update(source)
10
+ definition_store.update_definitions_defined_in(
11
+ source.path,
12
+ definitions: source.definitions
13
+ )
14
+ publish_diagnostics_on(uri)
15
+ end
16
+
17
+ private
18
+
19
+ # @return [Rucoa::Source]
20
+ def source
21
+ @source ||= Source.new(
22
+ content: text,
23
+ uri: uri
24
+ )
25
+ end
26
+
27
+ # @return [String]
28
+ def text
29
+ request.dig('params', 'textDocument', 'text')
30
+ end
31
+
32
+ # @return [String]
33
+ def uri
34
+ @uri ||= request.dig('params', 'textDocument', 'uri')
35
+ end
36
+ end
37
+ end
38
+ 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 ||= 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
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentFormattingHandler < Base
6
+ def call
7
+ respond(edits)
8
+ end
9
+
10
+ private
11
+
12
+ # @return [Boolean]
13
+ def formattable?
14
+ configuration.enables_formatting? &&
15
+ source &&
16
+ RubocopConfigurationChecker.call
17
+ end
18
+
19
+ # @return [Array<Hash>]
20
+ def edits
21
+ return [] unless formattable?
22
+
23
+ [text_edit]
24
+ end
25
+
26
+ # @return [Rucoa::Source, nil]
27
+ def source
28
+ @source ||= source_store.get(uri)
29
+ end
30
+
31
+ # @return [String]
32
+ def uri
33
+ request.dig('params', 'textDocument', 'uri')
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
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentRangeFormattingHandler < Base
6
+ def call
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')
45
+ )
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.contain?(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
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentSelectionRangeHandler < Base
6
+ def call
7
+ return unless respondable?
8
+
9
+ respond(
10
+ positions.filter_map do |position|
11
+ SelectionRangeProvider.call(
12
+ position: position,
13
+ source: source
14
+ )
15
+ end
16
+ )
17
+ end
18
+
19
+ private
20
+
21
+ # @return [Boolean]
22
+ def respondable?
23
+ configuration.enables_selection_range? &&
24
+ source
25
+ end
26
+
27
+ # @return [Rucoa::Source]
28
+ def source
29
+ @source ||= source_store.get(uri)
30
+ end
31
+
32
+ # @return [Array<Rucoa::Position>]
33
+ def positions
34
+ request.dig('params', 'positions').map do |position|
35
+ Position.from_vscode_position(position)
36
+ end
37
+ end
38
+
39
+ # @return [String]
40
+ def uri
41
+ request.dig('params', 'textDocument', 'uri')
42
+ end
43
+
44
+ class SelectionRangeProvider
45
+ class << self
46
+ # @param source [Rucoa::Source]
47
+ # @param position [Rucoa::Position]
48
+ # @return [Hash, nil]
49
+ def call(position:, source:)
50
+ new(
51
+ position: position,
52
+ source: source
53
+ ).call
54
+ end
55
+ end
56
+
57
+ # @param position [Rucoa::Position]
58
+ # @param source [Rucoa::Source]
59
+ def initialize(position:, source:)
60
+ @position = position
61
+ @source = source
62
+ end
63
+
64
+ # @return [Hash, nil]
65
+ def call
66
+ ranges.reverse.reduce(nil) do |result, range|
67
+ {
68
+ parent: result,
69
+ range: range.to_vscode_range
70
+ }
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ # @return [Rucoa::Nodes::Base, nil]
77
+ def node_at_position
78
+ if instance_variable_defined?(:@node_at_position)
79
+ @node_at_position
80
+ else
81
+ @node_at_position = @source.node_at(@position)
82
+ end
83
+ end
84
+
85
+ # @return [Array<Rucoa::Range>]
86
+ def ranges
87
+ return [] unless node_at_position
88
+
89
+ [node_at_position, *node_at_position.ancestors].flat_map do |node|
90
+ NodeToRangesMapper.call(node)
91
+ end
92
+ end
93
+
94
+ class NodeToRangesMapper
95
+ class << self
96
+ # @param node [Rucoa::Nodes::Base]
97
+ # @return [Array<Rucoa::Range>]
98
+ def call(node)
99
+ new(node).call
100
+ end
101
+ end
102
+
103
+ # @param node [Rucoa::Nodes::Base]
104
+ def initialize(node)
105
+ @node = node
106
+ end
107
+
108
+ # @return [Array<Rucoa::Range>]
109
+ def call
110
+ case @node
111
+ when Nodes::StrNode
112
+ [
113
+ inner_range,
114
+ expression_range
115
+ ]
116
+ else
117
+ [
118
+ expression_range
119
+ ]
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ # @return [Rucoa::Range]
126
+ def inner_range
127
+ Range.new(
128
+ Position.from_parser_range_ending(@node.location.begin),
129
+ Position.from_parser_range_beginning(@node.location.end)
130
+ )
131
+ end
132
+
133
+ # @return [Rucoa::Range]
134
+ def expression_range
135
+ Range.from_parser_range(@node.location.expression)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end