reek 0.3.1 → 1.0.0

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 (96) hide show
  1. data/History.txt +20 -0
  2. data/README.txt +4 -80
  3. data/Rakefile +15 -4
  4. data/bin/reek +10 -16
  5. data/config/defaults.reek +53 -0
  6. data/lib/reek.rb +1 -21
  7. data/lib/reek/block_context.rb +37 -0
  8. data/lib/reek/class_context.rb +73 -0
  9. data/lib/reek/code_context.rb +47 -0
  10. data/lib/reek/code_parser.rb +204 -0
  11. data/lib/reek/exceptions.reek +13 -0
  12. data/lib/reek/if_context.rb +25 -0
  13. data/lib/reek/method_context.rb +85 -0
  14. data/lib/reek/module_context.rb +34 -0
  15. data/lib/reek/name.rb +42 -0
  16. data/lib/reek/object_refs.rb +3 -6
  17. data/lib/reek/options.rb +60 -40
  18. data/lib/reek/rake_task.rb +20 -29
  19. data/lib/reek/report.rb +16 -27
  20. data/lib/reek/sexp_formatter.rb +52 -0
  21. data/lib/reek/singleton_method_context.rb +27 -0
  22. data/lib/reek/smell_warning.rb +49 -0
  23. data/lib/reek/smells/control_couple.rb +21 -13
  24. data/lib/reek/smells/duplication.rb +23 -27
  25. data/lib/reek/smells/feature_envy.rb +18 -25
  26. data/lib/reek/smells/large_class.rb +32 -17
  27. data/lib/reek/smells/long_method.rb +24 -16
  28. data/lib/reek/smells/long_parameter_list.rb +25 -18
  29. data/lib/reek/smells/long_yield_list.rb +7 -9
  30. data/lib/reek/smells/nested_iterators.rb +13 -9
  31. data/lib/reek/smells/smell_detector.rb +66 -0
  32. data/lib/reek/smells/smells.rb +71 -10
  33. data/lib/reek/smells/uncommunicative_name.rb +49 -41
  34. data/lib/reek/smells/utility_function.rb +18 -18
  35. data/lib/reek/source.rb +116 -0
  36. data/lib/reek/spec.rb +146 -0
  37. data/lib/reek/stop_context.rb +62 -0
  38. data/lib/reek/yield_call_context.rb +14 -0
  39. data/reek.gemspec +42 -0
  40. data/spec/integration/reek_source_spec.rb +20 -0
  41. data/spec/{script_spec.rb → integration/script_spec.rb} +11 -24
  42. data/spec/reek/class_context_spec.rb +198 -0
  43. data/spec/reek/code_context_spec.rb +92 -0
  44. data/spec/reek/code_parser_spec.rb +44 -0
  45. data/spec/reek/config_spec.rb +42 -0
  46. data/spec/reek/if_context_spec.rb +17 -0
  47. data/spec/reek/method_context_spec.rb +52 -0
  48. data/spec/reek/module_context_spec.rb +38 -0
  49. data/spec/reek/options_spec.rb +2 -28
  50. data/spec/reek/report_spec.rb +6 -40
  51. data/spec/reek/sexp_formatter_spec.rb +31 -0
  52. data/spec/reek/singleton_method_context_spec.rb +17 -0
  53. data/spec/reek/smells/control_couple_spec.rb +10 -18
  54. data/spec/reek/smells/duplication_spec.rb +53 -32
  55. data/spec/reek/smells/feature_envy_spec.rb +87 -49
  56. data/spec/reek/smells/large_class_spec.rb +45 -4
  57. data/spec/reek/smells/long_method_spec.rb +25 -41
  58. data/spec/reek/smells/long_parameter_list_spec.rb +30 -76
  59. data/spec/reek/smells/nested_iterators_spec.rb +19 -29
  60. data/spec/reek/smells/smell_spec.rb +9 -18
  61. data/spec/reek/smells/uncommunicative_name_spec.rb +88 -53
  62. data/spec/reek/smells/utility_function_spec.rb +45 -44
  63. data/spec/samples/inline_spec.rb +40 -0
  64. data/spec/samples/optparse_spec.rb +100 -0
  65. data/spec/samples/redcloth_spec.rb +93 -0
  66. data/spec/spec_helper.rb +3 -1
  67. data/tasks/reek.rake +1 -10
  68. data/tasks/rspec.rake +16 -35
  69. metadata +43 -46
  70. data/lib/reek/checker.rb +0 -66
  71. data/lib/reek/class_checker.rb +0 -25
  72. data/lib/reek/file_checker.rb +0 -20
  73. data/lib/reek/method_checker.rb +0 -198
  74. data/lib/reek/printer.rb +0 -154
  75. data/lib/reek/smells/smell.rb +0 -56
  76. data/lib/reek/version.rb +0 -9
  77. data/setup.rb +0 -1585
  78. data/spec/integration_spec.rb +0 -30
  79. data/spec/reek/class_checker_spec.rb +0 -48
  80. data/spec/reek/method_checker_spec.rb +0 -67
  81. data/spec/reek/printer_spec.rb +0 -30
  82. data/spec/reek_source_spec.rb +0 -12
  83. data/spec/samples/inline.reek +0 -27
  84. data/spec/samples/optparse.reek +0 -79
  85. data/spec/samples/optparse/date.rb +0 -17
  86. data/spec/samples/optparse/shellwords.rb +0 -6
  87. data/spec/samples/optparse/time.rb +0 -10
  88. data/spec/samples/optparse/uri.rb +0 -6
  89. data/spec/samples/optparse/version.rb +0 -70
  90. data/spec/samples/redcloth.reek +0 -65
  91. data/tasks/samples.rake +0 -17
  92. data/website/index.html +0 -71
  93. data/website/index.txt +0 -40
  94. data/website/javascripts/rounded_corners_lite.inc.js +0 -285
  95. data/website/stylesheets/screen.css +0 -138
  96. data/website/template.rhtml +0 -48
