reek 3.0.4 → 3.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +6 -0
  3. data/README.md +41 -20
  4. data/features/configuration_files/directory_specific_directives.feature +280 -0
  5. data/features/configuration_files/masking_smells.feature +0 -14
  6. data/features/step_definitions/sample_file_steps.rb +2 -2
  7. data/lib/reek/ast/sexp_extensions.rb +72 -60
  8. data/lib/reek/cli/application.rb +2 -5
  9. data/lib/reek/cli/reek_command.rb +1 -1
  10. data/lib/reek/configuration/app_configuration.rb +115 -61
  11. data/lib/reek/configuration/configuration_file_finder.rb +19 -0
  12. data/lib/reek/context/code_context.rb +102 -29
  13. data/lib/reek/context/method_context.rb +11 -48
  14. data/lib/reek/context/module_context.rb +2 -6
  15. data/lib/reek/context/root_context.rb +7 -14
  16. data/lib/reek/examiner.rb +15 -10
  17. data/lib/reek/report/report.rb +5 -4
  18. data/lib/reek/smells/boolean_parameter.rb +1 -1
  19. data/lib/reek/smells/duplicate_method_call.rb +1 -5
  20. data/lib/reek/smells/irresponsible_module.rb +2 -2
  21. data/lib/reek/smells/smell_repository.rb +30 -16
  22. data/lib/reek/smells/utility_function.rb +1 -1
  23. data/lib/reek/source/source_code.rb +10 -6
  24. data/lib/reek/source/source_locator.rb +27 -26
  25. data/lib/reek/spec.rb +8 -6
  26. data/lib/reek/spec/should_reek.rb +6 -2
  27. data/lib/reek/spec/should_reek_of.rb +5 -2
  28. data/lib/reek/spec/should_reek_only_of.rb +1 -1
  29. data/lib/reek/tree_walker.rb +49 -21
  30. data/lib/reek/version.rb +1 -1
  31. data/spec/reek/ast/sexp_extensions_spec.rb +4 -12
  32. data/spec/reek/configuration/app_configuration_spec.rb +80 -52
  33. data/spec/reek/configuration/configuration_file_finder_spec.rb +27 -15
  34. data/spec/reek/context/code_context_spec.rb +66 -17
  35. data/spec/reek/context/method_context_spec.rb +66 -64
  36. data/spec/reek/context/root_context_spec.rb +3 -1
  37. data/spec/reek/context/singleton_method_context_spec.rb +1 -2
  38. data/spec/reek/examiner_spec.rb +5 -8
  39. data/spec/reek/report/xml_report_spec.rb +3 -2
  40. data/spec/reek/smells/attribute_spec.rb +12 -17
  41. data/spec/reek/smells/duplicate_method_call_spec.rb +17 -25
  42. data/spec/reek/smells/feature_envy_spec.rb +4 -5
  43. data/spec/reek/smells/irresponsible_module_spec.rb +43 -0
  44. data/spec/reek/smells/nested_iterators_spec.rb +12 -26
  45. data/spec/reek/smells/too_many_statements_spec.rb +2 -210
  46. data/spec/reek/smells/utility_function_spec.rb +49 -5
  47. data/spec/reek/source/source_locator_spec.rb +39 -43
  48. data/spec/reek/spec/should_reek_of_spec.rb +3 -2
  49. data/spec/reek/spec/should_reek_spec.rb +25 -17
  50. data/spec/reek/tree_walker_spec.rb +214 -23
  51. data/spec/samples/configuration/full_configuration.reek +9 -0
  52. data/spec/spec_helper.rb +10 -10
  53. metadata +4 -3
  54. data/features/configuration_files/overrides_defaults.feature +0 -15
@@ -1,7 +1,6 @@
1
1
  require_relative '../../spec_helper'
2
2
  require_relative '../../../lib/reek/context/method_context'
3
3
  require_relative '../../../lib/reek/context/module_context'
4
- require_relative '../../../lib/reek/context/root_context'
5
4
 
6
5
  RSpec.describe Reek::Context::CodeContext do
