reek 1.3.6 → 1.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +6 -0
  3. data/README.md +11 -1
  4. data/config/defaults.reek +1 -0
  5. data/features/command_line_interface/options.feature +1 -0
  6. data/features/rake_task/rake_task.feature +3 -0
  7. data/features/ruby_api/api.feature +1 -3
  8. data/features/samples.feature +27 -20
  9. data/features/support/env.rb +2 -2
  10. data/lib/reek/cli/application.rb +0 -4
  11. data/lib/reek/cli/command_line.rb +10 -12
  12. data/lib/reek/cli/reek_command.rb +1 -1
  13. data/lib/reek/cli/report.rb +36 -8
  14. data/lib/reek/config_file_exception.rb +3 -0
  15. data/lib/reek/core/code_context.rb +18 -8
  16. data/lib/reek/core/code_parser.rb +65 -61
  17. data/lib/reek/core/method_context.rb +4 -0
  18. data/lib/reek/core/module_context.rb +2 -2
  19. data/lib/reek/core/smell_repository.rb +3 -0
  20. data/lib/reek/core/sniffer.rb +0 -1
  21. data/lib/reek/core/stop_context.rb +1 -1
  22. data/lib/reek/smells/attribute.rb +1 -1
  23. data/lib/reek/smells/control_parameter.rb +79 -45
  24. data/lib/reek/smells/data_clump.rb +1 -1
  25. data/lib/reek/smells/duplicate_method_call.rb +1 -1
  26. data/lib/reek/smells/long_parameter_list.rb +1 -1
  27. data/lib/reek/smells/long_yield_list.rb +1 -1
  28. data/lib/reek/smells/nested_iterators.rb +1 -1
  29. data/lib/reek/smells/nil_check.rb +10 -5
  30. data/lib/reek/smells/repeated_conditional.rb +1 -1
  31. data/lib/reek/smells/smell_detector.rb +2 -3
  32. data/lib/reek/smells/too_many_instance_variables.rb +1 -1
  33. data/lib/reek/smells/too_many_methods.rb +1 -1
  34. data/lib/reek/smells/too_many_statements.rb +1 -1
  35. data/lib/reek/smells/uncommunicative_method_name.rb +4 -4
  36. data/lib/reek/smells/uncommunicative_module_name.rb +4 -4
  37. data/lib/reek/smells/uncommunicative_parameter_name.rb +9 -9
  38. data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
  39. data/lib/reek/smells/unused_parameters.rb +2 -6
  40. data/lib/reek/smells/utility_function.rb +1 -1
  41. data/lib/reek/source/code_comment.rb +1 -1
  42. data/lib/reek/source/config_file.rb +9 -8
  43. data/lib/reek/source/sexp_extensions.rb +2 -2
  44. data/lib/reek/source/sexp_node.rb +8 -5
  45. data/lib/reek/source/source_repository.rb +5 -0
  46. data/lib/reek/version.rb +1 -1
  47. data/reek.gemspec +3 -2
  48. data/spec/reek/cli/report_spec.rb +38 -8
  49. data/spec/reek/core/code_context_spec.rb +35 -3
  50. data/spec/reek/core/module_context_spec.rb +1 -1
  51. data/spec/reek/smells/repeated_conditional_spec.rb +1 -1
  52. data/spec/reek/smells/smell_detector_shared.rb +1 -2
  53. data/spec/reek/smells/too_many_statements_spec.rb +39 -25
  54. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +44 -30
  55. data/spec/reek/smells/unused_parameters_spec.rb +15 -11
  56. data/spec/reek/source/sexp_extensions_spec.rb +2 -2
  57. data/spec/reek/source/sexp_node_spec.rb +0 -1
  58. data/spec/samples/ruby20_syntax.rb +1 -5
  59. metadata +172 -162
  60. data/lib/reek/cli/yaml_command.rb +0 -32
  61. data/lib/reek/core/hash_extensions.rb +0 -29
  62. data/spec/reek/cli/yaml_command_spec.rb +0 -47
  63. data/spec/reek/core/config_spec.rb +0 -38
