reek 1.0.0 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/History.txt +56 -22
  2. data/config/defaults.reek +3 -5
  3. data/lib/reek.rb +1 -1
  4. data/lib/reek/block_context.rb +27 -5
  5. data/lib/reek/class_context.rb +5 -9
  6. data/lib/reek/code_parser.rb +23 -50
  7. data/lib/reek/method_context.rb +18 -12
  8. data/lib/reek/module_context.rb +1 -1
  9. data/lib/reek/name.rb +8 -1
  10. data/lib/reek/object_refs.rb +2 -3
  11. data/lib/reek/object_source.rb +53 -0
  12. data/lib/reek/report.rb +41 -2
  13. data/lib/reek/sexp_formatter.rb +4 -46
  14. data/lib/reek/smells/large_class.rb +27 -8
  15. data/lib/reek/smells/long_parameter_list.rb +1 -1
  16. data/lib/reek/smells/smells.rb +4 -8
  17. data/lib/reek/source.rb +19 -8
  18. data/lib/reek/stop_context.rb +4 -16
  19. data/lib/reek/yield_call_context.rb +1 -3
  20. data/reek.gemspec +11 -9
  21. data/spec/reek/block_context_spec.rb +40 -0
  22. data/spec/reek/class_context_spec.rb +11 -40
  23. data/spec/reek/code_context_spec.rb +2 -1
  24. data/spec/reek/code_parser_spec.rb +0 -10
  25. data/spec/reek/config_spec.rb +2 -2
  26. data/spec/reek/method_context_spec.rb +14 -0
  27. data/spec/reek/name_spec.rb +13 -0
  28. data/spec/reek/object_refs_spec.rb +11 -9
  29. data/spec/reek/report_spec.rb +1 -1
  30. data/spec/reek/singleton_method_context_spec.rb +1 -1
  31. data/spec/reek/smells/duplication_spec.rb +2 -2
  32. data/spec/reek/smells/feature_envy_spec.rb +132 -36
  33. data/spec/reek/smells/large_class_spec.rb +48 -47
  34. data/spec/reek/smells/long_method_spec.rb +1 -1
  35. data/spec/reek/smells/long_parameter_list_spec.rb +4 -11
  36. data/spec/reek/smells/uncommunicative_name_spec.rb +6 -1
  37. data/spec/reek/smells/utility_function_spec.rb +6 -9
  38. data/spec/{samples → slow}/inline_spec.rb +13 -10
  39. data/spec/{samples → slow}/optparse_spec.rb +20 -12
  40. data/spec/{samples → slow}/redcloth_spec.rb +16 -8
  41. data/spec/{integration → slow}/reek_source_spec.rb +0 -0
  42. data/spec/{samples → slow/samples}/inline.rb +0 -0
  43. data/spec/{samples → slow/samples}/optparse.rb +0 -0
  44. data/spec/{samples → slow/samples}/redcloth.rb +0 -0
  45. data/spec/{integration → slow}/script_spec.rb +0 -0
  46. data/spec/slow/source_list_spec.rb +40 -0
  47. data/spec/spec_helper.rb +2 -0
  48. data/tasks/rspec.rake +1 -1
  49. metadata +30 -15
  50. data/spec/reek/sexp_formatter_spec.rb +0 -31
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ require 'reek/block_context'
4
+ require 'reek/method_context'
5
+
6
+ include Reek
7
+
8
+ describe BlockContext do
9
+
10
+ it "should record single parameter" do
11
+ element = StopContext.new
12
+ element = BlockContext.new(element, s(s(:lasgn, :x), nil))
13
+ element.variable_names.should == [Name.new(:x)]
14
+ end
15
+
16
+ it "should record single parameter within a method" do
17
+ element = StopContext.new
18
+ element = MethodContext.new(element, s(:defn, :help))
19
+ element = BlockContext.new(element, s(s(:lasgn, :x), nil))
20
+ element.variable_names.should == [Name.new(:x)]
21
+ end
22
+
23
+ it "records multiple parameters" do
24
+ element = StopContext.new
25
+ element = BlockContext.new(element, s(s(:masgn, s(:array, s(:lasgn, :x), s(:lasgn, :y))), nil))
26
+ element.variable_names.should == [Name.new(:x), Name.new(:y)]
27
+ end
28
+
29
+ it "should not pass parameters upward" do
30
+ mc = MethodContext.new(StopContext.new, s(:defn, :help))
31
+ element = BlockContext.new(mc, s(s(:lasgn, :x)))
32
+ mc.variable_names.should be_empty
33
+ end
34
+
35
+ it 'records local variables' do
36
+ bctx = BlockContext.new(StopContext.new, nil)
37
+ bctx.record_local_variable(:q2)
38
+ bctx.variable_names.should include(Name.new(:q2))
39
+ end
40
+ end
@@ -39,46 +39,6 @@ EOEX
39
39
  end