7
6
  context 'name recognition' do
@@ -55,16 +54,6 @@ RSpec.describe Reek::Context::CodeContext do
55
54
  end
56
55
  end
57
56
 
58
- context 'generics' do
59
- it 'should pass unknown method calls down the stack' do
60
- stop = Reek::Context::RootContext.new
61
- def stop.bananas(arg1, arg2) arg1 + arg2 + 43 end
62
- element = Reek::Context::ModuleContext.new(stop, s(:module, :mod, nil))
63
- element = Reek::Context::MethodContext.new(element, s(:def, :bad, s(:args), nil))
64
- expect(element.bananas(17, -5)).to eq(55)
65
- end
66
- end
67
-
68
57
  context 'enumerating syntax elements' do
69
58
  context 'in an empty module' do
70
59
  before :each do
@@ -161,22 +150,31 @@ EOS
161
150
  end
162
151
 
163
152
  describe '#config_for' do
164
- let(:expression) { double('exp') }
153
+ let(:src) do
154
+ <<-EOS
155
+ # :reek:DuplicateMethodCall: { allow_calls: [ puts ] }')
156
+ def repeated_greeting
157
+ puts 'Hello!'
158
+ puts 'Hello!'
159
+ end
160
+ EOS
161
+ end
162
+ let(:expression) { Reek::Source::SourceCode.from(src).syntax_tree }
165
163
  let(:outer) { nil }
166
164
  let(:context) { Reek::Context::CodeContext.new(outer, expression) }
167
165
  let(:sniffer) { double('sniffer') }
168
166
 
169
167
  before :each do
170
168
  allow(sniffer).to receive(:smell_type).and_return('DuplicateMethodCall')
171
- allow(expression).to receive(:full_comment).and_return(
172
- ':reek:DuplicateMethodCall: { allow_calls: [ puts ] }')
173
169
  end
174
170
 
175
- it 'gets its configuration from the expression comments' do
176
- expect(context.config_for(sniffer)).to eq('allow_calls' => ['puts'])
171
+ context 'when there is no outer context' do
172
+ it 'gets its configuration from the expression comments' do
173
+ expect(context.config_for(sniffer)).to eq('allow_calls' => ['puts'])
174
+ end
177
175
  end
178
176
 
179
- context 'when there is an outer' do
177
+ context 'when there is an outer context' do
180
178
  let(:outer) { double('outer') }
181
179
 
182
180
  before :each do
@@ -190,4 +188,55 @@ EOS
190
188
  end
191
189
  end
192
190
  end
191
+
192
+ describe '#append_child_context' do
193
+ let(:context) { Reek::Context::CodeContext.new(nil, double('exp1')) }
194
+ let(:first_child) { Reek::Context::CodeContext.new(context, double('exp2')) }
195
+ let(:second_child) { Reek::Context::CodeContext.new(context, double('exp3')) }
196
+
197
+ it 'appends the child to the list of children' do
198
+ context.append_child_context first_child
199
+ context.append_child_context second_child
200
+ expect(context.children).to eq [first_child, second_child]
201
+ end
202
+ end
203
+
204
+ describe '#track_visibility' do
205
+ let(:context) { Reek::Context::CodeContext.new(nil, double('exp1')) }
206
+ let(:first_child) { Reek::Context::CodeContext.new(context, double('exp2', name: :foo)) }
207
+ let(:second_child) { Reek::Context::CodeContext.new(context, double('exp3')) }
208
+
209
+ it 'sets visibility on subsequent child contexts' do
210
+ context.append_child_context first_child
211
+ context.track_visibility :private
212
+ context.append_child_context second_child
213
+ expect(first_child.visibility).to eq :public
214
+ expect(second_child.visibility).to eq :private
215
+ end
216
+
217
+ it 'sets visibility on specifically mentioned child contexts' do
218
+ context.append_child_context first_child
219
+ context.track_visibility :private, [first_child.name]
220
+ context.append_child_context second_child
221
+ expect(first_child.visibility).to eq :private
222
+ expect(second_child.visibility).to eq :public
223
+ end
224
+ end
225
+
226
+ describe '#each' do
227
+ let(:context) { Reek::Context::CodeContext.new(nil, double('exp1')) }
228
+ let(:first_child) { Reek::Context::CodeContext.new(context, double('exp2')) }
229
+ let(:second_child) { Reek::Context::CodeContext.new(context, double('exp3')) }
230
+
231
+ it 'yields each child' do
232
+ context.append_child_context first_child
233
+ context.append_child_context second_child
234
+ result = []
235
+ context.each do |ctx|
236
+ result << ctx
237
+ end
238
+
239
+ expect(result).to eq [context, first_child, second_child]
240
+ end
241
+ end
193
242
  end
