reek 3.8.3 → 3.9.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +16 -6
  5. data/README.md +1 -0
  6. data/features/command_line_interface/smells_count.feature +1 -1
  7. data/features/command_line_interface/stdin.feature +1 -1
  8. data/features/configuration_loading.feature +2 -2
  9. data/features/programmatic_access.feature +2 -2
  10. data/features/rake_task/rake_task.feature +4 -4
  11. data/features/reports/json.feature +2 -2
  12. data/features/reports/reports.feature +6 -6
  13. data/features/reports/yaml.feature +2 -2
  14. data/features/samples.feature +19 -19
  15. data/features/step_definitions/sample_file_steps.rb +1 -1
  16. data/lib/reek/ast/node.rb +12 -1
  17. data/lib/reek/ast/sexp_extensions.rb +1 -0
  18. data/lib/reek/ast/sexp_extensions/constant.rb +9 -0
  19. data/lib/reek/ast/sexp_extensions/methods.rb +1 -20
  20. data/lib/reek/ast/sexp_extensions/module.rb +2 -2
  21. data/lib/reek/ast/sexp_extensions/self.rb +12 -0
  22. data/lib/reek/ast/sexp_extensions/send.rb +4 -9
  23. data/lib/reek/ast/sexp_extensions/super.rb +1 -1
  24. data/lib/reek/ast/sexp_extensions/variables.rb +5 -0
  25. data/lib/reek/context/attribute_context.rb +12 -0
  26. data/lib/reek/context/code_context.rb +28 -26
  27. data/lib/reek/context/ghost_context.rb +54 -0
  28. data/lib/reek/context/method_context.rb +28 -1
  29. data/lib/reek/context/module_context.rb +55 -1
  30. data/lib/reek/context/root_context.rb +8 -0
  31. data/lib/reek/context/singleton_attribute_context.rb +15 -0
  32. data/lib/reek/context/singleton_method_context.rb +20 -0
  33. data/lib/reek/context/visibility_tracker.rb +25 -16
  34. data/lib/reek/context_builder.rb +61 -31
  35. data/lib/reek/examiner.rb +0 -6
  36. data/lib/reek/smells/control_parameter.rb +1 -1
  37. data/lib/reek/smells/nested_iterators.rb +1 -1
  38. data/lib/reek/smells/nil_check.rb +2 -2
  39. data/lib/reek/smells/utility_function.rb +1 -2
  40. data/lib/reek/version.rb +1 -1
  41. data/reek.gemspec +1 -12
  42. data/spec/reek/ast/node_spec.rb +51 -3
  43. data/spec/reek/ast/sexp_extensions_spec.rb +16 -3
  44. data/spec/reek/context/code_context_spec.rb +12 -31
  45. data/spec/reek/context/ghost_context_spec.rb +60 -0
  46. data/spec/reek/context/module_context_spec.rb +22 -2
  47. data/spec/reek/context_builder_spec.rb +225 -2
  48. data/spec/reek/examiner_spec.rb +1 -1
  49. data/spec/reek/smells/attribute_spec.rb +35 -0
  50. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
  51. data/spec/reek/tree_dresser_spec.rb +0 -1
  52. data/spec/samples/checkstyle.xml +2 -2
  53. metadata +8 -152
  54. data/lib/reek/ast/sexp_formatter.rb +0 -31
  55. data/spec/reek/ast/sexp_formatter_spec.rb +0 -35
@@ -45,11 +45,11 @@ RSpec.describe Reek::Context::CodeContext do
45
45
  context 'when there is an outer' do
46
46
  let(:ctx) { Reek::Context::CodeContext.new(outer, exp) }
47
47
  let(:outer_name) { 'another_random sting' }
48
- let(:outer) { double('outer') }
48
+ let(:outer) { Reek::Context::CodeContext.new(nil, double('exp1')) }
49
49
 
50
50
  before :each do
51
+ ctx.register_with_parent outer
51
52
  allow(outer).to receive(:full_name).at_least(:once).and_return(outer_name)
