Skip to content

Instantly share code, notes, and snippets.

@michaelfeathers
Created February 3, 2015 12:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaelfeathers/18ebc8c8d7d778ee1778 to your computer and use it in GitHub Desktop.
Save michaelfeathers/18ebc8c8d7d778ee1778 to your computer and use it in GitHub Desktop.
require 'ap'
require 'ripper'
CLASS_TEXT = "class A; end; class B; end"
CLASS_SEXP = Ripper.sexp(CLASS_TEXT)
BIG_TEXT = "class A; def a; @a = b; end; def b; @d = a; @e = a; end; end; module B; def b; end; end"
BIG_SEXP = Ripper.sexp(BIG_TEXT)
FIELDS_TEXT = "class A; def a; @a = @b; end; end"
FIELDS_SEXP = Ripper.sexp(FIELDS_TEXT)
def ary?; is_a? Array; end
def str?; is_a? String; end
def nonempty_ary?
ary? && (not empty?)
end
def sexp_select sexp, symbols
(1..(max_nesting_level = 20)).map {|n| sexp.flatten(n)
.select(&:nonempty_ary?)
.select {|e| symbols.include? e[0] }}
.select(&:nonempty_ary?)
.flatten(1)
end
class MethodNode
def initialize sexp
@expression = sexp
end
def name
@name ||= @expression.flatten.select(&:str?).first
end
def body_without_method_name
@expression.flatten.drop(3)
end
def dependencies names_in_scope
names_in_scope & body_without_method_name.select(&:str?)
end
end
class ClassNode
def initialize sexp
@expression = sexp
end
def field_names
@field_names ||= @expression.flatten
.each_cons(2)
.select {|marker, _| marker == :@ivar }
.map {|_,name| name }
.uniq
end
def method_names
method_nodes.keys
end
def names
@names ||= field_names + method_names
end
def method_nodes
@method_nodes ||= Hash[sexp_select(@expression, [:def]).map {|sexp| MethodNode.new(sexp) }
.map {|mn| [mn.name, mn] }]
end
def dependencies
@dependencies ||= method_nodes.map {|n,mn| [n, mn.dependencies(names)] }
end
end
def q string
"\"#{string}\""
end
def dot_names names
" { node [shape=ellipse] #{names.map(&:to_s).map {|s| q(s) }.join(' ')}}"
end
def dot_method_dependencies source_name, target_names
target_names.map {|tn| " #{q(source_name)} -> #{q(tn)}" }.join($/)
end
def dot_class class_node
<<-TEXT
digraph {
#{dot_names(class_node.names)}
#{class_node.dependencies.map {|source, targets| dot_method_dependencies(source, targets)}.join($/)}
}
TEXT
end
CS = sexp_select(BIG_SEXP, [:class, :module]).map { |e| ClassNode.new(e) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment