require 'ripper' require 'pp' module BetterHtml module TestHelper class RubyExpr attr_reader :calls BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ class ParseError < RuntimeError; end class MethodCall attr_accessor :instance, :method, :arguments end def initialize(code: nil, tree: nil) if code code = code.gsub(BLOCK_EXPR, '') tree = Ripper.sexp(code) raise ParseError, "cannot parse code" unless tree && tree.last.first.is_a?(Array) @tree = tree.last.first else @tree = tree end @calls = [] parse! end private def parse! parse_expr(@tree) end def parse_expr(expr) case expr.first when :var_ref parse_expr(expr[1]) when :paren parse_expr(expr[1].first) when :string_literal parse_expr(expr[1]) when :string_content expr[1..-1].each do |subexpr| parse_expr(subexpr) end when :string_embexpr expr[1].each do |subexpr| parse_expr(subexpr) end when :assign, :massign parse_expr(expr[2]) when :call @calls << obj = MethodCall.new obj.instance = expr[1] obj.method = parse_expr(expr[3]) obj when :fcall, :vcall @calls << obj = MethodCall.new obj.method = parse_expr(expr[1]) obj when :method_add_arg # foo(bar) -> foo=expr[1], bar=expr[2] obj = parse_expr(expr[1]) obj.arguments = parse_expr(expr[2]) obj when :command # foo bar -> foo=expr[1], bar=expr[2] @calls << obj = MethodCall.new obj.method = parse_expr(expr[1]) obj.arguments = parse_expr(expr[2]) obj when :arg_paren parse_expr(expr[1]) unless expr[1].nil? when :if_mod, :unless_mod # foo if bar -> bar=expr[1], foo=expr[2] parse_expr(expr[2]) when :ifop # foo ? bar : baz -> foo=expr[1], bar=expr[2], baz=expr[3] parse_expr(expr[2]) parse_expr(expr[3]) else expr[1] end end end end end