@@ -1,6 +1,8 @@
1
1
  require 'spec_helper'
2
2
  require 'reek/examiner'
3
3
  require 'reek/cli/report'
4
+ require 'rainbow'
5
+ require 'stringio'
4
6
 
5
7
  include Reek
6
8
  include Reek::Cli
@@ -17,18 +19,46 @@ describe QuietReport, " when empty" do
17
19
 
18
20
  context 'with a couple of smells' do
19
21
  before :each do
20
- examiner = Examiner.new('def simple(a) a[3] end')
21
- rpt = QuietReport.new
22
- @result = rpt.add_examiner(examiner).gather_results.first
22
+ @examiner = Examiner.new('def simple(a) a[3] end')
23
+ @rpt = QuietReport.new(SimpleWarningFormatter, ReportFormatter, false, :text)
23
24
  end
24
25
 
25
- it 'has a header' do
26
- @result.should match('string -- 2 warnings')
26
+ context 'with colors disabled' do
27
+ before :each do
28
+ Rainbow.enabled = false
29
+ @result = @rpt.add_examiner(@examiner).gather_results.first
30
+ end
31
+
32
+ it 'has a header' do
33
+ @result.should match('string -- 2 warnings')
34
+ end
35
+
36
+ it 'should mention every smell name' do
37
+ @result.should match('[UncommunicativeParameterName]')
38
+ @result.should match('[Feature Envy]')
39
+ end
27
40
  end
28
41
 
29
- it 'should mention every smell name' do
30
- @result.should match('[UncommunicativeParameterName]')
31
- @result.should match('[Feature Envy]')
42
+ context 'with colors enabled' do
43
+ before :each do
44
+ Rainbow.enabled = true
45
+ @rpt.add_examiner(Examiner.new('def simple(a) a[3] end'))
46
+ @rpt.add_examiner(Examiner.new('def simple(a) a[3] end'))
47
+ @result = @rpt.gather_results
48
+ end
49
+
50
+ it 'has a header in color' do
51
+ @result.first.should start_with "\e[36mstring -- \e[0m\e[33m2 warning\e[0m\e[33ms\e[0m"
52
+ end
53
+
54
+ it 'has a footer in color' do
55
+ stdout = StringIO.new
56
+ $stdout = stdout
57
+ @rpt.show
58
+ $stdout = STDOUT
59
+
60
+ stdout.string.should end_with "\e[31m4 total warnings\n\e[0m"
61
+ end
32
62
  end
33
63
  end
34
64
  end
@@ -36,8 +36,8 @@ describe CodeContext do
36
36
  before :each do
37
37
  @outer_name = 'another_random sting'
38
38
  outer = double('outer')
39
- outer.should_receive(:full_name).at_least(:once).and_return(@outer_name)
40
- outer.should_receive(:config).and_return({})
39
+ allow(outer).to receive(:full_name).at_least(:once).and_return(@outer_name)
40
+ allow(outer).to receive(:config).and_return({})
41
41
  @ctx = CodeContext.new(outer, @exp)
42
42
  end
43
43
  it 'creates the correct full name' do
@@ -56,7 +56,7 @@ describe CodeContext do
56
56
  it 'should pass unknown method calls down the stack' do
57
57
  stop = StopContext.new
58
58
  def stop.bananas(arg1, arg2) arg1 + arg2 + 43 end
59
- element = ModuleContext.new(stop, 'mod', ast(:module, :mod, nil))
59
+ element = ModuleContext.new(stop, ast(:module, :mod, nil))
60
60
  element = MethodContext.new(element, ast(:defn, :bad))
61
61
  element.bananas(17, -5).should == 55
62
62
  end
@@ -142,4 +142,36 @@ EOS
142
142
  ctx.each_node(:if, []).length.should == 3
143
143
  end
144
144
  end
