Search code examples
rubyparsingabstract-syntax-treecode-complexity

Extract a snippet of code from Ruby-based source file


I've been playing with flog lately, its a pretty good tool to generate reports on code complexity for ruby applications. As a result of running flog on a project codebase you get an output that looks similar to this:

  1272.3: flog total
     7.3: flog/method average

    62.2: MyClass#foobar lib/myclass#foobar:123
    ... more similar lines ...

Example above provides a score for a method and references exact line number in the source code where that method is defined. This could be a regular instance/class method or any other "dynamic" method, eg. rake task and such.

So the objective is to extract a snippet of code (most likely a method) from source file that starts with a line number defined in flog output. That snippet then could be used in some web UI to show various code metrics (based on other tools like flay) and/or stored in the database. As far as i can see, this task involves parsing ruby code into AST, and then going through the tree to find corresponding start line and figuring out end line number. I've done some experiments with this library - https://github.com/whitequark/parser, works most of the time, but it's a bit tricky to get a correct result.

Are there any other solutions to quickly extract a method code from a source file written in ruby?


Solution

  • You can implement feature that find method by name or line number.

    This sample code show how to find method code by name. It is dirty, but it works(on Ruby 2.1.2). This use parser(gem install parser).

    # method_finder.rb
    require 'parser'
    require 'parser/current'
    
    class MethodFinder
      def initialize(filename)
    @ast = parse(filename)
      end
    
      def find(method_name)
    recursive_search_ast(@ast, method_name)
    return @method_source
      end
    
      private
      def parse(filename)
    Parser::CurrentRuby.parse(File.open(filename, "r").read)
      end
    
      def recursive_search_ast(ast, method_name)
    ast.children.each do |child|
      if child.class.to_s == "Parser::AST::Node"
        if (child.type.to_s == "def" or child.type.to_s == "defs") and (child.children[0].to_s == method_name or child.children[1].to_s == method_name)
          @method_source = child.loc.expression.source
        else
          recursive_search_ast(child, method_name)
        end
      end
    end
      end
    end
    

    And you can use MethodFinder like below.

    mf = MethodFinder.new("./method_finder.rb")
    puts mf.find("find")
    
    => def find(method_name)
    =>     recursive_search_ast(@ast, method_name)
    =>     return @method_source
    =>   end