unless defined?(Parser::CurrentRuby) require 'parser/current' end require 'method_log/method_definition' require 'method_log/scope' module MethodLog class MethodFinder < Parser::AST::Processor attr_reader :methods def initialize(source_file) @source_file = source_file @scope = Scope::Root.new @methods = {} ast = Parser::CurrentRuby.parse(source_file.source) process(ast) end def find(method_identifier) @methods[method_identifier] end def on_module(node) const_node = node.children.first constants = process_const(const_node) new_constant = constants.pop with_scope(@scope.for(constants).define(new_constant)) { super } end alias_method :on_class, :on_module def on_sclass(node) target_node = node.children.first with_scope(singleton_scope_for(target_node)) { super } end def on_def(node) name, args_node, body_node = *node record_method_definition(@scope, name, node) super end def on_defs(node) definee_node, name, args_node, body_node = *node scope = singleton_scope_for(definee_node) record_method_definition(scope, name, node) super end private def singleton_scope_for(node) case node.type when :self @scope.singleton when :const constants = process_const(node) @scope.for(constants).singleton else @scope.define(node.inspect).singleton end end def process_const(node, namespaces = []) scope_node, name = *node namespace = (node.type == :cbase) ? :root : name namespaces.unshift(namespace) if scope_node process_const(scope_node, namespaces) end namespaces end def lines_for(node) expression = node.location.expression first_line = expression.line - 1 last_line = expression.source_buffer.decompose_position(expression.end_pos).first - 1 first_line..last_line end def record_method_definition(scope, name, node) definition = MethodDefinition.new(@source_file, lines_for(node)) identifier = scope.method_identifier(name) @methods[identifier] = definition end def with_scope(scope, &block) @scope = scope yield ensure @scope = scope.parent end end end