reek 5.4.0 → 6.0.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 (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)