40
40
  end
41
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
42
  describe ClassContext, 'overridden methods' do
83
43
  class Above
84
44
  def above() end
@@ -196,3 +156,14 @@ describe CodeContext, 'find class' do
196
156
  end
197
157
  end
198
158
  end
159
+
160
+ describe ClassContext do
161
+ it 'should not report empty class in another module' do
162
+ 'class Treetop::Runtime::SyntaxNode; end'.should_not reek
163
+ end
164
+
165
+ it 'should deal with :: scoped names' do
166
+ element = ClassContext.create(StopContext.new, [:colon2, [:colon2, [:const, :Treetop], :Runtime], :SyntaxNode])
167
+ element.num_methods.should == 0
168
+ end
169
+ end
@@ -45,7 +45,8 @@ describe CodeContext, 'instance variables' do
45
45
  element = MethodContext.new(class_element, [0, :bad])
46
46
  element = BlockContext.new(element, nil)
47
47
  element.record_instance_variable(:fred)
48
- class_element.variable_names.should == [Name.new(:fred)]
48
+ class_element.variable_names.size.should == 1
49
+ class_element.variable_names.should include(Name.new(:fred))
49
50
  end
50
51
  end
51
52
 
@@ -20,16 +20,6 @@ describe CodeParser, 'with a global method definition' do
20
20
  end
21
21
  end
22
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
23
  describe CodeParser, 'when a yield is the receiver' do
34
24
  it 'should report no problems' do
35
25
  source = 'def values(*args)
@@ -17,7 +17,7 @@ describe 'Config' do
17
17
  other = Hash.new {|hash,key| hash[key] = {} }
18
18
  other['one']['gunk'] = 45
19
19
  other['two']['four'] = false
20
- @first.value_merge!(other).to_yaml
20
+ other.push_keys(@first)
21
21
  @first['two']['four'].should == false
22
22
  @first['one'].keys.length.should == 3
23
23
  end
@@ -36,7 +36,7 @@ describe Config, 'merging arrays' do
36
36
  it 'should merge array values' do
37
37
  first = {'key' => {'one' => [1,2,3]}}
38
38
  second = {'key' => {'one' => [4,5]}}
39
- first.value_merge!(second)
39
+ second.push_keys(first)
40
40
  first['key']['one'].should == [1,2,3,4,5]
41
41
  end
42
42
  end
@@ -49,4 +49,18 @@ describe MethodContext do
49
49
  mctx.record_call_to([:call, [:ivar, :@cow], :feed_to])
50
50
  mctx.envious_receivers.should == []
51
51
  end
52
+
53
+ it 'should count calls to self' do
54
+ mctx = MethodContext.new(StopContext.new, [:defn, :equals])
55
+ mctx.refs.record_ref([:lvar, :other])
56
+ mctx.record_call_to([:call, [:self], :thing])
57
+ mctx.envious_receivers.should be_empty
58
+ end
59
+
60
+ it 'should recognise a call on self' do
61
+ mc = MethodContext.new(StopContext.new, s(:defn, :deep))
62
+ mc.record_call_to(s(:call, s(:lvar, :text), :each, s(:arglist)))
63
+ mc.record_call_to(s(:call, nil, :shelve, s(:arglist)))
64
+ mc.envious_receivers.should be_empty
65
+ end
52
66
  end
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+ require 'reek/name'
3
+
4
+ include Reek
5
+
6
+ describe Name, 'resolving symbols' do
7
+ it 'finds fq loaded class' do
8
+ exp = [:class, :"Reek::Smells::LargeClass", nil]
9
+ ctx = StopContext.new
10
+ res = Name.resolve(exp[1], ctx)
11
+ res[1].should == "LargeClass"
12
+ end
13
+ end
@@ -9,8 +9,8 @@ describe ObjectRefs, 'when empty' do
9
9
  @refs = ObjectRefs.new
10
10
  end
11
11
 
12
- it 'should report one ref to self' do
13
- @refs.refs_to_self.should == 1
12
+ it 'should report no refs to self' do
13
+ @refs.refs_to_self.should == 0
14
14
  end
15
15
  end
16
16
 
@@ -23,7 +23,7 @@ describe ObjectRefs, 'with no refs to self' do
23
23
  end
24
24
 
25
25
  it 'should report no refs to self' do
26
- @refs.refs_to_self.should == 1
26
+ @refs.refs_to_self.should == 0
27
27
  end
28
28
 
29
29
  it 'should report :a as the max' do
@@ -45,16 +45,16 @@ describe ObjectRefs, 'with one ref to self' do
45
45
  end
46
46
 
47
47
  it 'should report 1 ref to self' do
48
- @refs.refs_to_self.should == 2
48
+ @refs.refs_to_self.should == 1
49
49
  end
50
50
 
51
- it 'should report self among the max' do
51
+ it 'should not report self among the max' do
52
52
  @refs.max_keys.should be_include('a')
53
- @refs.max_keys.should be_include(Sexp.from_array([:lit, :self]))
53
+ @refs.max_keys.should_not include(Sexp.from_array([:lit, :self]))
54
54
  end
55
55
 
56
- it 'should report self as the max' do
57
- @refs.self_is_max?.should == true
56
+ it 'should not report self as the max' do
57
+ @refs.self_is_max?.should == false
58
58
  end
59
59
  end
60
60
 
@@ -62,6 +62,7 @@ describe ObjectRefs, 'with many refs to self' do
62
62
  before(:each) do
63
63
  @refs = ObjectRefs.new
64
64
  @refs.record_reference_to_self
65
+ @refs.record_reference_to_self
65
66
  @refs.record_ref('a')
66
67
  @refs.record_reference_to_self
67
68
  @refs.record_ref('b')
@@ -87,6 +88,7 @@ describe ObjectRefs, 'when self is not the only max' do
87
88
  @refs = ObjectRefs.new
88
89
  @refs.record_ref('a')
89
90
  @refs.record_reference_to_self
91
+ @refs.record_reference_to_self
90
92
  @refs.record_ref('b')
91
93
  @refs.record_ref('a')
92
94
  end
@@ -115,7 +117,7 @@ describe ObjectRefs, 'when self is not among the max' do
115
117
  end
116
118
 
117
119
  it 'should report all refs to self' do
118
- @refs.refs_to_self.should == 1
120
+ @refs.refs_to_self.should == 0
119
121
  end
120
122
 
121
123
  it 'should not report self among the max' do
@@ -28,7 +28,7 @@ describe Report, "to_s" do
28
28
  end
29
29
 
30
30
  it 'should place each detailed report on a separate line' do
31
- @report.length.should == 2
31
+ @report.should have_at_least(2).lines
32
32
  end
33
33
 
34
34
  it 'should mention every smell name' do
@@ -11,7 +11,7 @@ describe SingletonMethodContext, 'outer_name' do
11
11
  it "should report full context" do
12
12
  element = StopContext.new
13
13
  element = ModuleContext.new(element, [0, :mod])
14
- element = SingletonMethodContext.new(element, [:defs, [:vcall, :a], :b, nil])
14
+ element = SingletonMethodContext.new(element, [:defs, [:call, nil, :a, [:arglist]], :b, nil])
15
15
  element.outer_name.should match(/mod::a\.b/)
16
16
  end
17
17
  end
@@ -13,7 +13,7 @@ describe Duplication, "repeated method calls" do
13
13
  end
14
14
 
15
15
  it 'should report repeated call to lvar' do
16
- 'def double_thing() other[@thing] + other[@thing] end'.should reek_only_of(:Duplication, /other\[@thing\]/)
16
+ 'def double_thing(other) other[@thing] + other[@thing] end'.should reek_only_of(:Duplication, /other\[@thing\]/)
17
17
  end
18
18
 
19
19
  it 'should report call parameters' do
@@ -50,7 +50,7 @@ describe Duplication, '#examine' do
50
50
  end
51
51
 
52
52
  it 'should return true when reporting a smell' do
