mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00

Add locations to struct `RNode_IN`. memo: ```bash > ruby -e 'case 1; in 2 then 3; end' --parser=prism --dump=parsetree @ ProgramNode (location: (1,0)-(1,24)) +-- locals: [] +-- statements: @ StatementsNode (location: (1,0)-(1,24)) +-- body: (length: 1) +-- @ CaseMatchNode (location: (1,0)-(1,24)) +-- predicate: | @ IntegerNode (location: (1,5)-(1,6)) | +-- IntegerBaseFlags: decimal | +-- value: 1 +-- conditions: (length: 1) | +-- @ InNode (location: (1,8)-(1,19)) | +-- pattern: | | @ IntegerNode (location: (1,11)-(1,12)) | | +-- IntegerBaseFlags: decimal | | +-- value: 2 | +-- statements: | | @ StatementsNode (location: (1,18)-(1,19)) | | +-- body: (length: 1) | | +-- @ IntegerNode (location: (1,18)-(1,19)) | | +-- IntegerBaseFlags: decimal | | +-- value: 3 | +-- in_loc: (1,8)-(1,10) = "in" | +-- then_loc: (1,13)-(1,17) = "then" +-- else_clause: nil +-- case_keyword_loc: (1,0)-(1,4) = "case" +-- end_keyword_loc: (1,21)-(1,24) = "end" ```
1701 lines
52 KiB
Ruby
1701 lines
52 KiB
Ruby
# frozen_string_literal: false
|
|
require 'test/unit'
|
|
require 'tempfile'
|
|
require 'pp'
|
|
require_relative '../lib/parser_support'
|
|
|
|
class RubyVM
|
|
module AbstractSyntaxTree
|
|
class Node
|
|
class CodePosition
|
|
include Comparable
|
|
attr_reader :lineno, :column
|
|
def initialize(lineno, column)
|
|
@lineno = lineno
|
|
@column = column
|
|
end
|
|
|
|
def <=>(other)
|
|
case
|
|
when lineno < other.lineno
|
|
-1
|
|
when lineno == other.lineno
|
|
column <=> other.column
|
|
when lineno > other.lineno
|
|
1
|
|
end
|
|
end
|
|
end
|
|
|
|
def beg_pos
|
|
CodePosition.new(first_lineno, first_column)
|
|
end
|
|
|
|
def end_pos
|
|
CodePosition.new(last_lineno, last_column)
|
|
end
|
|
|
|
alias to_s inspect
|
|
end
|
|
end
|
|
end
|
|
|
|
class TestAst < Test::Unit::TestCase
|
|
class Helper
|
|
attr_reader :errors
|
|
|
|
def initialize(path, src: nil)
|
|
@path = path
|
|
@errors = []
|
|
@debug = false
|
|
@ast = RubyVM::AbstractSyntaxTree.parse(src) if src
|
|
end
|
|
|
|
def validate_range
|
|
@errors = []
|
|
validate_range0(ast)
|
|
|
|
@errors.empty?
|
|
end
|
|
|
|
def validate_not_cared
|
|
@errors = []
|
|
validate_not_cared0(ast)
|
|
|
|
@errors.empty?
|
|
end
|
|
|
|
def ast
|
|
return @ast if defined?(@ast)
|
|
@ast = RubyVM::AbstractSyntaxTree.parse_file(@path)
|
|
end
|
|
|
|
private
|
|
|
|
def validate_range0(node)
|
|
beg_pos, end_pos = node.beg_pos, node.end_pos
|
|
children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
|
|
|
|
return true if children.empty?
|
|
# These NODE_D* has NODE_LIST as nd_next->nd_next whose last locations
|
|
# we can not update when item is appended.
|
|
return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type
|
|
|
|
min = children.map(&:beg_pos).min
|
|
max = children.map(&:end_pos).max
|
|
|
|
unless beg_pos <= min
|
|
@errors << { type: :min_validation_error, min: min, beg_pos: beg_pos, node: node }
|
|
end
|
|
|
|
unless max <= end_pos
|
|
@errors << { type: :max_validation_error, max: max, end_pos: end_pos, node: node }
|
|
end
|
|
|
|
p "#{node} => #{children}" if @debug
|
|
|
|
children.each do |child|
|
|
p child if @debug
|
|
validate_range0(child)
|
|
end
|
|
end
|
|
|
|
def validate_not_cared0(node)
|
|
beg_pos, end_pos = node.beg_pos, node.end_pos
|
|
children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
|
|
|
|
@errors << { type: :first_lineno, node: node } if beg_pos.lineno == 0
|
|
@errors << { type: :first_column, node: node } if beg_pos.column == -1
|
|
@errors << { type: :last_lineno, node: node } if end_pos.lineno == 0
|
|
@errors << { type: :last_column, node: node } if end_pos.column == -1
|
|
|
|
children.each {|c| validate_not_cared0(c) }
|
|
end
|
|
end
|
|
|
|
SRCDIR = File.expand_path("../../..", __FILE__)
|
|
|
|
Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
|
|
define_method("test_ranges:#{path}") do
|
|
helper = Helper.new("#{SRCDIR}/#{path}")
|
|
helper.validate_range
|
|
|
|
assert_equal([], helper.errors)
|
|
end
|
|
end
|
|
|
|
Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
|
|
define_method("test_not_cared:#{path}") do
|
|
helper = Helper.new("#{SRCDIR}/#{path}")
|
|
helper.validate_not_cared
|
|
|
|
assert_equal([], helper.errors)
|
|
end
|
|
end
|
|
|
|
Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
|
|
define_method("test_all_tokens:#{path}") do
|
|
node = RubyVM::AbstractSyntaxTree.parse_file("#{SRCDIR}/#{path}", keep_tokens: true)
|
|
tokens = node.all_tokens.sort_by { [_1.last[0], _1.last[1]] }
|
|
tokens_bytes = tokens.map { _1[2]}.join.bytes
|
|
source_bytes = File.read("#{SRCDIR}/#{path}").bytes
|
|
|
|
assert_equal(source_bytes, tokens_bytes)
|
|
|
|
(tokens.count - 1).times do |i|
|
|
token_0 = tokens[i]
|
|
token_1 = tokens[i + 1]
|
|
end_pos = token_0.last[2..3]
|
|
beg_pos = token_1.last[0..1]
|
|
|
|
if end_pos[0] == beg_pos[0]
|
|
# When both tokens are same line, column should be consecutives
|
|
assert_equal(beg_pos[1], end_pos[1], "#{token_0}. #{token_1}")
|
|
else
|
|
# Line should be next
|
|
assert_equal(beg_pos[0], end_pos[0] + 1, "#{token_0}. #{token_1}")
|
|
# It should be on the beginning of the line
|
|
assert_equal(0, beg_pos[1], "#{token_0}. #{token_1}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
private def parse(src)
|
|
EnvUtil.suppress_warning {
|
|
RubyVM::AbstractSyntaxTree.parse(src)
|
|
}
|
|
end
|
|
|
|
def test_allocate
|
|
assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate}
|
|
end
|
|
|
|
def test_parse_argument_error
|
|
assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(0)}
|
|
assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(nil)}
|
|
assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(false)}
|
|
assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(true)}
|
|
assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(:foo)}
|
|
end
|
|
|
|
def test_column_with_long_heredoc_identifier
|
|
term = "A"*257
|
|
ast = parse("<<-#{term}\n""ddddddd\n#{term}\n")
|
|
node = ast.children[2]
|
|
assert_equal(:STR, node.type)
|
|
assert_equal(0, node.first_column)
|
|
end
|
|
|
|
def test_column_of_heredoc
|
|
node = parse("<<-SRC\nddddddd\nSRC\n").children[2]
|
|
assert_equal(:STR, node.type)
|
|
assert_equal(0, node.first_column)
|
|
assert_equal(6, node.last_column)
|
|
|
|
node = parse("<<SRC\nddddddd\nSRC\n").children[2]
|
|
assert_equal(:STR, node.type)
|
|
assert_equal(0, node.first_column)
|
|
assert_equal(5, node.last_column)
|
|
end
|
|
|
|
def test_parse_raises_syntax_error
|
|
assert_raise_with_message(SyntaxError, /\bend\b/) do
|
|
RubyVM::AbstractSyntaxTree.parse("end")
|
|
end
|
|
end
|
|
|
|
def test_parse_file_raises_syntax_error
|
|
Tempfile.create(%w"test_ast .rb") do |f|
|
|
f.puts "end"
|
|
f.close
|
|
assert_raise_with_message(SyntaxError, /\bend\b/) do
|
|
RubyVM::AbstractSyntaxTree.parse_file(f.path)
|
|
end
|
|
end
|
|
end
|
|
|
|
def assert_parse(code, warning: '')
|
|
node = assert_warning(warning) {RubyVM::AbstractSyntaxTree.parse(code)}
|
|
assert_kind_of(RubyVM::AbstractSyntaxTree::Node, node, code)
|
|
end
|
|
|
|
def assert_invalid_parse(msg, code)
|
|
assert_raise_with_message(SyntaxError, msg, code) do
|
|
RubyVM::AbstractSyntaxTree.parse(code)
|
|
end
|
|
end
|
|
|
|
def test_invalid_exit
|
|
[
|
|
"break",
|
|
"break true",
|
|
"next",
|
|
"next true",
|
|
"redo",
|
|
].each do |code, *args|
|
|
msg = /Invalid #{code[/\A\w+/]}/
|
|
assert_parse("while false; #{code}; end")
|
|
assert_parse("until true; #{code}; end")
|
|
assert_parse("begin #{code}; end while false")
|
|
assert_parse("begin #{code}; end until true")
|
|
assert_parse("->{#{code}}")
|
|
assert_parse("->{class X; #{code}; end}")
|
|
assert_invalid_parse(msg, "#{code}")
|
|
assert_invalid_parse(msg, "def m; #{code}; end")
|
|
assert_invalid_parse(msg, "begin; #{code}; end")
|
|
assert_parse("END {#{code}}")
|
|
|
|
assert_parse("!defined?(#{code})")
|
|
assert_parse("def m; defined?(#{code}); end")
|
|
assert_parse("!begin; defined?(#{code}); end")
|
|
|
|
next if code.include?(" ")
|
|
assert_parse("!defined? #{code}")
|
|
assert_parse("def m; defined? #{code}; end")
|
|
assert_parse("!begin; defined? #{code}; end")
|
|
end
|
|
end
|
|
|
|
def test_invalid_retry
|
|
msg = /Invalid retry/
|
|
assert_invalid_parse(msg, "retry")
|
|
assert_invalid_parse(msg, "def m; retry; end")
|
|
assert_invalid_parse(msg, "begin retry; end")
|
|
assert_parse("begin rescue; retry; end")
|
|
assert_invalid_parse(msg, "begin rescue; else; retry; end")
|
|
assert_invalid_parse(msg, "begin rescue; ensure; retry; end")
|
|
assert_parse("nil rescue retry")
|
|
assert_invalid_parse(msg, "END {retry}")
|
|
assert_invalid_parse(msg, "begin rescue; END {retry}; end")
|
|
|
|
assert_parse("!defined?(retry)")
|
|
assert_parse("def m; defined?(retry); end")
|
|
assert_parse("!begin defined?(retry); end")
|
|
assert_parse("begin rescue; else; defined?(retry); end")
|
|
assert_parse("begin rescue; ensure; p defined?(retry); end")
|
|
assert_parse("END {defined?(retry)}")
|
|
assert_parse("begin rescue; END {defined?(retry)}; end")
|
|
assert_parse("!defined? retry")
|
|
|
|
assert_parse("def m; defined? retry; end")
|
|
assert_parse("!begin defined? retry; end")
|
|
assert_parse("begin rescue; else; defined? retry; end")
|
|
assert_parse("begin rescue; ensure; p defined? retry; end")
|
|
assert_parse("END {defined? retry}")
|
|
assert_parse("begin rescue; END {defined? retry}; end")
|
|
|
|
assert_parse("#{<<-"begin;"}\n#{<<-'end;'}")
|
|
begin;
|
|
def foo
|
|
begin
|
|
yield
|
|
rescue StandardError => e
|
|
begin
|
|
puts "hi"
|
|
retry
|
|
rescue
|
|
retry unless e
|
|
raise e
|
|
else
|
|
retry
|
|
ensure
|
|
retry
|
|
end
|
|
end
|
|
end
|
|
end;
|
|
end
|
|
|
|
def test_invalid_yield
|
|
msg = /Invalid yield/
|
|
assert_invalid_parse(msg, "yield")
|
|
assert_invalid_parse(msg, "class C; yield; end")
|
|
assert_invalid_parse(msg, "BEGIN {yield}")
|
|
assert_invalid_parse(msg, "END {yield}")
|
|
assert_invalid_parse(msg, "-> {yield}")
|
|
|
|
assert_invalid_parse(msg, "yield true")
|
|
assert_invalid_parse(msg, "class C; yield true; end")
|
|
assert_invalid_parse(msg, "BEGIN {yield true}")
|
|
assert_invalid_parse(msg, "END {yield true}")
|
|
assert_invalid_parse(msg, "-> {yield true}")
|
|
|
|
assert_parse("!defined?(yield)")
|
|
assert_parse("class C; defined?(yield); end")
|
|
assert_parse("BEGIN {defined?(yield)}")
|
|
assert_parse("END {defined?(yield)}")
|
|
|
|
assert_parse("!defined?(yield true)")
|
|
assert_parse("class C; defined?(yield true); end")
|
|
assert_parse("BEGIN {defined?(yield true)}")
|
|
assert_parse("END {defined?(yield true)}")
|
|
|
|
assert_parse("!defined? yield")
|
|
assert_parse("class C; defined? yield; end")
|
|
assert_parse("BEGIN {defined? yield}")
|
|
assert_parse("END {defined? yield}")
|
|
end
|
|
|
|
def test_invalid_yield_no_memory_leak
|
|
# [Bug #21383]
|
|
assert_no_memory_leak([], "#{<<-"begin;"}", "#{<<-'end;'}", rss: true)
|
|
code = proc do
|
|
eval("class C; yield; end")
|
|
rescue SyntaxError
|
|
end
|
|
1_000.times(&code)
|
|
begin;
|
|
100_000.times(&code)
|
|
end;
|
|
end
|
|
|
|
def test_node_id_for_location
|
|
omit if ParserSupport.prism_enabled?
|
|
|
|
exception = begin
|
|
raise
|
|
rescue => e
|
|
e
|
|
end
|
|
loc = exception.backtrace_locations.first
|
|
node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(loc)
|
|
node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
|
|
|
|
assert_equal node.node_id, node_id
|
|
end
|
|
|
|
def test_node_id_for_backtrace_location_raises_argument_error
|
|
bug19262 = '[ruby-core:111435]'
|
|
|
|
assert_raise(TypeError, bug19262) { RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(1) }
|
|
end
|
|
|
|
def test_of_proc_and_method
|
|
omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
|
|
|
|
proc = Proc.new { 1 + 2 }
|
|
method = self.method(__method__)
|
|
|
|
node_proc = RubyVM::AbstractSyntaxTree.of(proc)
|
|
node_method = RubyVM::AbstractSyntaxTree.of(method)
|
|
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc)
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method)
|
|
|
|
Tempfile.create(%w"test_of .rb") do |tmp|
|
|
tmp.print "#{<<-"begin;"}\n#{<<-'end;'}"
|
|
begin;
|
|
SCRIPT_LINES__ = {}
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(proc {|x| x}))
|
|
end;
|
|
tmp.close
|
|
assert_separately(["-", tmp.path], "#{<<~"begin;"}\n#{<<~'end;'}")
|
|
begin;
|
|
load ARGV[0]
|
|
assert_empty(SCRIPT_LINES__)
|
|
end;
|
|
end
|
|
end
|
|
|
|
def sample_backtrace_location
|
|
[caller_locations(0).first, __LINE__]
|
|
end
|
|
|
|
def test_of_backtrace_location
|
|
omit if ParserSupport.prism_enabled?
|
|
|
|
backtrace_location, lineno = sample_backtrace_location
|
|
node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
|
|
assert_equal(lineno, node.first_lineno)
|
|
end
|
|
|
|
def test_of_error
|
|
assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") }
|
|
end
|
|
|
|
def test_of_proc_and_method_under_eval
|
|
omit if ParserSupport.prism_enabled?
|
|
|
|
keep_script_lines_back = RubyVM.keep_script_lines
|
|
RubyVM.keep_script_lines = false
|
|
|
|
method = self.method(eval("def example_method_#{$$}; end"))
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
|
|
|
|
method = self.method(eval("def self.example_singleton_method_#{$$}; end"))
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
|
|
|
|
method = eval("proc{}")
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
|
|
|
|
method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}){}"))
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
|
|
|
|
method = self.method(eval("define_singleton_method(:example_dsm_#{$$}){}"))
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
|
|
|
|
method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
|
|
|
|
method = eval("Class.new{def example_method; end}.instance_method(:example_method)")
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) }
|
|
|
|
ensure
|
|
RubyVM.keep_script_lines = keep_script_lines_back
|
|
end
|
|
|
|
def test_of_proc_and_method_under_eval_with_keep_script_lines
|
|
omit if ParserSupport.prism_enabled?
|
|
pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
|
|
|
|
keep_script_lines_back = RubyVM.keep_script_lines
|
|
RubyVM.keep_script_lines = true
|
|
|
|
method = self.method(eval("def example_method_#{$$}_with_keep_script_lines; end"))
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
|
|
|
|
method = self.method(eval("def self.example_singleton_method_#{$$}_with_keep_script_lines; end"))
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
|
|
|
|
method = eval("proc{}")
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
|
|
|
|
method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}_with_keep_script_lines){}"))
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
|
|
|
|
method = self.method(eval("define_singleton_method(:example_dsm_#{$$}_with_keep_script_lines){}"))
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
|
|
|
|
method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
|
|
|
|
method = eval("Class.new{def example_method_with_keep_script_lines; end}.instance_method(:example_method_with_keep_script_lines)")
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(method))
|
|
|
|
ensure
|
|
RubyVM.keep_script_lines = keep_script_lines_back
|
|
end
|
|
|
|
def test_of_backtrace_location_under_eval
|
|
omit if ParserSupport.prism_enabled?
|
|
|
|
keep_script_lines_back = RubyVM.keep_script_lines
|
|
RubyVM.keep_script_lines = false
|
|
|
|
m = Module.new do
|
|
eval(<<-END, nil, __FILE__, __LINE__)
|
|
def self.sample_backtrace_location
|
|
caller_locations(0).first
|
|
end
|
|
END
|
|
end
|
|
backtrace_location = m.sample_backtrace_location
|
|
assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(backtrace_location) }
|
|
|
|
ensure
|
|
RubyVM.keep_script_lines = keep_script_lines_back
|
|
end
|
|
|
|
def test_of_backtrace_location_under_eval_with_keep_script_lines
|
|
omit if ParserSupport.prism_enabled?
|
|
pend if ENV['RUBY_ISEQ_DUMP_DEBUG'] # TODO
|
|
|
|
keep_script_lines_back = RubyVM.keep_script_lines
|
|
RubyVM.keep_script_lines = true
|
|
|
|
m = Module.new do
|
|
eval(<<-END, nil, __FILE__, __LINE__)
|
|
def self.sample_backtrace_location
|
|
caller_locations(0).first
|
|
end
|
|
END
|
|
end
|
|
backtrace_location = m.sample_backtrace_location
|
|
node = RubyVM::AbstractSyntaxTree.of(backtrace_location)
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node)
|
|
assert_equal(2, node.first_lineno)
|
|
|
|
ensure
|
|
RubyVM.keep_script_lines = keep_script_lines_back
|
|
end
|
|
|
|
def test_of_c_method
|
|
c = Class.new { attr_reader :foo }
|
|
assert_nil(RubyVM::AbstractSyntaxTree.of(c.instance_method(:foo)))
|
|
end
|
|
|
|
def test_scope_local_variables
|
|
node = RubyVM::AbstractSyntaxTree.parse("_x = 0")
|
|
lv, _, body = *node.children
|
|
assert_equal([:_x], lv)
|
|
assert_equal(:LASGN, body.type)
|
|
end
|
|
|
|
def test_call
|
|
node = RubyVM::AbstractSyntaxTree.parse("nil.foo")
|
|
_, _, body = *node.children
|
|
assert_equal(:CALL, body.type)
|
|
recv, mid, args = body.children
|
|
assert_equal(:NIL, recv.type)
|
|
assert_equal(:foo, mid)
|
|
assert_nil(args)
|
|
end
|
|
|
|
def test_fcall
|
|
node = RubyVM::AbstractSyntaxTree.parse("foo()")
|
|
_, _, body = *node.children
|
|
assert_equal(:FCALL, body.type)
|
|
mid, args = body.children
|
|
assert_equal(:foo, mid)
|
|
assert_nil(args)
|
|
end
|
|
|
|
def test_vcall
|
|
node = RubyVM::AbstractSyntaxTree.parse("foo")
|
|
_, _, body = *node.children
|
|
assert_equal(:VCALL, body.type)
|
|
mid, args = body.children
|
|
assert_equal(:foo, mid)
|
|
assert_nil(args)
|
|
end
|
|
|
|
def test_defn
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a; end")
|
|
_, _, body = *node.children
|
|
assert_equal(:DEFN, body.type)
|
|
mid, defn = body.children
|
|
assert_equal(:a, mid)
|
|
assert_equal(:SCOPE, defn.type)
|
|
_, args, = defn.children
|
|
assert_equal(:ARGS, args.type)
|
|
end
|
|
|
|
def test_defn_endless
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a = nil")
|
|
_, _, body = *node.children
|
|
assert_equal(:DEFN, body.type)
|
|
mid, defn = body.children
|
|
assert_equal(:a, mid)
|
|
assert_equal(:SCOPE, defn.type)
|
|
_, args, = defn.children
|
|
assert_equal(:ARGS, args.type)
|
|
end
|
|
|
|
def test_defs
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a.b; end")
|
|
_, _, body = *node.children
|
|
assert_equal(:DEFS, body.type)
|
|
recv, mid, defn = body.children
|
|
assert_equal(:VCALL, recv.type)
|
|
assert_equal(:b, mid)
|
|
assert_equal(:SCOPE, defn.type)
|
|
_, args, = defn.children
|
|
assert_equal(:ARGS, args.type)
|
|
end
|
|
|
|
def test_defs_endless
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a.b = nil")
|
|
_, _, body = *node.children
|
|
assert_equal(:DEFS, body.type)
|
|
recv, mid, defn = body.children
|
|
assert_equal(:VCALL, recv.type)
|
|
assert_equal(:b, mid)
|
|
assert_equal(:SCOPE, defn.type)
|
|
_, args, = defn.children
|
|
assert_equal(:ARGS, args.type)
|
|
end
|
|
|
|
def test_dstr
|
|
node = parse('"foo#{1}bar"')
|
|
_, _, body = *node.children
|
|
assert_equal(:DSTR, body.type)
|
|
head, body = body.children
|
|
assert_equal("foo", head)
|
|
assert_equal(:EVSTR, body.type)
|
|
body, = body.children
|
|
assert_equal(:INTEGER, body.type)
|
|
assert_equal([1], body.children)
|
|
end
|
|
|
|
def test_while
|
|
node = RubyVM::AbstractSyntaxTree.parse('1 while qux')
|
|
_, _, body = *node.children
|
|
assert_equal(:WHILE, body.type)
|
|
type1 = body.children[2]
|
|
node = RubyVM::AbstractSyntaxTree.parse('begin 1 end while qux')
|
|
_, _, body = *node.children
|
|
assert_equal(:WHILE, body.type)
|
|
type2 = body.children[2]
|
|
assert_not_equal(type1, type2)
|
|
end
|
|
|
|
def test_until
|
|
node = RubyVM::AbstractSyntaxTree.parse('1 until qux')
|
|
_, _, body = *node.children
|
|
assert_equal(:UNTIL, body.type)
|
|
type1 = body.children[2]
|
|
node = RubyVM::AbstractSyntaxTree.parse('begin 1 end until qux')
|
|
_, _, body = *node.children
|
|
assert_equal(:UNTIL, body.type)
|
|
type2 = body.children[2]
|
|
assert_not_equal(type1, type2)
|
|
end
|
|
|
|
def test_rest_arg
|
|
rest_arg = lambda do |arg_str|
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
|
|
node = node.children.last.children.last.children[1].children[-4]
|
|
end
|
|
|
|
assert_equal(nil, rest_arg.call(''))
|
|
assert_equal(:r, rest_arg.call('*r'))
|
|
assert_equal(:r, rest_arg.call('a, *r'))
|
|
assert_equal(:*, rest_arg.call('*'))
|
|
assert_equal(:*, rest_arg.call('a, *'))
|
|
end
|
|
|
|
def test_block_arg
|
|
block_arg = lambda do |arg_str|
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
|
|
node = node.children.last.children.last.children[1].children[-1]
|
|
end
|
|
|
|
assert_equal(nil, block_arg.call(''))
|
|
assert_equal(:block, block_arg.call('&block'))
|
|
assert_equal(:&, block_arg.call('&'))
|
|
end
|
|
|
|
def test_keyword_rest
|
|
kwrest = lambda do |arg_str|
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
|
|
node = node.children.last.children.last.children[1].children[-2]
|
|
node ? node.children : node
|
|
end
|
|
|
|
assert_equal(nil, kwrest.call(''))
|
|
assert_equal([:**], kwrest.call('**'))
|
|
assert_equal(false, kwrest.call('**nil'))
|
|
assert_equal([:a], kwrest.call('**a'))
|
|
end
|
|
|
|
def test_argument_forwarding
|
|
forwarding = lambda do |arg_str|
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a(#{arg_str}) end")
|
|
node = node.children.last.children.last.children[1]
|
|
node ? [node.children[-4], node.children[-2]&.children, node.children[-1]] : []
|
|
end
|
|
|
|
assert_equal([:*, [:**], :&], forwarding.call('...'))
|
|
end
|
|
|
|
def test_ranges_numbered_parameter
|
|
helper = Helper.new(__FILE__, src: "1.times {_1}")
|
|
helper.validate_range
|
|
assert_equal([], helper.errors)
|
|
end
|
|
|
|
def test_op_asgn2
|
|
node = RubyVM::AbstractSyntaxTree.parse("struct.field += foo")
|
|
_, _, body = *node.children
|
|
assert_equal(:OP_ASGN2, body.type)
|
|
recv, _, mid, op, value = body.children
|
|
assert_equal(:VCALL, recv.type)
|
|
assert_equal(:field, mid)
|
|
assert_equal(:+, op)
|
|
assert_equal(:VCALL, value.type)
|
|
end
|
|
|
|
def test_args
|
|
rest = 6
|
|
node = RubyVM::AbstractSyntaxTree.parse("proc { |a| }")
|
|
_, args = *node.children.last.children[1].children
|
|
assert_equal(nil, args.children[rest])
|
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("proc { |a,| }")
|
|
_, args = *node.children.last.children[1].children
|
|
assert_equal(:NODE_SPECIAL_EXCESSIVE_COMMA, args.children[rest])
|
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("proc { |*a| }")
|
|
_, args = *node.children.last.children[1].children
|
|
assert_equal(:a, args.children[rest])
|
|
end
|
|
|
|
def test_return
|
|
assert_ast_eqaul(<<~STR, <<~EXP)
|
|
def m(a)
|
|
return a
|
|
end
|
|
STR
|
|
(SCOPE@1:0-3:3
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(DEFN@1:0-3:3
|
|
mid: :m
|
|
body:
|
|
(SCOPE@1:0-3:3
|
|
tbl: [:a]
|
|
args:
|
|
(ARGS@1:6-1:7
|
|
pre_num: 1
|
|
pre_init: nil
|
|
opt: nil
|
|
first_post: nil
|
|
post_num: 0
|
|
post_init: nil
|
|
rest: nil
|
|
kw: nil
|
|
kwrest: nil
|
|
block: nil)
|
|
body: (RETURN@2:2-2:10 (LVAR@2:9-2:10 :a)))))
|
|
EXP
|
|
end
|
|
|
|
def test_keep_script_lines_for_parse
|
|
node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_script_lines: true)
|
|
1.times do
|
|
2.times do
|
|
end
|
|
end
|
|
__END__
|
|
dummy
|
|
END
|
|
|
|
expected = [
|
|
"1.times do\n",
|
|
" 2.times do\n",
|
|
" end\n",
|
|
"end\n",
|
|
"__END__\n",
|
|
]
|
|
assert_equal(expected, node.script_lines)
|
|
|
|
expected =
|
|
"1.times do\n" +
|
|
" 2.times do\n" +
|
|
" end\n" +
|
|
"end"
|
|
assert_equal(expected, node.source)
|
|
|
|
expected =
|
|
"do\n" +
|
|
" 2.times do\n" +
|
|
" end\n" +
|
|
"end"
|
|
assert_equal(expected, node.children.last.children.last.source)
|
|
|
|
expected =
|
|
"2.times do\n" +
|
|
" end"
|
|
assert_equal(expected, node.children.last.children.last.children.last.source)
|
|
end
|
|
|
|
def test_keep_script_lines_for_of
|
|
omit if ParserSupport.prism_enabled?
|
|
|
|
proc = Proc.new { 1 + 2 }
|
|
method = self.method(__method__)
|
|
|
|
node_proc = RubyVM::AbstractSyntaxTree.of(proc, keep_script_lines: true)
|
|
node_method = RubyVM::AbstractSyntaxTree.of(method, keep_script_lines: true)
|
|
|
|
assert_equal("{ 1 + 2 }", node_proc.source)
|
|
assert_equal("def test_keep_script_lines_for_of\n", node_method.source.lines.first)
|
|
end
|
|
|
|
def test_keep_script_lines_for_of_with_existing_SCRIPT_LINES__that_has__FILE__as_a_key
|
|
omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
|
|
|
|
# This test confirms that the bug that previously occurred because of
|
|
# `AbstractSyntaxTree.of`s unnecessary dependence on SCRIPT_LINES__ does not reproduce.
|
|
# The bug occurred only if SCRIPT_LINES__ included __FILE__ as a key.
|
|
lines = [
|
|
"SCRIPT_LINES__ = {__FILE__ => []}",
|
|
"puts RubyVM::AbstractSyntaxTree.of(->{ 1 + 2 }, keep_script_lines: true).script_lines",
|
|
"p SCRIPT_LINES__"
|
|
]
|
|
test_stdout = lines + ['{"-e" => []}']
|
|
assert_in_out_err(["-e", lines.join("\n")], "", test_stdout, [])
|
|
end
|
|
|
|
def test_source_with_multibyte_characters
|
|
ast = RubyVM::AbstractSyntaxTree.parse(%{a("\u00a7");b("\u00a9")}, keep_script_lines: true)
|
|
a_fcall, b_fcall = ast.children[2].children
|
|
|
|
assert_equal(%{a("\u00a7")}, a_fcall.source)
|
|
assert_equal(%{b("\u00a9")}, b_fcall.source)
|
|
end
|
|
|
|
def test_keep_tokens_for_parse
|
|
node = RubyVM::AbstractSyntaxTree.parse(<<~END, keep_tokens: true)
|
|
1.times do
|
|
end
|
|
__END__
|
|
dummy
|
|
END
|
|
|
|
expected = [
|
|
[:tINTEGER, "1"],
|
|
[:".", "."],
|
|
[:tIDENTIFIER, "times"],
|
|
[:tSP, " "],
|
|
[:keyword_do, "do"],
|
|
[:tIGNORED_NL, "\n"],
|
|
[:keyword_end, "end"],
|
|
[:nl, "\n"],
|
|
]
|
|
assert_equal(expected, node.all_tokens.map { [_2, _3]})
|
|
end
|
|
|
|
def test_keep_tokens_unexpected_backslash
|
|
assert_raise_with_message(SyntaxError, /unexpected backslash/) do
|
|
RubyVM::AbstractSyntaxTree.parse("\\", keep_tokens: true)
|
|
end
|
|
end
|
|
|
|
def test_encoding_with_keep_script_lines
|
|
# Stop a warning "possibly useless use of a literal in void context"
|
|
verbose_bak, $VERBOSE = $VERBOSE, nil
|
|
|
|
enc = Encoding::EUC_JP
|
|
code = "__ENCODING__".encode(enc)
|
|
|
|
assert_equal(enc, eval(code))
|
|
|
|
node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: false)
|
|
assert_equal(enc, node.children[2].children[0])
|
|
|
|
node = RubyVM::AbstractSyntaxTree.parse(code, keep_script_lines: true)
|
|
assert_equal(enc, node.children[2].children[0])
|
|
|
|
ensure
|
|
$VERBOSE = verbose_bak
|
|
end
|
|
|
|
def test_e_option
|
|
omit if ParserSupport.prism_enabled? || ParserSupport.prism_enabled_in_subprocess?
|
|
|
|
assert_in_out_err(["-e", "def foo; end; pp RubyVM::AbstractSyntaxTree.of(method(:foo)).type"],
|
|
"", [":SCOPE"], [])
|
|
end
|
|
|
|
def test_error_tolerant
|
|
verbose_bak, $VERBOSE = $VERBOSE, false
|
|
node = RubyVM::AbstractSyntaxTree.parse(<<~STR, error_tolerant: true)
|
|
class A
|
|
def m
|
|
if;
|
|
a = 10
|
|
end
|
|
end
|
|
STR
|
|
assert_nil($!)
|
|
|
|
assert_equal(:SCOPE, node.type)
|
|
ensure
|
|
$VERBOSE = verbose_bak
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_method_define
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
def m
|
|
m2
|
|
STR
|
|
(SCOPE@1:0-2:4
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(DEFN@1:0-2:4
|
|
mid: :m
|
|
body:
|
|
(SCOPE@1:0-2:4
|
|
tbl: []
|
|
args:
|
|
(ARGS@1:5-1:5
|
|
pre_num: 0
|
|
pre_init: nil
|
|
opt: nil
|
|
first_post: nil
|
|
post_num: 0
|
|
post_init: nil
|
|
rest: nil
|
|
kw: nil
|
|
kwrest: nil
|
|
block: nil)
|
|
body: (VCALL@2:2-2:4 :m2))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_singleton_method_define
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
def obj.m
|
|
m2
|
|
STR
|
|
(SCOPE@1:0-2:4
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(DEFS@1:0-2:4 (VCALL@1:4-1:7 :obj) :m
|
|
(SCOPE@1:0-2:4
|
|
tbl: []
|
|
args:
|
|
(ARGS@1:9-1:9
|
|
pre_num: 0
|
|
pre_init: nil
|
|
opt: nil
|
|
first_post: nil
|
|
post_num: 0
|
|
post_init: nil
|
|
rest: nil
|
|
kw: nil
|
|
kwrest: nil
|
|
block: nil)
|
|
body: (VCALL@2:2-2:4 :m2))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_begin
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
begin
|
|
a = 1
|
|
STR
|
|
(SCOPE@1:0-2:7 tbl: [:a] args: nil body: (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1)))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_if
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
if cond
|
|
a = 1
|
|
STR
|
|
(SCOPE@1:0-2:7
|
|
tbl: [:a]
|
|
args: nil
|
|
body:
|
|
(IF@1:0-2:7 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
|
|
nil))
|
|
EXP
|
|
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
if cond
|
|
a = 1
|
|
else
|
|
STR
|
|
(SCOPE@1:0-3:4
|
|
tbl: [:a]
|
|
args: nil
|
|
body:
|
|
(IF@1:0-3:4 (VCALL@1:3-1:7 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
|
|
(BEGIN@3:4-3:4 nil)))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_unless
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
unless cond
|
|
a = 1
|
|
STR
|
|
(SCOPE@1:0-2:7
|
|
tbl: [:a]
|
|
args: nil
|
|
body:
|
|
(UNLESS@1:0-2:7 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
|
|
nil))
|
|
EXP
|
|
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
unless cond
|
|
a = 1
|
|
else
|
|
STR
|
|
(SCOPE@1:0-3:4
|
|
tbl: [:a]
|
|
args: nil
|
|
body:
|
|
(UNLESS@1:0-3:4 (VCALL@1:7-1:11 :cond) (LASGN@2:2-2:7 :a (INTEGER@2:6-2:7 1))
|
|
(BEGIN@3:4-3:4 nil)))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_while
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
while true
|
|
m
|
|
STR
|
|
(SCOPE@1:0-2:3
|
|
tbl: []
|
|
args: nil
|
|
body: (WHILE@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_until
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
until true
|
|
m
|
|
STR
|
|
(SCOPE@1:0-2:3
|
|
tbl: []
|
|
args: nil
|
|
body: (UNTIL@1:0-2:3 (TRUE@1:6-1:10) (VCALL@2:2-2:3 :m) true))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_case
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
case a
|
|
when 1
|
|
STR
|
|
(SCOPE@1:0-2:6
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(CASE@1:0-2:6 (VCALL@1:5-1:6 :a)
|
|
(WHEN@2:0-2:6 (LIST@2:5-2:6 (INTEGER@2:5-2:6 1) nil) (BEGIN@2:6-2:6 nil)
|
|
nil)))
|
|
EXP
|
|
|
|
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
case
|
|
when a == 1
|
|
STR
|
|
(SCOPE@1:0-2:11
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(CASE2@1:0-2:11 nil
|
|
(WHEN@2:0-2:11
|
|
(LIST@2:5-2:11
|
|
(OPCALL@2:5-2:11 (VCALL@2:5-2:6 :a) :==
|
|
(LIST@2:10-2:11 (INTEGER@2:10-2:11 1) nil)) nil)
|
|
(BEGIN@2:11-2:11 nil) nil)))
|
|
EXP
|
|
|
|
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
case a
|
|
in {a: String}
|
|
STR
|
|
(SCOPE@1:0-2:14
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(CASE3@1:0-2:14 (VCALL@1:5-1:6 :a)
|
|
(IN@2:0-2:14
|
|
(HSHPTN@2:4-2:13
|
|
const: nil
|
|
kw:
|
|
(HASH@2:4-2:13
|
|
(LIST@2:4-2:13 (SYM@2:4-2:6 :a) (CONST@2:7-2:13 :String) nil))
|
|
kwrest: nil) (BEGIN@2:14-2:14 nil) nil)))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_for
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
for i in ary
|
|
m
|
|
STR
|
|
(SCOPE@1:0-2:3
|
|
tbl: [:i]
|
|
args: nil
|
|
body:
|
|
(FOR@1:0-2:3 (VCALL@1:9-1:12 :ary)
|
|
(SCOPE@1:0-2:3
|
|
tbl: [nil]
|
|
args:
|
|
(ARGS@1:4-1:5
|
|
pre_num: 1
|
|
pre_init: (LASGN@1:4-1:5 :i (DVAR@1:4-1:5 nil))
|
|
opt: nil
|
|
first_post: nil
|
|
post_num: 0
|
|
post_init: nil
|
|
rest: nil
|
|
kw: nil
|
|
kwrest: nil
|
|
block: nil)
|
|
body: (VCALL@2:2-2:3 :m))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_class
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
class C
|
|
STR
|
|
(SCOPE@1:0-1:7
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(CLASS@1:0-1:7 (COLON2@1:6-1:7 nil :C) nil
|
|
(SCOPE@1:0-1:7 tbl: [] args: nil body: (BEGIN@1:7-1:7 nil))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_module
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
module M
|
|
STR
|
|
(SCOPE@1:0-1:8
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(MODULE@1:0-1:8 (COLON2@1:7-1:8 nil :M)
|
|
(SCOPE@1:0-1:8 tbl: [] args: nil body: (BEGIN@1:8-1:8 nil))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_do
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
m do
|
|
a
|
|
STR
|
|
(SCOPE@1:0-2:3
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(ITER@1:0-2:3 (FCALL@1:0-1:1 :m nil)
|
|
(SCOPE@1:2-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_do_block
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
m 1 do
|
|
a
|
|
STR
|
|
(SCOPE@1:0-2:3
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(ITER@1:0-2:3 (FCALL@1:0-1:3 :m (LIST@1:2-1:3 (INTEGER@1:2-1:3 1) nil))
|
|
(SCOPE@1:4-2:3 tbl: [] args: nil body: (VCALL@2:2-2:3 :a))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_end_is_short_for_do_LAMBDA
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
-> do
|
|
a
|
|
STR
|
|
(SCOPE@1:0-2:3
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(LAMBDA@1:0-2:3
|
|
(SCOPE@1:2-2:3
|
|
tbl: []
|
|
args:
|
|
(ARGS@1:2-1:2
|
|
pre_num: 0
|
|
pre_init: nil
|
|
opt: nil
|
|
first_post: nil
|
|
post_num: 0
|
|
post_init: nil
|
|
rest: nil
|
|
kw: nil
|
|
kwrest: nil
|
|
block: nil)
|
|
body: (VCALL@2:2-2:3 :a))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_treat_end_as_keyword_based_on_indent
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
module Z
|
|
class Foo
|
|
foo.
|
|
end
|
|
|
|
def bar
|
|
end
|
|
end
|
|
STR
|
|
(SCOPE@1:0-8:3
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(MODULE@1:0-8:3 (COLON2@1:7-1:8 nil :Z)
|
|
(SCOPE@1:0-8:3
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(BLOCK@1:8-7:5 (BEGIN@1:8-1:8 nil)
|
|
(CLASS@2:2-4:5 (COLON2@2:8-2:11 nil :Foo) nil
|
|
(SCOPE@2:2-4:5
|
|
tbl: []
|
|
args: nil
|
|
body: (BLOCK@2:11-4:5 (BEGIN@2:11-2:11 nil) (ERROR@3:4-4:5))))
|
|
(DEFN@6:2-7:5
|
|
mid: :bar
|
|
body:
|
|
(SCOPE@6:2-7:5
|
|
tbl: []
|
|
args:
|
|
(ARGS@6:9-6:9
|
|
pre_num: 0
|
|
pre_init: nil
|
|
opt: nil
|
|
first_post: nil
|
|
post_num: 0
|
|
post_init: nil
|
|
rest: nil
|
|
kw: nil
|
|
kwrest: nil
|
|
block: nil)
|
|
body: nil))))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_expr_value_can_be_error
|
|
assert_error_tolerant(<<~STR, <<~EXP)
|
|
def m
|
|
if
|
|
end
|
|
STR
|
|
(SCOPE@1:0-3:3
|
|
tbl: []
|
|
args: nil
|
|
body:
|
|
(DEFN@1:0-3:3
|
|
mid: :m
|
|
body:
|
|
(SCOPE@1:0-3:3
|
|
tbl: []
|
|
args:
|
|
(ARGS@1:5-1:5
|
|
pre_num: 0
|
|
pre_init: nil
|
|
opt: nil
|
|
first_post: nil
|
|
post_num: 0
|
|
post_init: nil
|
|
rest: nil
|
|
kw: nil
|
|
kwrest: nil
|
|
block: nil)
|
|
body: (IF@2:2-3:3 (ERROR@3:0-3:3) nil nil))))
|
|
EXP
|
|
end
|
|
|
|
def test_error_tolerant_unexpected_backslash
|
|
node = assert_error_tolerant("\\", <<~EXP, keep_tokens: true)
|
|
(SCOPE@1:0-1:1 tbl: [] args: nil body: (ERROR@1:0-1:1))
|
|
EXP
|
|
assert_equal([[0, :backslash, "\\", [1, 0, 1, 1]]], node.children.last.tokens)
|
|
end
|
|
|
|
def test_with_bom
|
|
assert_error_tolerant("\u{feff}nil", <<~EXP)
|
|
(SCOPE@1:0-1:3 tbl: [] args: nil body: (NIL@1:0-1:3))
|
|
EXP
|
|
end
|
|
|
|
def test_unused_block_local_variable
|
|
assert_warning('') do
|
|
RubyVM::AbstractSyntaxTree.parse(%{->(; foo) {}})
|
|
end
|
|
end
|
|
|
|
def test_memory_leak
|
|
assert_no_memory_leak([], "#{<<~"begin;"}", "\n#{<<~'end;'}", rss: true)
|
|
begin;
|
|
1_000_000.times do
|
|
eval("")
|
|
end
|
|
end;
|
|
end
|
|
|
|
def test_locations
|
|
begin
|
|
verbose_bak, $VERBOSE = $VERBOSE, false
|
|
node = RubyVM::AbstractSyntaxTree.parse("1 + 2")
|
|
ensure
|
|
$VERBOSE = verbose_bak
|
|
end
|
|
locations = node.locations
|
|
|
|
assert_equal(RubyVM::AbstractSyntaxTree::Location, locations[0].class)
|
|
end
|
|
|
|
private
|
|
|
|
def assert_error_tolerant(src, expected, keep_tokens: false)
|
|
assert_ast_eqaul(src, expected, error_tolerant: true, keep_tokens: keep_tokens)
|
|
end
|
|
|
|
def assert_ast_eqaul(src, expected, **options)
|
|
begin
|
|
verbose_bak, $VERBOSE = $VERBOSE, false
|
|
node = RubyVM::AbstractSyntaxTree.parse(src, **options)
|
|
ensure
|
|
$VERBOSE = verbose_bak
|
|
end
|
|
assert_nil($!)
|
|
str = ""
|
|
PP.pp(node, str, 80)
|
|
assert_equal(expected, str)
|
|
node
|
|
end
|
|
|
|
class TestLocation < Test::Unit::TestCase
|
|
def test_lineno_and_column
|
|
node = ast_parse("1 + 2")
|
|
assert_locations(node.locations, [[1, 0, 1, 5]])
|
|
end
|
|
|
|
def test_alias_locations
|
|
node = ast_parse("alias foo bar")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
|
|
end
|
|
|
|
def test_and_locations
|
|
node = ast_parse("1 and 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 2, 1, 5]])
|
|
|
|
node = ast_parse("1 && 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
|
|
end
|
|
|
|
def test_block_pass_locations
|
|
node = ast_parse("foo(&bar)")
|
|
assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 8], [1, 4, 1, 5]])
|
|
|
|
node = ast_parse("def a(&); b(&) end")
|
|
assert_locations(node.children[-1].children[-1].children[-1].children[-1].children[-1].locations, [[1, 12, 1, 13], [1, 12, 1, 13]])
|
|
end
|
|
|
|
def test_break_locations
|
|
node = ast_parse("loop { break 1 }")
|
|
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 14], [1, 7, 1, 12]])
|
|
end
|
|
|
|
def test_case_locations
|
|
node = ast_parse("case a; when 1; end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 4], [1, 16, 1, 19]])
|
|
end
|
|
|
|
def test_case2_locations
|
|
node = ast_parse("case; when 1; end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]])
|
|
end
|
|
|
|
def test_case3_locations
|
|
node = ast_parse("case a; in 1; end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 4], [1, 14, 1, 17]])
|
|
end
|
|
|
|
def test_class_locations
|
|
node = ast_parse("class A end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 5], nil, [1, 8, 1, 11]])
|
|
|
|
node = ast_parse("class A < B; end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 16], [1, 0, 1, 5], [1, 8, 1, 9], [1, 13, 1, 16]])
|
|
end
|
|
|
|
def test_colon2_locations
|
|
node = ast_parse("A::B")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]])
|
|
|
|
node = ast_parse("A::B::C")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 4, 1, 6], [1, 6, 1, 7]])
|
|
assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]])
|
|
end
|
|
|
|
def test_colon3_locations
|
|
node = ast_parse("::A")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]])
|
|
|
|
node = ast_parse("::A::B")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 3, 1, 5], [1, 5, 1, 6]])
|
|
assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]])
|
|
end
|
|
|
|
def test_defined_locations
|
|
node = ast_parse("defined? x")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 0, 1, 8]])
|
|
|
|
node = ast_parse("defined?(x)")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 0, 1, 8]])
|
|
end
|
|
|
|
def test_dot2_locations
|
|
node = ast_parse("1..2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3]])
|
|
|
|
node = ast_parse("foo(1..2)")
|
|
assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]])
|
|
|
|
node = ast_parse("foo(1..2, 3)")
|
|
assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 5, 1, 7]])
|
|
|
|
node = ast_parse("foo(..2)")
|
|
assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 7], [1, 4, 1, 6]])
|
|
end
|
|
|
|
def test_dot3_locations
|
|
node = ast_parse("1...2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 1, 1, 4]])
|
|
|
|
node = ast_parse("foo(1...2)")
|
|
assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]])
|
|
|
|
node = ast_parse("foo(1...2, 3)")
|
|
assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 9], [1, 5, 1, 8]])
|
|
|
|
node = ast_parse("foo(...2)")
|
|
assert_locations(node.children[-1].children[-1].children[0].locations, [[1, 4, 1, 8], [1, 4, 1, 7]])
|
|
end
|
|
|
|
def test_evstr_locations
|
|
node = ast_parse('"#{foo}"')
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 8], [1, 1, 1, 3], [1, 6, 1, 7]])
|
|
|
|
node = ast_parse('"#$1"')
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 0, 1, 5], [1, 1, 1, 2], nil])
|
|
end
|
|
|
|
def test_flip2_locations
|
|
node = ast_parse("if 'a'..'z'; foo; end")
|
|
assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 11], [1, 6, 1, 8]])
|
|
|
|
node = ast_parse('if 1..5; foo; end')
|
|
assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 7], [1, 4, 1, 6]])
|
|
end
|
|
|
|
def test_flip3_locations
|
|
node = ast_parse("if 'a'...('z'); foo; end")
|
|
assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 14], [1, 6, 1, 9]])
|
|
|
|
node = ast_parse('if 1...5; foo; end')
|
|
assert_locations(node.children[-1].children[0].locations, [[1, 3, 1, 8], [1, 4, 1, 7]])
|
|
end
|
|
|
|
def test_for_locations
|
|
node = ast_parse("for a in b; end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 3], [1, 6, 1, 8], nil, [1, 12, 1, 15]])
|
|
|
|
node = ast_parse("for a in b do; end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 18], [1, 0, 1, 3], [1, 6, 1, 8], [1, 11, 1, 13], [1, 15, 1, 18]])
|
|
end
|
|
|
|
def test_lambda_locations
|
|
node = ast_parse("-> (a, b) { foo }")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 17], [1, 0, 1, 2], [1, 10, 1, 11], [1, 16, 1, 17]])
|
|
|
|
node = ast_parse("-> (a, b) do foo end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 20], [1, 0, 1, 2], [1, 10, 1, 12], [1, 17, 1, 20]])
|
|
end
|
|
|
|
def test_module_locations
|
|
node = ast_parse('module A end')
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 12], [1, 0, 1, 6], [1, 9, 1, 12]])
|
|
end
|
|
|
|
def test_if_locations
|
|
node = ast_parse("if cond then 1 else 2 end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 25], [1, 0, 1, 2], [1, 8, 1, 12], [1, 22, 1, 25]])
|
|
|
|
node = ast_parse("1 if 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4], nil, nil])
|
|
|
|
node = ast_parse("if 1; elsif 2; else end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 23], [1, 0, 1, 2], [1, 4, 1, 5], [1, 20, 1, 23]])
|
|
assert_locations(node.children[-1].children[-1].locations, [[1, 6, 1, 19], [1, 6, 1, 11], [1, 13, 1, 14], [1, 20, 1, 23]])
|
|
|
|
node = ast_parse("true ? 1 : 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 12], nil, [1, 9, 1, 10], nil])
|
|
|
|
node = ast_parse("case a; in b if c; end")
|
|
assert_locations(node.children[-1].children[1].children[0].locations, [[1, 11, 1, 17], [1, 13, 1, 15], nil, nil])
|
|
end
|
|
|
|
def test_in_locations
|
|
node = ast_parse("case 1; in 2 then 3; end")
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 20], [1, 8, 1, 10], [1, 13, 1, 17], nil])
|
|
|
|
node = ast_parse("1 => a")
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], nil, nil, [1, 2, 1, 4]])
|
|
|
|
node = ast_parse("1 in a")
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], [1, 2, 1, 4], nil, nil])
|
|
|
|
node = ast_parse("case 1; in 2; 3; end")
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 16], [1, 8, 1, 10], [1, 12, 1, 13], nil])
|
|
end
|
|
|
|
def test_next_locations
|
|
node = ast_parse("loop { next 1 }")
|
|
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 13], [1, 7, 1, 11]])
|
|
end
|
|
|
|
def test_op_asgn1_locations
|
|
node = ast_parse("ary[1] += foo")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 13], nil, [1, 3, 1, 4], [1, 5, 1, 6], [1, 7, 1, 9]])
|
|
|
|
node = ast_parse("ary[1, 2] += foo")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 16], nil, [1, 3, 1, 4], [1, 8, 1, 9], [1, 10, 1, 12]])
|
|
end
|
|
|
|
def test_or_locations
|
|
node = ast_parse("1 or 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
|
|
|
|
node = ast_parse("1 || 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 2, 1, 4]])
|
|
end
|
|
|
|
def test_op_asgn2_locations
|
|
node = ast_parse("a.b += 1")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 1, 1, 2], [1, 2, 1, 3], [1, 4, 1, 6]])
|
|
|
|
node = ast_parse("A::B.c += d")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 11], [1, 4, 1, 5], [1, 5, 1, 6], [1, 7, 1, 9]])
|
|
|
|
node = ast_parse("a = b.c += d")
|
|
assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 12], [1, 5, 1, 6], [1, 6, 1, 7], [1, 8, 1, 10]])
|
|
|
|
node = ast_parse("a = A::B.c += d")
|
|
assert_locations(node.children[-1].children[-1].locations, [[1, 4, 1, 15], [1, 8, 1, 9], [1, 9, 1, 10], [1, 11, 1, 13]])
|
|
end
|
|
|
|
def test_postexe_locations
|
|
node = ast_parse("END { }")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 3], [1, 4, 1, 5], [1, 7, 1, 8]])
|
|
|
|
node = ast_parse("END { 1 }")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 3], [1, 4, 1, 5], [1, 8, 1, 9]])
|
|
end
|
|
|
|
def test_redo_locations
|
|
node = ast_parse("loop { redo }")
|
|
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 11], [1, 7, 1, 11]])
|
|
end
|
|
|
|
def test_regx_locations
|
|
node = ast_parse("/foo/")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 5], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 5]])
|
|
|
|
node = ast_parse("/foo/i")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 1], [1, 1, 1, 4], [1, 4, 1, 6]])
|
|
end
|
|
|
|
def test_return_locations
|
|
node = ast_parse("return 1")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 6]])
|
|
|
|
node = ast_parse("return")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 0, 1, 6]])
|
|
end
|
|
|
|
def test_splat_locations
|
|
node = ast_parse("a = *1")
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 4, 1, 6], [1, 4, 1, 5]])
|
|
|
|
node = ast_parse("a = *1, 2")
|
|
assert_locations(node.children[-1].children[1].children[0].locations, [[1, 4, 1, 6], [1, 4, 1, 5]])
|
|
|
|
node = ast_parse("case a; when *1; end")
|
|
assert_locations(node.children[-1].children[1].children[0].locations, [[1, 13, 1, 15], [1, 13, 1, 14]])
|
|
|
|
node = ast_parse("case a; when *1, 2; end")
|
|
assert_locations(node.children[-1].children[1].children[0].children[0].locations, [[1, 13, 1, 15], [1, 13, 1, 14]])
|
|
end
|
|
|
|
def test_super_locations
|
|
node = ast_parse("super 1")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 0, 1, 5], nil, nil])
|
|
|
|
node = ast_parse("super(1)")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 8], [1, 0, 1, 5], [1, 5, 1, 6], [1, 7, 1, 8]])
|
|
end
|
|
|
|
def test_unless_locations
|
|
node = ast_parse("unless cond then 1 else 2 end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 29], [1, 0, 1, 6], [1, 12, 1, 16], [1, 26, 1, 29]])
|
|
|
|
node = ast_parse("1 unless 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 10], [1, 2, 1, 8], nil, nil])
|
|
end
|
|
|
|
def test_undef_locations
|
|
node = ast_parse("undef foo")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 0, 1, 5]])
|
|
|
|
node = ast_parse("undef foo, bar")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 14], [1, 0, 1, 5]])
|
|
end
|
|
|
|
def test_valias_locations
|
|
node = ast_parse("alias $foo $bar")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 15], [1, 0, 1, 5]])
|
|
|
|
node = ast_parse("alias $foo $&")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
|
|
|
|
node = ast_parse("alias $foo $`")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
|
|
|
|
node = ast_parse("alias $foo $'")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
|
|
|
|
node = ast_parse("alias $foo $+")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 13], [1, 0, 1, 5]])
|
|
end
|
|
|
|
def test_when_locations
|
|
node = ast_parse("case a; when 1 then 2; end")
|
|
assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 22], [1, 8, 1, 12], [1, 15, 1, 19]])
|
|
end
|
|
|
|
def test_while_locations
|
|
node = ast_parse("while cond do 1 end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]])
|
|
|
|
node = ast_parse("1 while 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil])
|
|
end
|
|
|
|
def test_until_locations
|
|
node = ast_parse("until cond do 1 end")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 19], [1, 0, 1, 5], [1, 16, 1, 19]])
|
|
|
|
node = ast_parse("1 until 2")
|
|
assert_locations(node.children[-1].locations, [[1, 0, 1, 9], [1, 2, 1, 7], nil])
|
|
end
|
|
|
|
def test_yield_locations
|
|
node = ast_parse("def foo; yield end")
|
|
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 14], [1, 9, 1, 14], nil, nil])
|
|
|
|
node = ast_parse("def foo; yield() end")
|
|
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 16], [1, 9, 1, 14], [1, 14, 1, 15], [1, 15, 1, 16]])
|
|
|
|
node = ast_parse("def foo; yield 1, 2 end")
|
|
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 19], [1, 9, 1, 14], nil, nil])
|
|
|
|
node = ast_parse("def foo; yield(1, 2) end")
|
|
assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 9, 1, 20], [1, 9, 1, 14], [1, 14, 1, 15], [1, 19, 1, 20]])
|
|
end
|
|
|
|
private
|
|
def ast_parse(src, **options)
|
|
begin
|
|
verbose_bak, $VERBOSE = $VERBOSE, nil
|
|
RubyVM::AbstractSyntaxTree.parse(src, **options)
|
|
ensure
|
|
$VERBOSE = verbose_bak
|
|
end
|
|
end
|
|
|
|
def assert_locations(locations, expected)
|
|
ary = locations.map {|loc| loc && [loc.first_lineno, loc.first_column, loc.last_lineno, loc.last_column] }
|
|
|
|
assert_equal(ary, expected)
|
|
end
|
|
end
|
|
end
|