reek 5.4.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +27 -6
  4. data/.rubocop_todo.yml +4 -4
  5. data/.simplecov +1 -0
  6. data/.travis.yml +13 -11
  7. data/CHANGELOG.md +27 -1
  8. data/Dockerfile +2 -1
  9. data/Gemfile +15 -17
  10. data/README.md +15 -11
  11. data/bin/code_climate_reek +12 -2
  12. data/docs/Attribute.md +1 -1
  13. data/docs/Control-Couple.md +1 -1
  14. data/docs/Nil-Check.md +4 -1
  15. data/features/command_line_interface/options.feature +2 -3
  16. data/features/reports/codeclimate.feature +2 -2
  17. data/features/reports/json.feature +3 -3
  18. data/features/reports/reports.feature +4 -4
  19. data/features/reports/yaml.feature +3 -3
  20. data/features/step_definitions/reek_steps.rb +4 -0
  21. data/features/support/env.rb +1 -2
  22. data/lib/reek/ast/sexp_extensions/arguments.rb +11 -0
  23. data/lib/reek/cli/command/todo_list_command.rb +7 -2
  24. data/lib/reek/cli/options.rb +2 -2
  25. data/lib/reek/code_comment.rb +45 -38
  26. data/lib/reek/configuration/configuration_converter.rb +2 -2
  27. data/lib/reek/configuration/directory_directives.rb +7 -1
  28. data/lib/reek/errors/legacy_comment_separator_error.rb +36 -0
  29. data/lib/reek/examiner.rb +3 -3
  30. data/lib/reek/report.rb +5 -7
  31. data/lib/reek/report/code_climate/code_climate_report.rb +2 -1
  32. data/lib/reek/report/simple_warning_formatter.rb +0 -7
  33. data/lib/reek/report/text_report.rb +2 -2
  34. data/lib/reek/smell_detectors/base_detector.rb +2 -10
  35. data/lib/reek/smell_detectors/data_clump.rb +23 -56
  36. data/lib/reek/smell_detectors/nil_check.rb +1 -12
  37. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +3 -7
  38. data/lib/reek/smell_detectors/too_many_constants.rb +1 -1
  39. data/lib/reek/smell_warning.rb +18 -14
  40. data/lib/reek/source/source_code.rb +3 -2
  41. data/lib/reek/spec/smell_matcher.rb +2 -1
  42. data/lib/reek/version.rb +1 -1
  43. data/reek.gemspec +5 -6
  44. data/spec/reek/ast/sexp_extensions_spec.rb +15 -33
  45. data/spec/reek/cli/application_spec.rb +1 -1
  46. data/spec/reek/code_comment_spec.rb +41 -42
  47. data/spec/reek/configuration/directory_directives_spec.rb +6 -0
  48. data/spec/reek/context_builder_spec.rb +110 -113
  49. data/spec/reek/examiner_spec.rb +1 -0
  50. data/spec/reek/report/code_climate/code_climate_configuration_spec.rb +1 -3
  51. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +26 -26
  52. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  53. data/spec/reek/report/code_climate/code_climate_report_spec.rb +1 -1
  54. data/spec/reek/report/json_report_spec.rb +1 -1
  55. data/spec/reek/report/location_formatter_spec.rb +3 -3
  56. data/spec/reek/report/text_report_spec.rb +1 -7
  57. data/spec/reek/report/yaml_report_spec.rb +1 -1
  58. data/spec/reek/smell_configuration_spec.rb +2 -0
  59. data/spec/reek/smell_detectors/base_detector_spec.rb +3 -16
  60. data/spec/reek/smell_detectors/data_clump_spec.rb +14 -0
  61. data/spec/reek/smell_detectors/missing_safe_method_spec.rb +8 -2
  62. data/spec/reek/smell_detectors/nil_check_spec.rb +3 -3
  63. data/spec/reek/smell_warning_spec.rb +17 -28
  64. data/spec/reek/source/source_code_spec.rb +13 -0
  65. data/spec/reek/spec/should_reek_of_spec.rb +0 -1
  66. data/spec/reek/spec/should_reek_only_of_spec.rb +6 -13
  67. data/spec/reek/spec/smell_matcher_spec.rb +1 -2
  68. data/spec/spec_helper.rb +20 -6
  69. metadata +11 -26
  70. data/spec/factories/factories.rb +0 -48
@@ -89,6 +89,12 @@ RSpec.describe Reek::Configuration::DirectoryDirectives do
89
89
  expect(hit.to_s).to eq('bar/**/.spec/*')
