zombie_scout 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|