reek 0.3.0 → 0.3.1

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.
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