Class: Debride

Inherits:
MethodBasedSexpProcessor
  • Object
show all
Defined in:
lib/debride.rb

Overview

A static code analyzer that points out possible dead methods.

Constant Summary collapse

VERSION =

:nodoc:

"1.1.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Debride

Create a new Debride instance w/ options



83
84
85
86
87
88
89
# File 'lib/debride.rb', line 83

def initialize options = {}
  self.option = options
  self.known  = Hash.new { |h,k| h[k] = Set.new }
  self.called = Set.new
  self.map    = Hash.new { |h,k| h[k] = {} }
  super()
end

Instance Attribute Details

#calledObject

A set of called method names.



72
73
74
# File 'lib/debride.rb', line 72

def called
  @called
end

#knownObject

A collection of know methods, mapping method name to implementing classes.



67
68
69
# File 'lib/debride.rb', line 67

def known
  @known
end

#mapObject

:nodoc: # TODO: retire and use method_locations



78
79
80
# File 'lib/debride.rb', line 78

def map
  @map
end

#optionObject

Command-line options.



77
78
79
# File 'lib/debride.rb', line 77

def option
  @option
end

Class Method Details

.parse_options(args) ⇒ Object

Parse command line options and return a hash of parsed option values.



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
# File 'lib/debride.rb', line 34

def self.parse_options args
  options = {:whitelist => []}

  OptionParser.new do |opts|
    opts.banner  = "debride [options] files_or_dirs"
    opts.version = Debride::VERSION

    opts.separator ""
    opts.separator "Specific options:"
    opts.separator ""

    opts.on("-h", "--help", "Display this help.") do
      puts opts
      exit
    end

    opts.on("-w", "--whitelist FILE", String, "Whitelist these messages.") do |s|
      options[:whitelist] = File.read(s).split(/\n+/) rescue []
    end

    opts.on("-v", "--verbose", "Verbose. Show progress processing files.") do
      options[:verbose] = true
    end

    opts.parse! args
  end

  options
end

.run(args) ⇒ Object

Top level runner for bin/debride.



17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/debride.rb', line 17

def self.run args
  opt = parse_options args

  callers = Debride.new opt

  expand_dirs_to_files(args).each do |path|
    warn "processing: #{path}" if opt[:verbose]
    parser = RubyParser.new
    callers.process parser.process File.read(path), path
  end

  callers
end

Instance Method Details

#klass_nameObject

:nodoc:



91
92
93
# File 'lib/debride.rb', line 91

def klass_name # :nodoc:
  super.to_s
end

#method_nameObject

:nodoc:



95
96
97
# File 'lib/debride.rb', line 95

def method_name # :nodoc:
  super.to_s.sub(/^::|#/, "").to_sym
end

#missingObject

Calculate the difference between known methods and called methods.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/debride.rb', line 133

def missing
  whitelist_regexps = []

  option[:whitelist].each do |s|
    if s =~ /^\/.+?\/$/ then
      whitelist_regexps << Regexp.new(s[1..-2])
    else
      called << s.to_sym
    end
  end

  not_called = known.keys - called.to_a

  whitelist_regexp = Regexp.union whitelist_regexps
  not_called.reject! { |s| whitelist_regexp =~ s }

  by_class = Hash.new { |h,k| h[k] = [] }

  not_called.each do |meth|
    known[meth].each do |klass|
      by_class[klass] << meth
    end
  end

  by_class.each do |klass, meths|
    by_class[klass] = meths.sort_by(&:to_s)
  end

  by_class.sort_by { |k,v| k }
end

#process_call(sexp) ⇒ Object

:nodoc:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/debride.rb', line 115

def process_call sexp # :nodoc:
  method_name = sexp[2]
  method_name = :initialize if method_name == :new

  if method_name == :alias_method_chain
    known[sexp[3]] << klass_name
  end

  called << method_name

  process_until_empty sexp

  sexp
end

#process_defn(sexp) ⇒ Object

:nodoc:



99
100
101
102
103
104
105
# File 'lib/debride.rb', line 99

def process_defn sexp # :nodoc:
  super do
    map[klass_name][method_name] = signature
    known[method_name] << klass_name
    process_until_empty sexp
  end
end

#process_defs(sexp) ⇒ Object

:nodoc:



107
108
109
110
111
112
113
# File 'lib/debride.rb', line 107

def process_defs sexp # :nodoc:
  super do
    map[klass_name][method_name] = signature
    known[method_name] << klass_name
    process_until_empty sexp
  end
end

#reportObject

Print out a report of suspects.



167
168
169
170
171
172
173
174
175
176
177
# File 'lib/debride.rb', line 167

def report
  puts "These methods MIGHT not be called:"

  missing.each do |klass, meths|
    puts
    puts klass
    meths.each do |meth|
      puts "  %-35s %s" % [meth, method_locations[map[klass][meth]]]
    end
  end
end