katakata_irb 0.1.12 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|