reek 1.3.6 → 1.3.7

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