reek 1.0.0 → 1.1.3

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