reek 3.8.3 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
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