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