require 'parser/current' module BetterHtml module TestHelper class RubyExpr BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ class ParseError < RuntimeError; end class MethodCall attr_accessor :instance, :method, :arguments def initialize(instance, method, arguments) @instance = instance @method = method @arguments = arguments end def self.from_ast_node(node) new(node.children[0], node.children[1], node.children[2..-1]) end end def initialize(ast) raise ArgumentError, "expect first argument to be Parser::AST::Node" unless ast.is_a?(::Parser::AST::Node) @ast = ast end def self.parse(code) parser = ::Parser::CurrentRuby.new parser.diagnostics.ignore_warnings = true parser.diagnostics.all_errors_are_fatal = false parser.diagnostics.consumer = nil buf = ::Parser::Source::Buffer.new('(string)') buf.source = code.sub(BLOCK_EXPR, '') parsed = parser.parse(buf) raise ParseError, "error parsing code: #{code.inspect}" unless parsed new(parsed) end def start @ast.loc.expression.begin_pos end def end @ast.loc.expression.end_pos end def traverse(current=@ast, only: nil, &block) yield current if node_match?(current, only) each_child_node(current) do |child| traverse(child, only: only, &block) end end def each_child_node(current=@ast, only: nil, range: (0..-1)) current.children[range].each do |child| if child.is_a?(::Parser::AST::Node) && node_match?(child, only) yield child end end end def node_match?(current, type) type.nil? || Array[type].flatten.include?(current.type) end STATIC_TYPES = [:str, :int, :true, :false, :nil] def each_return_value_recursive(current=@ast, only: nil, &block) case current.type when :send, :csend, :ivar, *STATIC_TYPES yield current if node_match?(current, only) when :if, :masgn, :lvasgn # first child is ignored as it does not contain return values # for example, in `foo ? x : y` we only care about x and y, not foo each_child_node(current, range: 1..-1) do |child| each_return_value_recursive(child, only: only, &block) end else each_child_node(current) do |child| each_return_value_recursive(child, only: only, &block) end end end def static_value? returns = [] each_return_value_recursive do |node| returns << node end return false if returns.size == 0 returns.each do |node| if STATIC_TYPES.include?(node.type) next elsif node.type == :dstr each_child_node(node) do |child| return false if child.type != :str end else return false end end true end def calls calls = [] each_return_value_recursive(only: [:send, :csend]) do |node| calls << MethodCall.from_ast_node(node) end calls end end end end