katakata_irb 0.1.12 → 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.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/katakata_irb.gemspec +1 -0
- data/lib/katakata_irb/completor.rb +166 -218
- data/lib/katakata_irb/scope.rb +224 -81
- data/lib/katakata_irb/type_analyzer.rb +1168 -0
- data/lib/katakata_irb/types.rb +25 -11
- data/lib/katakata_irb/version.rb +1 -1
- data/lib/katakata_irb.rb +2 -0
- metadata +17 -4
- data/lib/katakata_irb/nesting_parser.rb +0 -257
- data/lib/katakata_irb/type_simulator.rb +0 -995
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 034055ed4a5fcf5bde6a1b86723c2963f82ace7c5b0411cd9a3e09fd734b10d2
|
4
|
+
data.tar.gz: 3e04438496f0241815cf7b674db57f7b644360520d9d829d75d5be61b63b78e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b406ecd5c611cdc86220efa506c403dff220db4be5fb1bc1fab10eeb70931f0d7eaee8a45a702a6addb5de793e0c219562975d769fe31616305cd87fe4cfa4a
|
7
|
+
data.tar.gz: 147b09a36c68a66c917fff2157797d1124ec06e0333ee7cec26ef83b9d726c398bb83bbdce81f88c1706067ecc3f9bf99506d5706d3b22ccc3c8aa2ee6ce5cb1
|
data/Gemfile
CHANGED
data/katakata_irb.gemspec
CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
31
31
|
# Uncomment to register a new dependency of your gem
|
32
32
|
spec.add_dependency 'irb', '>= 1.4.0'
|
33
33
|
spec.add_dependency 'reline', '>= 0.3.0'
|
34
|
+
spec.add_dependency 'prism', '>= 0.14.0'
|
34
35
|
spec.add_dependency 'rbs'
|
35
36
|
|
36
37
|
# For more information and examples about making a new gem, check out our
|
@@ -1,65 +1,72 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'rbs/cli'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'type_analyzer'
|
5
4
|
require 'irb'
|
5
|
+
require 'prism'
|
6
6
|
|
7
7
|
module KatakataIrb::Completor
|
8
|
-
using KatakataIrb::TypeSimulator::LexerElemMatcher
|
9
8
|
HIDDEN_METHODS = %w[Namespace TypeName] # defined by rbs, should be hidden
|
10
9
|
singleton_class.attr_accessor :prev_analyze_result
|
11
10
|
|
11
|
+
def self.candidates_from_result(result)
|
12
|
+
candidates = case result
|
13
|
+
in [:require | :require_relative => method, name]
|
14
|
+
if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
|
15
|
+
path_completor = IRB::RegexpCompletor.new
|
16
|
+
elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
|
17
|
+
path_completor = IRB::InputCompletor
|
18
|
+
end
|
19
|
+
if !path_completor
|
20
|
+
[]
|
21
|
+
elsif method == :require
|
22
|
+
path_completor.retrieve_files_to_require_from_load_path
|
23
|
+
else
|
24
|
+
path_completor.retrieve_files_to_require_relative_from_current_dir
|
25
|
+
end
|
26
|
+
in [:call_or_const, type, name, self_call]
|
27
|
+
((self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants
|
28
|
+
in [:const, type, name, scope]
|
29
|
+
if type
|
30
|
+
scope_constants = type.types.flat_map do |t|
|
31
|
+
scope.table_module_constants(t.module_or_class) if t.is_a?(KatakataIrb::Types::SingletonType)
|
32
|
+
end
|
33
|
+
(scope_constants.compact | type.constants.map(&:to_s)).sort
|
34
|
+
else
|
35
|
+
scope.constants.sort
|
36
|
+
end
|
37
|
+
in [:ivar, name, scope]
|
38
|
+
ivars = scope.instance_variables.sort
|
39
|
+
name == '@' ? ivars + scope.class_variables.sort : ivars
|
40
|
+
in [:cvar, name, scope]
|
41
|
+
scope.class_variables
|
42
|
+
in [:gvar, name, scope]
|
43
|
+
scope.global_variables
|
44
|
+
in [:symbol, name]
|
45
|
+
Symbol.all_symbols.map { _1.inspect[1..] }
|
46
|
+
in [:call, type, name, self_call]
|
47
|
+
(self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
|
48
|
+
in [:lvar_or_method, name, scope]
|
49
|
+
scope.self_type.all_methods.map(&:to_s) | scope.local_variables
|
50
|
+
else
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
[name || '', candidates]
|
54
|
+
end
|
55
|
+
|
12
56
|
def self.setup
|
13
57
|
KatakataIrb::Types.preload_in_thread
|
14
|
-
completion_proc = ->(target,
|
58
|
+
completion_proc = ->(preposing, target, _postposing, bind:) do
|
15
59
|
verbose, $VERBOSE = $VERBOSE, nil
|
16
60
|
code = "#{preposing}#{target}"
|
17
|
-
|
18
|
-
binding = irb_context.workspace.binding
|
19
|
-
result = analyze code, binding
|
61
|
+
result = analyze code, bind
|
20
62
|
KatakataIrb::Completor.prev_analyze_result = result
|
21
|
-
candidates =
|
22
|
-
|
23
|
-
if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
|
24
|
-
path_completor = IRB::RegexpCompletor.new
|
25
|
-
elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
|
26
|
-
path_completor = IRB::InputCompletor
|
27
|
-
end
|
28
|
-
if !path_completor
|
29
|
-
[]
|
30
|
-
elsif method == :require
|
31
|
-
path_completor.retrieve_files_to_require_from_load_path
|
32
|
-
else
|
33
|
-
path_completor.retrieve_files_to_require_relative_from_current_dir
|
34
|
-
end
|
35
|
-
in [:call_or_const, type, name, self_call]
|
36
|
-
((self_call ? type.all_methods: type.methods).map(&:to_s) - HIDDEN_METHODS) | type.constants
|
37
|
-
in [:const, type, name]
|
38
|
-
type.constants
|
39
|
-
in [:ivar, name, *_scope]
|
40
|
-
# TODO: scope
|
41
|
-
ivars = binding.eval('self').instance_variables rescue []
|
42
|
-
cvars = (binding.eval('self').class_variables rescue nil) if name == '@'
|
43
|
-
ivars | (cvars || [])
|
44
|
-
in [:cvar, name, *_scope]
|
45
|
-
# TODO: scope
|
46
|
-
binding.eval('self').class_variables rescue []
|
47
|
-
in [:gvar, name]
|
48
|
-
global_variables
|
49
|
-
in [:symbol, name]
|
50
|
-
Symbol.all_symbols.map { _1.inspect[1..] }
|
51
|
-
in [:call, type, name, self_call]
|
52
|
-
(self_call ? type.all_methods : type.methods).map(&:to_s) - HIDDEN_METHODS
|
53
|
-
in [:lvar_or_method, name, scope]
|
54
|
-
scope.self_type.all_methods.map(&:to_s) | scope.local_variables
|
55
|
-
else
|
56
|
-
[]
|
57
|
-
end
|
63
|
+
name, candidates = candidates_from_result(result).dup
|
64
|
+
|
58
65
|
all_symbols_pattern = /\A[ -\/:-@\[-`\{-~]*\z/
|
59
66
|
candidates.map(&:to_s).select { !_1.match?(all_symbols_pattern) && _1.start_with?(name) }.uniq.sort.map do
|
60
67
|
target + _1[name.size..]
|
61
68
|
end
|
62
|
-
rescue => e
|
69
|
+
rescue SyntaxError, StandardError => e
|
63
70
|
KatakataIrb.last_completion_error = e
|
64
71
|
KatakataIrb.log_puts
|
65
72
|
KatakataIrb.log_puts "#{e.inspect} stored to KatakataIrb.last_completion_error"
|
@@ -68,13 +75,13 @@ module KatakataIrb::Completor
|
|
68
75
|
$VERBOSE = verbose
|
69
76
|
end
|
70
77
|
|
71
|
-
doc_namespace_proc = ->
|
72
|
-
name = input[/[a-zA-Z_0-9]
|
78
|
+
doc_namespace_proc = -> input do
|
79
|
+
name = input[/[a-zA-Z_0-9]*[!?=]?\z/]
|
73
80
|
method_doc = -> type do
|
74
81
|
type = type.types.find { _1.all_methods.include? name.to_sym }
|
75
|
-
if type
|
82
|
+
if type.is_a? KatakataIrb::Types::SingletonType
|
76
83
|
"#{KatakataIrb::Types.class_name_of(type.module_or_class)}.#{name}"
|
77
|
-
elsif type
|
84
|
+
elsif type.is_a? KatakataIrb::Types::InstanceType
|
78
85
|
"#{KatakataIrb::Types.class_name_of(type.klass)}##{name}"
|
79
86
|
end
|
80
87
|
end
|
@@ -87,18 +94,44 @@ module KatakataIrb::Completor
|
|
87
94
|
end
|
88
95
|
end
|
89
96
|
|
97
|
+
value_doc = -> type do
|
98
|
+
return unless type
|
99
|
+
type.types.each do |t|
|
100
|
+
case t
|
101
|
+
when KatakataIrb::Types::SingletonType
|
102
|
+
return KatakataIrb::Types.class_name_of(t.module_or_class)
|
103
|
+
when KatakataIrb::Types::InstanceType
|
104
|
+
return KatakataIrb::Types.class_name_of(t.klass)
|
105
|
+
when KatakataIrb::Types::ProcType
|
106
|
+
return 'Proc'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
90
112
|
case KatakataIrb::Completor.prev_analyze_result
|
91
113
|
in [:call_or_const, type, _name, _self_call]
|
92
114
|
call_or_const_doc.call type
|
93
|
-
in [:const, type, _name]
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
115
|
+
in [:const, type, _name, scope]
|
116
|
+
if type
|
117
|
+
call_or_const_doc.call type
|
118
|
+
else
|
119
|
+
value_doc.call scope[name]
|
120
|
+
end
|
121
|
+
in [:gvar, _name, scope]
|
122
|
+
value_doc.call scope["$#{name}"]
|
123
|
+
in [:ivar, _name, scope]
|
124
|
+
value_doc.call scope["@#{name}"]
|
125
|
+
in [:cvar, _name, scope]
|
126
|
+
value_doc.call scope["@@#{name}"]
|
98
127
|
in [:call, type, _name, _self_call]
|
99
128
|
method_doc.call type
|
100
129
|
in [:lvar_or_method, _name, scope]
|
101
|
-
|
130
|
+
if scope.local_variables.include?(name)
|
131
|
+
value_doc.call scope[name]
|
132
|
+
else
|
133
|
+
method_doc.call scope.self_type
|
134
|
+
end
|
102
135
|
else
|
103
136
|
end
|
104
137
|
end
|
@@ -106,15 +139,16 @@ module KatakataIrb::Completor
|
|
106
139
|
if IRB.const_defined? :RegexpCompletor # IRB::VERSION >= 1.8.2
|
107
140
|
IRB::RegexpCompletor.class_eval do
|
108
141
|
define_method :completion_candidates do |preposing, target, postposing, bind:|
|
109
|
-
completion_proc.call(target,
|
142
|
+
completion_proc.call(preposing, target, postposing, bind: bind)
|
110
143
|
end
|
111
144
|
define_method :doc_namespace do |_preposing, matched, _postposing, bind:|
|
112
145
|
doc_namespace_proc.call matched
|
113
146
|
end
|
114
147
|
end
|
115
148
|
elsif IRB.const_defined? :InputCompletor # IRB::VERSION <= 1.8.1
|
116
|
-
IRB::InputCompletor::CompletionProc.define_singleton_method :call do
|
117
|
-
|
149
|
+
IRB::InputCompletor::CompletionProc.define_singleton_method :call do |target, preposing = '', postposing = ''|
|
150
|
+
bind = IRB.conf[:MAIN_CONTEXT].workspace.binding
|
151
|
+
completion_proc.call(preposing, target, postposing, bind: bind)
|
118
152
|
end
|
119
153
|
IRB::InputCompletor.singleton_class.prepend(
|
120
154
|
Module.new do
|
@@ -174,179 +208,93 @@ module KatakataIrb::Completor
|
|
174
208
|
Reline.add_dialog_proc(:show_type, type_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT)
|
175
209
|
end
|
176
210
|
|
177
|
-
def self.
|
178
|
-
|
179
|
-
|
211
|
+
def self.analyze(code, binding = Object::TOPLEVEL_BINDING)
|
212
|
+
# Workaround for https://github.com/ruby/prism/issues/1592
|
213
|
+
return if code.match?(/%[qQ]\z/)
|
180
214
|
|
181
|
-
def self.analyze(code, binding = empty_binding)
|
182
215
|
lvars_code = binding.local_variables.map do |name|
|
183
216
|
"#{name}="
|
184
217
|
end.join + "nil;\n"
|
185
218
|
code = lvars_code + code
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
when /\A%.?[<>]\z/
|
191
|
-
$/ + '>'
|
192
|
-
when '{', '#{', /\A%.?[{}]\z/
|
193
|
-
$/ + '}'
|
194
|
-
when '(', /\A%.?[()]\z/
|
195
|
-
# do not insert \n before closing paren. workaround to avoid syntax error of "a in ^(b\n)"
|
196
|
-
')'
|
197
|
-
when '[', /\A%.?[\[\]]\z/
|
198
|
-
$/ + ']'
|
199
|
-
when /\A%.?(.)\z/
|
200
|
-
$1
|
201
|
-
when '"', "'", '/', '`'
|
202
|
-
t.tok
|
203
|
-
when /\A<<[~-]?(?:"(?<s>.+)"|'(?<s>.+)'|(?<s>.+))/
|
204
|
-
$/ + ($1 || $2 || $3) + $/
|
205
|
-
when ':"', ":'", ':'
|
206
|
-
t.tok[1]
|
207
|
-
when '?'
|
208
|
-
# ternary operator
|
209
|
-
' : value'
|
210
|
-
when '|'
|
211
|
-
# block args
|
212
|
-
'|'
|
213
|
-
else
|
214
|
-
$/ + 'end'
|
215
|
-
end
|
216
|
-
end
|
217
|
-
# remove error tokens
|
218
|
-
tokens.pop while tokens&.last&.tok&.empty?
|
219
|
+
ast = Prism.parse(code).value
|
220
|
+
name = code[/(@@|@|\$)?\w*[!?=]?\z/]
|
221
|
+
*parents, target_node = find_target ast, code.bytesize - name.bytesize
|
222
|
+
return unless target_node
|
219
223
|
|
220
|
-
|
221
|
-
|
222
|
-
suffix = 'method'
|
223
|
-
name = ''
|
224
|
-
in { dot: true }
|
225
|
-
suffix = 'method'
|
226
|
-
name = ''
|
227
|
-
in { event: :on_symbeg }
|
228
|
-
suffix = 'symbol'
|
229
|
-
name = ''
|
230
|
-
in { event: :on_ident | :on_kw, tok: }
|
231
|
-
return unless code.delete_suffix! tok
|
232
|
-
suffix = 'method'
|
233
|
-
name = tok
|
234
|
-
in { event: :on_const, tok: }
|
235
|
-
return unless code.delete_suffix! tok
|
236
|
-
suffix = 'Const'
|
237
|
-
name = tok
|
238
|
-
in { event: :on_tstring_content, tok: }
|
239
|
-
return unless code.delete_suffix! tok
|
240
|
-
suffix = 'string'
|
241
|
-
name = tok.rstrip
|
242
|
-
in { event: :on_gvar, tok: }
|
243
|
-
return unless code.delete_suffix! tok
|
244
|
-
suffix = '$gvar'
|
245
|
-
name = tok
|
246
|
-
in { event: :on_ivar, tok: }
|
247
|
-
return unless code.delete_suffix! tok
|
248
|
-
suffix = '@ivar'
|
249
|
-
name = tok
|
250
|
-
in { event: :on_cvar, tok: }
|
251
|
-
return unless code.delete_suffix! tok
|
252
|
-
suffix = '@@cvar'
|
253
|
-
name = tok
|
254
|
-
else
|
255
|
-
return
|
256
|
-
end
|
257
|
-
sexp = Ripper.sexp code + suffix + closings.reverse.join
|
258
|
-
lines = code.lines
|
259
|
-
line_no = lines.size
|
260
|
-
col = lines.last.bytesize
|
261
|
-
if lines.last.end_with? "\n"
|
262
|
-
line_no += 1
|
263
|
-
col = 0
|
264
|
-
end
|
224
|
+
calculate_scope = -> { KatakataIrb::TypeAnalyzer.calculate_target_type_scope(binding, parents, target_node).last }
|
225
|
+
calculate_type_scope = ->(node) { KatakataIrb::TypeAnalyzer.calculate_target_type_scope binding, [*parents, target_node], node }
|
265
226
|
|
266
|
-
if
|
267
|
-
|
227
|
+
if target_node.is_a?(Prism::StringNode) || target_node.is_a?(Prism::InterpolatedStringNode)
|
228
|
+
args_node = parents[-1]
|
229
|
+
call_node = parents[-2]
|
230
|
+
return unless args_node.is_a?(Prism::ArgumentsNode) && args_node.arguments.size == 1
|
231
|
+
return unless call_node.is_a?(Prism::CallNode) && call_node.receiver.nil? && (call_node.message == 'require' || call_node.message == 'require_relative')
|
232
|
+
return [call_node.message.to_sym, name.rstrip]
|
268
233
|
end
|
269
234
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
return [:
|
280
|
-
end
|
281
|
-
return unless expression
|
282
|
-
calculate_scope = -> { KatakataIrb::TypeSimulator.calculate_binding_scope binding, parents, expression }
|
283
|
-
calculate_receiver = -> receiver { KatakataIrb::TypeSimulator.calculate_receiver binding, parents, receiver }
|
235
|
+
case target_node
|
236
|
+
when Prism::SymbolNode
|
237
|
+
if parents.last.is_a? Prism::BlockArgumentNode # method(&:target)
|
238
|
+
receiver_type, _scope = calculate_type_scope.call target_node
|
239
|
+
[:call, receiver_type, name, false]
|
240
|
+
else
|
241
|
+
[:symbol, name] unless name.empty?
|
242
|
+
end
|
243
|
+
when Prism::CallNode
|
244
|
+
return [:lvar_or_method, name, calculate_scope.call] if target_node.receiver.nil?
|
284
245
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
receiver_ref = [:var_ref, [:@ident, '_1', [0, 0]]]
|
292
|
-
block_statements = [receiver_ref]
|
293
|
-
parents[-1] = parents[-2][-1] = [:brace_block, nil, block_statements]
|
294
|
-
parents << block_statements
|
295
|
-
return [:call, calculate_receiver.call(receiver_ref), name, false]
|
296
|
-
end
|
297
|
-
case expression
|
298
|
-
in [:vcall | :var_ref, [:@ident,]]
|
246
|
+
self_call = target_node.receiver.is_a? Prism::SelfNode
|
247
|
+
op = target_node.call_operator
|
248
|
+
receiver_type, _scope = calculate_type_scope.call target_node.receiver
|
249
|
+
receiver_type = receiver_type.nonnillable if op == '&.'
|
250
|
+
[op == '::' ? :call_or_const : :call, receiver_type, name, self_call]
|
251
|
+
when Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode
|
299
252
|
[:lvar_or_method, name, calculate_scope.call]
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
[:
|
317
|
-
|
318
|
-
|
319
|
-
[:call, calculate_receiver.call(receiver).nonnillable, name, self_call]
|
320
|
-
in [:const_path_ref, receiver, [:@const,]]
|
321
|
-
[:const, calculate_receiver.call(receiver), name]
|
322
|
-
in [:top_const_ref, [:@const,]]
|
323
|
-
[:const, KatakataIrb::Types::SingletonType.new(Object), name]
|
324
|
-
in [:def,] | [:string_content,] | [:field | :var_field | :const_path_field,] | [:defs,] | [:rest_param,] | [:kwrest_param,] | [:blockarg,] | [[:@ident,],]
|
325
|
-
in [Array,] # `xstring`, /regexp/
|
326
|
-
else
|
327
|
-
KatakataIrb.log_puts
|
328
|
-
KatakataIrb.log_puts [:UNIMPLEMENTED_EXPRESSION, expression].inspect
|
329
|
-
KatakataIrb.log_puts
|
330
|
-
nil
|
253
|
+
when Prism::ConstantReadNode, Prism::ConstantTargetNode
|
254
|
+
if parents.last.is_a? Prism::ConstantPathNode
|
255
|
+
path_node = parents.last
|
256
|
+
if path_node.parent # A::B
|
257
|
+
receiver, scope = calculate_type_scope.call(path_node.parent)
|
258
|
+
[:const, receiver, name, scope]
|
259
|
+
else # ::A
|
260
|
+
scope = calculate_scope.call
|
261
|
+
[:const, KatakataIrb::Types::SingletonType.new(Object), name, scope]
|
262
|
+
end
|
263
|
+
else
|
264
|
+
[:const, nil, name, calculate_scope.call]
|
265
|
+
end
|
266
|
+
when Prism::GlobalVariableReadNode, Prism::GlobalVariableTargetNode
|
267
|
+
[:gvar, name, calculate_scope.call]
|
268
|
+
when Prism::InstanceVariableReadNode, Prism::InstanceVariableTargetNode
|
269
|
+
[:ivar, name, calculate_scope.call]
|
270
|
+
when Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
|
271
|
+
[:cvar, name, calculate_scope.call]
|
331
272
|
end
|
332
273
|
end
|
333
274
|
|
334
|
-
def self.find_target(
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
result = find_target(child, line, col, stack)
|
346
|
-
return result if result
|
347
|
-
stack.pop
|
275
|
+
def self.find_target(node, position)
|
276
|
+
location = (
|
277
|
+
case node
|
278
|
+
when Prism::CallNode
|
279
|
+
node.message_loc
|
280
|
+
when Prism::SymbolNode
|
281
|
+
node.value_loc
|
282
|
+
when Prism::StringNode
|
283
|
+
node.content_loc
|
284
|
+
when Prism::InterpolatedStringNode
|
285
|
+
node.closing_loc if node.parts.empty?
|
348
286
|
end
|
287
|
+
)
|
288
|
+
return [node] if location&.start_offset == position
|
289
|
+
|
290
|
+
node.child_nodes.each do |n|
|
291
|
+
next unless n.is_a? Prism::Node
|
292
|
+
match = find_target(n, position)
|
293
|
+
next unless match
|
294
|
+
match.unshift node
|
295
|
+
return match
|
349
296
|
end
|
350
|
-
|
297
|
+
|
298
|
+
[node] if node.location.start_offset == position
|
351
299
|
end
|
352
300
|
end
|