1

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

这样做的蛮力方法只是解析纯文本文件并在未注释的行上寻找“睡眠”,但我可以想象这容易出错,而且有点难看。

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

4

1 回答 1

2

I'm guessing this is just for debugging purposes, eg you are putting sleep statements in for testing, and don't want them in when you commit. If that is the case, the below code does what you want:

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

Example usage:

>> 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

Note that any dynamic method invocation, or just method aliasing will bypass this, eg eval("sl" + "eep 4"), or send(:sleep, 4). If it is just sanity testing committed code though it should suffice.

Finally it doesn't detect the sleep in Kernel.sleep 4, although it wouldn't be hard to fix that if need that.

于 2013-06-04T13:22:44.177 回答