Implement OP_ASGN1 NODE locations
[ruby.git] / test / ruby / test_ast.rb
blobf1328b15ed0cf33ed8d5fc7c128ce62ecfc1d7db
1 # frozen_string_literal: false
2 require 'test/unit'
3 require 'tempfile'
4 require 'pp'
5 require_relative '../lib/parser_support'
7 class RubyVM
8   module AbstractSyntaxTree
9     class Node
10       class CodePosition
11         include Comparable
12         attr_reader :lineno, :column
13         def initialize(lineno, column)
14           @lineno = lineno
15           @column = column
16         end
18         def <=>(other)
19           case
20           when lineno < other.lineno
21             -1
22           when lineno == other.lineno
23             column <=> other.column
24           when lineno > other.lineno
25             1
26           end
27         end
28       end
30       def beg_pos
31         CodePosition.new(first_lineno, first_column)
32       end
34       def end_pos
35         CodePosition.new(last_lineno, last_column)
36       end
38       alias to_s inspect
39     end
40   end
41 end
43 class TestAst < Test::Unit::TestCase
44   class Helper
45     attr_reader :errors
47     def initialize(path, src: nil)
48       @path = path
49       @errors = []
50       @debug = false
51       @ast = RubyVM::AbstractSyntaxTree.parse(src) if src
52     end
54     def validate_range
55       @errors = []
56       validate_range0(ast)
58       @errors.empty?
59     end
61     def validate_not_cared
62       @errors = []
63       validate_not_cared0(ast)
65       @errors.empty?
66     end
68     def ast
69       return @ast if defined?(@ast)
70       @ast = RubyVM::AbstractSyntaxTree.parse_file(@path)
71     end
73     private
75     def validate_range0(node)
76       beg_pos, end_pos = node.beg_pos, node.end_pos
77       children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
79       return true if children.empty?
80       # These NODE_D* has NODE_LIST as nd_next->nd_next whose last locations
81       # we can not update when item is appended.
82       return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type
84       min = children.map(&:beg_pos).min
85       max = children.map(&:end_pos).max
87       unless beg_pos <= min
88         @errors << { type: :min_validation_error, min: min, beg_pos: beg_pos, node: node }
89       end
91       unless max <= end_pos
92         @errors << { type: :max_validation_error, max: max, end_pos: end_pos, node: node }
93       end
95       p "#{node} => #{children}" if @debug
97       children.each do |child|
98         p child if @debug
99         validate_range0(child)
100       end
101     end
103     def validate_not_cared0(node)
104       beg_pos, end_pos = node.beg_pos, node.end_pos
105       children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
107       @errors << { type: :first_lineno, node: node } if beg_pos.lineno == 0
108       @errors << { type: :first_column, node: node } if beg_pos.column == -1
109       @errors << { type: :last_lineno,  node: node } if end_pos.lineno == 0
110       @errors << { type: :last_column,  node: node } if end_pos.column == -1
112       children.each {|c| validate_not_cared0(c) }
113     end
114   end
116   SRCDIR = File.expand_path("../../..", __FILE__)
118   Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
119     define_method("test_ranges:#{path}") do
120       helper = Helper.new("#{SRCDIR}/#{path}")
121       helper.validate_range
123       assert_equal([], helper.errors)
124     end
125   end
127   Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
128     define_method("test_not_cared:#{path}") do
129       helper = Helper.new("#{SRCDIR}/#{path}")
130       helper.validate_not_cared
132       assert_equal([], helper.errors)
133     end
134   end
136   Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
137     define_method("test_all_tokens:#{path}") do
138       node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true)
139       tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] }
140       tokens_bytes = tokens.map { _1[2]}.join.bytes
141       source_bytes = File.read("#{SRCDIR}/#{path}").bytes
143       assert_equal(source_bytes, tokens_bytes)
145       (tokens.count - 1).times do |i|
146         token_0 = tokens[i]
147         token_1 = tokens[i + 1]
148         end_pos = token_0.last[2..3]
149         beg_pos = token_1.last[0..1]
151         if end_pos[0] == beg_pos[0]
152           # When both tokens are same line, column should be consecutives
153           assert_equal(beg_pos[1], end_pos[1], "#{token_0}. #{token_1}")
154         else
155           # Line should be next
156           assert_equal(beg_pos[0], end_pos[0] + 1, "#{token_0}. #{token_1}")
157           # It should be on the beginning of the line
158           assert_equal(0, beg_pos[1], "#{token_0}. #{token_1}")
159         end
160       end
161     end
162   end
164   private def parse(src)
165     EnvUtil.suppress_warning {
166       RubyVM::AbstractSyntaxTree.parse(src)
167     }
168   end
170   def test_allocate
171     assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate}
172   end
174   def test_parse_argument_error
175     assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(0)}
176     assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(nil)}
177     assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(false)}
178     assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(true)}
179     assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(:foo)}
180   end
182   def test_column_with_long_heredoc_identifier
183     term = "A"*257
184     ast = parse("<<-#{term}\n""ddddddd\n#{term}\n")
185     node = ast.children[2]
186     assert_equal(:STR, node.type)
187     assert_equal(0, node.first_column)
188   end
190   def test_column_of_heredoc
191     node = parse("<<-SRC\nddddddd\nSRC\n").children[2]
192     assert_equal(:STR, node.type)
193     assert_equal(0, node.first_column)
194     assert_equal(6, node.last_column)
196     node = parse("<<SRC\nddddddd\nSRC\n").children[2]
197     assert_equal(:STR, node.type)
198     assert_equal(0, node.first_column)
199     assert_equal(5, node.last_column)
200   end
202   def test_parse_raises_syntax_error
203     assert_raise_with_message(SyntaxError, /\bend\b/) do
204       RubyVM::AbstractSyntaxTree.parse("end")
205     end
206   end
208   def test_parse_file_raises_syntax_error
209     Tempfile.create(%w"test_ast .rb") do |f|
210       f.puts "end"
211       f.close
212       assert_raise_with_message(SyntaxError, /\bend\b/) do
213         RubyVM::AbstractSyntaxTree.parse_file(f.path)
214       end
215     end
216   end
218   def assert_parse(code, warning: '')
219     node = assert_warning(warning) {RubyVM::AbstractSyntaxTree.parse(code)}
220     assert_kind_of(RubyVM::AbstractSyntaxTree::Node, node, code)
221   end
223   def assert_invalid_parse(msg, code)
224     assert_raise_with_message(SyntaxError, msg, code) do
225       RubyVM::AbstractSyntaxTree.parse(code)
226     end
227   end
229   def test_invalid_exit
230     [
231       "break",
232       "break true",
233       "next",
234       "next true",
235       "redo",
236     ].each do |code, *args|
237       msg = /Invalid #{code[/\A\w+/]}/
238       assert_parse("while false; #{code}; end")
239       assert_parse("until true; #{code}; end")
240       assert_parse("begin #{code}; end while false")
241       assert_parse("begin #{code}; end until true")
242       assert_parse("->{#{code}}")
243       assert_parse("->{class X; #{code}; end}")
244       assert_invalid_parse(msg, "#{code}")
245       assert_invalid_parse(msg, "def m; #{code}; end")
246       assert_invalid_parse(msg, "begin; #{code}; end")
247       assert_parse("END {#{code}}")
249       assert_parse("!defined?(#{code})")
250       assert_parse("def m; defined?(#{code}); end")
251       assert_parse("!begin; defined?(#{code}); end")
253       next if code.include?(" ")
254       assert_parse("!defined? #{code}")
255       assert_parse("def m; defined? #{code}; end")
256       assert_parse("!begin; defined? #{code}; end")
257     end
258   end
260   def test_invalid_retry
261     msg = /Invalid retry/
262     assert_invalid_parse(msg, "retry")
263     assert_invalid_parse(msg, "def m; retry; end")
264     assert_invalid_parse(msg, "begin retry; end")
265     assert_parse("begin rescue; retry; end")
266     assert_invalid_parse(msg, "begin rescue; else; retry; end")
267     assert_invalid_parse(msg, "begin rescue; ensure; retry; end")
268     assert_parse("nil rescue retry")
269     assert_invalid_parse(msg, "END {retry}")
270     assert_invalid_parse(msg, "begin rescue; END {retry}; end")
272     assert_parse("!defined?(retry)")
273     assert_parse("def m; defined?(retry); end")
274     assert_parse("!begin defined?(retry); end")
275     assert_parse("begin rescue; else; defined?(retry); end")
276     assert_parse("begin rescue; ensure; p defined?(retry); end")
277     assert_parse("END {defined?(retry)}")
278     assert_parse("begin rescue; END {defined?(retry)}; end")
279     assert_parse("!defined? retry")
281     assert_parse("def m; defined? retry; end")
282     assert_parse("!begin defined? retry; end")
283     assert_parse("begin rescue; else; defined? retry; end")
284     assert_parse("begin rescue; ensure; p defined? retry; end")
285     assert_parse("END {defined? retry}")
286     assert_parse("begin rescue; END {defined? retry}; end")
288     assert_parse("#{<<-"begin;"}\n#{<<-'end;'}")
289     begin;
290       def foo
291         begin
292           yield
293         rescue StandardError => e
294           begin
295             puts "hi"
296             retry
297           rescue
298             retry unless e
299             raise e
300           else
301             retry
302           ensure
303             retry
304           end
305         end
306       end
307     end;
308   end
310   def test_invalid_yield
311     msg = /Invalid yield/
312     assert_invalid_parse(msg, "yield")
313     assert_invalid_parse(msg, "class C; yield; end")
314     assert_invalid_parse(msg, "BEGIN {yield}")
315     assert_invalid_parse(msg, "END {yield}")
316     assert_invalid_parse(msg, "-> {yield}")
318     assert_invalid_parse(msg, "yield true")
319     assert_invalid_parse(msg, "class C; yield true; end")
320     assert_invalid_parse(msg, "BEGIN {yield true}")
321     assert_invalid_parse(msg, "END {yield true}")
322     assert_invalid_parse(msg, "-> {yield true}")
324     assert_parse("!defined?(yield)")
325     assert_parse("class C; defined?(yield); end")
326     assert_parse("BEGIN {defined?(yield)}")
327     assert_parse("END {defined?(yield)}")
329     assert_parse("!defined?(yield true)")
330     assert_parse("class C; defined?(yield true); end")
331     assert_parse("BEGIN {defined?(yield true)}")
332     assert_parse("END {defined?(yield true)}")
334     assert_parse("!defined? yield")
335     assert_parse("class C; defined? yield; end")
336     assert_parse("BEGIN {defined? yield}")
337     assert_parse("END {defined? yield}")
338   end
340   def test_node_id_for_location
341     omit if ParserSupport.prism_enabled?
343     exception = begin
344                   raise
345                 rescue => e
346                   e
347                 end
348     loc = exception.backtrace_locations.first
349     node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
350     node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
352     assert_equal node.node_id, node_id
353   end
355   def test_node_id_for_backtrace_location_raises_argument_error
356     bug19262 = '[ruby-core:111435]'
358     assert_raise(TypeError, bug19262) { RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(1) }
359   end
361   def test_of_proc_and_method
362     omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
364     proc = Proc.new { 1 + 2 }
365     method = self.method(__method__)
367     node_proc = RubyVM::AbstractSyntaxTree.of(proc)
368     node_method = RubyVM::AbstractSyntaxTree.of(method)
370     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc)
371     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method)
373     Tempfile.create(%w"test_of .rb") do |tmp|
374       tmp.print "#{<<-"begin;"}\n#{<<-'end;'}"
375       begin;
376         SCRIPT_LINES__ = {}
377         assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(proc {|x| x}))
378       end;
379       tmp.close
380       assert_separately(["-", tmp.path], "#{<<~"begin;"}\n#{<<~'end;'}")
381       begin;
382         load ARGV[0]
383         assert_empty(SCRIPT_LINES__)
384       end;
385     end
386   end
388   def sample_backtrace_location
389     [caller_locations(0).first, __LINE__]
390   end
392   def test_of_backtrace_location
393     omit if ParserSupport.prism_enabled?
395     backtrace_location, lineno = sample_backtrace_location
396     node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
397     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
398     assert_equal(lineno, node.first_lineno)
399   end
401   def test_of_error
402     assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") }
403   end
405   def test_of_proc_and_method_under_eval
406     omit if ParserSupport.prism_enabled?
408     keep_script_lines_back = RubyVM.keep_script_lines
409     RubyVM.keep_script_lines = false
411     method = self.method(eval("def example_method_#{$$}; end"))
412     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
414     method = self.method(eval("def self.example_singleton_method_#{$$}; end"))
415     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
417     method = eval("proc{}")
418     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
420     method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}){}"))
421     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
423     method = self.method(eval("define_singleton_method(:example_dsm_#{$$}){}"))
424     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
426     method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
427     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
429     method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
430     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
432   ensure
433     RubyVM.keep_script_lines = keep_script_lines_back
434   end
436   def test_of_proc_and_method_under_eval_with_keep_script_lines
437     omit if ParserSupport.prism_enabled?
438     pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
440     keep_script_lines_back = RubyVM.keep_script_lines
441     RubyVM.keep_script_lines = true
443     method = self.method(eval("def example_method_#{$$}_with_keep_script_lines; end"))
444     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
446     method = self.method(eval("def self.example_singleton_method_#{$$}_with_keep_script_lines; end"))
447     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
449     method = eval("proc{}")
450     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
452     method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}_with_keep_script_lines){}"))
453     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
455     method = self.method(eval("define_singleton_method(:example_dsm_#{$$}_with_keep_script_lines){}"))
456     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
458     method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
459     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
461     method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
462     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
464   ensure
465     RubyVM.keep_script_lines = keep_script_lines_back
466   end
468   def test_of_backtrace_location_under_eval
469     omit if ParserSupport.prism_enabled?
471     keep_script_lines_back = RubyVM.keep_script_lines
472     RubyVM.keep_script_lines = false
474     m = Module.new do
475       eval(<<-END, nil, __FILE__, __LINE__)
476         def self.sample_backtrace_location
477           caller_locations(0).first
478         end
479       END
480     end
481     backtrace_location = m.sample_backtrace_location
482     assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(backtrace_location) }
484   ensure
485     RubyVM.keep_script_lines = keep_script_lines_back
486   end
488   def test_of_backtrace_location_under_eval_with_keep_script_lines
489     omit if ParserSupport.prism_enabled?
490     pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
492     keep_script_lines_back = RubyVM.keep_script_lines
493     RubyVM.keep_script_lines = true
495     m = Module.new do
496       eval(<<-END, nil, __FILE__, __LINE__)
497         def self.sample_backtrace_location
498           caller_locations(0).first
499         end
500       END
501     end
502     backtrace_location = m.sample_backtrace_location
503     node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
504     assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
505     assert_equal(2, node.first_lineno)
507   ensure
508     RubyVM.keep_script_lines = keep_script_lines_back
509   end
511   def test_of_c_method
512     c = Class.new { attr_reader :foo }
513     assert_nil(RubyVM::AbstractSyntaxTree.of(c.instance_method(:foo)))
514   end
516   def test_scope_local_variables
517     node = RubyVM::AbstractSyntaxTree.parse("_x = 0")
518     lv, _, body = *node.children
519     assert_equal([:_x], lv)
520     assert_equal(:LASGN, body.type)
521   end
523   def test_call
524     node = RubyVM::AbstractSyntaxTree.parse("nil.foo")
525     _, _, body = *node.children
526     assert_equal(:CALL, body.type)
527     recv, mid, args = body.children
528     assert_equal(:NIL, recv.type)
529     assert_equal(:foo, mid)
530     assert_nil(args)
531   end
533   def test_fcall
534     node = RubyVM::AbstractSyntaxTree.parse("foo()")
535     _, _, body = *node.children
536     assert_equal(:FCALL, body.type)
537     mid, args = body.children
538     assert_equal(:foo, mid)
539     assert_nil(args)
540   end
542   def test_vcall
543     node = RubyVM::AbstractSyntaxTree.parse("foo")
544     _, _, body = *node.children
545     assert_equal(:VCALL, body.type)
546     mid, args = body.children
547     assert_equal(:foo, mid)
548     assert_nil(args)
549   end
551   def test_defn
552     node = RubyVM::AbstractSyntaxTree.parse("def a; end")
553     _, _, body = *node.children
554     assert_equal(:DEFN, body.type)
555     mid, defn = body.children
556     assert_equal(:a, mid)
557     assert_equal(:SCOPE, defn.type)
558     _, args, = defn.children
559     assert_equal(:ARGS, args.type)
560   end
562   def test_defn_endless
563     node = RubyVM::AbstractSyntaxTree.parse("def a = nil")
564     _, _, body = *node.children
565     assert_equal(:DEFN, body.type)
566     mid, defn = body.children
567     assert_equal(:a, mid)
568     assert_equal(:SCOPE, defn.type)
569     _, args, = defn.children
570     assert_equal(:ARGS, args.type)
571   end
573   def test_defs
574     node = RubyVM::AbstractSyntaxTree.parse("def a.b; end")
575     _, _, body = *node.children
576     assert_equal(:DEFS, body.type)
577     recv, mid, defn = body.children
578     assert_equal(:VCALL, recv.type)
579     assert_equal(:b, mid)
580     assert_equal(:SCOPE, defn.type)
581     _, args, = defn.children
582     assert_equal(:ARGS, args.type)
583   end
585   def test_defs_endless
586     node = RubyVM::AbstractSyntaxTree.parse("def a.b = nil")
587     _, _, body = *node.children
588     assert_equal(:DEFS, body.type)
589     recv, mid, defn = body.children
590     assert_equal(:VCALL, recv.type)
591     assert_equal(:b, mid)
592     assert_equal(:SCOPE, defn.type)
593     _, args, = defn.children
594     assert_equal(:ARGS, args.type)
595   end
597   def test_dstr
598     node = parse('"foo#{1}bar"')
599     _, _, body = *node.children
600     assert_equal(:DSTR, body.type)
601     head, body = body.children
602     assert_equal("foo", head)
603     assert_equal(:EVSTR, body.type)
604     body, = body.children
605     assert_equal(:INTEGER, body.type)
606     assert_equal([1], body.children)
607   end
609   def test_while
610     node = RubyVM::AbstractSyntaxTree.parse('1 while qux')
611     _, _, body = *node.children
612     assert_equal(:WHILE, body.type)
613     type1 = body.children[2]
614     node = RubyVM::AbstractSyntaxTree.parse('begin 1 end while qux')
615     _, _, body = *node.children
616     assert_equal(:WHILE, body.type)
617     type2 = body.children[2]
618     assert_not_equal(type1, type2)
619   end
621   def test_until
622     node = RubyVM::AbstractSyntaxTree.parse('1 until qux')
623     _, _, body = *node.children
624     assert_equal(:UNTIL, body.type)
625     type1 = body.children[2]
626     node = RubyVM::AbstractSyntaxTree.parse('begin 1 end until qux')
627     _, _, body = *node.children
628     assert_equal(:UNTIL, body.type)
629     type2 = body.children[2]
630     assert_not_equal(type1, type2)
631   end
633   def test_rest_arg
634     rest_arg = lambda do |arg_str|
635       node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
636       node = node.children.last.children.last.children[1].children[-4]
637     end
639     assert_equal(nil, rest_arg.call(''))
640     assert_equal(:r, rest_arg.call('*r'))
641     assert_equal(:r, rest_arg.call('a, *r'))
642     assert_equal(:*, rest_arg.call('*'))
643     assert_equal(:*, rest_arg.call('a, *'))
644   end
646   def test_block_arg
647     block_arg = lambda do |arg_str|
648       node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
649       node = node.children.last.children.last.children[1].children[-1]
650     end
652     assert_equal(nil, block_arg.call(''))
653     assert_equal(:block, block_arg.call('&block'))
654     assert_equal(:&, block_arg.call('&'))
655   end
657   def test_keyword_rest
658     kwrest = lambda do |arg_str|
659       node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
660       node = node.children.last.children.last.children[1].children[-2]
661       node ? node.children : node
662     end
664     assert_equal(nil, kwrest.call(''))
665     assert_equal([:**], kwrest.call('**'))
666     assert_equal(false, kwrest.call('**nil'))
667     assert_equal([:a], kwrest.call('**a'))
668   end
670   def test_argument_forwarding
671     forwarding = lambda do |arg_str|
672       node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
673       node = node.children.last.children.last.children[1]
674       node ? [node.children[-4], node.children[-2]&.children, node.children[-1]] : []
675     end
677     assert_equal([:*, [:**], :&], forwarding.call('...'))
678   end
680   def test_ranges_numbered_parameter
681     helper = Helper.new(__FILE__, src: "1.times {_1}")
682     helper.validate_range
683     assert_equal([], helper.errors)
684   end
686   def test_op_asgn2
687     node = RubyVM::AbstractSyntaxTree.parse("struct.field += foo")
688     _, _, body = *node.children
689     assert_equal(:OP_ASGN2, body.type)
690     recv, _, mid, op, value = body.children
691     assert_equal(:VCALL, recv.type)
692     assert_equal(:field, mid)
693     assert_equal(:+, op)
694     assert_equal(:VCALL, value.type)
695   end
697   def test_args
698     rest = 6
699     node = RubyVM::AbstractSyntaxTree.parse("proc { |a| }")
700     _, args = *node.children.last.children[1].children
701     assert_equal(nil, args.children[rest])
703     node = RubyVM::AbstractSyntaxTree.parse("proc { |a,| }")
704     _, args = *node.children.last.children[1].children
705     assert_equal(:NODE_SPECIAL_EXCESSIVE_COMMA, args.children[rest])
707     node = RubyVM::AbstractSyntaxTree.parse("proc { |*a| }")
708     _, args = *node.children.last.children[1].children
709     assert_equal(:a, args.children[rest])
710   end
712   def test_return
713     assert_ast_eqaul(<<~STR, <<~EXP)
714       def m(a)
715         return a
716       end
717     STR
718       (SCOPE@1:0-3:3
719        tbl: []
720        args: nil
721        body:
722          (DEFN@1:0-3:3
723           mid: :m
724           body:
725             (SCOPE@1:0-3:3
726              tbl: [:a]
727              args:
728                (ARGS@1:6-1:7
729                 pre_num: 1
730                 pre_init: nil
731                 opt: nil
732                 first_post: nil
733                 post_num: 0
734                 post_init: nil
735                 rest: nil
736                 kw: nil
737                 kwrest: nil
738                 block: nil)
739              body: (RETURN@2:2-2:10 (LVAR@2:9-2:10 :a)))))
740     EXP
741   end
743   def test_keep_script_lines_for_parse
744     node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_script_lines: true)
745 1.times do
746   2.times do
747   end
749 __END__
750 dummy
751     END
753     expected = [
754       "1.times do\n",
755       "  2.times do\n",
756       "  end\n",
757       "end\n",
758       "__END__\n",
759     ]
760     assert_equal(expected, node.script_lines)
762     expected =
763       "1.times do\n" +
764       "  2.times do\n" +
765       "  end\n" +
766       "end"
767     assert_equal(expected, node.source)
769     expected =
770              "do\n" +
771       "  2.times do\n" +
772       "  end\n" +
773       "end"
774     assert_equal(expected, node.children.last.children.last.source)
776     expected =
777         "2.times do\n" +
778       "  end"
779     assert_equal(expected, node.children.last.children.last.children.last.source)
780   end
782   def test_keep_script_lines_for_of
783     omit if ParserSupport.prism_enabled?
785     proc = Proc.new { 1 + 2 }
786     method = self.method(__method__)
788     node_proc = RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: true)
789     node_method = RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: true)
791     assert_equal("{ 1 + 2 }", node_proc.source)
792     assert_equal("def test_keep_script_lines_for_of\n", node_method.source.lines.first)
793   end
795   def test_keep_script_lines_for_of_with_existing_SCRIPT_LINES__that_has__FILE__as_a_key
796     omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
798     # This test confirms that the bug that previously occurred because of
799     # `AbstractSyntaxTree.of`s unnecessary dependence on SCRIPT_LINES__ does not reproduce.
800     # The bug occurred only if SCRIPT_LINES__ included __FILE__ as a key.
801     lines = [
802       "SCRIPT_LINES__ = {__FILE__ => []}",
803       "puts RubyVM::AbstractSyntaxTree.of(->{ 1 + 2 }, keep_script_lines: true).script_lines",
804       "p SCRIPT_LINES__"
805     ]
806     test_stdout = lines + ['{"-e"=>[]}']
807     assert_in_out_err(["-e", lines.join("\n")], "", test_stdout, [])
808   end
810   def test_source_with_multibyte_characters
811     ast = RubyVM::AbstractSyntaxTree.parse(%{a("\u00a7");b("\u00a9")}, keep_script_lines: true)
812     a_fcall, b_fcall = ast.children[2].children
814     assert_equal(%{a("\u00a7")}, a_fcall.source)
815     assert_equal(%{b("\u00a9")}, b_fcall.source)
816   end
818   def test_keep_tokens_for_parse
819     node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_tokens: true)
820     1.times do
821     end
822     __END__
823     dummy
824     END
826     expected = [
827       [:tINTEGER, "1"],
828       [:".", "."],
829       [:tIDENTIFIER, "times"],
830       [:tSP, " "],
831       [:keyword_do, "do"],
832       [:tIGNORED_NL, "\n"],
833       [:keyword_end, "end"],
834       [:nl, "\n"],
835     ]
836     assert_equal(expected, node.all_tokens.map { [_2, _3]})
837   end
839   def test_keep_tokens_unexpected_backslash
840     assert_raise_with_message(SyntaxError, /unexpected backslash/) do
841       RubyVM::AbstractSyntaxTree.parse("\\", keep_tokens: true)
842     end
843   end
845   def test_encoding_with_keep_script_lines
846     # Stop a warning "possibly useless use of a literal in void context"
847     verbose_bak, $VERBOSE = $VERBOSE, nil
849     enc = Encoding::EUC_JP
850     code = "__ENCODING__".encode(enc)
852     assert_equal(enc, eval(code))
854     node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: false)
855     assert_equal(enc, node.children[2].children[0])
857     node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: true)
858     assert_equal(enc, node.children[2].children[0])
860   ensure
861     $VERBOSE = verbose_bak
862   end
864   def test_e_option
865     omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
867     assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
868                       "", [":SCOPE"], [])
869   end
871   def test_error_tolerant
872     verbose_bak, $VERBOSE = $VERBOSE, false
873     node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true)
874       class A
875         def m
876           if;
877           a = 10
878         end
879       end
880     STR
881     assert_nil($!)
883     assert_equal(:SCOPE, node.type)
884   ensure
885     $VERBOSE = verbose_bak
886   end
888   def test_error_tolerant_end_is_short_for_method_define
889     assert_error_tolerant(<<~STR, <<~EXP)
890       def m
891         m2
892     STR
893       (SCOPE@1:0-2:4
894        tbl: []
895        args: nil
896        body:
897          (DEFN@1:0-2:4
898           mid: :m
899           body:
900             (SCOPE@1:0-2:4
901              tbl: []
902              args:
903                (ARGS@1:5-1:5
904                 pre_num: 0
905                 pre_init: nil
906                 opt: nil
907                 first_post: nil
908                 post_num: 0
909                 post_init: nil
910                 rest: nil
911                 kw: nil
912                 kwrest: nil
913                 block: nil)
914              body: (VCALL@2:2-2:4 :m2))))
915     EXP
916   end
918   def test_error_tolerant_end_is_short_for_singleton_method_define
919     assert_error_tolerant(<<~STR, <<~EXP)
920       def obj.m
921         m2
922     STR
923       (SCOPE@1:0-2:4
924        tbl: []
925        args: nil
926        body:
927          (DEFS@1:0-2:4 (VCALL@1:4-1:7 :obj) :m
928             (SCOPE@1:0-2:4
929              tbl: []
930              args:
931                (ARGS@1:9-1:9
932                 pre_num: 0
933                 pre_init: nil
934                 opt: nil
935                 first_post: nil
936                 post_num: 0
937                 post_init: nil
938                 rest: nil
939                 kw: nil
940                 kwrest: nil
941                 block: nil)
942              body: (VCALL@2:2-2:4 :m2))))
943     EXP
944   end
946   def test_error_tolerant_end_is_short_for_begin
947     assert_error_tolerant(<<~STR, <<~EXP)
948       begin
949         a = 1
950     STR
951       (SCOPE@1:0-2:7 tbl: [:a] args: nil body: (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)))
952     EXP
953   end
955   def test_error_tolerant_end_is_short_for_if
956     assert_error_tolerant(<<~STR, <<~EXP)
957       if cond
958         a = 1
959     STR
960       (SCOPE@1:0-2:7
961        tbl: [:a]
962        args: nil
963        body:
964          (IF@1:0-2:7 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
965             nil))
966     EXP
968     assert_error_tolerant(<<~STR, <<~EXP)
969       if cond
970         a = 1
971       else
972     STR
973       (SCOPE@1:0-3:4
974        tbl: [:a]
975        args: nil
976        body:
977          (IF@1:0-3:4 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
978             (BEGIN@3:4-3:4 nil)))
979     EXP
980   end
982   def test_error_tolerant_end_is_short_for_unless
983     assert_error_tolerant(<<~STR, <<~EXP)
984       unless cond
985         a = 1
986     STR
987       (SCOPE@1:0-2:7
988        tbl: [:a]
989        args: nil
990        body:
991          (UNLESS@1:0-2:7 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
992             nil))
993     EXP
995     assert_error_tolerant(<<~STR, <<~EXP)
996       unless cond
997         a = 1
998       else
999     STR
1000       (SCOPE@1:0-3:4
1001        tbl: [:a]
1002        args: nil
1003        body:
1004          (UNLESS@1:0-3:4 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
1005             (BEGIN@3:4-3:4 nil)))
1006     EXP
1007   end
1009   def test_error_tolerant_end_is_short_for_while
1010     assert_error_tolerant(<<~STR, <<~EXP)
1011       while true
1012         m
1013     STR
1014       (SCOPE@1:0-2:3
1015        tbl: []
1016        args: nil
1017        body: (WHILE@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
1018     EXP
1019   end
1021   def test_error_tolerant_end_is_short_for_until
1022     assert_error_tolerant(<<~STR, <<~EXP)
1023       until true
1024         m
1025     STR
1026       (SCOPE@1:0-2:3
1027        tbl: []
1028        args: nil
1029        body: (UNTIL@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
1030     EXP
1031   end
1033   def test_error_tolerant_end_is_short_for_case
1034     assert_error_tolerant(<<~STR, <<~EXP)
1035       case a
1036       when 1
1037     STR
1038       (SCOPE@1:0-2:6
1039        tbl: []
1040        args: nil
1041        body:
1042          (CASE@1:0-2:6 (VCALL@1:5-1:6 :a)
1043             (WHEN@2:0-2:6 (LIST@2:5-2:6 (INTEGER@2:5-2:6 1) nil) (BEGIN@2:6-2:6 nil)
1044                nil)))
1045     EXP
1048     assert_error_tolerant(<<~STR, <<~EXP)
1049       case
1050       when a == 1
1051     STR
1052       (SCOPE@1:0-2:11
1053        tbl: []
1054        args: nil
1055        body:
1056          (CASE2@1:0-2:11 nil
1057             (WHEN@2:0-2:11
1058                (LIST@2:5-2:11
1059                   (OPCALL@2:5-2:11 (VCALL@2:5-2:6 :a) :==
1060                      (LIST@2:10-2:11 (INTEGER@2:10-2:11 1) nil)) nil)
1061                (BEGIN@2:11-2:11 nil) nil)))
1062     EXP
1065     assert_error_tolerant(<<~STR, <<~EXP)
1066       case a
1067       in {a: String}
1068     STR
1069       (SCOPE@1:0-2:14
1070        tbl: []
1071        args: nil
1072        body:
1073          (CASE3@1:0-2:14 (VCALL@1:5-1:6 :a)
1074             (IN@2:0-2:14
1075                (HSHPTN@2:4-2:13
1076                 const: nil
1077                 kw:
1078                   (HASH@2:4-2:13
1079                      (LIST@2:4-2:13 (SYM@2:4-2:6 :a) (CONST@2:7-2:13 :String) nil))
1080                 kwrest: nil) (BEGIN@2:14-2:14 nil) nil)))
1081     EXP
1082   end
1084   def test_error_tolerant_end_is_short_for_for
1085     assert_error_tolerant(<<~STR, <<~EXP)
1086       for i in ary
1087         m
1088     STR
1089       (SCOPE@1:0-2:3
1090        tbl: [:i]
1091        args: nil
1092        body:
1093          (FOR@1:0-2:3 (VCALL@1:9-1:12 :ary)
1094             (SCOPE@1:0-2:3
1095              tbl: [nil]
1096              args:
1097                (ARGS@1:4-1:5
1098                 pre_num: 1
1099                 pre_init: (LASGN@1:4-1:5 :i (DVAR@1:4-1:5 nil))
1100                 opt: nil
1101                 first_post: nil
1102                 post_num: 0
1103                 post_init: nil
1104                 rest: nil
1105                 kw: nil
1106                 kwrest: nil
1107                 block: nil)
1108              body: (VCALL@2:2-2:3 :m))))
1109     EXP
1110   end
1112   def test_error_tolerant_end_is_short_for_class
1113     assert_error_tolerant(<<~STR, <<~EXP)
1114       class C
1115     STR
1116       (SCOPE@1:0-1:7
1117        tbl: []
1118        args: nil
1119        body:
1120          (CLASS@1:0-1:7 (COLON2@1:6-1:7 nil :C) nil
1121             (SCOPE@1:0-1:7 tbl: [] args: nil body: (BEGIN@1:7-1:7 nil))))
1122     EXP
1123   end
1125   def test_error_tolerant_end_is_short_for_module
1126     assert_error_tolerant(<<~STR, <<~EXP)
1127       module M
1128     STR
1129       (SCOPE@1:0-1:8
1130        tbl: []
1131        args: nil
1132        body:
1133          (MODULE@1:0-1:8 (COLON2@1:7-1:8 nil :M)
1134             (SCOPE@1:0-1:8 tbl: [] args: nil body: (BEGIN@1:8-1:8 nil))))
1135     EXP
1136   end
1138   def test_error_tolerant_end_is_short_for_do
1139     assert_error_tolerant(<<~STR, <<~EXP)
1140       m do
1141         a
1142     STR
1143       (SCOPE@1:0-2:3
1144        tbl: []
1145        args: nil
1146        body:
1147          (ITER@1:0-2:3 (FCALL@1:0-1:1 :m nil)
1148             (SCOPE@1:2-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
1149     EXP
1150   end
1152   def test_error_tolerant_end_is_short_for_do_block
1153     assert_error_tolerant(<<~STR, <<~EXP)
1154       m 1 do
1155         a
1156     STR
1157       (SCOPE@1:0-2:3
1158        tbl: []
1159        args: nil
1160        body:
1161          (ITER@1:0-2:3 (FCALL@1:0-1:3 :m (LIST@1:2-1:3 (INTEGER@1:2-1:3 1) nil))
1162             (SCOPE@1:4-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
1163     EXP
1164   end
1166   def test_error_tolerant_end_is_short_for_do_LAMBDA
1167     assert_error_tolerant(<<~STR, <<~EXP)
1168       -> do
1169         a
1170     STR
1171       (SCOPE@1:0-2:3
1172        tbl: []
1173        args: nil
1174        body:
1175          (LAMBDA@1:0-2:3
1176             (SCOPE@1:2-2:3
1177              tbl: []
1178              args:
1179                (ARGS@1:2-1:2
1180                 pre_num: 0
1181                 pre_init: nil
1182                 opt: nil
1183                 first_post: nil
1184                 post_num: 0
1185                 post_init: nil
1186                 rest: nil
1187                 kw: nil
1188                 kwrest: nil
1189                 block: nil)
1190              body: (VCALL@2:2-2:3 :a))))
1191     EXP
1192   end
1194   def test_error_tolerant_treat_end_as_keyword_based_on_indent
1195     assert_error_tolerant(<<~STR, <<~EXP)
1196       module Z
1197         class Foo
1198           foo.
1199         end
1201         def bar
1202         end
1203       end
1204     STR
1205       (SCOPE@1:0-8:3
1206        tbl: []
1207        args: nil
1208        body:
1209          (MODULE@1:0-8:3 (COLON2@1:7-1:8 nil :Z)
1210             (SCOPE@1:0-8:3
1211              tbl: []
1212              args: nil
1213              body:
1214                (BLOCK@1:8-7:5 (BEGIN@1:8-1:8 nil)
1215                   (CLASS@2:2-4:5 (COLON2@2:8-2:11 nil :Foo) nil
1216                      (SCOPE@2:2-4:5
1217                       tbl: []
1218                       args: nil
1219                       body: (BLOCK@2:11-4:5 (BEGIN@2:11-2:11 nil) (ERROR@3:4-4:5))))
1220                   (DEFN@6:2-7:5
1221                    mid: :bar
1222                    body:
1223                      (SCOPE@6:2-7:5
1224                       tbl: []
1225                       args:
1226                         (ARGS@6:9-6:9
1227                          pre_num: 0
1228                          pre_init: nil
1229                          opt: nil
1230                          first_post: nil
1231                          post_num: 0
1232                          post_init: nil
1233                          rest: nil
1234                          kw: nil
1235                          kwrest: nil
1236                          block: nil)
1237                       body: nil))))))
1238     EXP
1239   end
1241   def test_error_tolerant_expr_value_can_be_error
1242     assert_error_tolerant(<<~STR, <<~EXP)
1243       def m
1244         if
1245       end
1246     STR
1247       (SCOPE@1:0-3:3
1248        tbl: []
1249        args: nil
1250        body:
1251          (DEFN@1:0-3:3
1252           mid: :m
1253           body:
1254             (SCOPE@1:0-3:3
1255              tbl: []
1256              args:
1257                (ARGS@1:5-1:5
1258                 pre_num: 0
1259                 pre_init: nil
1260                 opt: nil
1261                 first_post: nil
1262                 post_num: 0
1263                 post_init: nil
1264                 rest: nil
1265                 kw: nil
1266                 kwrest: nil
1267                 block: nil)
1268              body: (IF@2:2-3:3 (ERROR@3:0-3:3) nil nil))))
1269     EXP
1270   end
1272   def test_error_tolerant_unexpected_backslash
1273     node = assert_error_tolerant("\\", <<~EXP, keep_tokens: true)
1274       (SCOPE@1:0-1:1 tbl: [] args: nil body: (ERROR@1:0-1:1))
1275     EXP
1276     assert_equal([[0, :backslash, "\\", [1, 0, 1, 1]]], node.children.last.tokens)
1277   end
1279   def test_with_bom
1280     assert_error_tolerant("\u{feff}nil", <<~EXP)
1281       (SCOPE@1:0-1:3 tbl: [] args: nil body: (NIL@1:0-1:3))
1282     EXP
1283   end
1285   def test_unused_block_local_variable
1286     assert_warning('') do
1287       RubyVM::AbstractSyntaxTree.parse(%{->(; foo) {}})
1288     end
1289   end
1291   def test_memory_leak
1292     assert_no_memory_leak([], "#{<<~"begin;"}", "\n#{<<~'end;'}", rss: true)
1293     begin;
1294       1_000_000.times do
1295         eval("")
1296       end
1297     end;
1298   end
1300   def test_locations
1301     begin
1302       verbose_bak, $VERBOSE = $VERBOSE, false
1303       node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
1304     ensure
1305       $VERBOSE = verbose_bak
1306     end
1307     locations = node.locations
1309     assert_equal(RubyVM::AbstractSyntaxTree::Location, locations[0].class)
1310   end
1312   private
1314   def assert_error_tolerant(src, expected, keep_tokens: false)
1315     assert_ast_eqaul(src, expected, error_tolerant: true, keep_tokens: keep_tokens)
1316   end
1318   def assert_ast_eqaul(src, expected, **options)
1319     begin
1320       verbose_bak, $VERBOSE = $VERBOSE, false
1321       node = RubyVM::AbstractSyntaxTree.parse(src, **options)
1322     ensure
1323       $VERBOSE = verbose_bak
1324     end
1325     assert_nil($!)
1326     str = ""
1327     PP.pp(node, str, 80)
1328     assert_equal(expected, str)
1329     node
1330   end
1332   class TestLocation < Test::Unit::TestCase
1333     def test_lineno_and_column
1334       node = ast_parse("1 + 2")
1335       assert_locations(node.locations, [[1, 0, 1, 5]])
1336     end
1338     def test_alias_locations
1339       node = ast_parse("alias foo bar")
1340       assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
1341     end
1343     def test_and_locations
1344       node = ast_parse("1 and 2")
1345       assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 2, 1, 5]])
1347       node = ast_parse("1 && 2")
1348       assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
1349     end
1351     def test_block_pass_locations
1352       node = ast_parse("foo(&bar)")
1353       assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 8], [1, 4, 1, 5]])
1355       node = ast_parse("def a(&); b(&) end")
1356       assert_locations(node.children[-1].children[-1].children[-1].children[-1].children[-1].locations, [[1, 12, 1, 13], [1, 12, 1, 13]])
1357     end
1359     def test_break_locations
1360       node = ast_parse("loop { break 1 }")
1361       assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 14], [1, 7, 1, 12]])
1362     end
1364     def test_case_locations
1365       node = ast_parse("case a; when 1; end")
1366       assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 4], [1, 16, 1, 19]])
1367     end
1369     def test_case2_locations
1370       node = ast_parse("case; when 1; end")
1371       assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]])
1372     end
1374     def test_case3_locations
1375       node = ast_parse("case a; in 1; end")
1376       assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]])
1377     end
1379     def test_next_locations
1380       node = ast_parse("loop { next 1 }")
1381       assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 13], [1, 7, 1, 11]])
1382     end
1384     def test_op_asgn1_locations
1385       node = ast_parse("ary[1] += foo")
1386       assert_locations(node.children[-1].locations, [[1, 0, 1, 13], nil, [1, 3, 1, 4], [1, 5, 1, 6], [1, 7, 1, 9]])
1388       node = ast_parse("ary[1, 2] += foo")
1389       assert_locations(node.children[-1].locations, [[1, 0, 1, 16], nil, [1, 3, 1, 4], [1, 8, 1, 9], [1, 10, 1, 12]])
1390     end
1392     def test_or_locations
1393       node = ast_parse("1 or 2")
1394       assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
1396       node = ast_parse("1 || 2")
1397       assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
1398     end
1400     def test_redo_locations
1401       node = ast_parse("loop { redo }")
1402       assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 11], [1, 7, 1, 11]])
1403     end
1405     def test_return_locations
1406       node = ast_parse("return 1")
1407       assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 6]])
1409       node = ast_parse("return")
1410       assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 6]])
1411     end
1413     def test_unless_locations
1414       node = ast_parse("unless cond then 1 else 2 end")
1415       assert_locations(node.children[-1].locations, [[1, 0, 1, 29], [1, 0, 1, 6], [1, 12, 1, 16], [1, 26, 1, 29]])
1417       node = ast_parse("1 unless 2")
1418       assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 2, 1, 8], nil, nil])
1419     end
1421     def test_undef_locations
1422       node = ast_parse("undef foo")
1423       assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 5]])
1425       node = ast_parse("undef foo, bar")
1426       assert_locations(node.children[-1].locations, [[1, 0, 1, 14], [1, 0, 1, 5]])
1427     end
1429     def test_valias_locations
1430       node = ast_parse("alias $foo $bar")
1431       assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 5]])
1433       node = ast_parse("alias $foo $&")
1434       assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
1435     end
1437     def test_when_locations
1438       node = ast_parse("case a; when 1 then 2; end")
1439       assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 22], [1, 8, 1, 12], [1, 15, 1, 19]])
1440     end
1442     def test_while_locations
1443       node = ast_parse("while cond do 1 end")
1444       assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]])
1446       node = ast_parse("1 while 2")
1447       assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil])
1448     end
1450     def test_until_locations
1451       node = ast_parse("until cond do 1 end")
1452       assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]])
1454       node = ast_parse("1 until 2")
1455       assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil])
1456     end
1458     private
1459     def ast_parse(src, **options)
1460       begin
1461         verbose_bak, $VERBOSE = $VERBOSE, false
1462         RubyVM::AbstractSyntaxTree.parse(src, **options)
1463       ensure
1464         $VERBOSE = verbose_bak
1465       end
1466     end
1468     def assert_locations(locations, expected)
1469       ary = locations.map {|loc| loc && [loc.first_lineno, loc.first_column, loc.last_lineno, loc.last_column] }
1471       assert_equal(ary, expected)
1472     end
1473   end