reek 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/History.txt +11 -1
  2. data/README.txt +1 -0
  3. data/lib/reek.rb +8 -9
  4. data/lib/reek/checker.rb +10 -2
  5. data/lib/reek/class_checker.rb +4 -7
  6. data/lib/reek/file_checker.rb +0 -6
  7. data/lib/reek/method_checker.rb +56 -30
  8. data/lib/reek/object_refs.rb +5 -2
  9. data/lib/reek/printer.rb +45 -7
  10. data/lib/reek/rake_task.rb +5 -3
  11. data/lib/reek/smells/control_couple.rb +53 -0
  12. data/lib/reek/smells/duplication.rb +54 -0
  13. data/lib/reek/smells/feature_envy.rb +65 -0
  14. data/lib/reek/smells/large_class.rb +35 -0
  15. data/lib/reek/smells/long_method.rb +35 -0
  16. data/lib/reek/smells/long_parameter_list.rb +36 -0
  17. data/lib/reek/smells/long_yield_list.rb +20 -0
  18. data/lib/reek/smells/nested_iterators.rb +24 -0
  19. data/lib/reek/smells/smell.rb +56 -0
  20. data/lib/reek/smells/smells.rb +24 -0
  21. data/lib/reek/smells/uncommunicative_name.rb +72 -0
  22. data/lib/reek/smells/utility_function.rb +34 -0
  23. data/lib/reek/version.rb +1 -1
  24. data/spec/integration_spec.rb +6 -6
  25. data/spec/reek/printer_spec.rb +21 -21
  26. data/spec/reek/report_spec.rb +5 -5
  27. data/spec/reek/{control_couple_spec.rb → smells/control_couple_spec.rb} +1 -1
  28. data/spec/reek/smells/duplication_spec.rb +60 -0
  29. data/spec/reek/smells/feature_envy_spec.rb +91 -0
  30. data/spec/reek/{large_class_spec.rb → smells/large_class_spec.rb} +8 -8
  31. data/spec/reek/{long_method_spec.rb → smells/long_method_spec.rb} +1 -1
  32. data/spec/reek/{long_parameter_list_spec.rb → smells/long_parameter_list_spec.rb} +1 -1
  33. data/spec/reek/smells/nested_iterators_spec.rb +43 -0
  34. data/spec/reek/{smell_spec.rb → smells/smell_spec.rb} +2 -2
  35. data/spec/reek/smells/uncommunicative_name_spec.rb +83 -0
  36. data/spec/reek/{utility_function_spec.rb → smells/utility_function_spec.rb} +1 -1
  37. data/spec/samples/inline.reek +13 -5
  38. data/spec/samples/optparse.reek +32 -10
  39. data/spec/samples/redcloth.reek +24 -6
  40. data/spec/script_spec.rb +1 -1
  41. data/tasks/reek.rake +9 -0
  42. data/website/index.html +3 -2
  43. data/website/index.txt +3 -1
  44. metadata +24 -12
  45. data/lib/reek/smells.rb +0 -192
  46. data/spec/reek/feature_envy_spec.rb +0 -222
  47. data/spec/reek/nested_iterators_spec.rb +0 -42
  48. data/spec/reek/uncommunicative_name_spec.rb +0 -106
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 = 3
5
- TINY = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -10,20 +10,20 @@ describe 'Integration test:' do
10
10
 
11
11
  it 'should report the correct smells' do
12
12
  actual = `ruby -Ilib bin/reek #{source} 2>/dev/null`.split(/\n/)
13
- actual.length.should == @expected.length
14
13
  @expected.zip(actual).each do |p|
15
- actual = p[1] ? p[1].chomp : p[1]
16
- actual.should == p[0]
14
+ actual_line = p[1] ? p[1].chomp : p[1]
15
+ actual_line.should == p[0]
17
16
  end
17
+ actual.length.should == @expected.length
18
18
  end
19
19
 
20
20
  it 'should report the correct smells in smell order' do
21
21
  actual = `ruby -Ilib bin/reek --sort smell #{source} 2>/dev/null`.split(/\n/)
22
- actual.length.should == @expected.length
23
22
  @expected.sort.zip(actual).each do |p|
24
- actual = p[1] ? p[1].chomp : p[1]
25
- actual.should == p[0]
23
+ actual_line = p[1] ? p[1].chomp : p[1]
24
+ actual_line.should == p[0]
26
25
  end