@@ -1,94 +1,96 @@
1
1
  require_relative '../../spec_helper'
2
2
  require_relative '../../../lib/reek/context/method_context'
3
- require_relative '../../../lib/reek/context/root_context'
4
3
 
5
- RSpec.describe Reek::Context::MethodContext, 'matching' do
6
- before :each do
7
- exp = double('exp').as_null_object
8
- expect(exp).to receive(:full_name).at_least(:once).and_return('mod')
9
- @element = Reek::Context::MethodContext.new(Reek::Context::RootContext.new, exp)
10
- end
4
+ RSpec.describe Reek::Context::MethodContext do
5
+ let(:method_context) { Reek::Context::MethodContext.new(nil, exp) }
11
6
 
12
- it 'should recognise itself in a collection of names' do
13
- expect(@element.matches?(['banana', 'mod'])).to eq(true)
14
- expect(@element.matches?(['banana'])).to eq(false)
15
- end
7
+ describe '#matches?' do
8
+ let(:exp) { double('exp').as_null_object }
16
9
 
17
- it 'should recognise itself in a collection of REs' do
18
- expect(@element.matches?([/banana/, /mod/])).to eq(true)
19
- expect(@element.matches?([/banana/])).to eq(false)
20
- end
21
- end
10
+ before :each do
11
+ expect(exp).to receive(:full_name).at_least(:once).and_return('mod')
12
+ end
22
13
 
23
- RSpec.describe Reek::Context::MethodContext do
24
- let(:mc) do
25
- sexp = s(:def, :foo, s(:args, s(:arg, :bar)), nil)
26
- Reek::Context::MethodContext.new(Reek::Context::RootContext.new, sexp)
14
+ it 'should recognise itself in a collection of names' do
15
+ expect(method_context.matches?(['banana', 'mod'])).to eq(true)
16
+ expect(method_context.matches?(['banana'])).to eq(false)
17
+ end
18
+
19
+ it 'should recognise itself in a collection of REs' do
20
+ expect(method_context.matches?([/banana/, /mod/])).to eq(true)
21
+ expect(method_context.matches?([/banana/])).to eq(false)
22
+ end
27
23
  end
28
24
 
29
25
  describe '#envious_receivers' do
26
+ let(:exp) { s(:def, :foo, s(:args, s(:arg, :bar)), nil) }
27
+
30
28
  it 'should ignore ivars as refs to self' do
31
- mc.record_call_to s(:send, s(:ivar, :@cow), :feed_to)
32
- expect(mc.envious_receivers).to be_empty
29
+ method_context.record_call_to s(:send, s(:ivar, :@cow), :feed_to)
30
+ expect(method_context.envious_receivers).to be_empty
33
31
  end
34
32
 
35
33
  it 'should ignore explicit calls to self' do
36
- mc.refs.record_reference_to :other
37
- mc.record_call_to s(:send, s(:self), :thing)
38
- expect(mc.envious_receivers).to be_empty
34
+ method_context.refs.record_reference_to :other
35
+ method_context.record_call_to s(:send, s(:self), :thing)
36
+ expect(method_context.envious_receivers).to be_empty
39
37
  end
40
38
 
41
39
  it 'should ignore implicit calls to self' do
42
- mc.record_call_to s(:send, s(:lvar, :text), :each, s(:arglist))
43
- mc.record_call_to s(:send, nil, :shelve, s(:arglist))
44
- expect(mc.envious_receivers).to be_empty
40
+ method_context.record_call_to s(:send, s(:lvar, :text), :each, s(:arglist))
41
+ method_context.record_call_to s(:send, nil, :shelve, s(:arglist))
42
+ expect(method_context.envious_receivers).to be_empty
45
43
  end
46
44
 
47
45
  it 'should record envious calls' do
48
- mc.record_call_to s(:send, s(:lvar, :bar), :baz)
49
- expect(mc.envious_receivers).to include(:bar)
46
+ method_context.record_call_to s(:send, s(:lvar, :bar), :baz)
47
+ expect(method_context.envious_receivers).to include(:bar)
50
48
  end
51
49
  end
52
- end
53
50
 
54
- RSpec.describe Reek::Context::MethodParameters, 'default assignments' do
55
- def assignments_from(src)
56
- exp = Reek::Source::SourceCode.from(src).syntax_tree
57
- ctx = Reek::Context::MethodContext.new(Reek::Context::RootContext.new, exp)
58
- ctx.parameters.default_assignments
59
- end
60
-
61
- context 'with no defaults' do
62
- it 'returns an empty hash' do
63
- src = 'def meth(arga, argb, &blk) end'
64
- expect(assignments_from(src)).to be_empty
51
+ describe '#default_assignments' do
52
+ def assignments_from(src)
53
+ exp = Reek::Source::SourceCode.from(src).syntax_tree
54
+ ctx = Reek::Context::MethodContext.new(nil, exp)
55
+ ctx.default_assignments
65
56
  end
66
- end
67
57
 
68
- context 'with 1 default' do
69
- before :each do
70
- src = 'def meth(arga, argb=456, &blk) end'
71
- @defaults = assignments_from(src)
72
- end
73
- it 'returns the param-value pair' do
74
- expect(@defaults[0]).to eq [:argb, s(:int, 456)]
58
+ context 'with no defaults' do
59
+ it 'returns an empty hash' do
60
+ src = 'def meth(arga, argb, &blk) end'
61
+ expect(assignments_from(src)).to be_empty
62
+ end
75
63
  end
76
- it 'returns the nothing else' do
77
- expect(@defaults.length).to eq(1)
78
- end
79
- end
80
64
 
81
- context 'with 2 defaults' do
82
- before :each do
83
- src = 'def meth(arga=123, argb=456, &blk) end'
84
- @defaults = assignments_from(src)
85
- end
86
- it 'returns both param-value pairs' do
87
- expect(@defaults[0]).to eq [:arga, s(:int, 123)]
88
- expect(@defaults[1]).to eq [:argb, s(:int, 456)]
65
+ context 'with 1 default' do
66
+ before :each do
67
+ src = 'def meth(arga, argb=456, &blk) end'
68
+ @defaults = assignments_from(src)
69
+ end
70
+
71
+ it 'returns the param-value pair' do
72
+ expect(@defaults[0]).to eq [:argb, s(:int, 456)]
73
+ end
74
+
75
+ it 'returns the nothing else' do
76
+ expect(@defaults.length).to eq(1)
77
+ end
89
78
  end
90
- it 'returns nothing else' do
91
- expect(@defaults.length).to eq(2)
79
+
80
+ context 'with 2 defaults' do
81
+ before :each do
82
+ src = 'def meth(arga=123, argb=456, &blk) end'
83
+ @defaults = assignments_from(src)
84
+ end
85
+
86
+ it 'returns both param-value pairs' do
87
+ expect(@defaults[0]).to eq [:arga, s(:int, 123)]
88
+ expect(@defaults[1]).to eq [:argb, s(:int, 456)]
89
+ end
90
+
91
+ it 'returns nothing else' do
92
+ expect(@defaults.length).to eq(2)
93
+ end
92
94
  end
93
95
  end
94
96
  end
@@ -3,7 +3,9 @@ require_relative '../../../lib/reek/context/root_context'
3
3
 
4
4
  RSpec.describe Reek::Context::RootContext do
5
5
  before :each do
