解析某些函数调用的Ruby代码

时间:2013-06-04 12:30:11

标签: ruby

我想编写一个gem,可用于解析Ruby代码并报告它是否检测到某些函数调用的存在。我的特定用例是,我想确保没有提交合并到包含sleep次调用的主代码库中。

执行此操作的蛮力方式只是解析纯文本文件并在未注释的行上查找“睡眠”,但我可以想象它容易出错,并且有些难看。

有没有办法在Ruby代码中搜索某些函数调用,可能会将代码“编译”成某种标记形式并解析它?

1 个答案:

答案 0 :(得分:2)

我猜这只是出于调试目的,例如你将sleep语句放入测试中,并且在你提交时不希望它们进入。如果是这种情况,以下代码可以满足您的需求:

require 'ripper'
class MethodParser
    def initialize(source)
        @ast = Ripper.sexp(source)
    end
    def is_method_called?(method_name)
        search_ast_for_method(@ast, method_name)
    end
    private

    def is_top_level_method_call(ast, method_name)
        # firstly check if possible command block
        unless ast.is_a?(Array) && ast.length > 1 && ast[1].is_a?(Array)
            return false
        end
        # now check if it is a function call or command, and check the method name
        if [:command, :fcall].include? ast[0]
            ast[1].include?(method_name.to_s)
        else
            false
        end
    end

    def search_ast_for_method(ast, method_name)
        return true if is_top_level_method_call(ast, method_name)
        return false unless ast.is_a? Array
        ast.any? { |e| search_ast_for_method(e, method_name) }
    end
end

使用示例:

>> m = MethodParser.new <<EOF
class TestClass
  def method
    puts "hello"
    sleep(42)
  end
end
EOF
=> #<MethodParser:0x007f9df3a493c0 @ast=[:program, [[:class, [:const_ref, [:@const, "TestClass", [1, 6]]], nil, [:bodystmt, [[:def, [:@ident, "method", [2, 6]], [:params, nil, nil, nil, nil, nil, nil, nil], [:bodystmt, [[:command, [:@ident, "puts", [3, 4]], [:args_add_block, [[:string_literal, [:string_content, [:@tstring_content, "hello", [3, 10]]]]], false]], [:method_add_arg, [:fcall, [:@ident, "sleep", [4, 4]]], [:arg_paren, [:args_add_block, [[:@int, "42", [4, 10]]], false]]]], nil, nil, nil]]], nil, nil, nil]]]]>
>> m.is_method_called? :sleep
=> true
>> m.is_method_called? :puts
=> true
>> m.is_method_called? :hello
=> false
>> m.is_method_called? "hello"
=> false

请注意,任何动态方法调用或方法别名都会绕过此方法,例如eval("sl" + "eep 4")send(:sleep, 4)。如果只是完整性测试已提交的代码,尽管它应该足够了。

最后它没有检测到Kernel.sleep 4中的睡眠,虽然如果需要的话也不难解决。