1# frozen_string_literal: false
2require 'test/unit'
3require 'tempfile'
4
5class RubyVM
6  module AbstractSyntaxTree
7    class Node
8      class CodePosition
9        include Comparable
10        attr_reader :lineno, :column
11        def initialize(lineno, column)
12          @lineno = lineno
13          @column = column
14        end
15
16        def <=>(other)
17          case
18          when lineno < other.lineno
19            -1
20          when lineno == other.lineno
21            column <=> other.column
22          when lineno > other.lineno
23            1
24          end
25        end
26      end
27
28      def beg_pos
29        CodePosition.new(first_lineno, first_column)
30      end
31
32      def end_pos
33        CodePosition.new(last_lineno, last_column)
34      end
35
36      alias to_s inspect
37    end
38  end
39end
40
41class TestAst < Test::Unit::TestCase
42  class Helper
43    attr_reader :errors
44
45    def initialize(path)
46      @path = path
47      @errors = []
48      @debug = false
49    end
50
51    def validate_range
52      @errors = []
53      validate_range0(ast)
54
55      @errors.empty?
56    end
57
58    def validate_not_cared
59      @errors = []
60      validate_not_cared0(ast)
61
62      @errors.empty?
63    end
64
65    def ast
66      return @ast if defined?(@ast)
67      @ast = RubyVM::AbstractSyntaxTree.parse_file(@path)
68    end
69
70    private
71
72    def validate_range0(node)
73      beg_pos, end_pos = node.beg_pos, node.end_pos
74      children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
75
76      return true if children.empty?
77      # These NODE_D* has NODE_ARRAY as nd_next->nd_next whose last locations
78      # we can not update when item is appended.
79      return true if [:DSTR, :DXSTR, :DREGX, :DSYM].include? node.type
80
81      min = children.map(&:beg_pos).min
82      max = children.map(&:end_pos).max
83
84      unless beg_pos <= min
85        @errors << { type: :min_validation_error, min: min, beg_pos: beg_pos, node: node }
86      end
87
88      unless max <= end_pos
89        @errors << { type: :max_validation_error, max: max, end_pos: end_pos, node: node }
90      end
91
92      p "#{node} => #{children}" if @debug
93
94      children.each do |child|
95        p child if @debug
96        validate_range0(child)
97      end
98    end
99
100    def validate_not_cared0(node)
101      beg_pos, end_pos = node.beg_pos, node.end_pos
102      children = node.children.grep(RubyVM::AbstractSyntaxTree::Node)
103
104      @errors << { type: :first_lineno, node: node } if beg_pos.lineno == 0
105      @errors << { type: :first_column, node: node } if beg_pos.column == -1
106      @errors << { type: :last_lineno,  node: node } if end_pos.lineno == 0
107      @errors << { type: :last_column,  node: node } if end_pos.column == -1
108
109      children.each {|c| validate_not_cared0(c) }
110    end
111  end
112
113  SRCDIR = File.expand_path("../../..", __FILE__)
114
115  Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
116    define_method("test_ranges:#{path}") do
117      helper = Helper.new("#{SRCDIR}/#{path}")
118      helper.validate_range
119
120      assert_equal([], helper.errors)
121    end
122  end
123
124  Dir.glob("test/**/*.rb", base: SRCDIR).each do |path|
125    define_method("test_not_cared:#{path}") do
126      helper = Helper.new("#{SRCDIR}/#{path}")
127      helper.validate_not_cared
128
129      assert_equal([], helper.errors)
130    end
131  end
132
133  def test_allocate
134    assert_raise(TypeError) {RubyVM::AbstractSyntaxTree::Node.allocate}
135  end
136
137  def test_parse_argument_error
138    assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(0)}
139    assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(nil)}
140    assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(false)}
141    assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(true)}
142    assert_raise(TypeError) {RubyVM::AbstractSyntaxTree.parse(:foo)}
143  end
144
145  def test_column_with_long_heredoc_identifier
146    term = "A"*257
147    ast = RubyVM::AbstractSyntaxTree.parse("<<-#{term}\n""ddddddd\n#{term}\n")
148    node = ast.children[2]
149    assert_equal(:STR, node.type)
150    assert_equal(0, node.first_column)
151  end
152
153  def test_column_of_heredoc
154    node = RubyVM::AbstractSyntaxTree.parse("<<-SRC\nddddddd\nSRC\n").children[2]
155    assert_equal(:STR, node.type)
156    assert_equal(0, node.first_column)
157    assert_equal(6, node.last_column)
158
159    node = RubyVM::AbstractSyntaxTree.parse("<<SRC\nddddddd\nSRC\n").children[2]
160    assert_equal(:STR, node.type)
161    assert_equal(0, node.first_column)
162    assert_equal(5, node.last_column)
163  end
164
165  def test_parse_raises_syntax_error
166    assert_raise_with_message(SyntaxError, /\bend\b/) do
167      RubyVM::AbstractSyntaxTree.parse("end")
168    end
169  end
170
171  def test_parse_file_raises_syntax_error
172    Tempfile.create(%w"test_ast .rb") do |f|
173      f.puts "end"
174      f.close
175      assert_raise_with_message(SyntaxError, /\bend\b/) do
176        RubyVM::AbstractSyntaxTree.parse_file(f.path)
177      end
178    end
179  end
180
181  def test_of
182    proc = Proc.new { 1 + 2 }
183    method = self.method(__method__)
184
185    node_proc = RubyVM::AbstractSyntaxTree.of(proc)
186    node_method = RubyVM::AbstractSyntaxTree.of(method)
187
188    assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_proc)
189    assert_instance_of(RubyVM::AbstractSyntaxTree::Node, node_method)
190    assert_raise(TypeError) { RubyVM::AbstractSyntaxTree.of("1 + 2") }
191
192    Tempfile.create(%w"test_of .rb") do |tmp|
193      tmp.print "#{<<-"begin;"}\n#{<<-'end;'}"
194      begin;
195        SCRIPT_LINES__ = {}
196        assert_instance_of(RubyVM::AbstractSyntaxTree::Node, RubyVM::AbstractSyntaxTree.of(proc {|x| x}))
197      end;
198      tmp.close
199      assert_separately(["-", tmp.path], "#{<<~"begin;"}\n#{<<~'end;'}")
200      begin;
201        load ARGV[0]
202        assert_empty(SCRIPT_LINES__)
203      end;
204    end
205  end
206
207  def test_scope_local_variables
208    node = RubyVM::AbstractSyntaxTree.parse("x = 0")
209    lv, _, body = *node.children
210    assert_equal([:x], lv)
211    assert_equal(:LASGN, body.type)
212  end
213
214  def test_call
215    node = RubyVM::AbstractSyntaxTree.parse("nil.foo")
216    _, _, body = *node.children
217    assert_equal(:CALL, body.type)
218    recv, mid, args = body.children
219    assert_equal(:NIL, recv.type)
220    assert_equal(:foo, mid)
221    assert_nil(args)
222  end
223
224  def test_fcall
225    node = RubyVM::AbstractSyntaxTree.parse("foo()")
226    _, _, body = *node.children
227    assert_equal(:FCALL, body.type)
228    mid, args = body.children
229    assert_equal(:foo, mid)
230    assert_nil(args)
231  end
232
233  def test_vcall
234    node = RubyVM::AbstractSyntaxTree.parse("foo")
235    _, _, body = *node.children
236    assert_equal(:VCALL, body.type)
237    mid, args = body.children
238    assert_equal(:foo, mid)
239    assert_nil(args)
240  end
241
242  def test_defn
243    node = RubyVM::AbstractSyntaxTree.parse("def a; end")
244    _, _, body = *node.children
245    assert_equal(:DEFN, body.type)
246    mid, defn = body.children
247    assert_equal(:a, mid)
248    assert_equal(:SCOPE, defn.type)
249  end
250
251  def test_defs
252    node = RubyVM::AbstractSyntaxTree.parse("def a.b; end")
253    _, _, body = *node.children
254    assert_equal(:DEFS, body.type)
255    recv, mid, defn = body.children
256    assert_equal(:VCALL, recv.type)
257    assert_equal(:b, mid)
258    assert_equal(:SCOPE, defn.type)
259  end
260
261  def test_dstr
262    node = RubyVM::AbstractSyntaxTree.parse('"foo#{1}bar"')
263    _, _, body = *node.children
264    assert_equal(:DSTR, body.type)
265    head, body = body.children
266    assert_equal("foo", head)
267    assert_equal(:EVSTR, body.type)
268    body, = body.children
269    assert_equal(:LIT, body.type)
270    assert_equal([1], body.children)
271  end
272
273  def test_op_asgn2
274    node = RubyVM::AbstractSyntaxTree.parse("struct.field += foo")
275    _, _, body = *node.children
276    assert_equal(:OP_ASGN2, body.type)
277    recv, _, mid, op, value = body.children
278    assert_equal(:VCALL, recv.type)
279    assert_equal(:field, mid)
280    assert_equal(:+, op)
281    assert_equal(:VCALL, value.type)
282  end
283end
284