@@ -1,13 +1,11 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
2
-
3
- require 'reek/version'
1
+ require 'reek'
4
2
 
5
3
  describe 'version number' do
6
4
  it 'should report the correct value' do
7
5
  actual = `ruby -Ilib bin/reek --version`.split
8
6
  $?.exitstatus.should == 0
9
7
  actual[0].should == 'reek'
10
- actual[1].should == Reek::VERSION::STRING
8
+ actual[1].should == Reek::VERSION
11
9
  end
12
10
  end
13
11
 
@@ -17,51 +15,40 @@ describe 'exit status', 'when reek is used incorrectly' do
17
15
  $?.exitstatus.should == 1
18
16
  end
19
17
 
20
- it 'should not complain about missing file' do
21
- `ruby -Ilib bin/reek nosuchfile.rb 2>/dev/null`
22
- $?.exitstatus.should == 0
18
+ it 'should complain about missing file' do
19
+ `ruby -Ilib bin/reek nosuchfile.rb`
20
+ $?.exitstatus.should == 1
23
21
  end
24
22
 
25
23
  it 'should return non-zero status on missing argument' do
26
- `ruby -Ilib bin/reek -s 2>/dev/null`
24
+ `ruby -Ilib bin/reek -f 2>/dev/null`
27
25
  $?.exitstatus.should == 1
28
26
  end
29
-
30
- it 'should not complain when no source given' do
31
- `ruby -Ilib bin/reek 2>/dev/null`
32
- $?.exitstatus.should == 0
33
- end
34
27
  end
35
28
 
36
29
  describe 'exit status', 'when reek is used correctly' do
37
30
  it 'should return non-zero status when smells are reported' do
38
- `ruby -Ilib bin/reek "def x() 3; end"`
31
+ `echo "def x() 3; end" | ruby -Ilib bin/reek`
39
32
  $?.exitstatus.should == 2
40
33
  end
41
34
 
42
35
  it 'should return zero status with no smells' do
43
- `ruby -Ilib bin/reek "def simple() @fred = 3 end"`
36
+ `echo "def simple() @fred = 3 end" | ruby -Ilib bin/reek`
44
37
  $?.exitstatus.should == 0
45
38
  end
46
39
  end
47
40
 
48
- describe 'report format', 'with no sources' do
49
- it 'should output nothing' do
50
- `ruby -Ilib bin/reek`.should be_empty
51
- end
52
- end
53
-
54
41
  describe 'report format', 'with one source' do
55
42
  it 'should output nothing with empty source' do
56
- `ruby -Ilib bin/reek ""`.should be_empty
43
+ `echo "" | ruby -Ilib bin/reek`.should be_empty
57
44
  end
58
45
 
59
46
  it 'should output nothing when no smells' do
60
- `ruby -Ilib bin/reek "def simple() @fred = 3; end"`.should be_empty
47
+ `echo "def simple() @fred = 3; end" | ruby -Ilib bin/reek`.should be_empty
61
48
  end
62
49
 
63
50
  it 'should not adorn the list of warnings' do
