2018-01-15 18:43:17 -05:00
|
|
|
# frozen_string_literal: false
|
|
|
|
require 'test/unit'
|
2018-09-06 23:39:30 -04:00
|
|
|
require 'tempfile'
|
2018-01-15 18:43:17 -05:00
|
|
|
|
2018-05-31 02:13:06 -04:00
|
|
|
class RubyVM
|
2018-11-08 20:37:41 -05:00
|
|
|
module AbstractSyntaxTree
|
2018-05-31 02:13:06 -04:00
|
|
|
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
|
2018-01-15 18:43:17 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-31 02:13:06 -04:00
|
|
|
def beg_pos
|
|
|
|
CodePosition.new(first_lineno, first_column)
|
|
|
|
end
|
2018-01-15 18:43:17 -05:00
|
|
|
|
2018-05-31 02:13:06 -04:00
|
|
|
def end_pos
|
|
|
|
CodePosition.new(last_lineno, last_column)
|
|
|
|
end
|
2018-01-15 18:43:17 -05:00
|
|
|
|
2018-05-31 02:13:06 -04:00
|
|
|
alias to_s inspect
|
|
|
|
end
|
2018-01-15 18:43:17 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class TestAst < Test::Unit::TestCase
|
|
|
|
class Helper
|
|
|
|
attr_reader :errors
|
|
|
|
|
|
|
|
def initialize(path)
|
|
|
|
@path = path
|
|
|
|
@errors = []
|
|
|
|
@debug = false
|
|
|
|
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)
|
2018-11-08 20:37:41 -05:00
|
|
|
@ast = RubyVM::AbstractSyntaxTree.parse_file(@path)
|
2018-01-15 18:43:17 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def validate_range0(node)
|
|
|
|
beg_pos, end_pos = node.beg_pos, node.end_pos
|
2018-11-08 20:37:41 -05:00
|
|
|
children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
|
2018-01-15 18:43:17 -05:00
|
|
|
|
|
|
|
return true if children.empty?
|
|
|
|
# These NODE_D* has NODE_ARRAY as nd_next->nd_next whose last locations
|
|
|
|
# we can not update when item is appended.
|
2018-12-02 20:06:34 -05:00
|
|
|
return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type
|
2018-01-15 18:43:17 -05:00
|
|
|
|
|
|
|
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
|
2018-11-08 20:37:41 -05:00
|
|
|
children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
|
2018-01-15 18:43:17 -05:00
|
|
|
|
|
|
|
@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
|
|
|
|
|
2018-06-04 20:18:29 -04:00
|
|
|
SRCDIR = File.expand_path("../../..", __FILE__)
|
2018-01-15 18:43:17 -05:00
|
|
|
|
2018-01-17 01:38:08 -05:00
|
|
|
Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
|
2018-01-15 18:43:17 -05:00
|
|
|
define_method("test_ranges:#{path}") do
|
2018-01-17 01:38:08 -05:00
|
|
|
helper = Helper.new("#{SRCDIR}/#{path}")
|
2018-01-15 18:43:17 -05:00
|
|
|
helper.validate_range
|
|
|
|
|
|
|
|
assert_equal([], helper.errors)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-17 01:38:08 -05:00
|
|
|
Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
|
2018-01-15 18:43:17 -05:00
|
|
|
define_method("test_not_cared:#{path}") do
|
2018-01-17 01:38:08 -05:00
|
|
|
helper = Helper.new("#{SRCDIR}/#{path}")
|
2018-01-15 18:43:17 -05:00
|
|
|
helper.validate_not_cared
|
|
|
|
|
|
|
|
assert_equal([], helper.errors)
|
|
|
|
end
|
|
|
|
end
|
2018-01-17 22:29:12 -05:00
|
|
|
|
2018-08-03 02:53:14 -04:00
|
|
|
def test_allocate
|
2018-11-08 20:37:41 -05:00
|
|
|
assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate}
|
2018-08-03 02:53:14 -04:00
|
|
|
end
|
|
|
|
|
2019-01-06 00:07:10 -05:00
|
|
|
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
|
|
|
|
|
2018-01-17 22:29:12 -05:00
|
|
|
def test_column_with_long_heredoc_identifier
|
|
|
|
term = "A"*257
|
2018-11-08 20:37:41 -05:00
|
|
|
ast = RubyVM::AbstractSyntaxTree.parse("<<-#{term}\n""ddddddd\n#{term}\n")
|
2018-06-07 10:46:25 -04:00
|
|
|
node = ast.children[2]
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:STR, node.type)
|
2018-01-17 22:29:12 -05:00
|
|
|
assert_equal(0, node.first_column)
|
|
|
|
end
|
2018-06-04 20:48:12 -04:00
|
|
|
|
|
|
|
def test_column_of_heredoc
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("<<-SRC\nddddddd\nSRC\n").children[2]
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:STR, node.type)
|
2018-06-04 20:48:12 -04:00
|
|
|
assert_equal(0, node.first_column)
|
|
|
|
assert_equal(6, node.last_column)
|
|
|
|
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("<<SRC\nddddddd\nSRC\n").children[2]
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:STR, node.type)
|
2018-06-04 20:48:12 -04:00
|
|
|
assert_equal(0, node.first_column)
|
|
|
|
assert_equal(5, node.last_column)
|
|
|
|
end
|
2018-06-07 10:04:49 -04:00
|
|
|
|
|
|
|
def test_parse_raises_syntax_error
|
2018-11-25 03:31:40 -05:00
|
|
|
assert_raise_with_message(SyntaxError, /\bend\b/) do
|
2018-11-08 20:37:41 -05:00
|
|
|
RubyVM::AbstractSyntaxTree.parse("end")
|
2018-09-06 23:42:51 -04:00
|
|
|
end
|
2018-06-07 10:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_parse_file_raises_syntax_error
|
|
|
|
Tempfile.create(%w"test_ast .rb") do |f|
|
|
|
|
f.puts "end"
|
|
|
|
f.close
|
2018-11-25 03:31:40 -05:00
|
|
|
assert_raise_with_message(SyntaxError, /\bend\b/) do
|
2018-11-08 20:37:41 -05:00
|
|
|
RubyVM::AbstractSyntaxTree.parse_file(f.path)
|
2018-09-06 23:42:51 -04:00
|
|
|
end
|
2018-06-07 10:04:49 -04:00
|
|
|
end
|
|
|
|
end
|
2018-06-07 10:46:25 -04:00
|
|
|
|
2018-11-04 21:13:45 -05:00
|
|
|
def test_of
|
|
|
|
proc = Proc.new { 1 + 2 }
|
|
|
|
method = self.method(__method__)
|
|
|
|
|
2018-11-08 20:37:41 -05:00
|
|
|
node_proc = RubyVM::AbstractSyntaxTree.of(proc)
|
|
|
|
node_method = RubyVM::AbstractSyntaxTree.of(method)
|
2018-11-04 21:13:45 -05:00
|
|
|
|
2018-11-08 20:37:41 -05:00
|
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc)
|
|
|
|
assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method)
|
|
|
|
assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") }
|
2018-11-09 08:39:36 -05:00
|
|
|
|
|
|
|
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
|
2018-11-04 21:13:45 -05:00
|
|
|
end
|
|
|
|
|
2018-06-07 10:46:25 -04:00
|
|
|
def test_scope_local_variables
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("x = 0")
|
2018-06-07 10:46:25 -04:00
|
|
|
lv, _, body = *node.children
|
|
|
|
assert_equal([:x], lv)
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:LASGN, body.type)
|
2018-06-07 10:46:25 -04:00
|
|
|
end
|
2018-06-08 07:03:39 -04:00
|
|
|
|
|
|
|
def test_call
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("nil.foo")
|
2018-06-08 07:03:39 -04:00
|
|
|
_, _, body = *node.children
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:CALL, body.type)
|
2018-06-08 07:03:39 -04:00
|
|
|
recv, mid, args = body.children
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:NIL, recv.type)
|
2018-06-08 07:03:39 -04:00
|
|
|
assert_equal(:foo, mid)
|
|
|
|
assert_nil(args)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_fcall
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("foo()")
|
2018-06-08 07:03:39 -04:00
|
|
|
_, _, body = *node.children
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:FCALL, body.type)
|
2018-06-08 07:03:39 -04:00
|
|
|
mid, args = body.children
|
|
|
|
assert_equal(:foo, mid)
|
|
|
|
assert_nil(args)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_vcall
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("foo")
|
2018-06-08 07:03:39 -04:00
|
|
|
_, _, body = *node.children
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:VCALL, body.type)
|
2018-06-08 07:03:39 -04:00
|
|
|
mid, args = body.children
|
|
|
|
assert_equal(:foo, mid)
|
|
|
|
assert_nil(args)
|
|
|
|
end
|
2018-06-28 10:33:28 -04:00
|
|
|
|
|
|
|
def test_defn
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a; end")
|
2018-06-28 10:33:28 -04:00
|
|
|
_, _, body = *node.children
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:DEFN, body.type)
|
2018-06-28 10:33:28 -04:00
|
|
|
mid, defn = body.children
|
|
|
|
assert_equal(:a, mid)
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:SCOPE, defn.type)
|
2018-06-28 10:33:28 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_defs
|
2018-11-08 20:37:41 -05:00
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("def a.b; end")
|
2018-06-28 10:33:28 -04:00
|
|
|
_, _, body = *node.children
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:DEFS, body.type)
|
2018-06-28 10:33:28 -04:00
|
|
|
recv, mid, defn = body.children
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:VCALL, recv.type)
|
2018-06-28 10:33:28 -04:00
|
|
|
assert_equal(:b, mid)
|
2018-12-02 20:06:34 -05:00
|
|
|
assert_equal(:SCOPE, defn.type)
|
2018-06-28 10:33:28 -04:00
|
|
|
end
|
2018-12-31 10:00:37 -05:00
|
|
|
|
|
|
|
def test_methref
|
|
|
|
node = RubyVM::AbstractSyntaxTree.parse("obj.:foo")
|
|
|
|
_, _, body = *node.children
|
|
|
|
assert_equal(:METHREF, body.type)
|
|
|
|
recv, mid = body.children
|
|
|
|
assert_equal(:VCALL, recv.type)
|
|
|
|
assert_equal(:foo, mid)
|
|
|
|
end
|
2019-01-14 05:16:54 -05:00
|
|
|
|
|
|
|
def test_dstr
|
|
|
|
node = RubyVM::AbstractSyntaxTree.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(:LIT, body.type)
|
|
|
|
assert_equal([1], body.children)
|
|
|
|
end
|
2018-01-15 18:43:17 -05:00
|
|
|
end
|