53
- @mc.calls = {'x' => 47}
53
+ @mc.calls = {s(:call, nil, :other, s(:arglist)) => 47}
54
54
  @dup.examine(@mc, []).should == true
55
55
  end
56
56
 
@@ -6,7 +6,7 @@ require 'reek/stop_context'
6
6
  include Reek
7
7
  include Reek::Smells
8
8
 
9
- describe FeatureEnvy, 'with only messages to self' do
9
+ describe FeatureEnvy do
10
10
  it 'should not report use of self' do
11
11
  'def simple() self.to_s + self.to_i end'.should_not reek
12
12
  end
@@ -18,23 +18,26 @@ describe FeatureEnvy, 'with only messages to self' do
18
18
  it 'should not report vcall with argument' do
19
19
  'def simple(arga) func(17); end'.should_not reek
20
20
  end
21
- end
22
21
 
23
- describe FeatureEnvy, 'when the receiver is a parameter' do
24
22
  it 'should not report single use' do
25
- 'def no_envy(arga) arga.barg(@item) end'.should_not reek
23
+ 'def no_envy(arga)
24
+ arga.barg(@item)
25
+ end'.should_not reek
26
26
  end
27
27
 
28
28
  it 'should not report return value' do
29
- 'def no_envy(arga) arga.barg(@item); arga end'.should_not reek
29
+ 'def no_envy(arga)
30
+ arga.barg(@item)
31
+ arga
32
+ end'.should_not reek
30
33
  end
31
34
 
32
35
  it 'should report many calls to parameter' do
33
- 'def envy(arga) arga.b(arga) + arga.c(@fred) end'.should reek_only_of(:FeatureEnvy, /arga/)
36
+ 'def envy(arga)
37
+ arga.b(arga) + arga.c(@fred)
38
+ end'.should reek_only_of(:FeatureEnvy, /arga/)
34
39
  end
35
- end
36
40
 
37
- describe FeatureEnvy, 'when there are many possible receivers' do
38
41
  it 'should report highest affinity' do
39
42
  ruby = 'def total_envy
40
43
  fred = @item
@@ -56,9 +59,7 @@ describe FeatureEnvy, 'when there are many possible receivers' do
56
59
  ruby.should reek_of(:FeatureEnvy, /total/)
57
60
  ruby.should reek_of(:FeatureEnvy, /fred/)
58
61
  end
59
- end
60
62
 
61
- describe FeatureEnvy, 'when the receiver is external' do
62
63
  it 'should ignore global variables' do
63
64
  'def no_envy() $s2.to_a; $s2[@item] end'.should_not reek
64
65
  end
@@ -66,9 +67,7 @@ describe FeatureEnvy, 'when the receiver is external' do
66
67
  it 'should not report class methods' do
67
68
  'def simple() self.class.new.flatten_merge(self) end'.should_not reek
68
69
  end
69
- end
70
70
 
71
- describe FeatureEnvy, 'when the receiver is an ivar' do
72
71
  it 'should not report single use of an ivar' do
73
72
  'def no_envy() @item.to_a end'.should_not reek
74
73
  end
@@ -78,17 +77,132 @@ describe FeatureEnvy, 'when the receiver is an ivar' do
78
77
  end
79
78
 
80
79
  it 'should not report ivar usage in a parameter' do
81
- 'def no_envy; @item.price + tax(@item) - savings(@item) end'.should_not reek
80
+ 'def no_envy
81
+ @item.price + tax(@item) - savings(@item)
82
+ end'.should_not reek
82
83
  end
83
84
 
84
85
  it 'should not be fooled by duplication' do
85
- ruby = Source.from_s('def feed(thing); @cow.feed_to(thing.pig); @duck.feed_to(thing.pig); end')
86
- ruby.should reek_only_of(:Duplication, /thing.pig/)
86
+ 'def feed(thing)
87
+ @cow.feed_to(thing.pig)
88
+ @duck.feed_to(thing.pig)
89
+ end'.should reek_only_of(:Duplication, /thing.pig/)
87
90
  end
88
91
 
89
92
  it 'should count local calls' do
