reek 4.5.0 → 4.6.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 (128) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -7
  3. data/.travis.yml +13 -9
  4. data/CHANGELOG.md +36 -3
  5. data/Dockerfile +6 -5
  6. data/Gemfile +8 -7
  7. data/README.md +45 -27
  8. data/docs/How-To-Write-New-Detectors.md +6 -6
  9. data/docs/Irresponsible-Module.md +8 -1
  10. data/docs/Nil-Check.md +1 -1
  11. data/docs/Prima-Donna-Method.md +27 -0
  12. data/docs/RSpec-matchers.md +5 -10
  13. data/docs/Unused-Private-Method.md +27 -0
  14. data/docs/templates/default/docstring/setup.rb +1 -8
  15. data/features/command_line_interface/options.feature +5 -1
  16. data/features/command_line_interface/stdin.feature +1 -1
  17. data/features/configuration_files/exclude_paths_directives.feature +43 -0
  18. data/features/configuration_files/warn_about_multiple_configuration_files.feature +44 -0
  19. data/features/configuration_via_source_comments/erroneous_source_comments.feature +15 -0
  20. data/features/samples.feature +3 -8
  21. data/features/step_definitions/reek_steps.rb +5 -5
  22. data/features/todo_list.feature +1 -2
  23. data/lib/reek/ast/reference_collector.rb +2 -4
  24. data/lib/reek/ast/sexp_extensions/arguments.rb +0 -5
  25. data/lib/reek/ast/sexp_extensions/constant.rb +1 -0
  26. data/lib/reek/cli/application.rb +7 -8
  27. data/lib/reek/cli/command/report_command.rb +3 -1
  28. data/lib/reek/cli/options.rb +29 -13
  29. data/lib/reek/cli/status.rb +10 -0
  30. data/lib/reek/code_comment.rb +48 -6
  31. data/lib/reek/configuration/configuration_file_finder.rb +87 -27
  32. data/lib/reek/context/ghost_context.rb +1 -2
  33. data/lib/reek/context_builder.rb +26 -1
  34. data/lib/reek/detector_repository.rb +64 -0
  35. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +37 -0
  36. data/lib/reek/errors/bad_detector_in_comment_error.rb +2 -1
  37. data/lib/reek/errors/base_error.rb +9 -0
  38. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +2 -1
  39. data/lib/reek/errors/incomprehensible_source_error.rb +47 -0
  40. data/lib/reek/errors/parse_error.rb +19 -0
  41. data/lib/reek/examiner.rb +17 -41
  42. data/lib/reek/logging_error_handler.rb +15 -0
  43. data/lib/reek/report/code_climate/code_climate_configuration.rb +12 -0
  44. data/lib/reek/report/code_climate/code_climate_configuration.yml +156 -0
  45. data/lib/reek/report/code_climate/code_climate_fingerprint.rb +4 -2
  46. data/lib/reek/report/code_climate/code_climate_formatter.rb +2 -2
  47. data/lib/reek/smell_configuration.rb +64 -0
  48. data/lib/reek/smell_detectors/attribute.rb +0 -2
  49. data/lib/reek/smell_detectors/base_detector.rb +24 -4
  50. data/lib/reek/smell_detectors/boolean_parameter.rb +0 -1
  51. data/lib/reek/smell_detectors/class_variable.rb +0 -1
  52. data/lib/reek/smell_detectors/control_parameter.rb +0 -1
  53. data/lib/reek/smell_detectors/data_clump.rb +0 -1
  54. data/lib/reek/smell_detectors/duplicate_method_call.rb +0 -1
  55. data/lib/reek/smell_detectors/feature_envy.rb +0 -1
  56. data/lib/reek/smell_detectors/instance_variable_assumption.rb +0 -1
  57. data/lib/reek/smell_detectors/irresponsible_module.rb +0 -1
  58. data/lib/reek/smell_detectors/long_parameter_list.rb +0 -2
  59. data/lib/reek/smell_detectors/long_yield_list.rb +0 -1
  60. data/lib/reek/smell_detectors/manual_dispatch.rb +4 -6
  61. data/lib/reek/smell_detectors/module_initialize.rb +5 -8
  62. data/lib/reek/smell_detectors/nested_iterators.rb +0 -1
  63. data/lib/reek/smell_detectors/nil_check.rb +0 -1
  64. data/lib/reek/smell_detectors/prima_donna_method.rb +30 -1
  65. data/lib/reek/smell_detectors/repeated_conditional.rb +0 -1
  66. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +0 -1
  67. data/lib/reek/smell_detectors/too_many_constants.rb +0 -1
  68. data/lib/reek/smell_detectors/too_many_instance_variables.rb +0 -1
  69. data/lib/reek/smell_detectors/too_many_methods.rb +0 -1
  70. data/lib/reek/smell_detectors/too_many_statements.rb +0 -1
  71. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +0 -1
  72. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +0 -1
  73. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +0 -1
  74. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +0 -1
  75. data/lib/reek/smell_detectors/unused_parameters.rb +0 -1
  76. data/lib/reek/smell_detectors/unused_private_method.rb +0 -2
  77. data/lib/reek/smell_detectors/utility_function.rb +0 -1
  78. data/lib/reek/smell_warning.rb +85 -0
  79. data/lib/reek/source/source_code.rb +2 -1
  80. data/lib/reek/source/source_locator.rb +15 -3
  81. data/lib/reek/spec/should_reek_of.rb +1 -1
  82. data/lib/reek/spec.rb +6 -4
  83. data/lib/reek/version.rb +1 -1
  84. data/reek.gemspec +1 -1
  85. data/samples/configuration/more_than_one_configuration_file/todo.reek +0 -0
  86. data/samples/configuration/single_configuration_file/.reek +0 -0
  87. data/spec/factories/factories.rb +2 -10
  88. data/spec/quality/reek_source_spec.rb +5 -3
  89. data/spec/reek/ast/reference_collector_spec.rb +0 -17
  90. data/spec/reek/cli/application_spec.rb +25 -1
  91. data/spec/reek/cli/command/report_command_spec.rb +2 -2
  92. data/spec/reek/cli/command/todo_list_command_spec.rb +14 -71
  93. data/spec/reek/cli/options_spec.rb +4 -0
  94. data/spec/reek/code_comment_spec.rb +47 -0
  95. data/spec/reek/configuration/configuration_file_finder_spec.rb +38 -15
  96. data/spec/reek/context/code_context_spec.rb +10 -10
  97. data/spec/reek/context/method_context_spec.rb +1 -1
  98. data/spec/reek/context/module_context_spec.rb +8 -4
  99. data/spec/reek/{smell_detectors/detector_repository_spec.rb → detector_repository_spec.rb} +3 -3
  100. data/spec/reek/examiner_spec.rb +39 -40
  101. data/spec/reek/logging_error_handler_spec.rb +24 -0
  102. data/spec/reek/report/code_climate/code_climate_configuration_spec.rb +24 -0
  103. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +9 -9
  104. data/spec/reek/report/yaml_report_spec.rb +4 -4
  105. data/spec/reek/{smell_detectors/smell_configuration_spec.rb → smell_configuration_spec.rb} +3 -3
  106. data/spec/reek/smell_detectors/base_detector_spec.rb +18 -0
  107. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +2 -2
  108. data/spec/reek/smell_detectors/feature_envy_spec.rb +18 -8
  109. data/spec/reek/smell_detectors/manual_dispatch_spec.rb +13 -0
  110. data/spec/reek/smell_detectors/module_initialize_spec.rb +23 -2
  111. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  112. data/spec/reek/smell_detectors/prima_donna_method_spec.rb +12 -0
  113. data/spec/reek/smell_detectors/subclassed_from_core_class_spec.rb +0 -5
  114. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +2 -2
  115. data/spec/reek/smell_detectors/unused_parameters_spec.rb +1 -1
  116. data/spec/reek/{smell_detectors/smell_warning_spec.rb → smell_warning_spec.rb} +9 -9
  117. data/spec/reek/source/source_code_spec.rb +6 -29
  118. data/spec/reek/source/source_locator_spec.rb +48 -16
  119. data/spec/reek/spec/should_reek_of_spec.rb +1 -1
  120. data/spec/reek/spec/should_reek_only_of_spec.rb +4 -4
  121. data/spec/spec_helper.rb +4 -3
  122. data/tasks/configuration.rake +2 -2
  123. metadata +26 -14
  124. data/lib/reek/smell_detectors/detector_repository.rb +0 -66
  125. data/lib/reek/smell_detectors/smell_configuration.rb +0 -66
  126. data/lib/reek/smell_detectors/smell_warning.rb +0 -88
  127. data/tasks/mutant.rake +0 -14
  128. /data/samples/configuration/{.reek → more_than_one_configuration_file/regular.reek} +0 -0