52
- allow(outer).to receive(:config).and_return({})
53
53
  end
54
54
 
55
55
  it 'creates the correct full name' do
@@ -174,6 +174,7 @@ RSpec.describe Reek::Context::CodeContext do
174
174
  let(:sniffer) { double('sniffer') }
175
175
 
176
176
  before :each do
177
+ context.register_with_parent(outer)
177
178
  allow(sniffer).to receive(:smell_type).and_return('DuplicateMethodCall')
178
179
  end
179
180
 
@@ -184,7 +185,7 @@ RSpec.describe Reek::Context::CodeContext do
184
185
  end
185
186
 
186
187
  context 'when there is an outer context' do
187
- let(:outer) { double('outer') }
188
+ let(:outer) { Reek::Context::CodeContext.new(nil, double('exp1')) }
188
189
 
189
190
  before :each do
190
191
  allow(outer).to receive(:config_for).with(sniffer).and_return(
@@ -198,37 +199,16 @@ RSpec.describe Reek::Context::CodeContext do
198
199
  end
199
200
  end
200
201
 
201
- describe '#append_child_context' do
202
+ describe '#register_with_parent' do
202
203
  let(:context) { Reek::Context::CodeContext.new(nil, double('exp1')) }
203
204
  let(:first_child) { Reek::Context::CodeContext.new(context, double('exp2')) }
204
205
  let(:second_child) { Reek::Context::CodeContext.new(context, double('exp3')) }
205
206
 
206
- it 'appends the child to the list of children' do
207
- context.append_child_context first_child
208
- context.append_child_context second_child
209
- expect(context.children).to eq [first_child, second_child]
210
- end
211
- end
212
-
213
- describe '#track_visibility' do
214
- let(:context) { Reek::Context::CodeContext.new(nil, double('exp1')) }
215
- let(:first_child) { Reek::Context::CodeContext.new(context, double('exp2', name: :foo)) }
216
- let(:second_child) { Reek::Context::CodeContext.new(context, double('exp3')) }
217
-
218
- it 'sets visibility on subsequent child contexts' do
219
- context.append_child_context first_child
220
- context.track_visibility :private, []
221
- context.append_child_context second_child
222
- expect(first_child.visibility).to eq :public
223
- expect(second_child.visibility).to eq :private
224
- end
207
+ it "appends the element to the parent context's list of children" do
208
+ first_child.register_with_parent context
209
+ second_child.register_with_parent context
225
210
 
226
- it 'sets visibility on specifically mentioned child contexts' do
227
- context.append_child_context first_child
228
- context.track_visibility :private, [first_child.name]
229
- context.append_child_context second_child
230
- expect(first_child.visibility).to eq :private
231
- expect(second_child.visibility).to eq :public
211
+ expect(context.children).to eq [first_child, second_child]
232
212
  end
233
213
  end
234
214
 
@@ -238,8 +218,9 @@ RSpec.describe Reek::Context::CodeContext do
238
218
  let(:second_child) { Reek::Context::CodeContext.new(context, double('exp3')) }
239
219
 
240
220
  it 'yields each child' do
241
- context.append_child_context first_child
242
- context.append_child_context second_child
221
+ first_child.register_with_parent context
222
+ second_child.register_with_parent context
223
+
243
224
  result = []
244
225
  context.each do |ctx|
245
226
  result << ctx
@@ -0,0 +1,60 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/context/code_context'
3
+ require_lib 'reek/context/ghost_context'
4
+
5
+ RSpec.describe Reek::Context::GhostContext do
6
+ let(:exp) { double('exp') }
7
+ let(:parent) { Reek::Context::CodeContext.new(nil, exp) }
8
+
9
+ describe '#register_with_parent' do
10
+ it 'does not append itself to its parent' do
11
+ ghost = described_class.new(parent, nil)
12
+ ghost.register_with_parent(parent)
13
+ expect(parent.children).not_to include ghost
14
+ end
15
+ end
16
+
17
+ describe '#append_child_context' do
18
+ let(:ghost) { described_class.new(parent, nil) }
19
+
20
+ before do
21
+ ghost.register_with_parent(parent)
22
+ end
23
+
24
+ it 'appends the child to the grandparent context' do
25
+ child = Reek::Context::CodeContext.new(ghost, sexp(:foo))
26
+ child.register_with_parent(ghost)
27
+
28
+ expect(parent.children).to include child
29
+ end
30
+
31
+ it "sets the child's parent to the grandparent context" do
32
+ child = Reek::Context::CodeContext.new(ghost, sexp(:foo))
33
+ child.register_with_parent(ghost)
34
+
35
+ expect(child.parent).to eq parent
36
+ end
37
+
38
+ it 'appends the child to the list of children' do
39
+ child = Reek::Context::CodeContext.new(ghost, sexp(:foo))
40
+ child.register_with_parent(ghost)
41
+
42
+ expect(ghost.children).to include child
43
+ end
44
+
45
+ context 'if the grandparent is also a ghost' do
46
+ let(:child_ghost) { described_class.new(ghost, nil) }
47
+
48
+ before do
49
+ child_ghost.register_with_parent(ghost)
50
+ end
51
+
52
+ it 'sets the childs parent to its remote ancestor' do
53
+ child = Reek::Context::CodeContext.new(child_ghost, sexp(:foo))
54
+ child.register_with_parent(child_ghost)
55
+
56
+ expect(child.parent).to eq parent
57
+ end
58
+ end
59
+ end
60
+ end
@@ -18,9 +18,7 @@ RSpec.describe Reek::Context::ModuleContext do
18
18
  # module for test
19
19
  class Jim; end; end').not_to reek
20
20
  end
21
- end
22
21
 
23
- RSpec.describe Reek::Context::ModuleContext do
24
22
  it 'should recognise global constant' do
25
23
  expect('
26
24
  # module for test
@@ -28,4 +26,26 @@ RSpec.describe Reek::Context::ModuleContext do
28
26
  # module for test
29
27
  class Inside; end; end').not_to reek
30
28
  end
29
+
30
+ describe '#track_visibility' do
31
+ let(:context) { Reek::Context::ModuleContext.new(nil, double('exp1')) }
32
+ let(:first_child) { Reek::Context::MethodContext.new(context, double('exp2', type: :def, name: :foo)) }
33
+ let(:second_child) { Reek::Context::MethodContext.new(context, double('exp3', type: :def)) }
34
+
35
+ it 'sets visibility on subsequent child contexts' do
36
+ context.append_child_context first_child
37
+ context.track_visibility :private, []
38
+ context.append_child_context second_child
39
+ expect(first_child.visibility).to eq :public
40
+ expect(second_child.visibility).to eq :private
41
+ end
42
+
43
+ it 'sets visibility on specifically mentioned child contexts' do
44
+ context.append_child_context first_child
45
+ context.track_visibility :private, [first_child.name]
46
+ context.append_child_context second_child
47
+ expect(first_child.visibility).to eq :private
48
+ expect(second_child.visibility).to eq :public
49
+ end
50
+ end
31
51
  end
@@ -27,7 +27,7 @@ RSpec.describe Reek::ContextBuilder do
27
27
  end
28
28
 
29
29
  it 'holds a reference to the parent context' do
30
- expect(module_context.send(:context)).to eq(context_tree)
30
+ expect(module_context.parent).to eq(context_tree)
31
31
  end
32
32
 
33
33
  describe 'the module node' do
@@ -39,7 +39,7 @@ RSpec.describe Reek::ContextBuilder do
39
39
  end
40
40
 
41
41
  it 'holds a reference to the parent context' do
42
- expect(method_context.send(:context)).to eq(module_context)
42
+ expect(method_context.parent).to eq(module_context)
43
43
  end
44
44
  end
45
45
  end
@@ -218,4 +218,227 @@ RSpec.describe Reek::ContextBuilder do
218
218
  end
219
219
  end
220
220
  end
221
+
222
+ describe 'visibility tracking' do
223
+ def context_tree_for(code)
224
+ described_class.new(syntax_tree(code)).context_tree
225
+ end
226
+
227
+ it 'does not mark class methods with instance visibility' do
228
+ code = <<-EOS
229
+ class Foo
230
+ private
231
+ def bar
232
+ end
233
+ def self.baz
234
+ end
235
+ end
236
+ EOS
237
+
238
+ root = context_tree_for(code)
239
+ module_context = root.children.first
240
+ method_contexts = module_context.children
241
+ expect(method_contexts[0].visibility).to eq :private
242
+ expect(method_contexts[1].visibility).to eq :public
243
+ end
244
+
245
+ it 'only marks existing instance methods using later instance method modifiers' do
246
+ code = <<-EOS
247
+ class Foo
248
+ def bar
249
+ end
250
+
251
+ def baz
252
+ end
253
+
254
+ def self.bar
255
+ end
256
+
257
+ class << self
258
+ def bar
259
+ end
260
+ end
261
+
262
+ private :bar, :baz
263
+ end
264
+ EOS
265
+
266
+ root = context_tree_for(code)
267
+ module_context = root.children.first
268
+ method_contexts = module_context.children
269
+ expect(method_contexts[0].visibility).to eq :private
270
+ expect(method_contexts[1].visibility).to eq :private
271
+ expect(method_contexts[2].visibility).to eq :public
272
+ expect(method_contexts[3].visibility).to eq :public
273
+ end
274
+
275
+ it 'only marks existing instance attributes using later instance method modifiers' do
276
+ code = <<-EOS
277
+ class Foo
278
+ attr_writer :bar
279
+
280
+ class << self
281
+ attr_writer :bar
282
+ end
283
+
284
+ private :bar
285
+ end
286
+ EOS
287
+
288
+ root = context_tree_for(code)
289
+ module_context = root.children.first
290
+ method_contexts = module_context.children
291
+ expect(method_contexts[0].visibility).to eq :private
292
+ expect(method_contexts[1].visibility).to eq :public
293
+ end
294
+
295
+ it 'marks class method visibility using private_class_method' do
296
+ code = <<-EOS
297
+ class Foo
298
+ def self.baz
299
+ end
300
+
301
+ private_class_method :baz
302
+ end
303
+ EOS
304
+
305
+ root = context_tree_for(code)
306
+ module_context = root.children.first
307
+ method_contexts = module_context.children
308
+ expect(method_contexts[0].visibility).to eq :private
309
+ end
310
+
311
+ it 'marks class method visibility using public_class_method' do
312
+ code = <<-EOS
313
+ class Foo
314
+ class << self
315
+ private
316
+
317
+ def baz
318
+ end
319
+ end
320
+
321
+ public_class_method :baz
322
+ end
323
+ EOS
324
+
325
+ root = context_tree_for(code)
326
+ module_context = root.children.first
327
+ method_contexts = module_context.children
328
+ expect(method_contexts[0].visibility).to eq :public
329
+ end
330
+
331
+ it 'correctly skips nested modules' do
332
+ code = <<-EOS
333
+ class Foo
334
+ class Bar
335
+ def baz
336
+ end
337
+ end
338
+
339
+ def baz
340
+ end
341
+
342
+ def self.bar
343
+ end
344
+
345
+ private :baz
346
+ private_class_method :bar
347
+ end
348
+ EOS
349
+
350
+ root = context_tree_for(code)
351
+ foo_context = root.children.first
352
+ bar_context = foo_context.children.first
353
+ nested_baz_context = bar_context.children.first
354
+ expect(nested_baz_context.visibility).to eq :public
355
+ end
356
+ end
357
+
358
+ describe '#context_tree' do
359
+ it 'creates the proper context for all kinds of singleton methods' do
360
+ src = <<-EOS
361
+ class Car
362
+ def self.start; end
363
+
364
+ class << self
365
+ def drive; end
366
+ end
367
+ end
368
+ EOS
369
+
370
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
371
+ context_tree = Reek::ContextBuilder.new(syntax_tree).context_tree
372
+
373
+ class_node = context_tree.children.first
374
+ start_method = class_node.children.first
375
+ drive_method = class_node.children.last
376
+
377
+ expect(start_method).to be_instance_of Reek::Context::SingletonMethodContext
378
+ expect(drive_method).to be_instance_of Reek::Context::SingletonMethodContext
379
+ end
380
+
381
+ it 'returns something sensible for nested metaclasses' do
382
+ src = <<-EOS
383
+ class Foo
384
+ class << self
385
+ class << self
386
+ def bar; end
387
+ end
388
+ end
389
+ end
390
+ EOS
391
+
392
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
393
+ context_tree = Reek::ContextBuilder.new(syntax_tree).context_tree
394
+
395
+ class_context = context_tree.children.first
396
+ method_context = class_context.children.first
397
+
398
+ expect(method_context).to be_instance_of Reek::Context::SingletonMethodContext
399
+ expect(method_context.parent).to eq class_context
400
+ end
401
+
402
+ it 'returns something sensible for nested method definitions' do
403
+ src = <<-EOS
404
+ class Foo
405
+ def foo
406
+ def bar
407
+ end
408
+ end
409
+ end
410
+ EOS
411
+
412
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
413
+ context_tree = Reek::ContextBuilder.new(syntax_tree).context_tree
414
+
415
+ class_context = context_tree.children.first
416
+ foo_context = class_context.children.first
417
+
418
+ bar_context = foo_context.children.first
419
+ expect(bar_context).to be_instance_of Reek::Context::MethodContext
420
+ expect(bar_context.parent).to eq foo_context
421
+ end
422
+
423
+ it 'returns something sensible for method definitions nested in singleton methods' do
424
+ src = <<-EOS
425
+ class Foo
426
+ def self.foo
427
+ def bar
428
+ end
429
+ end
430
+ end
431
+ EOS
432
+
433
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
434
+ context_tree = Reek::ContextBuilder.new(syntax_tree).context_tree
435
+
436
+ class_context = context_tree.children.first
437
+ foo_context = class_context.children.first
438
+
439
+ bar_context = foo_context.children.first
440
+ expect(bar_context).to be_instance_of Reek::Context::SingletonMethodContext
441
+ expect(bar_context.parent).to eq foo_context
442
+ end
443
+ end
221
444
  end
@@ -63,7 +63,7 @@ RSpec.describe Reek::Examiner do
63
63
 
64
64
  smell = examiner.smells.first
65
65
  expect(smell).to be_a(Reek::Smells::SmellWarning)
66
- expect(smell.message).to eq('calls bar.call_me 2 times')
66
+ expect(smell.message).to eq('calls bar.call_me() 2 times')
67
67
  end
68
68
  end
69
69
  end
@@ -137,5 +137,40 @@ RSpec.describe Reek::Smells::Attribute do
137
137
  '
138
138
  expect(src).to reek_of(:Attribute)
139
139
  end
140
+
141
+ it 'records attr_writer defining a class attribute' do
142
+ src = <<-EOS
143
+ class Klass
144
+ class << self
145
+ attr_writer :my_attr
146
+ end
147
+ end
148
+ EOS
149
+ expect(src).to reek_of(:Attribute, name: 'my_attr')
150
+ end
151
+
152
+ it 'does not record private class attributes' do
153
+ src = <<-EOS
154
+ class Klass
155
+ class << self
156
+ private
157
+ attr_writer :my_attr
158
+ end
159
+ end
160
+ EOS
161
+ expect(src).not_to reek_of(:Attribute, name: 'my_attr')
162
+ end
163
+
164
+ it 'tracks visibility in metaclasses separately' do
165
+ src = <<-EOS
166
+ class Klass
167
+ private
168
+ class << self
169
+ attr_writer :my_attr
170
+ end
171
+ end
172
+ EOS
173
+ expect(src).to reek_of(:Attribute, name: 'my_attr')
174
+ end
140
175
  end
141
176
  end