reek 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ == 0.2.1 2008-09-14
2
+
3
+ * Tweaks:
4
+ * Now works from the source code, instead of requiring each named file
5
+ * Added integration tests that run reek on a couple of gems
6
+
1
7
  == 0.2.0 2008-09-10
2
8
 
3
9
  * Minor enhancements:
data/bin/reek CHANGED
@@ -3,31 +3,8 @@
3
3
  # Created on 2008-2-17.
4
4
  # Copyright (c) 2008 Kevin Rutherford, Rutherford Software Ltd. All rights reserved.
5
5
 
6
- begin
7
- require 'rubygems'
8
- rescue LoadError
9
- # no rubygems to load, so we fail silently
10
- end
11
-
12
6
  require 'reek'
13
7
  require 'reek/options'
14
- require 'reek/version'
15
-
16
- def classes_currently_loaded
17
- result = []
18
- ObjectSpace.each_object(Module) { |klass| result << klass }
19
- result
20
- end
21
-
22
- Reek::Options.parse(ARGV)
23
8
 
24
- old_classes = classes_currently_loaded
25
- files = ARGV
26
- files = Dir['**/*.rb'] if files.empty?
27
- files.each { |name| require name }
28
- new_classes = classes_currently_loaded - old_classes
29
- if new_classes.empty?
30
- puts 'Nothing to analyse!'
31
- else
32
- puts Reek.analyse(*new_classes).to_s
33
- end
9
+ files = Reek::Options.parse(ARGV)
10
+ puts Reek.analyse(*files).to_s
data/lib/reek.rb CHANGED
@@ -1,18 +1,19 @@
1
1
  $:.unshift File.dirname(__FILE__)
2
2
 
3
- require 'reek/class_checker'
3
+ require 'reek/file_checker'
4
4
  require 'reek/report'
5
5
 
6
6
  module Reek # :doc:
7
7
 
8
8
  #
9
- # Analyse the given instances of class Class, looking for code smells.
9
+ # Analyse the given source files, looking for code smells.
10
10
  # Returns a +Report+ listing the smells found.
11
11
  #
12
- def self.analyse(*klasses) # :doc:
12
+ def self.analyse(*files) # :doc:
13
13
  report = Report.new
14
- klasses.each do |klass|
15
- ClassChecker.new(report).check_object(klass)
14
+ files.each do |file|
15
+ source = IO.readlines(file).join
16
+ FileChecker.new(report).check_source(source)
16
17
  end
17
18
  report
18
19
  end
@@ -21,8 +21,7 @@ module Reek
21
21
  end
22
22
 
23
23
  def process_defn(exp) # :nodoc:
24
- bc = Reek::MethodChecker.new(@smells, @description)
25
- bc.process(exp)
24
+ Reek::MethodChecker.new(@smells, @description).process(exp)
26
25
  s(exp)
27
26
  end
28
27
  end
@@ -0,0 +1,26 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'reek/checker'
4
+ require 'reek/class_checker'
5
+ require 'reek/method_checker'
6
+
7
+ module Reek
8
+
9
+ class FileChecker < Checker
10
+
11
+ def initialize(report)
12
+ super(report)
13
+ @description = ''
14
+ end
15
+
16
+ def process_class(exp) # :nodoc:
17
+ Reek::ClassChecker.new(@smells).process(exp)
18
+ s(exp)
19
+ end
20
+
21
+ def process_defn(exp) # :nodoc:
22
+ Reek::MethodChecker.new(@smells, @description).process(exp)
23
+ s(exp)
24
+ end
25
+ end
26
+ end
@@ -10,22 +10,17 @@ module Reek
10
10
 
11
11
  def initialize(smells, klass_name)
12
12
  super(smells)
13
- @class_name = klass_name
14
- @description = klass_name
13
+ @class_name = @description = klass_name
15
14
  @calls = Hash.new(0)
16
15
  @lvars = Set.new
17
- @inside_an_iter = false
16
+ @num_statements = 0
18
17
  end
19
18
 
20
19
  def process_defn(exp)
21
- @num_statements = 0
22
20
  @description = "#{@class_name}##{exp[1]}"
23
21
  UncommunicativeName.check(exp[1], self, 'method')
24
22
  process(exp[2])
25
- @lvars.each {|lvar| UncommunicativeName.check(lvar, self, 'local variable') }
26
- UtilityFunction.check(@calls, self)
27
- FeatureEnvy.check(@calls, self)
28
- LongMethod.check(@num_statements, self)
23
+ check_method_properties
29
24
  s(exp)
30
25
  end
31
26
 
@@ -49,7 +44,7 @@ module Reek
49
44
  end
50
45
 
51
46
  def process_block(exp)
52
- @num_statements += count_statements(exp)
47
+ @num_statements += MethodChecker.count_statements(exp)
53
48
  exp[1..-1].each { |s| process(s) }
54
49
  s(exp)
55
50
  end
@@ -61,12 +56,8 @@ module Reek
61
56
  end
62
57
 
63
58
  def process_call(exp)
64
- receiver = process(exp[1])
65
- receiver = receiver[0] if Array === receiver and Array === receiver[0] and receiver.length == 1
66
- if receiver[0] != :gvar
67
- receiver = :self if receiver == s(:self)
68
- @calls[receiver] += 1
69
- end
59
+ record_receiver(exp[1])
60
+ process_actual_parameters(exp[3])
70
61
  process(exp[3]) if exp.length > 3
71
62
  s(exp)
72
63
  end
@@ -107,11 +98,58 @@ module Reek
107
98
  end
108
99
 
109
100
  private
110
-
111
- def count_statements(exp)
101
+
102
+ def self.count_statements(exp)
112
103
  result = exp.length - 1
113
104
  result -= 1 if Array === exp[1] and exp[1][0] == :args
114
105
  result
115
106
  end
107
+
108
+ def self.is_global_variable?(exp)
109
+ Array === exp and exp[0] == :gvar
110
+ end
111
+
112
+ def record_receiver(exp)
113
+ receiver = MethodChecker.unpack_array(process(exp))
114
+ @calls[receiver] += 1 unless MethodChecker.is_global_variable?(receiver)
115
+ end
116
+
117
+ def self.unpack_array(receiver)
118
+ receiver = receiver[0] if Array === receiver and Array === receiver[0] and receiver.length == 1
119
+ receiver = :self if receiver == s(:self)
120
+ receiver
121
+ end
122
+
123
+ def is_override?
124
+ begin
125
+ klass = Object.const_get(@class_name)
126
+ rescue
127
+ return false
128
+ end
129
+ klass.superclass.instance_methods.include?(@description.to_s.split('#')[1])
130
+ end
131
+
132
+ def check_method_properties
133
+ @lvars.each {|lvar| UncommunicativeName.check(lvar, self, 'local variable') }
134
+ @calls[:self] += 1 if is_override?
135
+ UtilityFunction.check(@calls, self)
136
+ FeatureEnvy.check(@calls, self)
137
+ LongMethod.check(@num_statements, self)
138
+ end
139
+
140
+ def process_actual_parameters(exp)
141
+ return unless Array === exp and exp[0] == :array
142
+ exp[1..-1].each do |param|
143
+ if Array === param
144
+ if param.length == 1
145
+ @calls[:self] += 1 if param[0] == :self
146
+ else
147
+ @calls[param] += 1
148
+ end
149
+ else
150
+ @calls[:self] += 1 if param == :self
151
+ end
152
+ end
153
+ end
116
154
  end
117
155
  end
data/lib/reek/options.rb CHANGED
@@ -48,13 +48,21 @@ module Reek
48
48
  result
49
49
  end
50
50
 
51
+ def self.fatal_error(e) # :nodoc:
52
+ puts "Error: #{e}"
53
+ puts "Use '-h' for help."
54
+ exit(1)
55
+ end
56
+
51
57
  def self.parse(args)
