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
@@ -11,8 +11,8 @@ module Reek
11
11
  #
12
12
  class TextReport < BaseReport
13
13
  # @public
14
- def initialize(*args)
15
- super(*args)
14
+ def initialize(**args)
15
+ super
16
16
 
17
17
  print progress_formatter.header
18
18
  end
@@ -19,6 +19,7 @@ module Reek
19
19
  # @quality :reek:TooManyMethods { max_methods: 18 }
20
20
  class BaseDetector
21
21
  attr_reader :config
22
+
22
23
  # The name of the config field that lists the names of code contexts
23
24
  # that should not be checked. Add this field to the config for each
24
25
  # smell that should ignore this code element.
@@ -79,7 +80,7 @@ module Reek
79
80
  end
80
81
 
81
82
  def smell_warning(**options)
82
- SmellWarning.new(self,
83
+ SmellWarning.new(smell_type,
83
84
  source: expression.source,
84
85
  context: context.full_name,
85
86
  lines: options.fetch(:lines),
@@ -121,15 +122,6 @@ module Reek
121
122
  @descendants ||= []
122
123
  end
123
124
 
124
- #
125
- # @param detector [String] the detector in question, e.g. 'DuplicateMethodCall'
126
- # @return [Boolean]
127
- #
128
- def valid_detector?(detector)
129
- descendants.map { |descendant| descendant.to_s.split('::').last }.
130
- include?(detector)
131
- end
132
-
133
125
  #
134
126
  # Transform a detector name to the corresponding constant.
135
127
  # Note that we assume a valid name - exceptions are not handled here.
@@ -51,7 +51,7 @@ module Reek
51
51
  # @return [Array<SmellWarning>]
52
52
  #
53
53
  def sniff
54
- MethodGroup.new(context, min_clump_size, max_copies).clumps.map do |clump, methods|
54
+ clumps.map do |clump, methods|
55
55
  methods_length = methods.length
56
56
  smell_warning(
57
57
  lines: methods.map(&:line),
@@ -72,72 +72,39 @@ module Reek
72
72
  private
73
73
 
74
74
  def max_copies
75
- value(MAX_COPIES_KEY, context)
75
+ @max_copies ||= value(MAX_COPIES_KEY, context)
76
76
  end
77
77
 
78
78
  def min_clump_size
79
- value(MIN_CLUMP_SIZE_KEY, context)
79
+ @min_clump_size ||= value(MIN_CLUMP_SIZE_KEY, context)
80
80
  end
81
- end
82
- end
83
81
 
84
- # Represents a group of methods
85
- # @private
86
- class MethodGroup
87
- def initialize(ctx, min_clump_size, max_copies)
88
- @min_clump_size = min_clump_size
89
- @max_copies = max_copies
90
- @candidate_methods = ctx.node_instance_methods.map do |defn_node|
91
- CandidateMethod.new(defn_node)
82
+ def candidate_methods
83
+ @candidate_methods ||= context.node_instance_methods
92
84
  end
93
- end
94
85
 
95
- def candidate_clumps
96
- candidate_methods.each_cons(max_copies + 1).map do |methods|
97
- common_argument_names_for(methods)
98
- end.select do |clump|
99
- clump.length >= min_clump_size
100
- end.uniq
101
- end
102
-
103
- # @quality :reek:UtilityFunction
104
- def common_argument_names_for(methods)
105
- methods.map(&:arg_names).inject(:&)
106
- end
107
-
108
- def methods_containing_clump(clump)
109
- candidate_methods.select { |method| clump & method.arg_names == clump }
110
- end
111
-
112
- def clumps
113
- candidate_clumps.map do |clump|
114
- [clump, methods_containing_clump(clump)]
86
+ def candidate_clumps
87
+ candidate_methods.each_cons(max_copies + 1).map do |methods|
88
+ common_argument_names_for(methods)
89
+ end.select do |clump|
90
+ clump.length >= min_clump_size
91
+ end.uniq
115
92
  end
116
- end
117
-
118
- private
119
93
 
120
- attr_reader :candidate_methods, :max_copies, :min_clump_size
121
- end
122
-
123
- # A method definition and a copy of its parameters
124
- # @private
125
- class CandidateMethod
126
- extend Forwardable
127
-
128
- def_delegators :defn, :line, :name
94
+ # @quality :reek:UtilityFunction
95
+ def common_argument_names_for(methods)
96
+ methods.map(&:arg_names).inject(:&).compact.sort
97
+ end
129
98
 
130
- def initialize(defn_node)
131
- @defn = defn_node
132
- end
99
+ def methods_containing_clump(clump)
100
+ candidate_methods.select { |method| clump & method.arg_names == clump }
101
+ end
133
102
 
134
- def arg_names
135
- # TODO: Is all this sorting still needed?
136
- @arg_names ||= defn.arg_names.compact.sort
103
+ def clumps
104
+ candidate_clumps.map do |clump|
105
+ [clump, methods_containing_clump(clump)]
106
+ end
107
+ end
137
108
  end
138
-
139
- private
140
-
141
- attr_reader :defn
142
109
  end
143
110
  end
@@ -24,8 +24,7 @@ module Reek
24
24
 
25
25
  def detect_nodes
26
26
  finders = [NodeFinder.new(context, :send, NilCallNodeDetector),
27
- NodeFinder.new(context, :when, NilWhenNodeDetector),
28
- NodeFinder.new(context, :csend, SafeNavigationNodeDetector)]
27
+ NodeFinder.new(context, :when, NilWhenNodeDetector)]
29
28
  finders.flat_map(&:smelly_nodes)
30
29
  end
31
30
 
@@ -88,16 +87,6 @@ module Reek
88
87
  node.condition_list.any? { |it| it.type == :nil }
89
88
  end
90
89
  end
91
-
92
- # Detect safe navigation. Returns true for all nodes, since all :csend
93
- # nodes are considered smelly.
94
- module SafeNavigationNodeDetector
95
- module_function
96
-
97
- def detect(_node)
98
- true
99
- end
100
- end
101
90
  end
102
91
  end
103
92
  end
@@ -43,13 +43,9 @@ module Reek
43
43
  end
44
44
 
45
45
  def build_smell_warning(ancestor_name)
46
- smell_attributes = {
47
- lines: [source_line],
48
- message: "inherits from core class '#{ancestor_name}'",
49
- parameters: { ancestor: ancestor_name }
50
- }
51
-
52
- smell_warning(smell_attributes)
46
+ smell_warning(lines: [source_line],
47
+ message: "inherits from core class '#{ancestor_name}'",
48
+ parameters: { ancestor: ancestor_name })
53
49
  end
54
50
  end
55
51
  end
@@ -35,7 +35,7 @@ module Reek
35
35
  # @return [Array<SmellWarning>]
36
36
  #
37
37
  def sniff
38
- count = context.local_nodes(:casgn).reject(&:defines_module?).length
38
+ count = context.local_nodes(:casgn).count { |it| !it.defines_module? }
39
39
 
40
40
  return [] if count <= max_allowed_constants
41
41
 
@@ -15,22 +15,30 @@ module Reek
15
15
  extend Forwardable
16
16
 
17
17
  # @public
18
- attr_reader :context, :lines, :message, :parameters, :smell_detector, :source
19
- def_delegators :smell_detector, :smell_type
20
-
18
+ attr_reader :context, :lines, :message, :parameters, :smell_type, :source
19
+
20
+ # @param smell_type [String] type of detected smell; corresponds to
21
+ # detector#smell_type
22
+ # @param context [String] name of the context in which the smell occured
23
+ # @param lines [Array<Integer>] list of lines on which the smell occured
24
+ # @param message [String] text describing the smell in more detail
25
+ # @param source [String] name of the source (e.g., the file name) in which
26
+ # the smell occured
27
+ # @param parameters [Hash] smell-specific parameters
28
+ #
21
29
  # @note When using Reek's public API, you should not create SmellWarning
22
30
  # objects yourself. This is why the initializer is not part of the
23
31
  # public API.
24
32
  #
25
33
  # @quality :reek:LongParameterList { max_params: 6 }
26
- def initialize(smell_detector, context: '', lines:, message:,
34
+ def initialize(smell_type, context: '', lines:, message:,
27
35
  source:, parameters: {})
28
- @smell_detector = smell_detector
29
- @source = source
30
- @context = context.to_s
31
- @lines = lines
32
- @message = message
33
- @parameters = parameters
36
+ @smell_type = smell_type
37
+ @source = source
38
+ @context = context.to_s
39
+ @lines = lines
40
+ @message = message
41
+ @parameters = parameters
34
42
 
35
43
  freeze
36
44
  end
@@ -64,10 +72,6 @@ module Reek
64
72
  "#{smell_type}: #{context} #{message}"
65
73
  end
66
74
 
67
- def smell_class
68
- smell_detector.class
69
- end
70
-
71
75
  def explanatory_link
72
76
  DocumentationLink.build(smell_type)
73
77
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../cli/silencer'
4
- Reek::CLI::Silencer.without_warnings { require 'parser/ruby26' }
4
+ # Silence Parser's warnings about Ruby micro version differences
5
+ Reek::CLI::Silencer.silently { require 'parser/current' }
5
6
  require_relative '../tree_dresser'
6
7
  require_relative '../ast/node'
7
8
  require_relative '../ast/builder'
@@ -53,7 +54,7 @@ module Reek
53
54
  end
54
55
 
55
56
  def self.default_parser
56
- Parser::Ruby26.new(AST::Builder.new).tap do |parser|
57
+ Parser::CurrentRuby.new(AST::Builder.new).tap do |parser|
57
58
  diagnostics = parser.diagnostics
58
59
  diagnostics.all_errors_are_fatal = true
59
60
  diagnostics.ignore_warnings = true
@@ -43,8 +43,9 @@ module Reek
43
43
  raise ArgumentError, "The attribute '#{extra_keys.first}' is not available for comparison"
44
44
  end
45
45
 
46
+ # :reek:FeatureEnvy
46
47
  def common_parameters_equal?(other_parameters)
47
- smell_warning.parameters.slice(*other_parameters.keys) == other_parameters
48
+ smell_warning.parameters.values_at(*other_parameters.keys) == other_parameters.values
48
49
  end
49
50
 
50
51
  def common_attributes_equal?(attributes)
@@ -8,6 +8,6 @@ module Reek
8
8
  # @public
9
9
  module Version
10
10
  # @public
11
- STRING = '5.4.0'
11
+ STRING = '6.0.1'
12
12
  end
13
13
  end
@@ -16,12 +16,11 @@ Gem::Specification.new do |s|
16
16
  s.executables = s.files.grep(%r{^bin/}).map { |path| File.basename(path) }
17
17
  s.homepage = 'https://github.com/troessner/reek'
18
18
  s.rdoc_options = %w(--main README.md -x assets/|bin/|config/|features/|spec/|tasks/)
19
- s.required_ruby_version = '>= 2.3.0'
19
+ s.required_ruby_version = '>= 2.4.0'
20
20
  s.summary = 'Code smell detector for Ruby'
21
21
 
22
- s.add_runtime_dependency 'codeclimate-engine-rb', '~> 0.4.0'
23
- s.add_runtime_dependency 'kwalify', '~> 0.7.0'
24
- s.add_runtime_dependency 'parser', '< 2.7', '>= 2.5.0.0', '!= 2.5.1.1'
25
- s.add_runtime_dependency 'psych', '~> 3.1.0'
26
- s.add_runtime_dependency 'rainbow', '>= 2.0', '< 4.0'
22
+ s.add_runtime_dependency 'kwalify', '~> 0.7.0'
23
+ s.add_runtime_dependency 'parser', '< 2.8', '>= 2.5.0.0', '!= 2.5.1.1'
24
+ s.add_runtime_dependency 'psych', '~> 3.1.0'
25
+ s.add_runtime_dependency 'rainbow', '>= 2.0', '< 4.0'
27
26
  end
@@ -443,73 +443,55 @@ end
443
443
 
444
444
  RSpec.describe Reek::AST::SexpExtensions::CasgnNode do
445
445
  describe '#defines_module?' do
446
- context 'with single assignment' do
447
- it 'does not define a module' do
448
- exp = sexp(:casgn, nil, :Foo)
449
- expect(exp).not_to be_defines_module
450
- end
446
+ it 'is false for single assignment' do
447
+ exp = sexp(:casgn, nil, :Foo)
448
+ expect(exp).not_to be_defines_module
451
449
  end
452
450
 
453
- context 'with implicit receiver to new' do
454
- it 'does not define a module' do
455
- exp = sexp(:casgn, nil, :Foo, sexp(:send, nil, :new))
456
- expect(exp).not_to be_defines_module
457
- end
451
+ it 'is false for implicit receiver to new' do
452
+ exp = sexp(:casgn, nil, :Foo, sexp(:send, nil, :new))
453
+ expect(exp).not_to be_defines_module
458
454
  end
459
455
 
460
- context 'with implicit receiver to new' do
461
- it 'does not define a module' do
462
- exp = Reek::Source::SourceCode.from('Foo = Class.new(Bar)').syntax_tree
463
-
464
- expect(exp).to be_defines_module
465
- end
456
+ it 'is true for explicit receiver to new' do
457
+ exp = Reek::Source::SourceCode.from('Foo = Class.new(Bar)').syntax_tree
458
+ expect(exp).to be_defines_module
466
459
  end
467
460
 
468
- context 'when assigning a lambda to a constant' do
469
- it 'does not define a module' do
470
- exp = Reek::Source::SourceCode.from('C = ->{}').syntax_tree
471
-
472
- expect(exp).not_to be_defines_module
473
- end
461
+ it 'is false for assigning a lambda to a constant' do
462
+ exp = Reek::Source::SourceCode.from('C = ->{}').syntax_tree
463
+ expect(exp).not_to be_defines_module
474
464
  end
475
465
 
476
- context 'when assigning a string to a constant' do
477
- it 'does not define a module' do
478
- exp = Reek::Source::SourceCode.from('C = "hello"').syntax_tree
479
-
480
- expect(exp).not_to be_defines_module
481
- end
466
+ it 'is false for assigning a string to a constant' do
467
+ exp = Reek::Source::SourceCode.from('C = "hello"').syntax_tree
468
+ expect(exp).not_to be_defines_module
482
469
  end
483
470
  end
484
471
 
485
472
  describe '#superclass' do
486
473
  it 'returns the superclass from the class definition' do
487
474
  exp = Reek::Source::SourceCode.from('Foo = Class.new(Bar)').syntax_tree
488
-
489
475
  expect(exp.superclass).to eq sexp(:const, nil, :Bar)
490
476
  end
491
477
 
492
478
  it 'returns nil in case of no class definition' do
493
479
  exp = Reek::Source::SourceCode.from('Foo = 23').syntax_tree
494
-
495
480
  expect(exp.superclass).to be_nil
496
481
  end
497
482
 
498
483
  it 'returns nil in case of no superclass' do
499
484
  exp = Reek::Source::SourceCode.from('Foo = Class.new').syntax_tree
500
-
501
485
  expect(exp.superclass).to be_nil
502
486
  end
503
487
 
504
488
  it 'returns nothing for a class definition using Struct.new' do
505
489
  exp = Reek::Source::SourceCode.from('Foo = Struct.new("Bar")').syntax_tree
506
-
507
490
  expect(exp.superclass).to be_nil
508
491
  end
509
492
 
510
493
  it 'returns nothing for a constant assigned with a bare method call' do
511
494
  exp = Reek::Source::SourceCode.from('Foo = foo("Bar")').syntax_tree
512
-
513
495
  expect(exp.superclass).to be_nil
514
496
  end
515
497
  end
@@ -9,7 +9,7 @@ RSpec.describe Reek::CLI::Application do
9
9
  described_class.new ['--foo']
10
10
  end
11
11
  end
12
- expect(call).to raise_error(SystemExit) do |error|
12
+ expect(&call).to raise_error(SystemExit) do |error|
13
13
  expect(error.status).to eq Reek::CLI::Status::DEFAULT_ERROR_EXIT_CODE
14
14
  end
15
15
  end
@@ -3,7 +3,7 @@ require_lib 'reek/code_comment'
3
3
 
4
4
  RSpec.describe Reek::CodeComment do
5
5
  context 'with an empty comment' do
6
- let(:comment) { build(:code_comment, comment: '') }
6
+ let(:comment) { build_code_comment(comment: '') }
7
7
 
8
8
  it 'is not descriptive' do
9
9
  expect(comment).not_to be_descriptive
@@ -16,77 +16,63 @@ RSpec.describe Reek::CodeComment do
16
16
 
17
17
  describe '#descriptive' do
18
18
  it 'rejects an empty comment' do
19
- comment = build(:code_comment, comment: '#')
19
+ comment = build_code_comment(comment: '#')
20
20
  expect(comment).not_to be_descriptive
21
21
  end
22
22
 
23
23
  it 'rejects a 1-word comment' do
24
- comment = build(:code_comment, comment: "# alpha\n# ")
24
+ comment = build_code_comment(comment: "# alpha\n# ")
25
25
  expect(comment).not_to be_descriptive
26
26
  end
27
27
 
28
28
  it 'accepts a 2-word comment' do
29
- comment = build(:code_comment, comment: '# alpha bravo ')
29
+ comment = build_code_comment(comment: '# alpha bravo ')
30
30
  expect(comment).to be_descriptive
31
31
  end
32
32
 
33
33
  it 'accepts a multi-word comment' do
34
- comment = build(:code_comment, comment: "# alpha bravo \n# charlie \n # delta ")
34
+ comment = build_code_comment(comment: "# alpha bravo \n# charlie \n # delta ")
35
35
  expect(comment).to be_descriptive
36
36
  end
37
37
  end
38
38
 
39
39
  describe 'good comment config' do
40
40
  it 'parses hashed options' do
41
- comment = '# :reek:DuplicateMethodCall { enabled: false }'
42
- config = build(:code_comment,
43
- comment: comment).config
41
+ comment = '# :reek:DuplicateMethodCall { max_calls: 3 }'
42
+ config = build_code_comment(comment: comment).config
44
43
 
45
44
  expect(config).to include('DuplicateMethodCall')
46
- expect(config['DuplicateMethodCall']).to include('enabled')
47
- expect(config['DuplicateMethodCall']['enabled']).to be_falsey
48
- end
49
-
50
- it "supports hashed options with the legacy separator ':' after the smell detector" do
51
- comment = '# :reek:DuplicateMethodCall: { enabled: false }'
52
- config = build(:code_comment,
53
- comment: comment).config
54
-
55
- expect(config).to include('DuplicateMethodCall')
56
- expect(config['DuplicateMethodCall']).to include('enabled')
57
- expect(config['DuplicateMethodCall']['enabled']).to be_falsey
45
+ expect(config['DuplicateMethodCall']).to have_key 'max_calls'
46
+ expect(config['DuplicateMethodCall']['max_calls']).to eq 3
58
47
  end
59
48
 
60
49
  it 'parses multiple hashed options' do
61
50
  comment = <<-RUBY
62
- # :reek:DuplicateMethodCall { enabled: false }
51
+ # :reek:DuplicateMethodCall { max_calls: 3 }
63
52
  # :reek:NestedIterators { enabled: true }
64
53
  RUBY
65
- config = build(:code_comment, comment: comment).config
54
+ config = build_code_comment(comment: comment).config
66
55
 
67
56
  expect(config).to include('DuplicateMethodCall', 'NestedIterators')
68
- expect(config['DuplicateMethodCall']).to include('enabled')
69
- expect(config['DuplicateMethodCall']['enabled']).to be_falsey
70
- expect(config['NestedIterators']).to include('enabled')
57
+ expect(config['DuplicateMethodCall']['max_calls']).to eq 3
71
58
  expect(config['NestedIterators']['enabled']).to be_truthy
72
59
  end
73
60
 
74
61
  it 'parses multiple hashed options on the same line' do
75
62
  comment = <<-RUBY
76
- #:reek:DuplicateMethodCall { enabled: false } and :reek:NestedIterators { enabled: true }
63
+ #:reek:DuplicateMethodCall { max_calls: 3 } and :reek:NestedIterators { enabled: true }
77
64
  RUBY
78
- config = build(:code_comment, comment: comment).config
65
+ config = build_code_comment(comment: comment).config
79
66
 
80
67
  expect(config).to include('DuplicateMethodCall', 'NestedIterators')
81
- expect(config['DuplicateMethodCall']).to include('enabled')
82
- expect(config['DuplicateMethodCall']['enabled']).to be_falsey
68
+ expect(config['DuplicateMethodCall']['max_calls']).to eq 3
83
69
  expect(config['NestedIterators']).to include('enabled')
84
70
  expect(config['NestedIterators']['enabled']).to be_truthy
85
71
  end
86
72
 
87
73
  it 'parses multiple unhashed options on the same line' do
88
74
  comment = '# :reek:DuplicateMethodCall and :reek:NestedIterators'
89
- config = build(:code_comment, comment: comment).config
75
+ config = build_code_comment(comment: comment).config
90
76
 
91
77
  expect(config).to include('DuplicateMethodCall', 'NestedIterators')
92
78
  expect(config['DuplicateMethodCall']).to include('enabled')
@@ -97,27 +83,33 @@ RSpec.describe Reek::CodeComment do
97
83
 
98
84
  it 'disables the smell if no options are specifed' do
99
85
  comment = '# :reek:DuplicateMethodCall'
100
- config = build(:code_comment, comment: comment).config
86
+ config = build_code_comment(comment: comment).config
101
87
 
102
88
  expect(config).to include('DuplicateMethodCall')
103
89
  expect(config['DuplicateMethodCall']).to include('enabled')
104
90
  expect(config['DuplicateMethodCall']['enabled']).to be_falsey
105
91
  end
106
92
 
93
+ it 'does not disable the smell if options are specifed' do
94
+ comment = '# :reek:DuplicateMethodCall { max_calls: 3 }'
95
+ config = build_code_comment(comment: comment).config
96
+
97
+ expect(config['DuplicateMethodCall']).not_to include('enabled')
98
+ end
99
+
107
100
  it 'ignores smells after a space' do
108
- config = build(:code_comment,
109
- comment: '# :reek: DuplicateMethodCall').config
101
+ config = build_code_comment(comment: '# :reek: DuplicateMethodCall').config
110
102
  expect(config).not_to include('DuplicateMethodCall')
111
103
  end
112
104
 
113
105
  it 'removes the configuration options from the comment' do
114
106
  original_comment = <<-RUBY
115
107
  # Actual
116
- # :reek:DuplicateMethodCall { enabled: false }
108
+ # :reek:DuplicateMethodCall { max_calls: 3 }
117
109
  # :reek:NestedIterators { enabled: true }
118
110
  # comment
119
111
  RUBY
120
- comment = build(:code_comment, comment: original_comment)
112
+ comment = build_code_comment(comment: original_comment)
121
113
 
122
114
  expect(comment.send(:sanitized_comment)).to eq('Actual comment')
123
115
  end
@@ -128,8 +120,7 @@ RSpec.describe Reek::CodeComment::CodeCommentValidator do
128
120
  context 'when the comment contains an unknown detector name' do
129
121
  it 'raises BadDetectorInCommentError' do
130
122
  expect do
131
- build(:code_comment,
132
- comment: '# :reek:DoesNotExist')
123
+ build_code_comment(comment: '# :reek:DoesNotExist')
133
124
  end.to raise_error(Reek::Errors::BadDetectorInCommentError)
134
125
  end
135
126
  end
@@ -138,18 +129,26 @@ RSpec.describe Reek::CodeComment::CodeCommentValidator do
138
129
  it 'raises GarbageDetectorConfigurationInCommentError' do
139
130
  expect do
140
131
  comment = '# :reek:UncommunicativeMethodName { thats: a: bad: config }'
141
- build(:code_comment, comment: comment)
132
+ build_code_comment(comment: comment)
142
133
  end.to raise_error(Reek::Errors::GarbageDetectorConfigurationInCommentError)
143
134
  end
144
135
  end
145
136
 
137
+ context 'when the legacy comment format was used' do
138
+ it 'raises LegacyCommentSeparatorError' do
139
+ comment = '# :reek:DuplicateMethodCall:'
140
+ expect { build_code_comment(comment: comment) }.
141
+ to raise_error Reek::Errors::LegacyCommentSeparatorError
142
+ end
143
+ end
144
+
146
145
  describe 'validating configuration keys' do
147
146
  context 'when basic options are mispelled' do
148
147
  it 'raises BadDetectorConfigurationKeyInCommentError' do
149
148
  expect do
150
149
  # exclude -> exlude and enabled -> nabled
151
150
  comment = '# :reek:UncommunicativeMethodName { exlude: alfa, nabled: true }'
152
- build(:code_comment, comment: comment)
151
+ build_code_comment(comment: comment)
153
152
  end.to raise_error(Reek::Errors::BadDetectorConfigurationKeyInCommentError)
154
153
  end
155
154
  end
@@ -158,7 +157,7 @@ RSpec.describe Reek::CodeComment::CodeCommentValidator do
158
157
  it 'does not raise' do
159
158
  expect do
160
159
  comment = '# :reek:UncommunicativeMethodName { exclude: alfa, enabled: true }'
161
- build(:code_comment, comment: comment)
160
+ build_code_comment(comment: comment)
162
161
  end.not_to raise_error
163
162
  end
164
163
  end
@@ -168,7 +167,7 @@ RSpec.describe Reek::CodeComment::CodeCommentValidator do
168
167
  expect do
169
168
  # max_copies -> mx_copies and min_clump_size -> mn_clump_size
170
169
  comment = '# :reek:DataClump { mx_copies: 4, mn_clump_size: 3 }'
171
- build(:code_comment, comment: comment)
170
+ build_code_comment(comment: comment)
172
171
  end.to raise_error(Reek::Errors::BadDetectorConfigurationKeyInCommentError)
173
172
  end
174
173
  end
@@ -177,7 +176,7 @@ RSpec.describe Reek::CodeComment::CodeCommentValidator do
177
176
  it 'does not raise' do
178
177
  expect do
179
178
  comment = '# :reek:DataClump { max_copies: 4, min_clump_size: 3 }'
180
- build(:code_comment, comment: comment)
179
+ build_code_comment(comment: comment)
181
180
  end.not_to raise_error
182
181
  end
183
182
  end