90
90
  end
91
91
 
92
+ it 'returns the corresponding directory when source_base_dir is an absolute_path' do
93
+ source_base_dir = Pathname.new('foo/bar').expand_path
94
+ hit = directives.send :best_match_for, source_base_dir
95
+ expect(hit.to_s).to eq('foo/bar')
96
+ end
97
+
92
98
  it 'does not match an arbitrary directory when source_base_dir contains a character that could match the .' do
93
99
  source_base_dir = 'bar/something/aspec/direct'
94
100
  hit = directives.send :best_match_for, source_base_dir
@@ -3,42 +3,126 @@ require_lib 'reek/context_builder'
3
3
 
4
4
  RSpec.describe Reek::ContextBuilder do
5
5
  describe '#context_tree' do
6
- let(:walker) do
7
- code = 'class Car; def drive; end; end'
8
- described_class.new(syntax_tree(code))
6
+ context 'with some simple example code' do
7
+ let(:walker) do
8
+ code = 'class Car; def drive; end; end'
9
+ described_class.new(syntax_tree(code))
10
+ end
11
+ let(:context_tree) { walker.context_tree }
12
+ let(:module_context) { context_tree.children.first }
13
+ let(:method_context) { module_context.children.first }
14
+
15
+ describe 'the starting node' do
16
+ it 'is a root node' do
17
+ expect(context_tree.type).to eq(:root)
18
+ expect(context_tree).to be_a(Reek::Context::RootContext)
19
+ end
20
+
21
+ it 'has one module_context child' do
22
+ aggregate_failures do
23
+ expect(context_tree.children.count).to eq 1
24
+ expect(module_context).to be_a(Reek::Context::ModuleContext)
25
+ end
26
+ end
27
+ end
28
+
29
+ describe 'the module node' do
30
+ it 'has one method_context child' do
31
+ aggregate_failures do
32
+ expect(method_context).to be_a(Reek::Context::MethodContext)
33
+ expect(module_context.children.size).to eq(1)
34
+ end
35
+ end
36
+
37
+ it 'holds a reference to the parent context' do
38
+ expect(method_context.parent).to eq(module_context)
39
+ end
40
+ end
9
41
  end
10
- let(:context_tree) { walker.context_tree }
11
- let(:module_context) { context_tree.children.first }
12
- let(:method_context) { module_context.children.first }
13
42
 
14
- it 'starts with a root node' do
15
- expect(context_tree.type).to eq(:root)
16
- expect(context_tree).to be_a(Reek::Context::RootContext)
43
+ it 'creates the proper context for all kinds of singleton methods' do
44
+ src = <<-RUBY
45
+ class Car
46
+ def self.start; end
47
+
48
+ class << self
49
+ def drive; end
50
+ end
51
+ end
52
+ RUBY
53
+
54
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
55
+ context_tree = described_class.new(syntax_tree).context_tree
56
+
57
+ class_node = context_tree.children.first
58
+ start_method = class_node.children.first
59
+ drive_method = class_node.children.last
60
+
61
+ expect(start_method).to be_instance_of Reek::Context::SingletonMethodContext
62
+ expect(drive_method).to be_instance_of Reek::Context::SingletonMethodContext
17
63
  end
18
64
 
19
- it 'has one child' do
20
- expect(context_tree.children.size).to eq(1)
65
+ it 'returns something sensible for nested metaclasses' do
66
+ src = <<-RUBY
67
+ class Foo
68
+ class << self
69
+ class << self
70
+ def bar; end
71
+ end
72
+ end
73
+ end
74
+ RUBY
75
+
76
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
77
+ context_tree = described_class.new(syntax_tree).context_tree
78
+
79
+ class_context = context_tree.children.first
80
+ method_context = class_context.children.first
81
+
82
+ expect(method_context).to be_instance_of Reek::Context::SingletonMethodContext
83
+ expect(method_context.parent).to eq class_context
21
84
  end
22
85
 
23
- describe 'the root node' do
24
- it 'has one module_context' do
25
- expect(module_context).to be_a(Reek::Context::ModuleContext)
26
- end
86
+ it 'returns something sensible for nested method definitions' do
87
+ src = <<-RUBY
88
+ class Foo
89
+ def foo
90
+ def bar
91
+ end
92
+ end
93
+ end
94
+ RUBY
27
95
 