145
+
146
+ describe '#config_for' do
147
+ let(:exp) { double('exp') }
148
+ let(:outer) { nil }
149
+ let(:ctx) { CodeContext.new(outer, exp) }
150
+ let(:sniffer) { double('sniffer') }
151
+
152
+ before :each do
153
+ allow(sniffer).to receive(:smell_class_name).and_return('DuplicateMethodCall')
154
+ allow(exp).to receive(:comments).and_return(
155
+ ':reek:DuplicateMethodCall: { allow_calls: [ puts ] }')
156
+ end
157
+
158
+ it 'gets its configuration from the exp comments' do
159
+ ctx.config_for(sniffer).should == {
160
+ 'allow_calls' => [ 'puts' ] }
161
+ end
162
+
163
+ context 'when there is an outer' do
164
+ let(:outer) { double('outer') }
165
+
166
+ before :each do
167
+ allow(outer).to receive(:config_for).with(sniffer).and_return(
168
+ { 'max_calls' => 2 })
169
+ end
170
+
171
+ it 'merges the outer config with its own configuration' do
172
+ ctx.config_for(sniffer).should == { 'allow_calls' => [ 'puts' ],
173
+ 'max_calls' => 2 }
174
+ end
175
+ end
176
+ end
145
177
  end
@@ -6,7 +6,7 @@ include Reek::Core
6
6
 
7
7
  describe ModuleContext do
8
8
  it 'should report module name for smell in method' do
9
- 'module Fred; def simple(x) true; end; end'.should reek_of(:UncommunicativeParameterName, /x/, /simple/)
9
+ 'module Fred; def simple(x) x + 1; end; end'.should reek_of(:UncommunicativeParameterName, /x/, /simple/)
10
10
  end
11
11
 
12
12
  it 'should not report module with empty class' do
@@ -86,7 +86,7 @@ EOS
86
86
  @yaml.should match(/subclass:\s*RepeatedConditional/)
87
87
  end
88
88
  it 'reports the expression' do