64
- report = `ruby -Ilib bin/reek "def y() @x = 3; end"`
51
+ report = `echo "class Turn; def y() @x = 3; end end" | ruby -Ilib bin/reek`
65
52
  report.split(/\n/).length.should == 2
66
53
  report.should_not match(/\n\n/)
67
54
  end
@@ -0,0 +1,198 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ require 'reek/class_context'
4
+ require 'reek/stop_context'
5
+ require 'reek/smells/feature_envy'
6
+
7
+ include Reek
8
+ include Reek::Smells
9
+
10
+ describe ClassContext do
11
+ it 'should report Long Parameter List' do
12
+ ruby = 'class Inner; def simple(arga, argb, argc, argd) f(3);true end end'
13
+ ruby.should reek_of(:LongParameterList, /Inner/, /simple/, /4 parameters/)
14
+ end
15
+
16
+ it 'should report two different methods' do
17
+ src = <<EOEX
18
+ class Fred
19
+ def simple(arga, argb, argc, argd) f(3);true end
20
+ def simply(arga, argb, argc, argd) f(3);false end
21
+ end
22
+ EOEX
23
+ src.should reek_of(:LongParameterList, /Fred/, /simple/)
24
+ src.should reek_of(:LongParameterList, /Fred/, /simply/)
25
+ end
26
+
27
+ it 'should report many different methods' do
28
+ src = <<EOEX
29
+ class Fred
30
+ def textile_bq(tag, atts, cite, content) f(3);end
31
+ def textile_p(tag, atts, cite, content) f(3);end
32
+ def textile_fn_(tag, num, atts, cite, content) f(3);end
33
+ def textile_popup_help(name, windowW, windowH) f(3);end
34
+ end
35
+ EOEX
36
+ src.should reek_of(:LongParameterList, /Fred/, /textile_bq/)
37
+ src.should reek_of(:LongParameterList, /Fred/, /textile_fn_/)
38
+ src.should reek_of(:LongParameterList, /Fred/, /textile_p/)
39
+ end
40
+ end
41
+
42
+ describe Class do
43
+
44
+ module Insert
45
+ def meth_a() end
46
+ private
47
+ def meth_b() end
48
+ protected
49
+ def meth_c() end
50
+ end
51
+
52
+ class Parent
53
+ def meth1() end
54
+ private
55
+ def meth2() end
56
+ protected
57
+ def meth3() end
58
+ end
59
+
60
+ class FullChild < Parent
61
+ include Insert
62
+ def meth7() end
63
+ private
64
+ def meth8() end
65
+ protected
66
+ def meth6() end
67
+ end
68
+
69
+ describe 'with no superclass or modules' do
70
+ it 'should report correct number of methods' do
71
+ Parent.non_inherited_methods.length.should == 3
72
+ end
73
+ end
74
+
75
+ describe 'with superclass and modules' do
76
+ it 'should report correct number of methods' do
77
+ FullChild.non_inherited_methods.length.should == 3
78
+ end
79
+ end
80
+ end
81
+
82
+ describe ClassContext, 'overridden methods' do
83
+ class Above
84
+ def above() end
85
+ def both() end
86
+ end
87
+
88
+ class Below < Above
89
+ def both() end
90
+ def below() end
91
+ end
92
+
93
+ describe 'of loaded class' do
94
+ before :each do
95
+ @ctx = ClassContext.create(StopContext.new, [0, :Below])
96
+ end
97
+
98
+ it 'should recognise non-overridden method' do
99
+ @ctx.is_overriding_method?('below').should == false
100
+ @ctx.is_overriding_method?('above').should == false
101
+ end
102
+
103
+ it 'should recognise overridden method' do
104
+ @ctx.is_overriding_method?('both').should == true
105
+ end
106
+
107
+ it 'should recognise methods in current codebase' do
108
+ ctx = ClassContext.create(StopContext.new, [0, :FeatureEnvy])
109
+ ctx.is_overriding_method?('examine_context').should == true
110
+ end
111
+ end
112
+
113
+ describe 'of non-loaded class' do
114
+ before :each do
115
+ @ctx = ClassContext.create(StopContext.new, [0, :Missing])
116
+ end
117
+
118
+ it 'should recognise non-overridden method' do
119
+ @ctx.is_overriding_method?('below').should == false
120
+ @ctx.is_overriding_method?('above').should == false
121
+ end
122
+
123
+ it 'should recognise overridden method' do
124
+ @ctx.is_overriding_method?('both').should == false
125
+ end
126
+ end
127
+ end
128
+
129
+ describe 'Integration defect:' do
130
+ it 'should not report UtilityFunction for FeatureEnvy#examine_context' do
131
+ kelement = ClassContext.create(StopContext.new, [0, :FeatureEnvy, s(:const, :SmellDetector)])
132
+ meth = Name.new(:examine_context)
133
+ kelement.is_overriding_method?(meth).should == true
134
+ melement = MethodContext.new(kelement, [0, :examine_context])
135
+ melement.is_overriding_method?(meth).should == true
136
+ melement.depends_on_instance?.should == true
137
+ end
138
+ end
139
+
140
+ describe CodeContext, 'find class' do
141
+ module Mod1
142
+ class Klass1
143
+ module Mod2
144
+ class Klass2
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ before :each do
151
+ @stop = StopContext.new
152
+ @mod1 = ModuleContext.create(@stop, [0, :Mod1])
153
+ @klass1 = ClassContext.create(@mod1, [0, :Klass1])
154
+ @mod2 = ModuleContext.create(@klass1, [0, :Mod2])
155
+ @klass2 = ClassContext.create(@mod2, [0, :Klass2])
156
+ end
157
+
158
+ describe StopContext do
159
+ it 'should not find unqualified class' do
160
+ @stop.find_module('Klass2').should == nil
161
+ end
162
+
163
+ it 'should find unqualified module' do
164
+ @stop.find_module('Mod1').name.should == 'Mod1'
165
+ end
166
+ end
167
+
168
+ describe ModuleContext do
169
+ it 'should find local name' do
170
+ @mod1.find_module('Klass1').name.should == 'Mod1::Klass1'
171
+ @mod2.find_module('Klass2').name.should == 'Mod1::Klass1::Mod2::Klass2'
172
+ end
173
+
174
+ it 'should not find deeper class' do
175
+ @mod1.find_module('Klass2').should == nil
176
+ end
177
+
178
+ it 'should find own Module' do
179
+ @mod1.myself.name.should == 'Mod1'
180
+ @mod2.myself.name.should == 'Mod1::Klass1::Mod2'
181
+ end
182
+ end
183
+
184
+ describe ClassContext do
185
+ it 'should find local module' do
186
+ @klass1.find_module('Mod2').name.should == 'Mod1::Klass1::Mod2'
187
+ end
188
+
189
+ it 'should not find deeper module' do
190
+ @klass1.find_module('Klass2').should == nil
191
+ end
192
+
193
+ it 'should find own Class' do
194
+ @klass1.myself.name.should == 'Mod1::Klass1'
195
+ @klass2.myself.name.should == 'Mod1::Klass1::Mod2::Klass2'
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,92 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ require 'reek/block_context'
4
+ require 'reek/if_context'
5
+ require 'reek/class_context'
6
+ require 'reek/module_context'
7
+ require 'reek/method_context'
8
+ require 'reek/stop_context'
9
+
10
+ include Reek
11
+
12
+ describe CodeContext, 'to_s' do
13
+
14
+ it "should report full context" do
15
+ element = StopContext.new
16
+ element = ModuleContext.new(element, [0, :mod])
17
+ element = ClassContext.new(element, [0, :klass])
18
+ element = MethodContext.new(element, [0, :bad])
19
+ element = BlockContext.new(element, nil)
20
+ element.to_s.should match(/bad/)
21
+ element.to_s.should match(/klass/)
22
+ element.to_s.should match(/mod/)
23
+ end
24
+
25
+ it "should report method name via if context" do
26
+ element1 = StopContext.new
27
+ element2 = MethodContext.new(element1, [0, :bad])
28
+ element3 = IfContext.new(element2, [0,1])
29
+ BlockContext.new(element3, nil).to_s.should match(/bad/)
30
+ end
31
+
32
+ it "should report method name via nested blocks" do
33
+ element1 = StopContext.new
34
+ element2 = MethodContext.new(element1, [0, :bad])
35
+ element3 = BlockContext.new(element2, nil)
36
+ BlockContext.new(element3, nil).to_s.should match(/bad/)
37
+ end
38
+ end
39
+
40
+ describe CodeContext, 'instance variables' do
41
+ it 'should pass instance variables down to the first class' do
42
+ element = StopContext.new
43
+ element = ModuleContext.new(element, [0, :mod])
44
+ class_element = ClassContext.new(element, [0, :klass])
45
+ element = MethodContext.new(class_element, [0, :bad])
46
+ element = BlockContext.new(element, nil)
47
+ element.record_instance_variable(:fred)
48
+ class_element.variable_names.should == [Name.new(:fred)]
49
+ end
50
+ end
51
+
52
+ describe CodeContext, 'generics' do
53
+ it 'should pass unknown method calls down the stack' do
54
+ stop = StopContext.new
55
+ def stop.bananas(arg1, arg2) arg1 + arg2 + 43 end
56
+ element = ModuleContext.new(stop, [0, :mod])
57
+ class_element = ClassContext.new(element, [0, :klass])
58
+ element = MethodContext.new(class_element, [0, :bad])
59
+ element = BlockContext.new(element, nil)
60
+ element.bananas(17, -5).should == 55
61
+ end
62
+ end
63
+
64
+ describe CodeContext do
65
+ it 'should recognise itself in a collection of names' do
66
+ element = StopContext.new
67
+ element = ModuleContext.new(element, [0, :mod])
68
+ element.matches?(['banana', 'mod']).should == true
69
+ end
70
+
71
+ it 'should recognise itself in a collection of REs' do
72
+ element = StopContext.new
73
+ element = ModuleContext.new(element, [0, :mod])
74
+ element.matches?([/banana/, /mod/]).should == true
75
+ end
76
+
77
+ it 'should recognise its fq name in a collection of names' do
78
+ element = StopContext.new
79
+ element = ModuleContext.new(element, [0, :mod])
80
+ element = ClassContext.create(element, [0, :klass])
81
+ element.matches?(['banana', 'mod']).should == true
82
+ element.matches?(['banana', 'mod::klass']).should == true
83
+ end
84
+
85
+ it 'should recognise its fq name in a collection of names' do
86
+ element = StopContext.new
87
+ element = ModuleContext.new(element, [0, :mod])
88
+ element = ClassContext.create(element, [0, :klass])
89
+ element.matches?([/banana/, /mod/]).should == true
90
+ element.matches?([/banana/, /mod::klass/]).should == true
91
+ end
92
+ end
@@ -0,0 +1,44 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ require 'reek/code_parser'
4
+ require 'reek/report'
5
+
6
+ include Reek
7
+
8
+ describe CodeParser, "with no method definitions" do
9
+ it 'should report no problems for empty source code' do
10
+ ''.should_not reek
11
+ end
12
+ it 'should report no problems for empty class' do
13
+ 'class Fred; end'.should_not reek
14
+ end
15
+ end
16
+
17
+ describe CodeParser, 'with a global method definition' do
18
+ it 'should report no problems for simple method' do
19
+ 'def Outermost::fred() true; end'.should_not reek
20
+ end
21
+ end
22
+
23
+ describe CodeParser, 'when given a C extension' do
24
+ before(:each) do
25
+ @cchk = CodeParser.new(Report.new, SmellConfig.new.smell_listeners)
26
+ end
27
+
28
+ it 'should ignore :cfunc' do
29
+ @cchk.check_object(Enumerable)
30
+ end
31
+ end
32
+
33
+ describe CodeParser, 'when a yield is the receiver' do
34
+ it 'should report no problems' do
35
+ source = 'def values(*args)
36
+ @to_sql += case
37
+ when block_given? then " #{yield.to_sql}"
38
+ else " values (#{args.to_sql})"
39
+ end
40
+ self
41
+ end'
42
+ source.should_not reek
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ require 'reek/smells/smells'
4
+ require 'yaml'
5
+
6
+ include Reek
7
+
8
+ describe 'Config' do
9
+ before :each do
10
+ @first = {
11
+ 'one' => {'two' => 3, 'three' => 4},
12
+ 'two' => {'four' => true}
13
+ }
14
+ end
15
+
16
+ it 'should deep merge Hashes' do
17
+ other = Hash.new {|hash,key| hash[key] = {} }
18
+ other['one']['gunk'] = 45
19
+ other['two']['four'] = false
20
+ @first.value_merge!(other).to_yaml
21
+ @first['two']['four'].should == false
22
+ @first['one'].keys.length.should == 3
23
+ end
24
+
25
+ it 'should deep copy Hashes' do
26
+ second = @first.deep_copy
27
+ second['two'].object_id.should_not be_eql(@first['two'].object_id)
28
+ end
29
+
30
+ it 'should merge array values' do
31
+ @first['three'] = [1,2,3]
32
+ end
33
+ end
34
+
35
+ describe Config, 'merging arrays' do
36
+ it 'should merge array values' do
37
+ first = {'key' => {'one' => [1,2,3]}}
38
+ second = {'key' => {'one' => [4,5]}}
39
+ first.value_merge!(second)
40
+ first['key']['one'].should == [1,2,3,4,5]
41
+ end
42
+ end