28
- it 'holds a reference to the parent context' do
29
- expect(module_context.parent).to eq(context_tree)
30
- end
96
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
97
+ context_tree = described_class.new(syntax_tree).context_tree
98
+
99
+ class_context = context_tree.children.first
100
+ foo_context = class_context.children.first
101
+
102
+ bar_context = foo_context.children.first
103
+ expect(bar_context).to be_instance_of Reek::Context::MethodContext
104
+ expect(bar_context.parent).to eq foo_context
31
105
  end
32
106
 
33
- describe 'the module node' do
34
- it 'has one method_context' do
35
- expect(method_context).to be_a(Reek::Context::MethodContext)
36
- expect(module_context.children.size).to eq(1)
37
- end
107
+ it 'returns something sensible for method definitions nested in singleton methods' do
108
+ src = <<-RUBY
109
+ class Foo
110
+ def self.foo
111
+ def bar
112
+ end
113
+ end
114
+ end
115
+ RUBY
38
116
 
39
- it 'holds a reference to the parent context' do
40
- expect(method_context.parent).to eq(module_context)
41
- end
117
+ syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
118
+ context_tree = described_class.new(syntax_tree).context_tree
119
+
120
+ class_context = context_tree.children.first
121
+ foo_context = class_context.children.first
122
+
123
+ bar_context = foo_context.children.first
124
+ expect(bar_context).to be_instance_of Reek::Context::SingletonMethodContext
125
+ expect(bar_context.parent).to eq foo_context
42
126
  end
43
127
  end
44
128
 
@@ -370,91 +454,4 @@ RSpec.describe Reek::ContextBuilder do
370
454
  expect(nested_baz_context.visibility).to eq :public
371
455
  end
372
456
  end
373
-
374
- describe '#context_tree' do
375
- it 'creates the proper context for all kinds of singleton methods' do
376
- src = <<-RUBY
377
- class Car
378
- def self.start; end
379
-
380
- class << self
381
- def drive; end
382
- end
383
- end
384
- RUBY
385
-
386
- syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
387
- context_tree = described_class.new(syntax_tree).context_tree
388
-
389
- class_node = context_tree.children.first
390
- start_method = class_node.children.first
391
- drive_method = class_node.children.last
392
-
393
- expect(start_method).to be_instance_of Reek::Context::SingletonMethodContext
394
- expect(drive_method).to be_instance_of Reek::Context::SingletonMethodContext
395
- end
396
-
397
- it 'returns something sensible for nested metaclasses' do
398
- src = <<-RUBY
399
- class Foo
400
- class << self
401
- class << self
402
- def bar; end
403
- end
404
- end
405
- end
406
- RUBY
407
-
408
- syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
409
- context_tree = described_class.new(syntax_tree).context_tree
410
-
411
- class_context = context_tree.children.first
412
- method_context = class_context.children.first
413
-
414
- expect(method_context).to be_instance_of Reek::Context::SingletonMethodContext
415
- expect(method_context.parent).to eq class_context
416
- end
417
-
418
- it 'returns something sensible for nested method definitions' do
419
- src = <<-RUBY
420
- class Foo
421
- def foo
422
- def bar
423
- end
424
- end
425
- end
426
- RUBY
427
-
428
- syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
429
- context_tree = described_class.new(syntax_tree).context_tree
430
-
431
- class_context = context_tree.children.first
432
- foo_context = class_context.children.first
433
-
434
- bar_context = foo_context.children.first
435
- expect(bar_context).to be_instance_of Reek::Context::MethodContext
436
- expect(bar_context.parent).to eq foo_context
437
- end
438
-
439
- it 'returns something sensible for method definitions nested in singleton methods' do
440
- src = <<-RUBY
441
- class Foo
442
- def self.foo
443
- def bar
444
- end
445
- end
446
- end
447
- RUBY
448
-
449
- syntax_tree = Reek::Source::SourceCode.from(src).syntax_tree
450
- context_tree = described_class.new(syntax_tree).context_tree
451
-
452
- class_context = context_tree.children.first
453
- foo_context = class_context.children.first
454
-
455
- bar_context = foo_context.children.first
456
- expect(bar_context).to be_instance_of Reek::Context::SingletonMethodContext
457
- expect(bar_context.parent).to eq foo_context
458
- end
459
- end
460
457
  end
@@ -6,6 +6,7 @@ RSpec.shared_examples_for 'no smells found' do
6
6
  it 'is not smelly' do
7
7
  expect(examiner).not_to be_smelly
8
8
  end
9
+
9
10
  it 'finds no smells' do
10
11
  expect(examiner.smells.length).to eq(0)
11
12
  end