@@ -63,7 +63,7 @@ Feature: Basic smell detection
63
63
  UncommunicativeVariableName: Inline::C#module_name has the variable name 'x' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
64
64
  UncommunicativeVariableName: Inline::C#parse_signature has the variable name 'x' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
65
65
  UtilityFunction: Inline::C#strip_comments doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
66
- optparse.rb -- 130 warnings:
66
+ optparse.rb -- 126 warnings:
67
67
  Attribute: OptionParser#banner is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
68
68
  Attribute: OptionParser#default_argv is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
69
69
  Attribute: OptionParser#program_name is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
@@ -127,10 +127,6 @@ Feature: Basic smell detection
127
127
  LongParameterList: OptionParser::Switch#initialize has 7 parameters [https://github.com/troessner/reek/blob/master/docs/Long-Parameter-List.md]
128
128
  LongParameterList: OptionParser::Switch#summarize has 5 parameters [https://github.com/troessner/reek/blob/master/docs/Long-Parameter-List.md]
129
129
  ManualDispatch: OptionParser#make_switch manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
130
- ManualDispatch: OptionParser#make_switch manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
131
- ManualDispatch: OptionParser#make_switch manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
132
- ManualDispatch: OptionParser::List#accept manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
133
- ManualDispatch: OptionParser::List#accept manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
134
130
  ManualDispatch: OptionParser::List#accept manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