6
- @root = Reek::Context::RootContext.new
6
+ src = 'foo = 1'
7
+ ast = Reek::Source::SourceCode.from(src).syntax_tree
8
+ @root = Reek::Context::RootContext.new(ast)
7
9
  end
8
10
 
9
11
  context 'full_name' do
@@ -1,11 +1,10 @@
1
1
  require_relative '../../spec_helper'
2
2
  require_relative '../../../lib/reek/context/singleton_method_context'
3
- require_relative '../../../lib/reek/context/root_context'
4
3
 
5
4
  RSpec.describe Reek::Context::SingletonMethodContext do
6
5
  let(:smc) do
7
6
  sexp = s(:def, :foo, s(:args, s(:arg, :bar)), nil)
8
- Reek::Context::SingletonMethodContext.new(Reek::Context::RootContext.new, sexp)
7
+ Reek::Context::SingletonMethodContext.new(nil, sexp)
9
8
  end
10
9
 
11
10
  describe '#envious_receivers' do
@@ -45,15 +45,12 @@ RSpec.describe Reek::Examiner do
45
45
  end
46
46
 
47
47
  context 'with a partially masked smelly File' do
48
- around(:each) do |example|
49
- with_test_config('spec/samples/all_but_one_masked/masked.reek') do
50
- example.run
51
- end
52
- end
48
+ let(:path) { SAMPLES_PATH.join('all_but_one_masked/masked.reek') }
49
+ let(:configuration) { test_configuration_for(path) }
53
50
 
54
51
  before :each do
55
- smelly_file = File.new(Dir['spec/samples/all_but_one_masked/d*.rb'][0])
56
- @examiner = described_class.new(smelly_file)
52
+ smelly_file = Pathname.glob(SAMPLES_PATH.join('all_but_one_masked/d*.rb')).first
53
+ @examiner = described_class.new(smelly_file, [], configuration: configuration)
57
54
  end
58
55
 
59
56
  it_should_behave_like 'one smell found'
@@ -61,7 +58,7 @@ RSpec.describe Reek::Examiner do
61
58
 
62
59
  context 'with a fragrant File' do
63
60
  before :each do
64
- clean_file = File.new(Dir['spec/samples/three_clean_files/*.rb'][0])
61
+ clean_file = Pathname.glob(SAMPLES_PATH.join('three_clean_files/*.rb')).first
65
62
  @examiner = described_class.new(clean_file)
66
63
  end
67
64
 
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  require_relative '../../spec_helper'
2
3
  require_relative '../../../lib/reek/examiner'
3
4
  require_relative '../../../lib/reek/report/report'
@@ -27,8 +28,8 @@ RSpec.describe Reek::Report::XMLReport do
27
28
  end
28
29
 
29
30
  it 'prints non-empty checkstyle xml' do
30
- sample_path = File.expand_path 'checkstyle.xml', 'spec/samples'
31
- expect { instance.show }.to output(File.read(sample_path)).to_stdout
31
+ sample_path = SAMPLES_PATH.join('checkstyle.xml')
32
+ expect { instance.show }.to output(sample_path.read).to_stdout
32
33
  end
33
34
  end
34
35
  end
@@ -6,21 +6,16 @@ require_relative 'smell_detector_shared'
6
6
  RSpec.describe Reek::Smells::Attribute do
7
7
  let(:config) do
8
8
  {
9
- Attribute: { Reek::Smells::SmellConfiguration::ENABLED_KEY => true }
9
+ Reek::Smells::Attribute => { Reek::Smells::SmellConfiguration::ENABLED_KEY => true }
10
10
  }
11
11
  end
12
+ let(:configuration) { test_configuration_for(config) }
12
13
 
13
14
  before(:each) do
14
15
  @source_name = 'dummy_source'
15
16
  @detector = build(:smell_detector, smell_type: :Attribute, source: @source_name)
16
17
  end
17
18
 
18
- around(:each) do |example|
19
- with_test_config(config) do
20
- example.run
21
- end
22
- end
23
-
24
19
  it_should_behave_like 'SmellDetector'