26
+ actual.length.should == @expected.length
27
27
  end
28
28
  end
29
29
  end
@@ -1,30 +1,30 @@
1
1
  require File.dirname(__FILE__) + '/../spec_helper.rb'
2
2
 
3
- require 'reek/method_checker'
4
- require 'reek/smells'
5
- require 'reek/report'
3
+ require 'reek/checker'
4
+ require 'reek/printer'
6
5
 
7
6
  include Reek
8
7
 
9
- def render(source)
10
- sexp = Checker.parse_tree_for(source)[0]
11
- Printer.print(sexp)
8
+ def check(examples)
9
+ examples.each do |actual|
10
+ it "should format #{actual} correctly" do
11
+ sexp = Checker.parse_tree_for(actual)[0]
12
+ Printer.print(sexp).should == actual
13
+ end
14
+ end
12
15
  end
13
16
 
14
17
  describe Printer do
15
- it 'should format a simple constant' do
16
- render('Alpha').should == 'Alpha'
17
- end
18
-
19
- it 'should format "::" correctly' do
20
- render('Alpha::Beta').should == 'Alpha::Beta'
21
- end
22
-
23
- it 'should format class variables correctly' do
24
- render('@@fred').should == '@@fred'
25
- end
26
-
27
- it 'should format xstr correctly' do
28
- render('`ls`').should == '`ls`'
29
- end
18
+ check 'Alpha'
19
+ check 'Alpha::Beta'
20
+ check '@@fred'
21
+ check '`ls`'
22
+ check 'array[0]'
23
+ check 'array[0, 1, 2]'
24
+ check 'obj.method(arg1, arg2)'
25
+ check 'obj.method'
26
+ check '$1'
27
+ check 'o=q.downcase'
28
+ check 'true'
29
+ check '"-#{q}xxx#{z.size}"'
30
30
  end
@@ -54,12 +54,12 @@ describe SortByContext do
54
54
  end
55
55
 
56
56
  it 'should return 0 for identical smells' do
57
- smell = LongMethod.new('Class#method')
57
+ smell = LongMethod.new('Class#method', 30)
58
58
  @sorter.compare(smell, smell).should == 0
59
59
  end
60
60
 
61
61
  it 'should return non-0 for different smells' do
62
- @sorter.compare(LongMethod.new('x'), LargeClass.new('y')).should == -1
62
+ @sorter.compare(LongMethod.new('x', 30), LargeClass.new('y')).should == -1
63
63
  end
64
64
  end
65
65
 
@@ -69,14 +69,14 @@ describe SortBySmell do
69
69
  end
70
70
 
71
71
  it 'should return 0 for identical smells' do
72
- @sorter.compare(LongMethod.new('x'), LongMethod.new('x')).should == 0
72
+ @sorter.compare(LongMethod.new('x', 30), LongMethod.new('x', 30)).should == 0
73
73
  end
74
74
 
75
75
  it 'should differentiate identical smells with different contexts' do
76
- @sorter.compare(LongMethod.new('x'), LongMethod.new('y')).should == -1
76
+ @sorter.compare(LongMethod.new('x', 29), LongMethod.new('y', 29)).should == -1
77
77
  end
78
78
 
79
79
  it 'should differentiate different smells with identical contexts' do
80
- @sorter.compare(LongMethod.new('x'), LargeClass.new('x')).should == 1
80
+ @sorter.compare(LongMethod.new('x', 28), LargeClass.new('x')).should == 1
81
81
  end
82
82
  end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
2
 
3
3
  require 'reek/method_checker'
4
4
  require 'reek/report'
