🕷 zenspider.com

by ryan davis



sitemap
Looking for the Ruby Quickref?

Refax the Automatic Refactoring Engine, improved

Published 2005-02-06 @ 22:53

Tagged toys, parsetree, ruby, ruby2c

Refax the Automatic Refactoring Engine (the blog linked is now dead and archive.org has no copy) is a very cool proof of concept that uses ParseTree to discover redundant code and suggest a refactoring. The only problem with it is that it outputs raw sexp as the suggested refactoring:

1
Suggest refactoring weirdfunc in HastilyWritten: [:block, [:args], [:while, [:vcall, :keepgoing], [:block, [:fcall, :puts, [:array, [:str, "This is a weird loop"]]], [:fcall, :doSomethingWeird]]]]

Not very comprehensible. I spent some time with it and plugged RubyToRuby (more on that coming soon) so it would output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Suggest refactoring HastilyWritten#weirdfunc from:
def weirdfunc()
  puts("This is a weird loop")
  doSomethingWeird()
  begin
    puts("This is a weird loop")
    doSomethingWeird()
  end while keepgoing
end

to:

def weirdfunc()
  begin
    puts("This is a weird loop")
    doSomethingWeird()
  end while keepgoing
end

Reworked Refax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
require 'parse_tree'
require 'ruby_to_ruby'

class Refax

  def couldPossiblyRefactor?(p, ind)
    return false unless p[ind].is_a?(Array)
    return false unless p[ind].first == :while
    return false if p[ind][-1] == :post
    return true unless p[ind][2].is_a?(Array)
    p[ind][2].first == :block
  end

  def howManyInsn(p)
    fail "Must be a while, not a #{p}" unless p.first == :while
    if p[2].is_a?(Array)
      fail unless p[2].first == :block
      p[2].size - 1
    else
      1
    end
  end

  def grabInsnArray(p)
    fail "Must be a while, not a #{p}" unless p[0] == :while
      if p[2].is_a?(Array)
        p[2][1..-1]
      else
        [p[2]]
      end
  end

  def isEquiv(a, b)
    a.to_s == b.to_s
  end

  def fixcode(p, ind)
    loopsize = howManyInsn(p[ind])
    goodcode = p.clone
    goodcode.slice!(ind-loopsize..ind-1)
    goodcode # todo : make correcter
  end

  def recurseOn(p)
    if p.is_a?(Array)
      @lastclass = p[1] if p.first == :class
      @lastfunc = p[1] if p.first == :defn
      p.each { |i| recurseOn(i) }
      p.each_index do |ind|
        if couldPossiblyRefactor?(p,ind)
          loopsize = howManyInsn(p[ind])
          if loopsize < ind
            if isEquiv(p[ind-loopsize,loopsize], grabInsnArray(p[ind]))
              goodstuff = fixcode(p, ind)
              puts "Suggest refactoring #{@lastclass}##{@lastfunc} from:"
              puts 
              puts RubyToRuby.translate(eval(@lastclass.to_s), @lastfunc)
              print "\nto:\n\n"
              puts RubyToRuby.new.process(s(:defn, @lastfunc, s(:scope, goodstuff)))
            end
          end
        end
      end
    end
  end

  def refactor(c)
    fail "Must have class or module" unless c.is_a?(Module)
    p = ParseTree.new.parse_tree(c)
    recurseOn(p)
  end

  r = Refax.new
  ObjectSpace.each_object(Module) { |c|
    r.refactor(c)
  }

end