135
131
  ManualDispatch: OptionParser::List#add_banner manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
136
132
  ManualDispatch: OptionParser::List#summarize manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
@@ -194,7 +190,7 @@ Feature: Basic smell detection
194
190
  UnusedParameters: OptionParser::Completion#convert has unused parameter 'opt' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
195
191
  UnusedParameters: OptionParser::Switch::NoArgument#parse has unused parameter 'argv' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
196
192
  UnusedParameters: OptionParser::Switch::OptionalArgument#parse has unused parameter 'argv' [https://github.com/troessner/reek/blob/master/docs/Unused-Parameters.md]
197
- redcloth.rb -- 111 warnings:
193
+ redcloth.rb -- 110 warnings:
198
194
  Attribute: RedCloth#filter_html is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
199
195
  Attribute: RedCloth#filter_styles is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
200
196
  Attribute: RedCloth#hard_breaks is a writable attribute [https://github.com/troessner/reek/blob/master/docs/Attribute.md]
@@ -238,7 +234,6 @@ Feature: Basic smell detection
238
234
  LongParameterList: RedCloth#textile_fn_ has 5 parameters [https://github.com/troessner/reek/blob/master/docs/Long-Parameter-List.md]
239
235
  LongParameterList: RedCloth#textile_p has 4 parameters [https://github.com/troessner/reek/blob/master/docs/Long-Parameter-List.md]
240
236
  ManualDispatch: RedCloth#block_textile_prefix manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
241
- ManualDispatch: RedCloth#block_textile_prefix manually dispatches method call [https://github.com/troessner/reek/blob/master/docs/Manual-Dispatch.md]
242
237
  NestedIterators: RedCloth#block_textile_lists contains iterators nested 3 deep [https://github.com/troessner/reek/blob/master/docs/Nested-Iterators.md]
243
238
  NestedIterators: RedCloth#block_textile_table contains iterators nested 2 deep [https://github.com/troessner/reek/blob/master/docs/Nested-Iterators.md]
244
239
  NestedIterators: RedCloth#block_textile_table contains iterators nested 3 deep [https://github.com/troessner/reek/blob/master/docs/Nested-Iterators.md]
@@ -306,5 +301,5 @@ Feature: Basic smell detection
306
301
  UtilityFunction: RedCloth#lT doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
307
302
  UtilityFunction: RedCloth#no_textile doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
308
303
  UtilityFunction: RedCloth#v_align doesn't depend on instance state (maybe move it to another class?) [https://github.com/troessner/reek/blob/master/docs/Utility-Function.md]