@@ -0,0 +1,60 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ require 'reek/method_checker'
4
+ require 'reek/smells/duplication'
5
+ require 'reek/report'
6
+
7
+ include Reek
8
+ include Reek::Smells
9
+
10
+ def check(desc, src, expected, pending_str = nil)
11
+ it(desc) do
12
+ pending(pending_str) unless pending_str.nil?
13
+ rpt = Report.new
14
+ cchk = MethodChecker.new(rpt, 'Thing')
15
+ cchk.check_source(src)
16
+ rpt.length.should == expected.length
17
+ (0...rpt.length).each do |smell|
18
+ expected[smell].each { |patt| rpt[smell].detailed_report.should match(patt) }
19
+ end
20
+ end
21
+ end
22
+
23
+ describe Duplication, "repeated method calls" do
24
+ check 'should report repeated call',
25
+ 'def double_thing() @other.thing + @other.thing end', [[/@other.thing/]]
26
+ check 'should report repeated call to lvar',
27
+ 'def double_thing() other[@thing] + other[@thing] end', [[/other\[@thing\]/]]
28
+ check 'should report call parameters',
29
+ 'def double_thing() @other.thing(2,3) + @other.thing(2,3) end', [[/@other.thing\(2, 3\)/]]
30
+ check 'should report nested calls',
31
+ 'def double_thing() @other.thing.foo + @other.thing.foo end', [[/@other.thing[^\.]/], [/@other.thing.foo/]]
32
+ check 'should ignore calls to new',
33
+ 'def double_thing() @other.new + @other.new end', []
34
+ end
35
+
36
+ describe Duplication, "non-repeated method calls" do
37
+ check 'should not report similar calls',
38
+ 'def equals(other) other.thing == self.thing end', []
39
+ check 'should respect call parameters',
40
+ 'def double_thing() @other.thing(3) + @other.thing(2) end', []
41
+ end
42
+
43
+ require 'ostruct'
44
+
45
+ describe Duplication, '#examine' do
46
+
47
+ before :each do
48
+ @mc = OpenStruct.new
49
+ end
50
+
51
+ it 'should return true when reporting a smell' do
52
+ @mc.calls = {'x' => 47}
53
+ Duplication.examine(@mc, []).should == true
54
+ end
55
+
56
+ it 'should return false when not reporting a smell' do
57
+ @mc.calls = []
58
+ Duplication.examine(@mc, []).should == false
59
+ end
60
+ end
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ require 'reek/method_checker'
4
+ require 'reek/smells'
5
+ require 'reek/report'
6
+
7
+ include Reek
8
+ include Reek::Smells
9
+
10
+ def check(desc, src, expected, pending_str = nil)
11
+ it(desc) do
12
+ pending(pending_str) unless pending_str.nil?
13
+ rpt = Report.new
14
+ cchk = MethodChecker.new(rpt, 'Thing')
15
+ cchk.check_source(src)
16
+ rpt.length.should == expected.length
17
+ (0...rpt.length).each do |smell|
18
+ expected[smell].each { |patt| rpt[smell].detailed_report.should match(patt) }
19
+ end
20
+ end
21
+ end
22
+
23
+ describe FeatureEnvy, 'with only messages to self' do
24
+ check 'should not report use of self', 'def simple() self.to_s + self.to_i end', []
25
+ check 'should not report vcall with no argument', 'def simple() func + grunc end', []
26
+ check 'should not report vcall with argument', 'def simple(arga) func(17) + grunc(arga) end', []
27
+ end
28
+
29
+ describe FeatureEnvy, 'when the receiver is a parameter' do
30
+ check 'should not report single use', 'def no_envy(arga) arga.barg(@item) end', []
31
+ check 'should not report return value', 'def no_envy(arga) arga.barg(@item); arga end', []
32
+ check 'should report many calls to parameter', 'def envy(arga) arga.b(arga) + arga.c(@fred) end', [[/arga/]]
33
+ end
34
+
35
+ describe FeatureEnvy, 'when there are many possible receivers' do
36
+ check 'should report highest affinity',
37
+ 'def total_envy
38
+ fred = @item
39
+ total = 0
40
+ total += fred.price
41
+ total += fred.tax
42
+ total *= 1.15
43
+ end', [[/total/]]
44
+ check 'should report multiple affinities',
45
+ 'def total_envy
46
+ fred = @item
47
+ total = 0
48
+ total += fred.price
49
+ total += fred.tax
50
+ end', [[/fred/], [/total/]]
51
+ end
52
+
53
+ describe FeatureEnvy, 'when the receiver is external' do
54
+ check 'should ignore global variables', 'def no_envy() $s2.to_a; $s2[@item] end', []
55
+ check 'should not report class methods', 'def simple() self.class.new.flatten_merge(self) end', []
56
+ end
57
+
58
+ describe FeatureEnvy, 'when the receiver is an ivar' do
59
+ check 'should not report single use of an ivar', 'def no_envy() @item.to_a end', []
60
+ check 'should not report returning an ivar', 'def no_envy() @item.to_a; @item end', []
61
+ check 'should not report ivar usage in a parameter',
62
+ 'def no_envy; @item.price + tax(@item) - savings(@item) end', []
63
+ end
64
+
65
+ describe FeatureEnvy, 'when the receiver is an lvar' do
66
+ check 'should not report single use of an lvar', 'def no_envy() lv = @item; lv.to_a end', []
67
+ check 'should not report returning an lvar', 'def no_envy() lv = @item; lv.to_a; lv end', []
68
+ check 'should report many calls to lvar', 'def envy; lv = @item; lv.price + lv.tax end', [[/lv/]]
69
+ check 'should not report lvar usage in a parameter', 'def no_envy; lv = @item; lv.price + tax(lv) - savings(lv) end', []
70
+ end
71
+
72
+ require 'ostruct'
73
+
74
+ describe FeatureEnvy, '#examine' do
75
+
76
+ before :each do
77
+ @mc = OpenStruct.new # SMELL: this is a mock!!
78
+ @mc.name = 'cool'
79
+ @mc.refs = ObjectRefs.new
80
+ end
81
+
82
+ it 'should return true when reporting a smell' do
83
+ @mc.refs.record_ref([:lvar, :thing])
84
+ @mc.refs.record_ref([:lvar, :thing])
85
+ FeatureEnvy.examine(@mc, []).should == true
86
+ end
87
+
88
+ it 'should return false when not reporting a smell' do
89
+ FeatureEnvy.examine(@mc, []).should == false
90
+ end
91
+ end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
2
 