@@ -3,9 +3,7 @@ require_lib 'reek/report/code_climate/code_climate_configuration'
3
3
 
4
4
  RSpec.describe Reek::Report::CodeClimateConfiguration do
5
5
  yml = described_class.load
6
- smell_types = Reek::SmellDetectors::BaseDetector.descendants.map do |descendant|
7
- descendant.name.demodulize
8
- end
6
+ smell_types = Reek::SmellDetectors::BaseDetector.descendants.map(&:smell_type)
9
7
 
10
8
  smell_types.each do |name|
11
9
  config = yml.fetch(name)
@@ -8,12 +8,12 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
8
8
  context 'when fingerprinting a warning with no parameters' do
9
9
  let(:expected_fingerprint) { 'e68badd29db51c92363a7c6a2438d722' }
10
10
  let(:warning) do
11
- build(:smell_warning,
12
- smell_detector: Reek::SmellDetectors::UtilityFunction.new,
13
- context: 'alfa',
14
- message: "doesn't depend on instance state (maybe move it to another class?)",
15
- lines: lines,
16
- source: 'a/ruby/source/file.rb')
11
+ Reek::SmellWarning.new(
12
+ 'UtilityFunction',
13
+ context: 'alfa',
14
+ message: "doesn't depend on instance state (maybe move it to another class?)",
15
+ lines: lines,
16
+ source: 'a/ruby/source/file.rb')
17
17
  end
18
18
 
19
19
  context 'with code at a specific location' do
@@ -35,12 +35,12 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
35
35
 
36
36
  context 'when the fingerprint should not be computed' do
37
37
  let(:warning) do
38
- build(:smell_warning,
39
- smell_detector: Reek::SmellDetectors::ManualDispatch.new,
40
- context: 'Alfa#bravo',
41
- message: 'manually dispatches method call',
42
- lines: [4],
43
- source: 'a/ruby/source/file.rb')
38
+ Reek::SmellWarning.new(
39
+ 'ManualDispatch',
40
+ context: 'Alfa#bravo',
41
+ message: 'manually dispatches method call',
42
+ lines: [4],
43
+ source: 'a/ruby/source/file.rb')
44
44
  end
45
45
 
46
46
  it 'returns nil' do
@@ -50,13 +50,13 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
50
50
 
51
51
  context 'when the smell warning has only identifying parameters' do
52
52
  let(:warning) do
53
- build(:smell_warning,
54
- smell_detector: Reek::SmellDetectors::ClassVariable.new,
55
- context: 'Alfa',
56
- message: "declares the class variable '@@#{name}'",
57
- lines: [4],
58
- parameters: { name: "@@#{name}" },
59
- source: 'a/ruby/source/file.rb')
53
+ Reek::SmellWarning.new(
54
+ 'ClassVariable',
55
+ context: 'Alfa',
56
+ message: "declares the class variable '@@#{name}'",
57
+ lines: [4],
58
+ parameters: { name: "@@#{name}" },
59
+ source: 'a/ruby/source/file.rb')
60
60
  end
61
61
 
62
62
  context 'when the name is one thing' do
@@ -80,13 +80,13 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
80
80
 
81
81
  context 'when the smell warning has identifying and non-identifying parameters' do
82
82
  let(:warning) do
83
- build(:smell_warning,
84
- smell_detector: Reek::SmellDetectors::DuplicateMethodCall.new,
85
- context: "Alfa##{name}",
86
- message: "calls '#{name}' #{count} times",
87
- lines: lines,
88
- parameters: { name: "@@#{name}", count: count },
89
- source: 'a/ruby/source/file.rb')
83
+ Reek::SmellWarning.new(
84
+ 'DuplicateMethodCall',
85
+ context: "Alfa##{name}",
86
+ message: "calls '#{name}' #{count} times",
87
+ lines: lines,
88
+ parameters: { name: "@@#{name}", count: count },
89
+ source: 'a/ruby/source/file.rb')
90
90
  end
91
91
 
92
92
  context 'when the parameters are provided' do
@@ -4,12 +4,12 @@ require_lib 'reek/report/code_climate/code_climate_formatter'
4
4
  RSpec.describe Reek::Report::CodeClimateFormatter do
5
5
  describe '#render' do
6
6
  let(:warning) do