309
- 292 total warnings
304
+ 287 total warnings
310
305
  """
@@ -23,17 +23,17 @@ Then /^stdout includes "(.*)"$/ do |text|
23
23
  end
24
24
 
25
25
  Then /^it succeeds$/ do
26
- success = Reek::CLI::Options::DEFAULT_SUCCESS_EXIT_CODE
26
+ success = Reek::CLI::Status::DEFAULT_SUCCESS_EXIT_CODE
27
27
  expect(last_command_started).to have_exit_status(success)
28
28
  end
29
29
 
30
30
  Then /^the exit status indicates an error$/ do
31
- error = Reek::CLI::Options::DEFAULT_ERROR_EXIT_CODE
31
+ error = Reek::CLI::Status::DEFAULT_ERROR_EXIT_CODE
32
32
  expect(last_command_started).to have_exit_status(error)
33
33
  end
34
34
 
35
35
  Then /^the exit status indicates smells$/ do
36
- smells = Reek::CLI::Options::DEFAULT_FAILURE_EXIT_CODE
36
+ smells = Reek::CLI::Status::DEFAULT_FAILURE_EXIT_CODE
37
37
  expect(last_command_started).to have_exit_status(smells)
38
38
  end
39
39
 
@@ -46,8 +46,8 @@ Then /^it reports:$/ do |report|
46
46
  end
47
47
 
48
48
  Then /^it reports this yaml:$/ do |expected_yaml|
49
- expected_warnings = YAML.load(expected_yaml.chomp)
50
- actual_warnings = YAML.load(last_command_started.stdout)
49
+ expected_warnings = YAML.safe_load(expected_yaml.chomp)
50
+ actual_warnings = YAML.safe_load(last_command_started.stdout)
51
51
  expect(actual_warnings).to eq expected_warnings
52
52
  end
53
53
 
@@ -1,5 +1,4 @@
1
- Feature:
2
-
1
+ Feature: Auto-generate a todo file
3
2
  Write a Reek configuration as a kind of todo list that will prevent Reek
4
3
  from reporting smells on the current code.
5
4
  This can then be worked on later on.
@@ -6,8 +6,6 @@ module Reek
6
6
  # of an abstract syntax tree.
7
7
  #
8
8
  class ReferenceCollector
9
- STOP_NODES = [:class, :module, :def, :defs].freeze
10
-
11
9
  def initialize(ast)
12
10
  @ast = ast
13
11
  end
@@ -22,12 +20,12 @@ module Reek
22
20
 
23
21
  def explicit_self_calls
24
22
  [:self, :super, :zsuper, :ivar, :ivasgn].flat_map do |node_type|
25
- ast.each_node(node_type, STOP_NODES)
23
+ ast.each_node(node_type)
26
24
  end
27
25
  end
28
26
 
29
27
  def implicit_self_calls
30
- ast.each_node(:send, STOP_NODES).reject(&:receiver)
28
+ ast.each_node(:send).reject(&:receiver)
31
29
  end
32
30
  end
33
31
  end
@@ -8,11 +8,6 @@ module Reek
8
8
  children.first
9
9
  end
10
10
 
11
- # Other is a symbol?
12
- def ==(other)
13
- name == other
14
- end
15
-
16
11
  def marked_unused?
17
12
  plain_name.start_with?('_')
18
13
  end
@@ -4,6 +4,7 @@ module Reek
4
4
  module SexpExtensions
5
5
  # Utility methods for :const nodes.
6
6
  module ConstNode
7
+ # TODO: name -> full_name, simple_name -> name
7
8
  def name
8
9
  if namespace
9
10
  "#{namespace.format_to_ruby}::#{simple_name}"
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative 'options'
3
+ require_relative 'status'
3
4
  require_relative '../configuration/app_configuration'
4
5
  require_relative '../source/source_locator'
5
6
  require_relative 'command/report_command'
@@ -17,7 +18,6 @@ module Reek
17
18
 
18
19
  def initialize(argv)
19
20
  @options = configure_options(argv)
20
- @status = options.success_exit_code
21
21
  @configuration = configure_app_configuration(options.config_file)
22
22
  @command = command_class.new(options: options,
23
23
  sources: sources,
@@ -30,21 +30,20 @@ module Reek
30
30
 
31
31
  private
32
32
 
33
- attr_accessor :status
34
33
  attr_reader :command, :options
35
34
 
36
35
  def configure_options(argv)
37
36
  Options.new(argv).parse
38
37
  rescue OptionParser::InvalidOption => error
39
- $stderr.puts "Error: #{error}"
40
- exit Options::DEFAULT_ERROR_EXIT_CODE
38
+ warn "Error: #{error}"
39
+ exit Status::DEFAULT_ERROR_EXIT_CODE
41
40
  end
42
41
 
43
42
  def configure_app_configuration(config_file)
44
43
  Configuration::AppConfiguration.from_path(config_file)
45
44
  rescue Reek::Configuration::ConfigFileException => error
46
- $stderr.puts "Error: #{error}"
47
- exit Options::DEFAULT_ERROR_EXIT_CODE
45
+ warn "Error: #{error}"
46
+ exit Status::DEFAULT_ERROR_EXIT_CODE
48
47
  end
49
48
 
50
49
  def command_class
@@ -80,11 +79,11 @@ module Reek
80
79
  end
81
80
 
82
81
  def working_directory_as_source
83
- Source::SourceLocator.new(['.'], configuration: configuration).sources
82
+ Source::SourceLocator.new(['.'], configuration: configuration, options: options).sources
84
83
  end
85
84
 
86
85
  def sources_from_argv
87
- Source::SourceLocator.new(argv).sources
86
+ Source::SourceLocator.new(argv, configuration: configuration, options: options).sources
88
87
  end
89
88
 
90
89
  def source_from_pipe
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative 'base_command'
3
3
  require_relative '../../examiner'
4
+ require_relative '../../logging_error_handler'
4
5
  require_relative '../../report'
5
6
 
6
7
  module Reek
@@ -23,7 +24,8 @@ module Reek
23
24
  sources.each do |source|
24
25
  reporter.add_examiner Examiner.new(source,
25
26
  filter_by_smells: smell_names,
26
- configuration: configuration)
27
+ configuration: configuration,
28
+ error_handler: LoggingErrorHandler.new)
27
29
  end
28
30
  end
29
31
 
@@ -2,6 +2,7 @@
2
2
  require 'optparse'
3
3
  require 'rainbow'
4
4
  require_relative '../version'
5
+ require_relative 'status'
5
6
 
6
7
  module Reek
7
8
  module CLI
@@ -10,15 +11,11 @@ module Reek
10
11
  #
11
12
  # See {file:docs/Command-Line-Options.md} for details.
12
13
  #
13
- # :reek:TooManyInstanceVariables: { max_instance_variables: 11 }
14
- # :reek:TooManyMethods: { max_methods: 17 }
14
+ # :reek:TooManyInstanceVariables: { max_instance_variables: 12 }
15
+ # :reek:TooManyMethods: { max_methods: 18 }
15
16
  # :reek:Attribute: { enabled: false }
16
17
  #
17
18
  class Options
18
- DEFAULT_SUCCESS_EXIT_CODE = 0
19
- DEFAULT_ERROR_EXIT_CODE = 1
20
- DEFAULT_FAILURE_EXIT_CODE = 2
21
-
22
19
  attr_reader :argv, :parser, :smells_to_detect
23
20
  attr_accessor :colored,
24
21
  :config_file,
@@ -30,7 +27,8 @@ module Reek
30
27
  :sorting,
31
28
  :success_exit_code,
32
29
  :failure_exit_code,
33
- :generate_todo_list
30
+ :generate_todo_list,
31
+ :force_exclusion
34
32
 
35
33
  def initialize(argv = [])
36
34
  @argv = argv
@@ -41,9 +39,10 @@ module Reek
41
39
  @show_links = true
42
40
  @smells_to_detect = []
43
41
  @colored = tty_output?
44
- @success_exit_code = DEFAULT_SUCCESS_EXIT_CODE
45
- @failure_exit_code = DEFAULT_FAILURE_EXIT_CODE
42
+ @success_exit_code = Status::DEFAULT_SUCCESS_EXIT_CODE
43
+ @failure_exit_code = Status::DEFAULT_FAILURE_EXIT_CODE
46
44
  @generate_todo_list = false
45
+ @force_exclusion = false
47
46
 
48
47
  set_up_parser
49
48
  end
@@ -54,6 +53,10 @@ module Reek
54
53
  self
55
54
  end
56
55
 
56
+ def force_exclusion?
57
+ @force_exclusion
58
+ end
59
+
57
60
  private
58
61
 
59
62
  # TTY output generally means the output will not undergo further
@@ -98,7 +101,11 @@ module Reek
98
101
  parser.on('-c', '--config FILE', 'Read configuration options from FILE') do |file|
99
102
  self.config_file = Pathname.new(file)
100
103
  end
101
- parser.on('--smell SMELL', 'Detect smell SMELL (default: all enabled smells)') do |smell|
104
+ parser.on('--smell SMELL',
105
+ 'Only look for a specific smell.',
106
+ 'Call it like this: reek --smell PrimaDonnaMethod source.rb',
107
+ 'Check out https://github.com/troessner/reek/blob/master/docs/Code-Smells.md '\
108
+ 'for a list of smells') do |smell|
102
109
  smells_to_detect << smell
103
110
  end
104
111
  end
@@ -120,7 +127,7 @@ module Reek
120
127
  end
121
128
  end
122
129
 
123
- # :reek:TooManyStatements: { max_statements: 6 }
130
+ # :reek:TooManyStatements: { max_statements: 7 }
124
131
  def set_report_formatting_options
125
132
  parser.separator "\nText format options:"
126
133
  set_up_color_option
@@ -128,6 +135,7 @@ module Reek
128
135
  set_up_location_formatting_options
129
136
  set_up_progress_formatting_options
130
137
  set_up_sorting_option
138
+ set_up_force_exclusion_option
131
139
  end
132
140
 
133
141
  def set_up_color_option
@@ -174,17 +182,25 @@ module Reek
174
182
  end
175
183
  end
176
184
 
185
+ def set_up_force_exclusion_option
186
+ parser.on('--force-exclusion',
187
+ 'Force excluding files specified in the configuration `exclude_paths`',
188
+ ' even if they are explicitly passed as arguments') do |force_exclusion|
189
+ self.force_exclusion = force_exclusion
190
+ end
191
+ end
192
+
177
193
  # :reek:DuplicateMethodCall: { max_calls: 2 }
178
194
  def set_exit_codes
179
195
  parser.separator "\nExit codes:"
180
196
  parser.on('--success-exit-code CODE',
181
197
  'The exit code when no smells are found '\
182
- "(default: #{DEFAULT_SUCCESS_EXIT_CODE})") do |option|
198
+ "(default: #{Status::DEFAULT_SUCCESS_EXIT_CODE})") do |option|
183
199
  self.success_exit_code = Integer(option)
184
200
  end
185
201
  parser.on('--failure-exit-code CODE',
186
202
  'The exit code when smells are found '\
187
- "(default: #{DEFAULT_FAILURE_EXIT_CODE})") do |option|
203
+ "(default: #{Status::DEFAULT_FAILURE_EXIT_CODE})") do |option|
188
204
  self.failure_exit_code = Integer(option)
189
205
  end
190
206
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module Reek
3
+ module CLI
4
+ module Status
5
+ DEFAULT_SUCCESS_EXIT_CODE = 0
6
+ DEFAULT_ERROR_EXIT_CODE = 1
7
+ DEFAULT_FAILURE_EXIT_CODE = 2
8
+ end
9
+ end
10
+ end
@@ -4,6 +4,7 @@ require 'yaml'
4
4
 
5
5
  require_relative 'smell_detectors/base_detector'
6
6
  require_relative 'errors/bad_detector_in_comment_error'
7
+ require_relative 'errors/bad_detector_configuration_key_in_comment_error'
7
8
  require_relative 'errors/garbage_detector_configuration_in_comment_error'
8
9
 
9
10
  module Reek
@@ -45,7 +46,8 @@ module Reek
45
46
  line: line,
46
47
  source: source,
47
48
  options: options).validate
48
- @config.merge! detector_name => YAML.load(options || DISABLE_DETECTOR_CONFIGURATION)
49
+ @config.merge! detector_name => YAML.safe_load(options || DISABLE_DETECTOR_CONFIGURATION,
50
+ [Regexp])
49
51
  end
50
52
  end
51
53
 
@@ -75,10 +77,10 @@ module Reek
75
77
  # 2.) Garbage in the detector configuration like { thats: a: bad: config }
76
78
  # 3.) Unknown configuration keys (e.g. by doing a simple typo: "exclude" vs. "exlude" )
77
79
  # 4.) Bad data types given as values for those keys
78
- # This class validates [1] and [2] at the moment but will also validate [3]
79
- # and [4] in the future.
80
+ # This class validates [1], [2] and [3] at the moment but will also validate
81
+ # [4] in the future.
80
82
  #
81
- # :reek:TooManyInstanceVariables: { max_instance_variables: 5 }
83
+ # :reek:TooManyInstanceVariables: { max_instance_variables: 7 }
82
84
  class CodeCommentValidator
83
85
  #
84
86
  # @param detector_name [String] - the detector class that was parsed out of the original
@@ -94,16 +96,20 @@ module Reek
94
96
  @line = line
95
97
  @source = source
96
98
  @options = options
99
+ @detector_class = nil # We only know this one after our first initial checks
100
+ @parsed_options = nil # We only know this one after our first initial checks
97
101
  end
98
102
 
99
103
  #
100
104
  # Method can raise the following errors:
101
105
  # * Errors::BadDetectorInCommentError
102
106
  # * Errors::GarbageDetectorConfigurationInCommentError
107
+ # * Errors::BadDetectorConfigurationKeyInCommentError
103
108
  # @return [undefined]
104
109
  def validate
105
110
  escalate_bad_detector
106
111
  escalate_bad_detector_configuration
112
+ escalate_unknown_configuration_key
107
113
  end
108
114
 
109
115
  private
@@ -112,7 +118,9 @@ module Reek
112
118
  :original_comment,
113
119
  :line,
114
120
  :source,
115
- :options
121
+ :options,
122
+ :detector_class,
123
+ :parsed_options
116
124
 
117
125
  def escalate_bad_detector
118
126
  return if SmellDetectors::BaseDetector.valid_detector?(detector_name)
@@ -123,13 +131,47 @@ module Reek
123
131
  end
124
132
 
125
133
  def escalate_bad_detector_configuration
126
- YAML.load(options || CodeComment::DISABLE_DETECTOR_CONFIGURATION)
134
+ @parsed_options = YAML.safe_load(options || CodeComment::DISABLE_DETECTOR_CONFIGURATION,
135
+ [Regexp])
127
136
  rescue Psych::SyntaxError
128
137
  raise Errors::GarbageDetectorConfigurationInCommentError, detector_name: detector_name,
129
138
  original_comment: original_comment,
130
139
  source: source,
131
140
  line: line
132
141
  end
142
+
143
+ def escalate_unknown_configuration_key
144
+ @detector_class = SmellDetectors::BaseDetector.to_detector(detector_name)
145
+
146
+ return if given_keys_legit?
147
+ raise Errors::BadDetectorConfigurationKeyInCommentError, detector_name: detector_name,
148
+ offensive_keys: configuration_keys_difference,
149
+ original_comment: original_comment,
150
+ source: source,
151
+ line: line
152
+ end
153
+
154
+ # @return [Boolean] - all keys in code comment are applicable to the detector in question
155
+ def given_keys_legit?
156
+ given_configuration_keys.subset? valid_detector_keys
157
+ end
158
+
159
+ # @return [Set] - the configuration keys that are found in the code comment
160
+ def given_configuration_keys
161
+ parsed_options.keys.map(&:to_sym).to_set
162
+ end
163
+
164
+ # @return [String] - all keys from the code comment that look bad
165
+ def configuration_keys_difference
166
+ given_configuration_keys.difference(valid_detector_keys).
167
+ to_a.map { |key| "'#{key}'" }.
168
+ join(', ')
169
+ end
170
+
171
+ # @return [Set] - all keys that are legit for the given detector
172
+ def valid_detector_keys
173
+ detector_class.configuration_keys
174
+ end
133
175
  end
134
176
  end
135
177
  end
@@ -17,42 +17,102 @@ module Reek
17
17
  # The order in which ConfigurationFileFinder tries to find such a
18
18
  # configuration file is exactly like above.
19
19
  module ConfigurationFileFinder
20
- module_function
20
+ TOO_MANY_CONFIGURATION_FILES_MESSAGE = <<-EOS.freeze
21
21
 
22
- def find_and_load(path: nil)
23
- load_from_file(find(path: path))
24
- end
22
+ Error: Found multiple configuration files %s
23
+ while scanning directory %s.
25
24
 
26
- # :reek:ControlParameter
27
- def find(path: nil, current: Pathname.pwd, home: Pathname.new(Dir.home))
28
- path || find_by_dir(current) || find_in_dir(home)
29
- end
25
+ Reek supports only one configuration file. You have 2 options now:
26
+ 1) Remove all offending files.
27
+ 2) Be specific about which one you want to load via the -c switch.
28
+
29
+ EOS
30
30
 
31
- def find_by_dir(start)
32
- start.ascend do |dir|
33
- found = find_in_dir(dir)
34
- return found if found
31
+ class << self
32
+ #
33
+ # Finds and loads a configuration file from a given path.
34
+ #
35
+ # @return [Hash]
36
+ #
37
+ def find_and_load(path: nil)
38
+ load_from_file(find(path: path))
35
39
  end
36
- end
37
40
 
38
- def find_in_dir(dir)
39
- files = dir.children.select(&:file?).sort
40
- files.find { |file| file.to_s.end_with?('.reek') }
41
- end
41
+ #
42
+ # Tries to find a configuration file via:
43
+ # * given path (e.g. via cli switch)
44
+ # * ascending down from the current directory
45
+ # * looking into the home directory
46
+ #
47
+ # @return [File|nil]
48
+ #
49
+ # :reek:ControlParameter
50
+ def find(path: nil, current: Pathname.pwd, home: Pathname.new(Dir.home))
51
+ path || find_by_dir(current) || find_in_dir(home)
52
+ end
53
+
54
+ #
55
+ # Loads a configuration file from a given path.
56
+ # Raises on invalid data.
57
+ #
58
+ # @param path [String]
59
+ # @return [Hash]
60
+ #
61
+ # :reek:TooManyStatements: { max_statements: 6 }
62
+ def load_from_file(path)
63
+ return {} unless path
64
+ begin
65
+ configuration = YAML.load_file(path) || {}
66
+ rescue => error
67
+ raise ConfigFileException, "Invalid configuration file #{path}, error is #{error}"
68
+ end
69
+
70
+ unless configuration.is_a? Hash
71
+ raise ConfigFileException, "Invalid configuration file \"#{path}\" -- Not a hash"
72
+ end
73
+ configuration
74
+ end
75
+
76
+ private
77
+
78
+ #
79
+ # Recursively traverse directories down to find a configuration file.
80
+ #
81
+ # @return [File|nil]
82
+ #
83
+ def find_by_dir(start)
84
+ start.ascend do |dir|
85
+ file = find_in_dir(dir)
86
+ return file if file
87
+ end
88
+ end
42
89
 
43
- # :reek:TooManyStatements: { max_statements: 6 }
44
- def load_from_file(path)
45
- return {} unless path
46
- begin
47
- configuration = YAML.load_file(path) || {}
48
- rescue => error
49
- raise ConfigFileException, "Invalid configuration file #{path}, error is #{error}"
90
+ #
91
+ # Checks a given directory for a configuration file and returns it.
92
+ # Raises an exception if we find more than one.
93
+ #
94
+ # @return [File|nil]
95
+ #
96
+ # :reek:FeatureEnvy
97
+ def find_in_dir(dir)
98
+ found = dir.children.select { |item| item.file? && item.to_s.end_with?('.reek') }.sort
99
+ if found.size > 1
100
+ escalate_too_many_configuration_files found, dir
101
+ else
102
+ found.first
103
+ end
50
104
  end
51
105
 
52
- unless configuration.is_a? Hash
53
- raise ConfigFileException, "Invalid configuration file \"#{path}\" -- Not a hash"
106
+ #
107
+ # Writes a proper warning message to STDERR and then exits the program.
108
+ #
109
+ # @return [undefined]
110
+ #
111
+ def escalate_too_many_configuration_files(found, directory)
112
+ offensive_files = found.map { |file| "'#{file.basename}'" }.join(', ')
113
+ warn format(TOO_MANY_CONFIGURATION_FILES_MESSAGE, offensive_files, directory)
114
+ exit 1
54
115
  end
55
- configuration
56
116
  end
57
117
  end
58
118
  end
@@ -40,8 +40,7 @@ module Reek
40
40
  names: names
41
41
  end
42
42
 
43
- def track_singleton_visibility(_visibility, _names)
44
- end
43
+ def track_singleton_visibility(_visibility, _names); end
45
44
 
46
45
  def record_use_of_self
47
46
  parent.record_use_of_self
@@ -19,7 +19,7 @@ module Reek
19
19
  # counting. Ideally `ContextBuilder` would only build up the context tree and leave the
20
20
  # statement and reference counting to the contexts.
21
21
  #
22
- # :reek:TooManyMethods: { max_methods: 30 }
22
+ # :reek:TooManyMethods: { max_methods: 31 }
23
23
  # :reek:UnusedPrivateMethod: { exclude: [ !ruby/regexp /process_/ ] }
24
24
  class ContextBuilder
25
25
  attr_reader :context_tree
@@ -228,6 +228,31 @@ module Reek
228
228
  current_context.record_use_of_self
229
229
  end
230
230
 
231
+ # Handles `super` nodes a.k.a. calls to `super` with arguments
232
+ #
233
+ # An input example that would trigger this method would be:
234
+ #
235
+ # def call_me; super(); end
236
+ #
237
+ # or
238
+ #
239
+ # def call_me; super(bar); end
240
+ #
241
+ # but not
242
+ #
243
+ # def call_me; super; end
244
+ #
245
+ # and not
246
+ #
247
+ # def call_me; super do end; end
248
+ #
249
+ # We record one reference to `self`.
250
+ #
251
+ def process_super(exp)
252
+ current_context.record_use_of_self
253
+ process(exp)
254
+ end
255
+
231
256
  # Handles `block` nodes.
232
257
  #
233
258
  # An input example that would trigger this method would be: