zombie_scout 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +7 -0
- data/README.md +78 -12
- data/Rakefile +4 -0
- data/bin/zombie_scout +1 -1
- data/lib/zombie_scout/app.rb +23 -1
- data/lib/zombie_scout/flog_scorer.rb +27 -0
- data/lib/zombie_scout/mission.rb +50 -14
- data/lib/zombie_scout/{method_finder.rb → parser.rb} +10 -17
- data/lib/zombie_scout/version.rb +1 -1
- data/spec/zombie_scout/parser_spec.rb +184 -0
- data/zombie_scout.gemspec +4 -3
- metadata +24 -7
- data/spec/zombie_scout/method_finder_spec.rb +0 -160
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1d48e23ce3d846d2a1bb64d2c5a698386a7ccbb
|
4
|
+
data.tar.gz: dec98dc4fcf229576c71c080c00e4a24e1afa7d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 989aedb17a61b6aa6dc899a3b9426117dc771fece220a5fb1505d5b21b36e40dde5a06af2e17a424e438fffb212b765e4500aa227f3bec11c9350081ab6e1d91
|
7
|
+
data.tar.gz: 973b0f339bbfef34d128924df0d66902be6c929ac96c617516f230c28c7b2ebce8761026376a11cb6b5efcf104a22a914574bbf62e24fb5c4c4c03d9ebf810e5
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -4,6 +4,9 @@ GEM
|
|
4
4
|
ast (1.1.0)
|
5
5
|
diff-lcs (1.1.3)
|
6
6
|
fakefs (0.5.0)
|
7
|
+
flog (4.2.0)
|
8
|
+
ruby_parser (~> 3.1, > 3.1.0)
|
9
|
+
sexp_processor (~> 4.4)
|
7
10
|
parser (2.1.4)
|
8
11
|
ast (~> 1.1)
|
9
12
|
slop (~> 3.4, >= 3.4.5)
|
@@ -16,6 +19,9 @@ GEM
|
|
16
19
|
rspec-expectations (2.11.3)
|
17
20
|
diff-lcs (~> 1.1.3)
|
18
21
|
rspec-mocks (2.11.3)
|
22
|
+
ruby_parser (3.4.1)
|
23
|
+
sexp_processor (~> 4.1)
|
24
|
+
sexp_processor (4.4.1)
|
19
25
|
slop (3.4.7)
|
20
26
|
thor (0.18.1)
|
21
27
|
|
@@ -24,6 +30,7 @@ PLATFORMS
|
|
24
30
|
|
25
31
|
DEPENDENCIES
|
26
32
|
fakefs
|
33
|
+
flog (~> 4.2)
|
27
34
|
parser (~> 2.1)
|
28
35
|
rake
|
29
36
|
rspec
|
data/README.md
CHANGED
@@ -14,7 +14,26 @@ parses your code to find method declarations, and then greps through your
|
|
14
14
|
project's source, looking for each method. If Zombie Scout can't find any
|
15
15
|
calls to a method, it presumes the method is dead, and reports back to you.
|
16
16
|
|
17
|
-
###
|
17
|
+
### How Does It Work?
|
18
|
+
|
19
|
+
#### Phase 1: Parse
|
20
|
+
|
21
|
+
Zombie Scout parses your ruby files, and remembers all the methods it sees
|
22
|
+
defined.
|
23
|
+
|
24
|
+
#### Phase 2: Grep
|
25
|
+
|
26
|
+
Then it greps your whole project - currently *.rb and *.erb files - for
|
27
|
+
whole-word occurrances of any of the methods it found. The whole-word grep
|
28
|
+
(`grep -w`) means that searching for `dead_method` won't count `dead_methods`
|
29
|
+
as a match, and accidentally think it's live.
|
30
|
+
|
31
|
+
So, if you have 1,000 methods, you have to run 1,000 greps? That's slow, right?
|
32
|
+
Good news: back in Phase 1, when Zombie Scout parsed your code, it also
|
33
|
+
remembered all the method CALLS it saw, and it automatically counts those
|
34
|
+
methods as not-zombies, so it saves a bunch of time by not grepping for them.
|
35
|
+
|
36
|
+
#### Fair Warning
|
18
37
|
|
19
38
|
Zombie Scout isn't exhaustive or thorough - it's a scout, not a spy. (That
|
20
39
|
could be another project, though - a Zombie Spy.)
|
@@ -57,23 +76,69 @@ Or, add this to your Gemfile:
|
|
57
76
|
|
58
77
|
## Usage
|
59
78
|
|
79
|
+
### From the Command Line
|
80
|
+
|
60
81
|
You can run it on a whole folder:
|
61
82
|
|
62
83
|
dan@aleph:~/projects/zombie_scout$ zombie_scout scout
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
lib/zombie_scout/
|
67
|
-
lib/zombie_scout/
|
68
|
-
|
84
|
+
Scouted 43 methods in 9 files, in 1.096459054 seconds.
|
85
|
+
Found 11 potential zombies, with a combined flog score of 51.5.
|
86
|
+
|
87
|
+
lib/zombie_scout/parser.rb:29 on_send 8.3
|
88
|
+
lib/zombie_scout/parser.rb:23 on_defs 5.9
|
89
|
+
lib/zombie_scout/parser.rb:65 handle_def_delegators 5.8
|
90
|
+
lib/zombie_scout/parser.rb:56 handle_attr_accessor 5.6
|
91
|
+
lib/zombie_scout/parser.rb:17 on_def 4.9
|
92
|
+
lib/zombie_scout/parser.rb:48 handle_attr_writer 4.3
|
93
|
+
lib/zombie_scout/parser.rb:40 handle_attr_reader 4.3
|
94
|
+
lib/zombie_scout/parser.rb:73 handle_def_delegator 3.8
|
95
|
+
lib/zombie_scout/parser.rb:79 handle_scope 3.8
|
96
|
+
lib/zombie_scout/parser.rb:100 on_sym 2.4
|
97
|
+
lib/zombie_scout/mission.rb:34 zombie_count 2.4
|
69
98
|
|
70
99
|
(See what I meant about callbacks and false-positives?)
|
71
100
|
|
72
101
|
Or you can run it on a given file or glob:
|
73
102
|
|
74
|
-
dan@aleph:~/projects/zombie_scout$ zombie_scout scout lib/
|
75
|
-
|
76
|
-
|
103
|
+
dan@aleph:~/projects/zombie_scout$ zombie_scout scout lib/app.rb
|
104
|
+
Scouted 1 methods in 1 files, in 0.041942649 seconds.
|
105
|
+
Found 0 potential zombies, with a combined flog score of 0.0.
|
106
|
+
|
107
|
+
ZombieScout will also report in CSV, if you like:
|
108
|
+
|
109
|
+
dan@aleph:~/projects/zombie_scout$ zombie_scout scout --format csv
|
110
|
+
location,name,flog_score
|
111
|
+
lib/zombie_scout/parser.rb:29,on_send,8.3
|
112
|
+
lib/zombie_scout/parser.rb:23,on_defs,5.9
|
113
|
+
lib/zombie_scout/parser.rb:65,handle_def_delegators,5.8
|
114
|
+
lib/zombie_scout/parser.rb:56,handle_attr_accessor,5.6
|
115
|
+
lib/zombie_scout/parser.rb:17,on_def,4.9
|
116
|
+
lib/zombie_scout/parser.rb:48,handle_attr_writer,4.3
|
117
|
+
lib/zombie_scout/parser.rb:40,handle_attr_reader,4.3
|
118
|
+
lib/zombie_scout/parser.rb:73,handle_def_delegator,3.8
|
119
|
+
lib/zombie_scout/parser.rb:79,handle_scope,3.8
|
120
|
+
lib/zombie_scout/parser.rb:100,on_sym,2.4
|
121
|
+
lib/zombie_scout/mission.rb:34,zombie_count,2.4
|
122
|
+
|
123
|
+
### In Ruby
|
124
|
+
|
125
|
+
You can also embed ZombieScout in your own code, if you need that kind of
|
126
|
+
thing:
|
127
|
+
|
128
|
+
irb> require 'zombie_scout'
|
129
|
+
=> true
|
130
|
+
irb> require 'pp'
|
131
|
+
=> true
|
132
|
+
irb> > pp ZombieScout::Mission.new('.').scout
|
133
|
+
[{:location=>"./lib/zombie_scout/parser.rb:17",
|
134
|
+
:name=>:on_def,
|
135
|
+
:flog_score=>4.9},
|
136
|
+
{:location=>"./lib/zombie_scout/parser.rb:23",
|
137
|
+
:name=>:on_defs,
|
138
|
+
:flog_score=>5.9},
|
139
|
+
{:location=>"./lib/zombie_scout/parser.rb:29",
|
140
|
+
:name=>:on_send,
|
141
|
+
:flog_score=>8.3}, ...]
|
77
142
|
|
78
143
|
## Code Status
|
79
144
|
|
@@ -84,10 +149,11 @@ Or you can run it on a given file or glob:
|
|
84
149
|
* [x] parse for attr_reader/writer/accessors, & forwardables, & rails scopes
|
85
150
|
* [ ] parse for rails delegators
|
86
151
|
* [ ] let users configure: files to search for methods, files to search for calls...probably in `.zombie_scout`.
|
152
|
+
* [x] option for CSV output
|
87
153
|
|
88
154
|
ToThinkAbouts:
|
89
|
-
* [
|
90
|
-
* [
|
155
|
+
* [x] extract a hash-y report structure that can be used by whatever, from the default report
|
156
|
+
* [x] pass a ruby file, or a glob
|
91
157
|
* [ ] rspec/mini-test drop-in tests that can be added easily, to fail your
|
92
158
|
build if the scout or spy finds dead code. (This is probably a bad idea.)
|
93
159
|
|
data/Rakefile
CHANGED
data/bin/zombie_scout
CHANGED
data/lib/zombie_scout/app.rb
CHANGED
@@ -4,8 +4,30 @@ require 'zombie_scout/mission'
|
|
4
4
|
module ZombieScout
|
5
5
|
class App < Thor
|
6
6
|
desc "scout", "scout for zombie code in current directory"
|
7
|
+
option :format, default: 'report'
|
7
8
|
def scout(*globs)
|
8
|
-
Mission.new(globs)
|
9
|
+
mission = Mission.new(globs)
|
10
|
+
report = mission.scout.sort_by { |z| -z[:flog_score] }
|
11
|
+
|
12
|
+
if options[:format] == 'report'
|
13
|
+
total_flog_score = report.map { |z| z[:flog_score] }.reduce(0, :+)
|
14
|
+
|
15
|
+
puts "Scouted #{mission.defined_method_count} methods in #{mission.source_count} files, in #{mission.duration} seconds."
|
16
|
+
puts "Found #{report.size} potential zombies, with a combined flog score of #{total_flog_score.round(1)}."
|
17
|
+
puts
|
18
|
+
|
19
|
+
report.each do |zombie|
|
20
|
+
puts [zombie[:location], zombie[:name], zombie[:flog_score]] * "\t"
|
21
|
+
end
|
22
|
+
elsif options[:format] == 'csv'
|
23
|
+
require 'csv'
|
24
|
+
CSV do |csv|
|
25
|
+
csv << %w(location name flog_score)
|
26
|
+
report.each do |zombie|
|
27
|
+
csv << [zombie[:location], zombie[:name], zombie[:flog_score]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
9
31
|
end
|
10
32
|
end
|
11
33
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'flog'
|
2
|
+
|
3
|
+
module ZombieScout
|
4
|
+
class FlogScorer
|
5
|
+
def initialize(zombie_location)
|
6
|
+
@zombie_location = zombie_location
|
7
|
+
end
|
8
|
+
|
9
|
+
def score
|
10
|
+
raw_score.round(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def raw_score
|
16
|
+
flog = Flog.new(methods: true, quiet: true, score:false)
|
17
|
+
all_scores = flog.flog(zombie_path)
|
18
|
+
method_locations = flog.instance_variable_get(:@method_locations)
|
19
|
+
scores = all_scores.fetch(method_locations.invert.fetch(@zombie_location, {}), {}) # default to {} in case there is no score. (it's a 0)
|
20
|
+
flog.score_method(scores)
|
21
|
+
end
|
22
|
+
|
23
|
+
def zombie_path
|
24
|
+
@zombie_location.sub(/\:\d+$/, '')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/zombie_scout/mission.rb
CHANGED
@@ -1,38 +1,74 @@
|
|
1
1
|
require 'zombie_scout/ruby_project'
|
2
|
-
require 'zombie_scout/
|
2
|
+
require 'zombie_scout/parser'
|
3
3
|
require 'zombie_scout/method_call_finder'
|
4
|
+
require 'zombie_scout/flog_scorer'
|
4
5
|
|
5
6
|
module ZombieScout
|
6
7
|
class Mission
|
8
|
+
attr_reader :defined_method_count
|
9
|
+
|
7
10
|
def initialize(globs)
|
8
|
-
puts "Scouting out #{Dir.pwd}!"
|
9
11
|
@ruby_project = RubyProject.new(*globs)
|
10
12
|
end
|
11
13
|
|
12
14
|
def scout
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
@start_time = Time.now
|
16
|
+
zombies.map { |zombie|
|
17
|
+
{ location: zombie.location,
|
18
|
+
name: zombie.name,
|
19
|
+
flog_score: flog_score(zombie.location)
|
20
|
+
}
|
21
|
+
}.tap {
|
22
|
+
@end_time = Time.now
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def duration
|
27
|
+
@end_time - @start_time
|
28
|
+
end
|
16
29
|
|
17
|
-
|
30
|
+
def source_count
|
31
|
+
sources.size
|
32
|
+
end
|
33
|
+
|
34
|
+
def zombie_count
|
35
|
+
zombies.size
|
18
36
|
end
|
19
37
|
|
20
38
|
private
|
21
39
|
|
40
|
+
def sources
|
41
|
+
@sources ||= @ruby_project.ruby_sources
|
42
|
+
end
|
43
|
+
|
44
|
+
def flog_score(zombie_location)
|
45
|
+
ZombieScout::FlogScorer.new(zombie_location).score
|
46
|
+
end
|
47
|
+
|
22
48
|
def zombies
|
23
|
-
@zombies
|
49
|
+
return @zombies unless @zombies.nil?
|
50
|
+
|
51
|
+
scout!
|
52
|
+
@zombies ||= @defined_methods.select { |method|
|
24
53
|
might_be_dead?(method)
|
25
54
|
}
|
26
55
|
end
|
27
56
|
|
28
|
-
def
|
29
|
-
@
|
30
|
-
MethodFinder.new(ruby_source).find_methods
|
31
|
-
}.flatten
|
32
|
-
end
|
57
|
+
def scout!
|
58
|
+
@defined_methods, @called_methods = [], []
|
33
59
|
|
34
|
-
|
35
|
-
|
60
|
+
sources.each do |ruby_source|
|
61
|
+
parser = ZombieScout::Parser.new(ruby_source)
|
62
|
+
@defined_methods.concat(parser.defined_methods)
|
63
|
+
@called_methods.concat(parser.called_methods)
|
64
|
+
end
|
65
|
+
|
66
|
+
@defined_method_count = @defined_methods.size
|
67
|
+
|
68
|
+
@called_methods.uniq!
|
69
|
+
@defined_methods.reject! do |method|
|
70
|
+
@called_methods.include?(method.name)
|
71
|
+
end
|
36
72
|
end
|
37
73
|
|
38
74
|
def might_be_dead?(method)
|
@@ -3,21 +3,15 @@ require 'parser/current'
|
|
3
3
|
module ZombieScout
|
4
4
|
Method = Class.new(Struct.new(:name, :location))
|
5
5
|
|
6
|
-
class
|
6
|
+
class Parser < Parser::AST::Processor
|
7
|
+
attr_reader :defined_methods, :called_methods
|
8
|
+
|
7
9
|
def initialize(ruby_source)
|
8
10
|
@ruby_source = ruby_source
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@methods = []
|
13
|
-
@private_method_calls = []
|
14
|
-
|
15
|
-
node = Parser::CurrentRuby.parse(@ruby_source.source)
|
11
|
+
@defined_methods = []
|
12
|
+
@called_methods = []
|
13
|
+
node = ::Parser::CurrentRuby.parse(@ruby_source.source)
|
16
14
|
process(node)
|
17
|
-
|
18
|
-
@methods.reject { |method|
|
19
|
-
@private_method_calls.include?(method.name)
|
20
|
-
}
|
21
15
|
end
|
22
16
|
|
23
17
|
def on_def(node)
|
@@ -36,10 +30,9 @@ module ZombieScout
|
|
36
30
|
receiver, method_name, *args = *node
|
37
31
|
if respond_to?(:"handle_#{method_name}", true)
|
38
32
|
send(:"handle_#{method_name}", args, node)
|
39
|
-
elsif receiver.nil? # Then it's a private method call
|
40
|
-
@private_method_calls << method_name
|
41
|
-
process_all(args)
|
42
33
|
end
|
34
|
+
@called_methods << method_name
|
35
|
+
process_all(args)
|
43
36
|
end
|
44
37
|
|
45
38
|
private
|
@@ -99,11 +92,11 @@ module ZombieScout
|
|
99
92
|
def stash_method(method_name, node)
|
100
93
|
line_number = node.location.line
|
101
94
|
location = [@ruby_source.path, line_number].join(":")
|
102
|
-
@
|
95
|
+
@defined_methods << Method.new(method_name, location)
|
103
96
|
end
|
104
97
|
end
|
105
98
|
|
106
|
-
class SymbolExtracter < Parser::AST::Processor
|
99
|
+
class SymbolExtracter < ::Parser::AST::Processor
|
107
100
|
def on_sym(node)
|
108
101
|
node.to_a[0]
|
109
102
|
end
|
data/lib/zombie_scout/version.rb
CHANGED
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'zombie_scout/parser'
|
3
|
+
|
4
|
+
describe ZombieScout::Parser do
|
5
|
+
let(:ruby_source) {
|
6
|
+
double(:ruby_source, path: 'lib/fizzbuzz.rb', source: ruby_code)
|
7
|
+
}
|
8
|
+
|
9
|
+
describe '#called_methods' do
|
10
|
+
let(:called_methods) {
|
11
|
+
ZombieScout::Parser.new(ruby_source).called_methods
|
12
|
+
}
|
13
|
+
context 'when a ruby file has code that calls methods' do
|
14
|
+
let(:ruby_code) {
|
15
|
+
"class RockBand
|
16
|
+
def rock_out
|
17
|
+
turn_up_amps
|
18
|
+
play_tunes
|
19
|
+
end
|
20
|
+
end"
|
21
|
+
}
|
22
|
+
it 'can find the called methods' do
|
23
|
+
expect(called_methods).to match_array %i(play_tunes turn_up_amps)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#defined_methods' do
|
29
|
+
let(:defined_methods) {
|
30
|
+
ZombieScout::Parser.new(ruby_source).defined_methods.sort_by(&:name)
|
31
|
+
}
|
32
|
+
context 'when a ruby file has instance or class methods' do
|
33
|
+
let(:ruby_code) {
|
34
|
+
"class FizzBuzz
|
35
|
+
def fizz
|
36
|
+
'plop plop'
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.buzz
|
40
|
+
'bzz bzz bzz'
|
41
|
+
end
|
42
|
+
end"
|
43
|
+
}
|
44
|
+
|
45
|
+
it 'can find the methods' do
|
46
|
+
expect(defined_methods[0].name).to eq :buzz
|
47
|
+
expect(defined_methods[0].location).to eq 'lib/fizzbuzz.rb:6'
|
48
|
+
|
49
|
+
expect(defined_methods[1].name).to eq :fizz
|
50
|
+
expect(defined_methods[1].location).to eq 'lib/fizzbuzz.rb:2'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when a ruby file has attr_readers' do
|
55
|
+
context "when they're declared with symbols, the normal way" do
|
56
|
+
let(:ruby_code) {
|
57
|
+
"class Book
|
58
|
+
attr_reader :title, :author
|
59
|
+
end"
|
60
|
+
}
|
61
|
+
it 'can find attr_readers' do
|
62
|
+
expect(defined_methods.map(&:name)).to match_array(%i[author title])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
context "when they're declare with a splat from an array of symbols" do
|
66
|
+
let(:ruby_code) {
|
67
|
+
"class Book
|
68
|
+
attributes = %i(title author)
|
69
|
+
attr_reader *attributes
|
70
|
+
end"
|
71
|
+
}
|
72
|
+
it 'will ignore them' do
|
73
|
+
expect(defined_methods).to be_empty
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when a ruby file has attr_writers' do
|
79
|
+
context "when they're declared with symbols, the normal way" do
|
80
|
+
let(:ruby_code) {
|
81
|
+
"class Book
|
82
|
+
attr_writer :title, :author
|
83
|
+
end"
|
84
|
+
}
|
85
|
+
it 'can find attr_writers' do
|
86
|
+
expect(defined_methods.map(&:name)).to match_array(%i[author= title=])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
context "when they're declare with a splat from an array of symbols" do
|
90
|
+
let(:ruby_code) {
|
91
|
+
"class Book
|
92
|
+
attributes = %i(title author)
|
93
|
+
attr_reader *attributes
|
94
|
+
end"
|
95
|
+
}
|
96
|
+
it 'will ignore them' do
|
97
|
+
expect(defined_methods).to be_empty
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when a ruby file has attr_accessors' do
|
103
|
+
context "when they're declared with symbols, the normal way" do
|
104
|
+
let(:ruby_code) {
|
105
|
+
"class Book
|
106
|
+
attr_accessor :title, :author
|
107
|
+
end"
|
108
|
+
}
|
109
|
+
it 'can find attr_accessors' do
|
110
|
+
expect(defined_methods.map(&:name)).to match_array(%i[author author= title title=])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
context "when they're declare with a splat from an array of symbols" do
|
114
|
+
let(:ruby_code) {
|
115
|
+
"class Book
|
116
|
+
attributes = %i(title author)
|
117
|
+
attr_accessor *attributes
|
118
|
+
end"
|
119
|
+
}
|
120
|
+
it 'will ignore them' do
|
121
|
+
expect(defined_methods).to be_empty
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when a ruby file uses Forwardable::def_delegator' do
|
127
|
+
let(:ruby_code) {
|
128
|
+
"class RecordCollection
|
129
|
+
extend Forwardable
|
130
|
+
def_delegator :@records, :[], :record_number
|
131
|
+
end"
|
132
|
+
}
|
133
|
+
it 'can find methods created by def_delegator' do
|
134
|
+
expect(defined_methods.map(&:name)).to match_array([:record_number])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'when a ruby file uses Forwardable::def_delegators' do
|
139
|
+
let(:ruby_code) {
|
140
|
+
"class RecordCollection
|
141
|
+
extend Forwardable
|
142
|
+
def_delegators :@records, :size, :<<, :map
|
143
|
+
end"
|
144
|
+
}
|
145
|
+
it 'can find methods created by def_delegators' do
|
146
|
+
expect(defined_methods.map(&:name)).to match_array(%i[<< map size])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when a rails model has scopes' do
|
151
|
+
let(:ruby_code) {
|
152
|
+
"class Post
|
153
|
+
scope :published, -> { where(published: true) }
|
154
|
+
scope :draft, -> { where(published: true) }
|
155
|
+
end"
|
156
|
+
}
|
157
|
+
it 'can find scopes' do
|
158
|
+
expect(defined_methods.map(&:name)).to match_array(%i[draft published])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'when a ruby file has private methods' do
|
163
|
+
let(:ruby_code) {
|
164
|
+
"class FizzBuzz
|
165
|
+
def fizz
|
166
|
+
magick_helper
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
def magick_helper
|
171
|
+
'magick sauce'
|
172
|
+
end
|
173
|
+
|
174
|
+
def other_helper
|
175
|
+
'boo'
|
176
|
+
end
|
177
|
+
end"
|
178
|
+
}
|
179
|
+
it 'excludes private method calls, since we KNOW they are called' do
|
180
|
+
expect(defined_methods.map(&:name)).to match_array([:fizz, :magick_helper, :other_helper])
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/zombie_scout.gemspec
CHANGED
@@ -10,12 +10,12 @@ Gem::Specification.new do |s|
|
|
10
10
|
|
11
11
|
s.summary = "Find dead methods in your Ruby app"
|
12
12
|
s.description = "
|
13
|
-
zombie_scout finds methods in classes in your ruby project,
|
14
|
-
and then searches for calls to them, and tells you which ones
|
13
|
+
zombie_scout finds methods in classes in your ruby project,
|
14
|
+
and then searches for calls to them, and tells you which ones
|
15
15
|
are never called.".strip.gsub(/^\s*/, '')
|
16
16
|
s.homepage = 'https://github.com/danbernier/zombie_scout'
|
17
17
|
s.license = 'ASL2'
|
18
|
-
|
18
|
+
|
19
19
|
s.authors = ["Dan Bernier"]
|
20
20
|
s.email = ['danbernier@gmail.com']
|
21
21
|
|
@@ -26,5 +26,6 @@ Gem::Specification.new do |s|
|
|
26
26
|
|
27
27
|
s.add_dependency('parser', '~> 2.1')
|
28
28
|
s.add_dependency('thor', '~> 0.18')
|
29
|
+
s.add_dependency('flog', '~> 4.2')
|
29
30
|
s.add_development_dependency "bundler", "~> 1.5"
|
30
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zombie_scout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Bernier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.18'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: flog
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.2'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,8 +66,10 @@ dependencies:
|
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '1.5'
|
55
|
-
description:
|
56
|
-
|
69
|
+
description: |-
|
70
|
+
zombie_scout finds methods in classes in your ruby project,
|
71
|
+
and then searches for calls to them, and tells you which ones
|
72
|
+
are never called.
|
57
73
|
email:
|
58
74
|
- danbernier@gmail.com
|
59
75
|
executables:
|
@@ -74,14 +90,15 @@ files:
|
|
74
90
|
- bin/zombie_scout
|
75
91
|
- lib/zombie_scout.rb
|
76
92
|
- lib/zombie_scout/app.rb
|
93
|
+
- lib/zombie_scout/flog_scorer.rb
|
77
94
|
- lib/zombie_scout/method_call_finder.rb
|
78
|
-
- lib/zombie_scout/method_finder.rb
|
79
95
|
- lib/zombie_scout/mission.rb
|
96
|
+
- lib/zombie_scout/parser.rb
|
80
97
|
- lib/zombie_scout/ruby_project.rb
|
81
98
|
- lib/zombie_scout/ruby_source.rb
|
82
99
|
- lib/zombie_scout/version.rb
|
83
100
|
- spec/spec_helper.rb
|
84
|
-
- spec/zombie_scout/
|
101
|
+
- spec/zombie_scout/parser_spec.rb
|
85
102
|
- spec/zombie_scout/ruby_project_spec.rb
|
86
103
|
- spec/zombie_scout/ruby_source_spec.rb
|
87
104
|
- zombie_scout.gemspec
|
@@ -111,6 +128,6 @@ specification_version: 4
|
|
111
128
|
summary: Find dead methods in your Ruby app
|
112
129
|
test_files:
|
113
130
|
- spec/spec_helper.rb
|
114
|
-
- spec/zombie_scout/
|
131
|
+
- spec/zombie_scout/parser_spec.rb
|
115
132
|
- spec/zombie_scout/ruby_project_spec.rb
|
116
133
|
- spec/zombie_scout/ruby_source_spec.rb
|
@@ -1,160 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'zombie_scout/method_finder'
|
3
|
-
|
4
|
-
describe ZombieScout::MethodFinder, '#find_methods' do
|
5
|
-
let(:ruby_source) {
|
6
|
-
double(:ruby_source, path: 'lib/fizzbuzz.rb', source: ruby_code)
|
7
|
-
}
|
8
|
-
let(:zombies) {
|
9
|
-
ZombieScout::MethodFinder.new(ruby_source).find_methods.sort_by(&:name)
|
10
|
-
}
|
11
|
-
|
12
|
-
context 'when a ruby file has instance or class methods' do
|
13
|
-
let(:ruby_code) {
|
14
|
-
"class FizzBuzz
|
15
|
-
def fizz
|
16
|
-
'plop plop'
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.buzz
|
20
|
-
'bzz bzz bzz'
|
21
|
-
end
|
22
|
-
end"
|
23
|
-
}
|
24
|
-
|
25
|
-
it 'can find the methods' do
|
26
|
-
expect(zombies[0].name).to eq :buzz
|
27
|
-
expect(zombies[0].location).to eq 'lib/fizzbuzz.rb:6'
|
28
|
-
|
29
|
-
expect(zombies[1].name).to eq :fizz
|
30
|
-
expect(zombies[1].location).to eq 'lib/fizzbuzz.rb:2'
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
context 'when a ruby file has attr_readers' do
|
35
|
-
context "when they're declared with symbols, the normal way" do
|
36
|
-
let(:ruby_code) {
|
37
|
-
"class Book
|
38
|
-
attr_reader :title, :author
|
39
|
-
end"
|
40
|
-
}
|
41
|
-
it 'can find attr_readers' do
|
42
|
-
expect(zombies.map(&:name)).to eq(%i[author title])
|
43
|
-
end
|
44
|
-
end
|
45
|
-
context "when they're declare with a splat from an array of symbols" do
|
46
|
-
let(:ruby_code) {
|
47
|
-
"class Book
|
48
|
-
attributes = %i(title author)
|
49
|
-
attr_reader *attributes
|
50
|
-
end"
|
51
|
-
}
|
52
|
-
it 'will ignore them' do
|
53
|
-
expect(zombies).to be_empty
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
context 'when a ruby file has attr_writers' do
|
59
|
-
context "when they're declared with symbols, the normal way" do
|
60
|
-
let(:ruby_code) {
|
61
|
-
"class Book
|
62
|
-
attr_writer :title, :author
|
63
|
-
end"
|
64
|
-
}
|
65
|
-
it 'can find attr_writers' do
|
66
|
-
expect(zombies.map(&:name)).to eq(%i[author= title=])
|
67
|
-
end
|
68
|
-
end
|
69
|
-
context "when they're declare with a splat from an array of symbols" do
|
70
|
-
let(:ruby_code) {
|
71
|
-
"class Book
|
72
|
-
attributes = %i(title author)
|
73
|
-
attr_reader *attributes
|
74
|
-
end"
|
75
|
-
}
|
76
|
-
it 'will ignore them' do
|
77
|
-
expect(zombies).to be_empty
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
context 'when a ruby file has attr_accessors' do
|
83
|
-
context "when they're declared with symbols, the normal way" do
|
84
|
-
let(:ruby_code) {
|
85
|
-
"class Book
|
86
|
-
attr_accessor :title, :author
|
87
|
-
end"
|
88
|
-
}
|
89
|
-
it 'can find attr_accessors' do
|
90
|
-
expect(zombies.map(&:name)).to eq(%i[author author= title title=])
|
91
|
-
end
|
92
|
-
end
|
93
|
-
context "when they're declare with a splat from an array of symbols" do
|
94
|
-
let(:ruby_code) {
|
95
|
-
"class Book
|
96
|
-
attributes = %i(title author)
|
97
|
-
attr_accessor *attributes
|
98
|
-
end"
|
99
|
-
}
|
100
|
-
it 'will ignore them' do
|
101
|
-
expect(zombies).to be_empty
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
context 'when a ruby file uses Forwardable::def_delegator' do
|
107
|
-
let(:ruby_code) {
|
108
|
-
"class RecordCollection
|
109
|
-
extend Forwardable
|
110
|
-
def_delegator :@records, :[], :record_number
|
111
|
-
end"
|
112
|
-
}
|
113
|
-
it 'can find methods created by def_delegator' do
|
114
|
-
expect(zombies.map(&:name)).to match_array([:record_number])
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
context 'when a ruby file uses Forwardable::def_delegators' do
|
119
|
-
let(:ruby_code) {
|
120
|
-
"class RecordCollection
|
121
|
-
extend Forwardable
|
122
|
-
def_delegators :@records, :size, :<<, :map
|
123
|
-
end"
|
124
|
-
}
|
125
|
-
it 'can find methods created by def_delegators' do
|
126
|
-
expect(zombies.map(&:name)).to eq(%i[<< map size])
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
context 'when a rails model has scopes' do
|
131
|
-
let(:ruby_code) {
|
132
|
-
"class Post
|
133
|
-
scope :published, -> { where(published: true) }
|
134
|
-
scope :draft, -> { where(published: true) }
|
135
|
-
end"
|
136
|
-
}
|
137
|
-
it 'can find scopes' do
|
138
|
-
expect(zombies.map(&:name)).to eq(%i[draft published])
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
context 'when a ruby file has private methods' do
|
143
|
-
let(:ruby_code) {
|
144
|
-
"class FizzBuzz
|
145
|
-
def fizz
|
146
|
-
magick_helper
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
def magick_helper
|
151
|
-
'magick sauce'
|
152
|
-
end
|
153
|
-
end"
|
154
|
-
}
|
155
|
-
it 'excludes private method calls, since we KNOW they are called' do
|
156
|
-
expect(zombies.map(&:name)).to match_array([:fizz])
|
157
|
-
expect(zombies.map(&:name)).not_to include :magick_helper
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|