7
- build(:smell_warning,
8
- smell_detector: Reek::SmellDetectors::UtilityFunction.new,
9
- context: 'context foo',
10
- message: 'message bar',
11
- lines: [1, 2],
12
- source: 'a/ruby/source/file.rb')
7
+ Reek::SmellWarning.new(
8
+ 'UtilityFunction',
9
+ context: 'context foo',
10
+ message: 'message bar',
11
+ lines: [1, 2],
12
+ source: 'a/ruby/source/file.rb')
13
13
  end
14
14
  let(:rendered) { described_class.new(warning).render }
15
15
  let(:json) { JSON.parse rendered.chop }
@@ -7,7 +7,7 @@ require 'stringio'
7
7
 
8
8
  RSpec.describe Reek::Report::CodeClimateReport do
9
9
  let(:options) { {} }
10
- let(:instance) { described_class.new(options) }
10
+ let(:instance) { described_class.new(**options) }
11
11
  let(:examiner) { Reek::Examiner.new(source) }
12
12
 
13
13
  before do
@@ -7,7 +7,7 @@ require 'stringio'
7
7
 
8
8
  RSpec.describe Reek::Report::JSONReport do
9
9
  let(:options) { {} }
10
- let(:instance) { described_class.new(options) }
10
+ let(:instance) { described_class.new(**options) }
11
11
  let(:examiner) { Reek::Examiner.new(source) }
12
12
 
13
13
  before do
@@ -2,7 +2,7 @@ require_relative '../../spec_helper'
2
2
  require_lib 'reek/report/location_formatter'
3
3
 
4
4
  RSpec.describe Reek::Report::BlankLocationFormatter do
5
- let(:warning) { build(:smell_warning, lines: [11, 9, 250, 100]) }
5
+ let(:warning) { build_smell_warning(lines: [11, 9, 250, 100]) }
6
6
 
7
7
  describe '.format' do
8
8
  it 'returns a blank String regardless of the warning' do
@@ -12,7 +12,7 @@ RSpec.describe Reek::Report::BlankLocationFormatter do
12
12
  end
13
13
 
14
14
  RSpec.describe Reek::Report::DefaultLocationFormatter do
15
- let(:warning) { build(:smell_warning, lines: [11, 9, 250, 100]) }
15
+ let(:warning) { build_smell_warning(lines: [11, 9, 250, 100]) }
16
16
 
17
17
  describe '.format' do
18
18
  it 'returns a prefix with sorted line numbers' do
@@ -22,7 +22,7 @@ RSpec.describe Reek::Report::DefaultLocationFormatter do
22
22
  end
23
23
 
24
24
  RSpec.describe Reek::Report::SingleLineLocationFormatter do
25
- let(:warning) { build(:smell_warning, lines: [11, 9, 250, 100]) }
25
+ let(:warning) { build_smell_warning(lines: [11, 9, 250, 100]) }
26
26
 
27
27
  describe '.format' do
28
28
  it 'returns the first line where the smell was found' do
@@ -6,13 +6,7 @@ require_lib 'reek/report/simple_warning_formatter'
6
6
  require 'rainbow'
7
7
 
8
8
  RSpec.describe Reek::Report::TextReport do
9
- let(:report_options) do
10
- {
11
- warning_formatter: Reek::Report::SimpleWarningFormatter.new,
12
- heading_formatter: Reek::Report::QuietHeadingFormatter
13
- }
14
- end
15
- let(:instance) { described_class.new report_options }
9
+ let(:instance) { described_class.new }
16
10
 
17
11
  context 'with a single empty source' do
18
12
  before do
@@ -7,7 +7,7 @@ require 'stringio'
7
7
 
8
8
  RSpec.describe Reek::Report::YAMLReport do
9
9
  let(:options) { {} }
10
- let(:instance) { described_class.new(options) }
10
+ let(:instance) { described_class.new(**options) }
11
11
  let(:examiner) { Reek::Examiner.new(source) }
12
12
 
13
13
  before do
@@ -18,10 +18,12 @@ RSpec.describe Reek::SmellConfiguration do
18
18
  it { expect(smell_config.merge('enabled' => true)).to eq(base_config) }
19
19
  it { expect(smell_config.merge('exclude' => [])).to eq(base_config) }
20
20
  it { expect(smell_config.merge('accept' => ['_'])).to eq(base_config) }
21
+
21
22
  it do
22
23
  updated = smell_config.merge('reject' => [/^.$/, /[0-9]$/, /[A-Z]/])
23
24
  expect(updated).to eq(base_config)
24
25
  end
26
+
25
27
  it do
26
28
  updated = smell_config.merge('accept' => ['_'], 'enabled' => true)
27
29
  expect(updated).to eq(base_config)