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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc91be8df398812d74758f0f52b4fb5314debcbe
4
- data.tar.gz: 908249e99886cd2546e40a59d6a68cbe379ed2df
3
+ metadata.gz: c1d48e23ce3d846d2a1bb64d2c5a698386a7ccbb
4
+ data.tar.gz: dec98dc4fcf229576c71c080c00e4a24e1afa7d1
5
5
  SHA512:
6
- metadata.gz: a2854512c5126c80f7c33dd118ec0577adfab4adeba60c823eaa47602933eee4fc81c3c91a6b9483f927a262de8cae58524e5a7d4138475c5f718e0552310b97
7
- data.tar.gz: ad6e1a064157c087b9b081c0aa889af10f85f337d47eb9beab4c52a141f9143265f0a406338cae3c6c50a0cbf9c5055750625f991e00a9fd256465eae54d51d1
6
+ metadata.gz: 989aedb17a61b6aa6dc899a3b9426117dc771fece220a5fb1505d5b21b36e40dde5a06af2e17a424e438fffb212b765e4500aa227f3bec11c9350081ab6e1d91
7
+ data.tar.gz: 973b0f339bbfef34d128924df0d66902be6c929ac96c617516f230c28c7b2ebce8761026376a11cb6b5efcf104a22a914574bbf62e24fb5c4c4c03d9ebf810e5
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
3
  gem 'parser', '~> 2.1'
4
+ gem 'flog', '~> 4.2'
4
5
  gem 'thor', '~> 0.18'
5
6
 
6
7
  group :test do
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
- ### Fair Warning
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
- Scouting out /home/dan/projects/zombie_scout!
64
- lib/zombie_scout/method_finder.rb:23 on_def
65
- lib/zombie_scout/method_finder.rb:29 on_defs
66
- lib/zombie_scout/method_finder.rb:35 on_send
67
- lib/zombie_scout/method_finder.rb:77 on_sym
68
- Scouted 23 methods in 8 files. Found 4 potential zombies.
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/zombie_scout.rb
75
- Scouting out /home/dan/projects/zombie_scout!
76
- Scouted 0 methods in 1 files. Found 0 potential zombies.
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
- * [ ] extract a hash-y report structure that can be used by whatever, from the default report
90
- * [ ] pass a method name, or a ruby class
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
@@ -4,3 +4,7 @@ require 'rspec/core/rake_task'
4
4
  RSpec::Core::RakeTask.new
5
5
 
6
6
  task :default => :spec
7
+
8
+ task :console do
9
+ sh('irb -I lib -r zombie_scout')
10
+ end
data/bin/zombie_scout CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby
2
2
  require 'rubygems'
3
3
  require 'zombie_scout'
4
4
 
@@ -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).scout
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
@@ -1,38 +1,74 @@
1
1
  require 'zombie_scout/ruby_project'
2
- require 'zombie_scout/method_finder'
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
- zombies.each do |zombie|
14
- puts [zombie.location, zombie.name] * "\t"
15
- end
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
- puts "Scouted #{methods.size} methods in #{sources.size} files. Found #{zombies.size} potential zombies."
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 ||= methods.select { |method|
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 methods
29
- @methods ||= sources.map { |ruby_source|
30
- MethodFinder.new(ruby_source).find_methods
31
- }.flatten
32
- end
57
+ def scout!
58
+ @defined_methods, @called_methods = [], []
33
59
 
34
- def sources
35
- @sources ||= @ruby_project.ruby_sources
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 MethodFinder < Parser::AST::Processor
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
- end
10
-
11
- def find_methods
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
- @methods << Method.new(method_name, location)
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
@@ -1,3 +1,3 @@
1
1
  module ZombieScout
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -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.2
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-02-28 00:00:00.000000000 Z
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: "zombie_scout finds methods in classes in your ruby project, \nand then
56
- searches for calls to them, and tells you which ones \nare never called."
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/method_finder_spec.rb
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/method_finder_spec.rb
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