我想编写一个gem,可用于解析Ruby代码并报告它是否检测到某些函数调用的存在。我的特定用例是,我想确保没有提交合并到包含sleep
次调用的主代码库中。
执行此操作的蛮力方式只是解析纯文本文件并在未注释的行上查找“睡眠”,但我可以想象它容易出错,并且有些难看。
有没有办法在Ruby代码中搜索某些函数调用,可能会将代码“编译”成某种标记形式并解析它?
答案 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
中的睡眠,虽然如果需要的话也不难解决。