Created
July 7, 2012 04:21
-
-
Save egonSchiele/3064593 to your computer and use it in GitHub Desktop.
Find undefined symbols in a ruby script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rubygems' | |
require 'ruby2ruby' | |
require 'ruby_parser' | |
require 'set' | |
require 'trollop' | |
require 'contracts' | |
include Contracts | |
class Foo < SexpProcessor | |
include Contracts | |
@@assigns = Set.new | |
def initialize | |
super | |
self.auto_shift_type = true | |
end | |
Contract Sexp => Set | |
def self.find_symbols(_node) | |
to_check = [_node] | |
found = Set.new | |
to_check.each do |node| | |
node.each do |part| | |
if part.class == Sexp | |
to_check << part | |
elsif part.class == Symbol | |
if part == :colon2 | |
to_check << node[1] | |
@@assigns.add node[2] | |
else | |
found.add part | |
# this is how we have to check classes | |
if part == :class | |
find_deep(node, :const).each { |x| @@assigns.add x[1] } | |
end | |
end | |
end | |
end | |
end | |
found | |
end | |
Contract Sexp, Symbol => ArrayOf[Sexp] | |
def self.find_deep(_node, node_name) | |
to_check = [_node] | |
nodes_found = [] | |
to_check.each do |node| | |
node.each do |part| | |
if part.class == Sexp | |
to_check << part | |
if part.node_type == node_name | |
nodes_found << part | |
end | |
end | |
end | |
end | |
nodes_found | |
end | |
Contract Sexp => nil | |
def self.process_assigns(_sexp) | |
# adds to lasgns an array [variable name, variable's class] | |
sexp = _sexp.to_a | |
name = sexp[1] | |
assn = sexp[2] | |
real_assn = nil | |
return unless assn | |
case assn[0] | |
when :call | |
# instance of a class | |
if assn[2] == :new | |
real_assn = assn[1][1] | |
end | |
end | |
@@assigns.add name | |
end | |
def self.parsetree_symbols | |
Set.new([:lit, :arglist, :defn, :or, :str, :dstr, :if, :scope, :call, :ivar, :return, :hash, :colon2, :block, :iter, :args]) | |
end | |
def self.methods | |
Set.new(Kernel.methods) # default methods on every object | |
end | |
Contract Sexp => { :symbols => Set, :defined => Set } | |
def self.process(sexp) | |
# function definitions | |
find_deep(sexp, :defn).each {|x| process_calls(x) } | |
klasses = sexp.find_nodes(:class) | |
# instance methods in classes | |
klasses.each do |klass| | |
defns = find_deep(klass, :defn) | |
defns.each { |x| process_calls(x) } | |
end | |
# assignments | |
find_deep(sexp, :lasgn).each { |x| process_assigns(x) } | |
# function calls | |
process_calls(sexp) | |
symbols = find_symbols(sexp) - methods - parsetree_symbols | |
# undefined = symbols - @@assigns | |
{ :symbols => symbols, :defined => @@assigns } | |
end | |
Contract Sexp => nil | |
def self.process_calls(sexp) | |
@@assigns.add sexp[1] | |
find_deep(sexp, :call).each do |call| | |
@@assigns += find_symbols(call.find_nodes(:arglist).first) | |
end | |
nil | |
end | |
end | |
Contract String => nil | |
def log x | |
puts x if OPTS[:verbose] | |
end | |
Contract None => Set | |
def erector_symbols | |
full_tags = [ | |
'a', 'abbr', 'acronym', 'address', 'article', 'aside', 'audio', | |
'b', 'bdo', 'big', 'blockquote', 'body', 'button', | |
'canvas', 'caption', 'center', 'cite', 'code', 'colgroup', 'command', | |
'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', | |
'em', | |
'fieldset', 'figure', 'footer', 'form', 'frameset', | |
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'html', 'i', | |
'iframe', 'ins', 'keygen', 'kbd', 'label', 'legend', 'li', | |
'map', 'mark', 'meter', | |
'nav', 'noframes', 'noscript', | |
'object', 'ol', 'optgroup', 'option', | |
'p', 'pre', 'progress', | |
'q', 'ruby', 'rt', 'rp', 's', | |
'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strike', | |
'strong', 'style', 'sub', 'sup', | |
'table', 'tbody', 'td', 'textarea', 'tfoot', | |
'th', 'thead', 'time', 'title', 'tr', 'tt', | |
'u', 'ul', | |
'var', 'video' | |
] | |
empty_tags = ['area', 'base', 'br', 'col', 'embed', 'frame', | |
'hr', 'img', 'input', 'link', 'meta', 'param'] | |
erector = ['needs', 'text'] | |
Set.new(full_tags.map(&:to_sym) + empty_tags.map(&:to_sym) + erector.map(&:to_sym)) | |
end | |
Contract None => Set | |
def rails_symbols | |
Set.new [ | |
:text_field_tag, | |
:submit_tag, | |
:form_tag, | |
:link_to_remote, | |
] | |
end | |
OPTS = Trollop::options do | |
banner <<-EOS | |
Find undefined symbols in a file or files. | |
Usage: #{$0} [files] | |
EOS | |
opt :verbose, "Print log info" | |
opt :skip_large_files, "Skip large files", :default => true | |
opt :size_limit, "Threshold for skip-large-files", :default => 2000 | |
opt :first_only, "If true, only lists symbols that are used in the first file and are undefined.", :default => true | |
opt :erector, "Use if you're using this with erector...it will ignore erector tags", :default => false | |
opt :rails, "Use if you're using this with rails...it will ignore rails tags", :default => false | |
end | |
Trollop::die("no files specified") if ARGV.empty? | |
parser = RubyParser.new | |
first_symbols = nil | |
all_symbols = Set.new | |
defined_symbols = Set.new | |
ARGV.each_with_index do |file, i| | |
log "checking #{file}" | |
data = File.open(file, "r").readlines | |
next if data.size > OPTS[:size_limit] && OPTS[:skip_large_files] | |
parsed = parser.process(data.join("\n")) | |
results = Foo.process(parsed) | |
all_symbols += results[:symbols] | |
if i == 0 | |
first_symbols = results[:symbols] | |
end | |
defined_symbols += results[:defined] | |
end | |
final_symbols = OPTS[:first_only] ? first_symbols : all_symbols | |
if OPTS[:erector] | |
defined_symbols += erector_symbols | |
end | |
if OPTS[:rails] | |
defined_symbols += rails_symbols | |
end | |
undefined_symbols = (final_symbols - defined_symbols).to_a | |
if undefined_symbols.empty? | |
puts "No undefined symbols." | |
else | |
puts "All undefined symbols:" | |
undefined_symbols.each do |symbol| | |
p symbol | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment