Refax the Automatic Refactoring Engine, improved
Published 2005-02-06 @ 22:53
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
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
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
|