25
20
 
26
21
  context 'with no attributes' do
@@ -28,7 +23,7 @@ RSpec.describe Reek::Smells::Attribute do
28
23
  expect('
29
24
  class Klass
30
25
  end
31
- ').to_not reek_of(:Attribute)
26
+ ').to_not reek_of(:Attribute, {}, configuration)
32
27
  end
33
28
  end
34
29
 
@@ -45,7 +40,7 @@ RSpec.describe Reek::Smells::Attribute do
45
40
  private
46
41
  attr :super_thing2
47
42
  end
48
- ').to_not reek_of(:Attribute)
43
+ ').to_not reek_of(:Attribute, {}, configuration)
49
44
  end
50
45
 
51
46
  it 'records attr attribute in a module' do
@@ -53,7 +48,7 @@ RSpec.describe Reek::Smells::Attribute do
53
48
  module Mod
54
49
  attr :my_attr
55
50
  end
56
- ').to reek_of(:Attribute, name: 'my_attr')
51
+ ').to reek_of(:Attribute, { name: 'my_attr' }, configuration)
57
52
  end
58
53
 
59
54
  it 'records attr attribute' do
@@ -61,7 +56,7 @@ RSpec.describe Reek::Smells::Attribute do
61
56
  class Klass
62
57
  attr :my_attr
63
58
  end
64
- ').to reek_of(:Attribute, name: 'my_attr')
59
+ ').to reek_of(:Attribute, { name: 'my_attr' }, configuration)
65
60
  end
66
61
 
67
62
  it 'records reader attribute' do
@@ -69,7 +64,7 @@ RSpec.describe Reek::Smells::Attribute do
69
64
  class Klass
70
65
  attr_reader :my_attr
71
66
  end
72
- ').to reek_of(:Attribute, name: 'my_attr')
67
+ ').to reek_of(:Attribute, { name: 'my_attr' }, configuration)
73
68
  end
74
69
 
75
70
  it 'records writer attribute' do
@@ -77,7 +72,7 @@ RSpec.describe Reek::Smells::Attribute do
77
72
  class Klass
78
73
  attr_writer :my_attr
79
74
  end
80
- ').to reek_of(:Attribute, name: 'my_attr')
75
+ ').to reek_of(:Attribute, { name: 'my_attr' }, configuration)
81
76
  end
82
77
 
83
78
  it 'records accessor attribute' do
@@ -85,7 +80,7 @@ RSpec.describe Reek::Smells::Attribute do
85
80
  class Klass
86
81
  attr_accessor :my_attr
87
82
  end
88
- ').to reek_of(:Attribute, name: 'my_attr')
83
+ ').to reek_of(:Attribute, { name: 'my_attr' }, configuration)
89
84
  end
90
85
 
91
86
  it 'records attr attribute after switching visbility' do
@@ -97,7 +92,7 @@ RSpec.describe Reek::Smells::Attribute do
97
92
  private :my_attr
98
93
  public :my_attr
99
94
  end
100
- ').to reek_of(:Attribute, name: 'my_attr')
95
+ ').to reek_of(:Attribute, { name: 'my_attr' }, configuration)
101
96
  end
102
97
 
103
98
  it "doesn't record protected attributes" do
@@ -107,7 +102,7 @@ RSpec.describe Reek::Smells::Attribute do
107
102
  attr :iam_protected
108
103
  end
109
104
  '
110
- expect(src).to_not reek_of(:Attribute, name: 'iam_protected')
105
+ expect(src).to_not reek_of(:Attribute, { name: 'iam_protected' }, configuration)
111
106
  end
112
107
 
113
108
  it "doesn't record private attributes" do
@@ -117,7 +112,7 @@ RSpec.describe Reek::Smells::Attribute do
117
112
  attr :iam_private
118
113
  end
119
114
  '
120
- expect(src).to_not reek_of(:Attribute, name: 'iam_private')
115
+ expect(src).to_not reek_of(:Attribute, { name: 'iam_private' }, configuration)
121
116
  end
122
117
  end
123
118
  end