Ruby traverser

A DSL for traversing ruby code as an object model (graph), which lets you find parts of the ruby code of interest. The traverser leverages ripper2ruby, which leverages ripper, which comes with ruby 1.9. Ruby 1.9 is thus required.

See the unit tests in the test directory for examples of use.

Finders

  • find_module(name)
  • find_class(name, options = {})
  • find_call(name, options = {})
  • find_block(name, options = {})
  • find_def(name, options = {})
  • find_variable(name, options = {})
  • find_assignment(name, options = {})

Find module


  src = %q{      
    module Xyz::Xxx::Blip
      2
    end    
  }
  
  code = Ripper::RubyBuilder.build(src)          
  module_node = code.find_module('Xyz::Xxx::Blip') 
  assert_equal Ruby::Module, module_node.class 

Find class


# class Monty::Python ... end
clazz_node = code.find_class('Monty::Python')   

Find class inheriting from a certain superclass


# class Monty < Abc::Blip ... end
clazz_node = code.find_class('Monty', :superclass => 'Abc::Blip')   

Find call


# gem 'ripper', :src => 'github' 
gem_call = code.find_call('gem', :args => ['ripper', {:src => 'github'}])   

Find block ##


# my_block do ... end
block_node = code.find_block('my_block')   

# my_block do |v| ... end
block_node = code.find_block('my_block', :block_params => ['v'])   

# my_block 7, 'a' do ... end
block_node = code.find_block('my_block', :args => [7, 'a'])   

# my_block 7, 'a', :k => 32 do |v| ... end
block_node = code.find_block('my_block', :args => [7, 'a', {:k => 32}], :block_params => ['v'])   

# my_block :a => 7, b => 3 do |v| ... end
block_node = code.find_block('my_block', :args => [{:a => 7, 'b' => 3}])   
                                                                      
# my_block ['a', 'b'] do |v| ... end  
block_node = code.find_block('my_block', :args => [{:array =>['a', 'b']}])   

Find variable

Source code:


  def hello_world(a)
    my_var
  end  

Ruby code find DSL:


code = Ripper::RubyBuilder.build(src)               
code.inside_def('hello_world', :params => ['a']) do |b|
  call_node = b.find_variable('my_var')
  assert_equal Ruby::Variable, call_node.class
  puts call_node.to_ruby
end

Find assignment

Source code:


  def hello_world(a)
    my_var = 2
  end    

Ruby code find DSL:


  code = Ripper::RubyBuilder.build(src)               
  code.inside_def('hello_world', :params => ['a']) do |b|
    call_node = b.find_assignment('my_var')
  end  

Inside

The following finder methods have corresponding inside_ functions, which support block DSL constructs as shown below.

  • inside_module
  • inside_class
  • inside_def
  • inside_block

  # source code
  src = %q{      
    gem 'ripper', :src => 'github', :blip => 'blap'       
    group :test do
      gem 'ripper', :src => 'github' 
    end  
  }

  code = Ripper::RubyBuilder.build(src)              
  # chaining finders using 'inside__' DSL block constructs  
  code.inside_block('group', :args => [:test]) do |b|
    call_node = b.find_call('gem', :args => ['ripper', {:src => 'github'}])
    assert_equal Ruby::Call, call_node.class
    puts call_node.to_ruby # output ruby code as string for found node 
  end    
  
  src = %q{    
    def hello_world(b)
      3
    end

    def hello_world(a)
      gem 'ripper', :src => 'github' 
    end

  }  
     
  # chaining finders using 'inside__' DSL block constructs
  code = Ripper::RubyBuilder.build(src)            
  code.inside_def('hello_world', :params => ['a']) do |b|
    call_node = b.find_call('gem', :args => ['ripper', {:src => 'github'}])
    assert_equal Ruby::Call, call_node.class
    puts call_node.to_ruby # output ruby code as string for found node 
  end  

Code Mutation API

The API now also supports a wide variety of code mutations using a DSL. More information will soon be available here or on the github wiki. Check the test/mutate folder for test demonstrating what is currently possible.

Note: The mutation API code was developed in a test-driven fashion, but is in need of a major refactoring overhaul sometime soon...

Note on Patches/Pull Requests

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  • Send me a pull request. Bonus points for topic branches.

Copyright (c) 2010 Kristian Mandrup. See LICENSE for details.