3
3
  require 'reek/class_checker'
4
4
  require 'reek/report'
@@ -9,7 +9,7 @@ describe ClassChecker, "(Large Class)" do
9
9
 
10
10
  class BigOne
11
11
  26.times do |i|
12
- define_method "m#{i}".to_sym do
12
+ define_method "method#{i}".to_sym do
13
13
  @melting
14
14
  end
15
15
  end
@@ -22,12 +22,12 @@ describe ClassChecker, "(Large Class)" do
22
22
 
23
23
  it 'should not report short class' do
24
24
  class ShortClass
25
- def m1() @m1; end
26
- def m2() @m2; end
27
- def m3() @m3; end
28
- def m4() @m4; end
29
- def m5() @m5; end
30
- def m6() @m6; end
25
+ def method1() @var1; end
26
+ def method2() @var2; end
27
+ def method3() @var3; end
28
+ def method4() @var4; end
29
+ def method5() @var5; end
30
+ def method6() @var6; end
31
31
  end
32
32
  @cchk.check_object(ShortClass)
33
33
  @rpt.should be_empty
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
2
 
3
3
  require 'reek/method_checker'
4
4
  require 'reek/report'
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
2
 
3
3
  require 'reek/method_checker'
4
4
  require 'reek/report'
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ require 'reek/method_checker'
4
+ require 'reek/report'
5
+
6
+ include Reek
7
+
8
+ def check(desc, src, expected, pending_str = nil)
9
+ it(desc) do
10
+ pending(pending_str) unless pending_str.nil?
11
+ rpt = Report.new
12
+ cchk = MethodChecker.new(rpt, 'Thing')
13
+ cchk.check_source(src)
14
+ rpt.length.should == expected.length
15
+ (0...rpt.length).each do |smell|
16
+ expected[smell].each { |patt| rpt[smell].detailed_report.should match(patt) }
17
+ end
18
+ end
19
+ end
20
+
21
+ describe MethodChecker, " nested iterators" do
22
+
23
+ check 'should report nested iterators in a method',
24
+ 'def bad(fred) @fred.each {|item| item.each {|ting| ting.ting} } end', [[/nested iterators/]]
25
+
26
+ check 'should not report method with successive iterators',
27
+ 'def bad(fred)
28
+ @fred.each {|item| item.each }
29
+ @jim.each {|ting| ting.each }
30
+ end', []
31
+
32
+ check 'should not report method with chained iterators',
33
+ 'def chained
34
+ @sig.keys.sort_by { |x| x.to_s }.each { |m| md5 << m.to_s }
35
+ end', []
36
+
37
+ check 'should report nested iterators only once per method',
38
+ 'def bad(fred)
39
+ @fred.each {|item| item.each {|part| @joe.send} }
40
+ @jim.each {|ting| ting.each {|piece| @hal.send} }
41
+ end', [[/nested iterators/]]
42
+ end
43
+
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper.rb'
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
2
 