90
- ruby = Source.from_s('def feed(thing); cow.feed_to(thing.pig); duck.feed_to(thing.pig); end')
91
- ruby.should reek_only_of(:Duplication, /thing.pig/)
93
+ 'def feed(thing)
94
+ cow.feed_to(thing.pig)
95
+ duck.feed_to(thing.pig)
96
+ end'.should reek_only_of(:Duplication, /thing.pig/)
97
+ end
98
+
99
+ it 'should not report single use of an lvar' do
100
+ 'def no_envy()
101
+ lv = @item
102
+ lv.to_a
103
+ end'.should_not reek
104
+ end
105
+
106
+ it 'should not report returning an lvar' do
107
+ 'def no_envy()
108
+ lv = @item
109
+ lv.to_a
110
+ lv
111
+ end'.should_not reek
112
+ end
113
+
114
+ it 'should report many calls to lvar' do
115
+ 'def envy
116
+ lv = @item
117
+ lv.price + lv.tax
118
+ end'.should reek_only_of(:FeatureEnvy, /lv/)
119
+ #
120
+ # def moved_version
121
+ # price + tax
122
+ # end
123
+ #
124
+ # def envy
125
+ # @item.moved_version
126
+ # end
127
+ end
128
+
129
+ it 'ignores lvar usage in a parameter' do
130
+ 'def no_envy
131
+ lv = @item
132
+ lv.price + tax(lv) - savings(lv)
133
+ end'.should_not reek
134
+ end
135
+
136
+ it 'reports the most-used ivar' do
137
+ pending('bug')
138
+ 'def func
139
+ @other.a
140
+ @other.b
141
+ @nother.c
142
+ end'.should reek_of(:FeatureEnvy, /@other/)
143
+ #
144
+ # def other.func(me)
145
+ # a
146
+ # b
147
+ # me.nother_c
148
+ # end
149
+ #
150
+ end
151
+
152
+ it 'ignores multiple ivars' do
153
+ 'def func
154
+ @other.a
155
+ @other.b
156
+ @nother.c
157
+ @nother.d
158
+ end'.should_not reek
159
+ #
160
+ # def other.func(me)
161
+ # a
162
+ # b
163
+ # me.nother_c
164
+ # me.nother_d
165
+ # end
166
+ #
167
+ end
168
+
169
+ it 'ignores frequent use of a call' do
170
+ 'def func
171
+ other.a
172
+ other.b
173
+ nother.c
174
+ end'.should_not reek_of(:FeatureEnvy)
175
+ end
176
+
177
+ it 'counts self references correctly' do
178
+ 'def adopt!(other)
179
+ other.keys.each do |key|
180
+ ov = other[key]
181
+ if Array === ov and has_key?(key)
182
+ self[key] += ov
183
+ else
184
+ self[key] = ov
185
+ end
186
+ end
187
+ self
188
+ end'.should_not reek
189
+ end
190
+ end
191
+
192
+ describe FeatureEnvy do
193
+ it 'counts references to self correctly' do
194
+ ruby = <<EOS
195
+ def report
196
+ unless @report
197
+ @report = Report.new
198
+ cf = SmellConfig.new
199
+ cf = cf.load_local(@dir) if @dir
200
+ CodeParser.new(@report, cf.smell_listeners).check_source(@source)
201
+ end
202
+ @report
203
+ end
204
+ EOS
205
+ ruby.should reek_only_of(:LongMethod)
92
206
  end
93
207
  end
94
208
 
@@ -104,26 +218,8 @@ describe FeatureEnvy, '#examine' do
104
218
  @context.refs.record_ref([:lvar, :thing])
105
219
  @fe.examine(@context, []).should == true
106
220
  end
107
-
221
+
108
222
  it 'should return false when not reporting a smell' do
109
223
  @fe.examine(@context, []).should == false
110
224
  end
111
225
  end
112
-
113
- describe FeatureEnvy, 'when the receiver is an lvar' do
114
- it 'should not report single use of an lvar' do
115
- 'def no_envy() lv = @item; lv.to_a end'.should_not reek
116
- end
117
-
118
- it 'should not report returning an lvar' do
119
- 'def no_envy() lv = @item; lv.to_a; lv end'.should_not reek
120
- end
121
-
122
- it 'should report many calls to lvar' do
123
- 'def envy; lv = @item; lv.price + lv.tax end'.should reek_only_of(:FeatureEnvy, /lv/)
124
- end
125
-
126
- it 'should not report lvar usage in a parameter' do
127
- 'def no_envy; lv = @item; lv.price + tax(lv) - savings(lv) end'.should_not reek
128
- end
129
- end