89
- @yaml.should match(/expression:\s*\(#{@cond}\)/)
89
+ @yaml.should match(/expression:\s*"?\(#{@cond}\)"?/)
90
90
  end
91
91
  it 'reports the number of occurrences' do
92
92
  @yaml.should match(/occurrences:\s*3/)
@@ -7,8 +7,7 @@ shared_examples_for 'SmellDetector' do
7
7
  context 'exception matching follows the context' do
8
8
  before :each do
9
9
  @ctx = double('context')
10
- # @ctx.should_receive(:exp).and_return(nil)
11
- @ctx.should_receive(:config).and_return({})
10
+ allow(@ctx).to receive(:config_for).and_return({})
12
11
  end
13
12
  it 'when false' do
14
13
  @ctx.should_receive(:matches?).at_least(:once).and_return(false)
@@ -110,6 +110,11 @@ describe TooManyStatements do
110
110
  method = process_method('def one() val = 4; true; end')
111
111
  method.num_statements.should == 2
112
112
  end
113
+
114
+ it 'counts nil returns' do
115
+ method = process_method('def one() val = 4; nil; end')
116
+ method.num_statements.should == 2
117
+ end
113
118
  end
114
119
 
115
120
  describe TooManyStatements, 'does not count control statements' do
@@ -123,11 +128,26 @@ describe TooManyStatements, 'does not count control statements' do
123
128
  method.num_statements.should == 3
124
129
  end
125
130
 
131
+ it 'counts 1 statements in an else' do
132
+ method = process_method('def one() if val == 4; callee(); else; callee(); end; end')
133
+ method.num_statements.should == 2
134
+ end
135
+
136
+ it 'counts 3 statements in an else' do
137
+ method = process_method('def one() if val == 4; callee(); callee(); callee(); else; callee(); callee(); callee(); end; end')
138
+ method.num_statements.should == 6
139
+ end
140
+
126
141
  it 'does not count empty conditional expression' do
127
142
  method = process_method('def one() if val == 4; ; end; end')
128
143
  method.num_statements.should == 0
129
144
  end
130
145
 
146
+ it 'does not count empty else' do
147
+ method = process_method('def one() if val == 4; ; else; ; end; end')
148
+ method.num_statements.should == 0
149
+ end
150
+
131
151
  it 'counts 1 statement in a while loop' do
132
152
  method = process_method('def one() while val < 4; callee(); end; end')
133
153
  method.num_statements.should == 1
@@ -178,18 +198,32 @@ describe TooManyStatements, 'does not count control statements' do
178
198
  method.num_statements.should == 3
179
199
  end
180
200
 
201
+ it 'counts 1 statement in a case else' do
202
+ method = process_method('def one() case fred; when "hi"; callee(); else; callee(); end; end')
203
+ method.num_statements.should == 2
204
+ end
205
+
206
+ it 'counts 3 statements in a case else' do
207
+ method = process_method('def one() case fred; when "hi"; callee(); callee(); callee(); else; callee(); callee(); callee(); end; end')
208
+ method.num_statements.should == 6
209
+ end
210
+
181
211
  it 'does not count empty case' do
182
212
  method = process_method('def one() case fred; when "hi"; ; when "lo"; ; end; end')
183
213
  method.num_statements.should == 0
184
214
  end
185
215
 
186
- it 'counts 1 statement in a block' do
216
+ it 'does not count empty case else' do
217
+ method = process_method('def one() case fred; when "hi"; ; else; ; end; end')
218
+ method.num_statements.should == 0
219
+ end
220
+
221
+ it 'counts 2 statement in an iterator' do
187
222
  method = process_method('def one() fred.each do; callee(); end; end')
188
- method.num_statements.should == 1
223
+ method.num_statements.should == 2
189
224
  end
190
225
 
191
- # FIXME: I think this is wrong, but it specs current behavior.
192
- it 'counts 4 statements in a block' do
226
+ it 'counts 4 statements in an iterator' do
193
227
  method = process_method('def one() fred.each do; callee(); callee(); callee(); end; end')
194
228
  method.num_statements.should == 4
195
229
  end
@@ -198,26 +232,6 @@ describe TooManyStatements, 'does not count control statements' do
198
232
  method = process_singleton_method('def self.foo; callee(); end')
199
233
  method.num_statements.should == 1
200
234
  end
201
-
202
- it 'counts else statement' do
203
- src = <<EOS
204
- def parse(arg, argv, &error)
205
- if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
206
- return nil, block, nil
207
- end
208
- opt = (val = parse_arg(val, &error))[1]
209
- val = conv_arg(*val)
210
- if opt and !arg
211
- argv.shift
212
- else
213
- val[0] = nil
214
- end
215
- val
216
- end
217
- EOS
218
- method = process_method(src)
219
- method.num_statements.should == 6
220
- end
221
235
  end
222
236
 
223
237
  describe TooManyStatements do
@@ -232,7 +246,7 @@ describe TooManyStatements do
232
246
  @num_statements = 30
233
247
  ctx = double('method_context').as_null_object
234
248
  ctx.should_receive(:num_statements).and_return(@num_statements)
235
- ctx.should_receive(:config).and_return({})
249
+ ctx.should_receive(:config_for).with(TooManyStatements).and_return({})
236
250
  @smells = @detector.examine_context(ctx)
237
251
  end
238
252
  it 'reports only 1 smell' do
@@ -14,49 +14,63 @@ describe UncommunicativeParameterName do
14
14
 
15
15
  it_should_behave_like 'SmellDetector'
16
16
 
17
- context "parameter name" do
18
- ['obj.', ''].each do |host|
17
+ { 'obj.' => 'with a receiveer',
18
+ '' => 'without a receiver'}.each do |host, description|
19
+ context "in a method definition #{description}" do
19
20
  it 'does not recognise *' do
20
- "def #{host}help(xray, *) basics(17) end".should_not smell_of(UncommunicativeParameterName)
21
+ "def #{host}help(xray, *) basics(17) end".
22
+ should_not smell_of(UncommunicativeParameterName)
21
23
  end
24
+
22
25
  it "reports parameter's name" do
26
+ src = "def #{host}help(x) basics(x) end"
27
+ src.should smell_of(UncommunicativeParameterName,
28
+ {UncommunicativeParameterName::PARAMETER_NAME_KEY => 'x'})
29
+ end
30
+
31
+ it "does not report unused parameters" do
23
32
  src = "def #{host}help(x) basics(17) end"
24
- src.should smell_of(UncommunicativeParameterName, {UncommunicativeParameterName::PARAMETER_NAME_KEY => 'x'})
33
+ src.should_not smell_of(UncommunicativeParameterName)
25
34
  end
26
35
 
27
- context 'with a name of the form "x2"' do
28
- before :each do
29
- @bad_param = 'x2'
30
- src = "def #{host}help(#{@bad_param}) basics(17) end"
31
- ctx = CodeContext.new(nil, src.to_reek_source.syntax_tree)
32
- @smells = @detector.examine_context(ctx)
33
- end
34
- it 'reports only 1 smell' do
35
- @smells.length.should == 1
36
- end
37
- it 'reports uncommunicative parameter name' do
38
- @smells[0].subclass.should == UncommunicativeParameterName::SMELL_SUBCLASS
39
- end
40
- it 'reports the parameter name' do
41
- @smells[0].smell[UncommunicativeParameterName::PARAMETER_NAME_KEY].should == @bad_param
42
- end
36
+ it 'does not report two-letter parameter names' do
37
+ "def #{host}help(ab) basics(ab) end".
38
+ should_not smell_of(UncommunicativeParameterName)
43
39
  end
40
+
41
+ it 'reports names of the form "x2"' do
42
+ src = "def #{host}help(x2) basics(x2) end"
43
+ src.should smell_of(UncommunicativeParameterName,
44
+ {UncommunicativeParameterName::PARAMETER_NAME_KEY => 'x2'})
45
+ end
46
+
44
47
  it 'reports long name ending in a number' do
45
- @bad_param = 'param2'
46
- src = "def #{host}help(#{@bad_param}) basics(17) end"
47
- ctx = CodeContext.new(nil, src.to_reek_source.syntax_tree)
48
- smells = @detector.examine_context(ctx)
49
- smells.length.should == 1
50
- smells[0].subclass.should == UncommunicativeParameterName::SMELL_SUBCLASS
51
- smells[0].smell[UncommunicativeParameterName::PARAMETER_NAME_KEY].should == @bad_param
48
+ src = "def #{host}help(param2) basics(param2) end"
49
+ src.should smell_of(UncommunicativeParameterName,
50
+ {UncommunicativeParameterName::PARAMETER_NAME_KEY => 'param2'})
51
+ end
52
+
53
+ it 'does not report unused anonymous parameter' do
54
+ "def #{host}help(_) basics(17) end".
55
+ should_not smell_of(UncommunicativeParameterName)
56
+ end
57
+
58
+ it 'reports used anonymous parameter' do
59
+ "def #{host}help(_) basics(_) end".
60
+ should smell_of(UncommunicativeParameterName)
61
+ end
62
+
63
+ it 'reports used parameters marked as unused' do
64
+ "def #{host}help(_unused) basics(_unused) end".
65
+ should smell_of(UncommunicativeParameterName)
52
66
  end
53
67
  end
54
68
  end
55
69
 
56
- context 'looking at the YAML' do
70
+ context 'looking at the smell result fields' do
57
71
  before :each do
58
- src = 'def bad(good, bad2, good_again) end'
59
- ctx = CodeContext.new(nil, src.to_reek_source.syntax_tree)
72
+ src = 'def bad(good, bad2, good_again); basics(good, bad2, good_again); end'
73
+ ctx = MethodContext.new(nil, src.to_reek_source.syntax_tree)
60
74
  @smells = @detector.examine_context(ctx)
61
75
  @warning = @smells[0]
62
76
  end
@@ -9,54 +9,58 @@ describe UnusedParameters do
9
9
 
10
10
  context 'for methods' do
11
11
 
12
- it 'should report nothing for no parameters' do
12
+ it 'reports nothing for no parameters' do
13
13
  'def simple; true end'.should_not smell_of(UnusedParameters)
14
14
  end
15
15
 
16
- it 'should report nothing for used parameter' do
16
+ it 'reports nothing for used parameter' do
17
17
  'def simple(sum); sum end'.should_not smell_of(UnusedParameters)
18
18
  end
19
19
 
20
- it 'should report for 1 used and 2 unused parameter' do
20
+ it 'reports for 1 used and 2 unused parameter' do
21
21
  src = 'def simple(num,sum,denum); sum end'
22
22
  src.should smell_of(UnusedParameters,
23
23
  {UnusedParameters::PARAMETER_KEY => 'num'},
24
24
  {UnusedParameters::PARAMETER_KEY => 'denum'})
25
25
  end
26
26
 
27
- it 'should report for 3 used and 1 unused parameter' do
27
+ it 'reports for 3 used and 1 unused parameter' do
28
28
  src = 'def simple(num,sum,denum,quotient); num + denum + sum end'
29
29
  src.should smell_of(UnusedParameters,
30
30
  {UnusedParameters::PARAMETER_KEY => 'quotient'})
31
31
  end
32
32
 
33
- it 'should report nothing for used splatted parameter' do
33
+ it 'reports nothing for used splatted parameter' do
34
34
  'def simple(*sum); sum end'.should_not smell_of(UnusedParameters)
35
35
  end
36
36
 
37
- it 'should report nothing for unused anonymous parameter' do
37
+ it 'reports nothing for unused anonymous parameter' do
38
38
  'def simple(_); end'.should_not smell_of(UnusedParameters)
39
39
  end
40
40
 
41
- it 'should report nothing for named parameters prefixed with _' do
41
+ it 'reports nothing for named parameters prefixed with _' do
42
42
  'def simple(_name); end'.should_not smell_of(UnusedParameters)
43
43
  end
44
44
 
45
- it 'should report nothing for unused anonymous splatted parameter' do
45
+ it 'reports nothing for unused anonymous splatted parameter' do
46
46
  'def simple(*); end'.should_not smell_of(UnusedParameters)
47
47
  end
48
48
 
49
- it 'should report nothing when using super with implicit arguments' do
49
+ it 'reports nothing when using super with implicit arguments' do
50
50
  'def simple(*args); super; end'.should_not smell_of(UnusedParameters)
51
51
  end
52
52
 
53
- it 'should report something when using super explicitely passing no arguments' do
53
+ it 'reports something when using super explicitely passing no arguments' do
54
54
  'def simple(*args); super(); end'.should smell_of(UnusedParameters)
55
55
  end
56
56
 
57
- it 'should report nothing when using super explicitely passing all arguments' do
57
+ it 'reports nothing when using super explicitely passing all arguments' do
58
58
  'def simple(*args); super(*args); end'.should_not smell_of(UnusedParameters)
59
59
  end
60
60
 
61
+ it 'reports nothing when using super in a nested context' do
62
+ 'def simple(*args); call_other("something", super); end'.
63
+ should_not smell_of(UnusedParameters)
64
+ end
61
65
  end
62
66
  end
@@ -92,7 +92,7 @@ describe SexpExtensions::DefnNode do
92
92
 
93
93
  it 'has a body extended with SexpNode' do
94
94
  b = @node.body
95
- (class << b; self; end).ancestors.first.should == SexpNode
95
+ (class << b; self; end).included_modules.first.should == SexpNode
96
96
  end
97
97
  end
98
98
  end
@@ -186,7 +186,7 @@ describe SexpExtensions::DefsNode do
186
186
 
187
187
  it 'has a body extended with SexpNode' do
188
188
  b = @node.body
189
- (class << b; self; end).ancestors.first.should == SexpNode
189
+ (class << b; self; end).included_modules.first.should == SexpNode
190
190
  end
191
191
  end
192
192
  end
@@ -25,4 +25,3 @@ describe SexpNode do
25
25
  end
26
26
  end
27
27
  end
28
-