3
3
  require 'reek/smells'
4
4
 
@@ -10,7 +10,7 @@ describe Smell, "camel case converter" do
10
10
  end
11
11
 
12
12
  it "should display correct name in report" do
13
- smell = LongMethod.new(self)
13
+ smell = LongMethod.new(self, 25)
14
14
  smell.report.should match(/[#{smell.name}]/)
15
15
  end
16
16
  end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper.rb'
2
+
3
+ require 'reek/method_checker'
4
+ require 'reek/report'
5
+
6
+ include Reek
7
+
8
+ def check(desc, src, expected, pending_str = nil)
9
+ it(desc) do
10
+ pending(pending_str) unless pending_str.nil?
11
+ rpt = Report.new
12
+ cchk = MethodChecker.new(rpt, 'Thing')
13
+ cchk.check_source(src)
14
+ rpt.length.should == expected.length
15
+ (0...rpt.length).each do |smell|
16
+ expected[smell].each { |patt| rpt[smell].detailed_report.should match(patt) }
17
+ end
18
+ end
19
+ end
20
+
21
+ describe MethodChecker, "uncommunicative method name" do
22
+ check 'should not report one-word method name', 'def help(fred) basics(17) end', []
23
+ check 'should report one-letter method name', 'def x(fred) basics(17) end', [[/x/, /method name/]]
24
+ check 'should report name of the form "x2"', 'def x2(fred) basics(17) end', [[/x2/, /method name/]]
25
+ end
26
+
27
+ describe MethodChecker, "uncommunicative field name" do
28
+ check 'should not report one-word field name', 'def help(fred) @simple end', []
29
+ check 'should report one-letter fieldname', 'def simple(fred) @x end', [[/@x/, /field name/]]
30
+ check 'should report name of the form "x2"', 'def simple(fred) @x2 end', [[/@x2/, /field name/]]
31
+ check 'should report one-letter fieldname in assignment', 'def simple(fred) @x = fred end', [[/@x/, /field name/]]
32
+ end
33
+
34
+ describe MethodChecker, "uncommunicative local variable name" do
35
+ check 'should not report one-word variable name', 'def help(fred) simple = jim(45) end', []
36
+ check 'should report one-letter variable name', 'def simple(fred) x = jim(45) end', [[/x/, /local variable name/]]
37
+ check 'should report name of the form "x2"', 'def simple(fred) x2 = jim(45) end', [[/x2/, /local variable name/]]
38
+ check 'should report variable name only once', 'def simple(fred) x = jim(45); x = y end', [[]]
39
+ end
40
+
41
+ describe MethodChecker, "uncommunicative parameter name" do
42
+ check 'should recognise short parameter name', 'def help(x) basics(17) end', [[]]
43
+ check 'should not recognise *', 'def help(xray, *) basics(17) end', []
44
+ check "should report parameter's name", 'def help(x) basics(17) end', [[/x/, /parameter name/]]
45
+ check 'should report name of the form "x2"', 'def help(x2) basics(17) end', [[/x2/, /parameter name/]]
46
+ end
47
+
48
+ describe MethodChecker, "several uncommunicative names" do
49
+
50
+ check 'should report all bad names', 'def y(x) @z = x end', [[/'@z'/], [/'y'/], [/'x'/]]
51
+
52
+ source =<<EOS
53
+ def bad(fred)
54
+ @fred.each {|x| 4 - x }
55
+ @jim.each {|y| y - 4 }
56
+ end
57
+ EOS
58
+ check 'should report all bad block parameters', source, [[/'x'/], [/'y'/]], 'bug'
59
+ end
60
+
61
+ require 'ostruct'
62
+ require 'reek/smells/uncommunicative_name'
63
+ include Reek::Smells
64
+
65
+ describe UncommunicativeName, '#examine' do
66
+
67
+ before :each do
68
+ @mc = OpenStruct.new
69
+ @mc.parameters = []
70
+ @mc.local_variables = []
71
+ @mc.instance_variables = []
72
+ end
73
+
74
+ it 'should return true when reporting a smell' do
75
+ @mc.name = 'x'
76
+ UncommunicativeName.examine(@mc, []).should == true
77
+ end
78
+
79
+ it 'should return false when not reporting a smell' do
80
+ @mc.name = 'not_bad'
81
+ UncommunicativeName.examine(@mc, []).should == false
82
+ end
83
+ end