52
58
  begin
53
59
  @@opts = parse_args(args)
60
+ files = ARGV
61
+ files = Dir['**/*.rb'] if files.empty?
62
+ fatal_error('no source files specified') if files.empty?
63
+ files
54
64
  rescue OptionParser::ParseError => e
55
- puts "Error: #{e}"
56
- puts "Use '-h' for help."
57
- exit(1)
65
+ fatal_error(e)
58
66
  end
59
67
  end
60
68
  end
data/lib/reek/printer.rb CHANGED
@@ -19,6 +19,7 @@ module Reek
19
19
 
20
20
  def print(sexp)
21
21
  @report = sexp.inspect
22
+ return @report unless Array === sexp
22
23
  begin
23
24
  process(sexp)
24
25
  rescue
data/lib/reek/report.rb CHANGED
@@ -5,13 +5,13 @@ require 'set'
5
5
  module Reek
6
6
 
7
7
  class SortByContext
8
- def compare(smell1, smell2)
8
+ def self.compare(smell1, smell2)
9
9
  smell1.detailed_report <=> smell2.detailed_report
10
10
  end
11
11
  end
12
12
 
13
13
  class SortBySmell
14
- def compare(smell1, smell2)
14
+ def self.compare(smell1, smell2)
15
15
  smell1.report <=> smell2.report
16
16
  end
17
17
  end
@@ -19,8 +19,8 @@ module Reek
19
19
  class Report
20
20
 
21
21
  SORT_ORDERS = {
22
- :context => SortByContext.new,
23
- :smell => SortBySmell.new
22
+ :context => SortByContext,
23
+ :smell => SortBySmell
24
24
  }
25
25
 
26
26
  def initialize # :nodoc:
@@ -39,8 +39,8 @@ module Reek
39
39
  @smells.length
40
40
  end
41
41
 
42
- def [](i) # :nodoc:
43
- @smells.to_a[i]
42
+ def [](index) # :nodoc:
43
+ @smells.to_a[index]
44
44
  end
45
45
 
46
46
  # Creates a formatted report of all the smells recorded in
data/lib/reek/smells.rb CHANGED
@@ -18,7 +18,16 @@ module Reek
18
18
 
19
19
  def self.check(exp, context, arg=nil)
20
20
  smell = new(context, arg)
21
- context.report(smell) if smell.recognise?(exp)
21
+ if smell.recognise?(exp)
22
+ context.report(smell)
23
+ true
24
+ else
25
+ false
26
+ end
27
+ end
28
+
29
+ def recognise?(stuff)
30
+ @context != nil
22
31
  end
23
32
 
24
33
  def hash # :nodoc:
@@ -39,6 +48,8 @@ module Reek
39
48
  "[#{name}] #{detailed_report}"
40
49
  end
41
50
 
51
+ alias inspect report
52
+
42
53
  def to_s
43
54
  report
44
55
  end
@@ -47,28 +58,30 @@ module Reek
47
58
  class LongParameterList < Smell
48
59
  MAX_ALLOWED = 3
49
60
 
50
- def count_parameters(exp)
61
+ def self.count_parameters(exp)
51
62
  result = exp.length - 1
52
63
  result -= 1 if Array === exp[-1] and exp[-1][0] == :block
53
64
  result
54
65
  end
55
66
 
56
67
  def recognise?(args)
57
- count_parameters(args) > MAX_ALLOWED
68
+ @num_params = LongParameterList.count_parameters(args)
69
+ @num_params > MAX_ALLOWED
58
70
  end
59
71
 
60
72
  def detailed_report
61
- "#{@context.to_s} has > #{MAX_ALLOWED} parameters"
73
+ "#{@context.to_s} has #{@num_params} parameters"
62
74
  end
63
75
  end
64
76
 
65
77
  class LongYieldList < LongParameterList
66
78
  def recognise?(args)
67
- Array === args and args.length > MAX_ALLOWED
79
+ @num_params = args.length
80
+ Array === args and @num_params > MAX_ALLOWED
68
81
  end
69
82
 
70
83
  def detailed_report
71
- "#{@context} yields > #{MAX_ALLOWED} parameters"
84
+ "#{@context} yields #{@num_params} parameters"
72
85
  end
73
86
  end
74
87
 
@@ -86,21 +99,28 @@ module Reek
86
99
  end
87
100
 
88
101
  class FeatureEnvy < Smell
89
- def initialize(context, receiver)
90
- super
91
- @receiver = receiver
102
+
103
+ # TODO
104
+ # Should be moved to Hash; but Hash has 58 methods, and there's currently
105
+ # no way to turn off that report; which would therefore make the tests fail
106
+ def self.max_keys(calls)
107
+ max = calls.values.max or return [:self]
108
+ calls.keys.select { |key| calls[key] == max }
109
+ end
110
+
111
+ def initialize(context, *receivers)
112
+ super(context)
113
+ @receivers = receivers
92
114
  end
93
115
 
94
116
  def recognise?(calls)
95
- max = calls.empty? ? 0 : calls.values.max
96
- return false unless max > calls[:self]
97
- receivers = calls.keys.select { |key| calls[key] == max }
98
- @receiver = receivers.map {|r| Printer.print(r)}.sort.join(' and ')
99
- return true
117
+ @receivers = FeatureEnvy.max_keys(calls)
118
+ return !(@receivers.include?(:self))
100
119
  end
101
120
 
102
121
  def detailed_report
103
- "#{@context} uses #{@receiver} more than self"
122
+ receiver = @receivers.map {|r| Printer.print(r)}.sort.join(' and ')
123
+ "#{@context} uses #{receiver} more than self"
104
124
  end
105
125
  end
106
126
 
@@ -118,13 +138,13 @@ module Reek
118
138
  MAX_ALLOWED = 25
119
139
 
120
140
  def recognise?(name)
121
- kl = Object.const_get(name) rescue return
122
- num_methods = kl.instance_methods.length - kl.superclass.instance_methods.length
123
- num_methods > MAX_ALLOWED
141
+ klass = Object.const_get(name) rescue return
142
+ @num_methods = klass.instance_methods.length - klass.superclass.instance_methods.length
143
+ @num_methods > MAX_ALLOWED
124
144
  end
125
145
 
126
146
  def detailed_report
127
- "#{@context} has > #{MAX_ALLOWED} methods"
147
+ "#{@context} has #{@num_methods} methods"
128
148
  end
129
149
  end
130
150
 
data/lib/reek/version.rb CHANGED
@@ -2,7 +2,7 @@ module Reek #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 2
5
- TINY = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe 'Integration test:' do
4
+ Dir['spec/samples/*.rb'].each do |source|
5
+ describe source do
6
+ before :each do
7
+ @expected = IO.readlines(source.sub(/\.rb/, '.reek'))
8
+ @expected.each {|line| line.chomp!}
9
+ end
10
+
11
+ it 'should report the correct smells' do
12
+ actual = `ruby -Ilib bin/reek #{source} 2>/dev/null`.split(/\n/)
13
+ @expected.zip(actual).each do |p|
14
+ actual = p[1] ? p[1].chomp : p[1]
15
+ actual.should == p[0]
16
+ end
17
+ end
18
+
19
+ it 'should report the correct smells in smell order' do
20
+ actual = `ruby -Ilib bin/reek --sort smell #{source} 2>/dev/null`.split(/\n/)
21
+ @expected.sort.zip(actual).each do |p|
22
+ actual = p[1] ? p[1].chomp : p[1]
23
+ actual.should == p[0]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'Reek source code:' do
31
+ Dir['lib/**/*.rb'].each do |source|
32
+ describe source do
33
+ it 'should report no smells' do
34
+ `ruby -Ilib bin/reek #{source}`.should == "\n"
35
+ end
36
+ end
37
+ end
38
+ end