reek 4.7.3 → 5.0.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 (248) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +18 -3
  4. data/.simplecov +1 -0
  5. data/.travis.yml +3 -4
  6. data/.yardopts +1 -1
  7. data/CHANGELOG.md +45 -0
  8. data/Gemfile +4 -4
  9. data/README.md +134 -104
  10. data/Rakefile +16 -3
  11. data/bin/reek +1 -3
  12. data/docs/API.md +2 -9
  13. data/docs/Basic-Smell-Options.md +51 -11
  14. data/docs/Code-Smells.md +1 -1
  15. data/docs/Command-Line-Options.md +14 -4
  16. data/docs/Duplicate-Method-Call.md +49 -1
  17. data/docs/Feature-Envy.md +44 -0
  18. data/docs/How-To-Write-New-Detectors.md +2 -3
  19. data/docs/Instance-Variable-Assumption.md +1 -1
  20. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  21. data/docs/Rake-Task.md +1 -1
  22. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  23. data/docs/Reek-Driven-Development.md +1 -1
  24. data/docs/Uncommunicative-Method-Name.md +45 -6
  25. data/docs/Uncommunicative-Module-Name.md +49 -7
  26. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  27. data/docs/Uncommunicative-Variable-Name.md +73 -2
  28. data/docs/Unused-Private-Method.md +2 -2
  29. data/docs/defaults.reek.yml +129 -0
  30. data/docs/yard_plugin.rb +1 -0
  31. data/features/command_line_interface/basic_usage.feature +2 -2
  32. data/features/command_line_interface/options.feature +46 -4
  33. data/features/command_line_interface/show_progress.feature +4 -4
  34. data/features/command_line_interface/smell_selection.feature +1 -1
  35. data/features/command_line_interface/smells_count.feature +6 -6
  36. data/features/command_line_interface/stdin.feature +30 -8
  37. data/features/configuration_files/accept_setting.feature +45 -28
  38. data/features/configuration_files/directory_specific_directives.feature +78 -73
  39. data/features/configuration_files/exclude_directives.feature +11 -10
  40. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  41. data/features/configuration_files/masking_smells.feature +38 -9
  42. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  43. data/features/configuration_files/reject_setting.feature +52 -41
  44. data/features/configuration_files/schema_validation.feature +59 -0
  45. data/features/configuration_files/unused_private_method.feature +18 -16
  46. data/features/configuration_loading.feature +53 -10
  47. data/features/configuration_via_source_comments/erroneous_source_comments.feature +2 -2
  48. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  49. data/features/locales.feature +2 -2
  50. data/features/rake_task/rake_task.feature +15 -15
  51. data/features/reports/json.feature +3 -3
  52. data/features/reports/reports.feature +34 -34
  53. data/features/reports/yaml.feature +3 -3
  54. data/features/rspec_matcher.feature +9 -1
  55. data/features/samples.feature +287 -287
  56. data/features/step_definitions/reek_steps.rb +4 -0
  57. data/features/step_definitions/sample_file_steps.rb +9 -4
  58. data/features/support/env.rb +2 -2
  59. data/features/todo_list.feature +26 -23
  60. data/lib/reek/ast/node.rb +40 -55
  61. data/lib/reek/ast/object_refs.rb +1 -1
  62. data/lib/reek/ast/reference_collector.rb +2 -4
  63. data/lib/reek/ast/sexp_extensions/case.rb +1 -1
  64. data/lib/reek/ast/sexp_extensions/if.rb +8 -1
  65. data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
  66. data/lib/reek/ast/sexp_extensions/methods.rb +4 -6
  67. data/lib/reek/cli/application.rb +4 -3
  68. data/lib/reek/cli/command/report_command.rb +1 -2
  69. data/lib/reek/cli/command/todo_list_command.rb +8 -8
  70. data/lib/reek/cli/options.rb +29 -14
  71. data/lib/reek/cli/silencer.rb +14 -3
  72. data/lib/reek/code_comment.rb +14 -16
  73. data/lib/reek/configuration/app_configuration.rb +32 -28
  74. data/lib/reek/configuration/configuration_converter.rb +110 -0
  75. data/lib/reek/configuration/configuration_file_finder.rb +15 -40
  76. data/lib/reek/configuration/configuration_validator.rb +12 -23
  77. data/lib/reek/configuration/default_directive.rb +17 -3
  78. data/lib/reek/configuration/directory_directives.rb +17 -11
  79. data/lib/reek/configuration/excluded_paths.rb +1 -1
  80. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  81. data/lib/reek/configuration/schema.yml +210 -0
  82. data/lib/reek/configuration/schema_validator.rb +38 -0
  83. data/lib/reek/context/attribute_context.rb +1 -1
  84. data/lib/reek/context/code_context.rb +8 -11
  85. data/lib/reek/context/method_context.rb +7 -12
  86. data/lib/reek/context/module_context.rb +4 -4
  87. data/lib/reek/context_builder.rb +11 -11
  88. data/lib/reek/detector_repository.rb +6 -0
  89. data/lib/reek/documentation_link.rb +28 -0
  90. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +13 -12
  91. data/lib/reek/errors/bad_detector_in_comment_error.rb +11 -10
  92. data/lib/reek/errors/base_error.rb +3 -0
  93. data/lib/reek/errors/config_file_error.rb +11 -0
  94. data/lib/reek/errors/encoding_error.rb +16 -11
  95. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +11 -10
  96. data/lib/reek/errors/incomprehensible_source_error.rb +20 -22
  97. data/lib/reek/errors/syntax_error.rb +41 -0
  98. data/lib/reek/examiner.rb +19 -25
  99. data/lib/reek/logging_error_handler.rb +7 -5
  100. data/lib/reek/rake/task.rb +3 -3
  101. data/lib/reek/report/base_report.rb +8 -12
  102. data/lib/reek/report/code_climate/code_climate_configuration.rb +1 -1
  103. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -10
  104. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  105. data/lib/reek/report/heading_formatter.rb +54 -0
  106. data/lib/reek/report/json_report.rb +1 -1
  107. data/lib/reek/report/location_formatter.rb +40 -0
  108. data/lib/reek/report/progress_formatter.rb +79 -0
  109. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  110. data/lib/reek/report/text_report.rb +1 -2
  111. data/lib/reek/report/xml_report.rb +3 -3
  112. data/lib/reek/report/yaml_report.rb +1 -1
  113. data/lib/reek/report.rb +15 -10
  114. data/lib/reek/smell_configuration.rb +2 -2
  115. data/lib/reek/smell_detectors/attribute.rb +0 -1
  116. data/lib/reek/smell_detectors/base_detector.rb +9 -12
  117. data/lib/reek/smell_detectors/boolean_parameter.rb +0 -1
  118. data/lib/reek/smell_detectors/class_variable.rb +3 -11
  119. data/lib/reek/smell_detectors/control_parameter.rb +17 -32
  120. data/lib/reek/smell_detectors/data_clump.rb +3 -4
  121. data/lib/reek/smell_detectors/duplicate_method_call.rb +6 -7
  122. data/lib/reek/smell_detectors/feature_envy.rb +1 -1
  123. data/lib/reek/smell_detectors/instance_variable_assumption.rb +1 -10
  124. data/lib/reek/smell_detectors/irresponsible_module.rb +0 -1
  125. data/lib/reek/smell_detectors/long_parameter_list.rb +1 -2
  126. data/lib/reek/smell_detectors/long_yield_list.rb +2 -3
  127. data/lib/reek/smell_detectors/manual_dispatch.rb +3 -3
  128. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +6 -7
  129. data/lib/reek/smell_detectors/module_initialize.rb +1 -2
  130. data/lib/reek/smell_detectors/nested_iterators.rb +6 -6
  131. data/lib/reek/smell_detectors/nil_check.rb +0 -1
  132. data/lib/reek/smell_detectors/repeated_conditional.rb +3 -4
  133. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +0 -1
  134. data/lib/reek/smell_detectors/too_many_constants.rb +2 -3
  135. data/lib/reek/smell_detectors/too_many_instance_variables.rb +1 -2
  136. data/lib/reek/smell_detectors/too_many_methods.rb +1 -2
  137. data/lib/reek/smell_detectors/too_many_statements.rb +1 -2
  138. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +2 -3
  139. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +2 -3
  140. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +2 -3
  141. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +6 -7
  142. data/lib/reek/smell_detectors/unused_parameters.rb +0 -1
  143. data/lib/reek/smell_detectors/unused_private_method.rb +0 -1
  144. data/lib/reek/smell_detectors/utility_function.rb +2 -3
  145. data/lib/reek/smell_detectors.rb +1 -2
  146. data/lib/reek/smell_warning.rb +15 -8
  147. data/lib/reek/source/source_code.rb +50 -72
  148. data/lib/reek/source/source_locator.rb +7 -7
  149. data/lib/reek/spec/should_reek.rb +2 -2
  150. data/lib/reek/spec/should_reek_of.rb +9 -16
  151. data/lib/reek/spec/should_reek_only_of.rb +4 -4
  152. data/lib/reek/spec.rb +6 -6
  153. data/lib/reek/tree_dresser.rb +5 -5
  154. data/lib/reek/version.rb +1 -1
  155. data/reek.gemspec +5 -5
  156. data/samples/checkstyle.xml +1 -1
  157. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  158. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  159. data/samples/configuration/full_configuration.reek +8 -4
  160. data/samples/configuration/full_mask.reek +5 -4
  161. data/samples/configuration/partial_mask.reek +3 -2
  162. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  163. data/samples/paths.rb +5 -4
  164. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  165. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  166. data/spec/factories/factories.rb +2 -13
  167. data/spec/reek/ast/node_spec.rb +103 -10
  168. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  169. data/spec/reek/ast/sexp_extensions_spec.rb +2 -2
  170. data/spec/reek/cli/application_spec.rb +50 -38
  171. data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
  172. data/spec/reek/cli/silencer_spec.rb +28 -0
  173. data/spec/reek/code_comment_spec.rb +31 -38
  174. data/spec/reek/configuration/app_configuration_spec.rb +46 -33
  175. data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -49
  176. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  177. data/spec/reek/configuration/directory_directives_spec.rb +6 -7
  178. data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
  179. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  180. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  181. data/spec/reek/context/code_context_spec.rb +60 -96
  182. data/spec/reek/context/ghost_context_spec.rb +1 -1
  183. data/spec/reek/context/root_context_spec.rb +1 -1
  184. data/spec/reek/documentation_link_spec.rb +20 -0
  185. data/spec/reek/errors/base_error_spec.rb +13 -0
  186. data/spec/reek/examiner_spec.rb +100 -30
  187. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -80
  188. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  189. data/spec/reek/report/json_report_spec.rb +13 -46
  190. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  191. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  192. data/spec/reek/report/text_report_spec.rb +4 -4
  193. data/spec/reek/report/xml_report_spec.rb +3 -3
  194. data/spec/reek/report/yaml_report_spec.rb +9 -38
  195. data/spec/reek/report_spec.rb +3 -3
  196. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  197. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  198. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  199. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  200. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  201. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +9 -9
  202. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  203. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  204. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  205. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  206. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  207. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  208. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +6 -4
  209. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
  210. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  211. data/spec/reek/smell_detectors/unused_private_method_spec.rb +10 -10
  212. data/spec/reek/smell_detectors/utility_function_spec.rb +5 -5
  213. data/spec/reek/smell_warning_spec.rb +12 -8
  214. data/spec/reek/source/source_code_spec.rb +17 -43
  215. data/spec/reek/source/source_locator_spec.rb +17 -17
  216. data/spec/reek/spec/should_reek_of_spec.rb +7 -11
  217. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  218. data/spec/reek/spec/should_reek_spec.rb +3 -3
  219. data/spec/reek/spec/smell_matcher_spec.rb +3 -3
  220. data/spec/reek/tree_dresser_spec.rb +12 -17
  221. data/spec/spec_helper.rb +6 -17
  222. data/tasks/configuration.rake +8 -5
  223. metadata +71 -41
  224. data/defaults.reek +0 -131
  225. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  226. data/lib/reek/report/formatter/heading_formatter.rb +0 -52
  227. data/lib/reek/report/formatter/location_formatter.rb +0 -42
  228. data/lib/reek/report/formatter/progress_formatter.rb +0 -81
  229. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  230. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -36
  231. data/lib/reek/report/formatter.rb +0 -33
  232. data/lib/reek/smell_detectors/syntax.rb +0 -37
  233. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  234. data/samples/smelly_with_inline_mask.rb +0 -8
  235. data/samples/smelly_with_modifiers.rb +0 -12
  236. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  237. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  238. data/spec/reek/smell_detectors/syntax_spec.rb +0 -17
  239. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  240. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  241. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  242. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  243. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  244. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  245. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  246. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  247. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  248. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -17,22 +17,20 @@ module Reek
17
17
  :reek: # prefix
18
18
  (\w+) # smell detector e.g.: UncommunicativeVariableName
19
19
  (
20
- :? # legacy separator
21
20
  \s*
22
21
  (\{.*?\}) # optional details in hash style e.g.: { max_methods: 30 }
23
22
  )?
24
23
  /x
25
24
  SANITIZE_REGEX = /(#|\n|\s)+/ # Matches '#', newlines and > 1 whitespaces.
26
- DISABLE_DETECTOR_CONFIGURATION = '{ enabled: false }'.freeze
25
+ DISABLE_DETECTOR_CONFIGURATION = '{ enabled: false }'
27
26
  MINIMUM_CONTENT_LENGTH = 2
28
- LEGACY_SEPARATOR = ':'.freeze
29
27
 
30
28
  attr_reader :config
31
29
 
32
30
  #
33
- # @param comment [String] - the original comment as found in the source code
34
- # @param line [Integer] - start of the expression the comment belongs to
35
- # @param source [String] - Path to source file or "string"
31
+ # @param comment [String] the original comment as found in the source code
32
+ # @param line [Integer] start of the expression the comment belongs to
33
+ # @param source [String] Path to source file or "string"
36
34
  #
37
35
  def initialize(comment:, line: nil, source: nil)
38
36
  @original_comment = comment
@@ -80,15 +78,15 @@ module Reek
80
78
  # This class validates [1], [2] and [3] at the moment but will also validate
81
79
  # [4] in the future.
82
80
  #
83
- # :reek:TooManyInstanceVariables: { max_instance_variables: 7 }
81
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 7 }
84
82
  class CodeCommentValidator
85
83
  #
86
- # @param detector_name [String] - the detector class that was parsed out of the original
84
+ # @param detector_name [String] the detector class that was parsed out of the original
87
85
  # comment, e.g. "DuplicateMethodCall" or "UnknownSmellDetector"
88
- # @param original_comment [String] - the original comment as found in the source code
89
- # @param line [Integer] - start of the expression the comment belongs to
90
- # @param source [String] - path to source file or "string"
91
- # @param options [String] - the configuration options as String for the detector that were
86
+ # @param original_comment [String] the original comment as found in the source code
87
+ # @param line [Integer] start of the expression the comment belongs to
88
+ # @param source [String] path to source file or "string"
89
+ # @param options [String] the configuration options as String for the detector that were
92
90
  # extracted from the original comment
93
91
  def initialize(detector_name:, original_comment:, line:, source:, options: {})
94
92
  @detector_name = detector_name
@@ -151,24 +149,24 @@ module Reek
151
149
  line: line
152
150
  end
153
151
 
154
- # @return [Boolean] - all keys in code comment are applicable to the detector in question
152
+ # @return [Boolean] all keys in code comment are applicable to the detector in question
155
153
  def given_keys_legit?
156
154
  given_configuration_keys.subset? valid_detector_keys
157
155
  end
158
156
 
159
- # @return [Set] - the configuration keys that are found in the code comment
157
+ # @return [Set] the configuration keys that are found in the code comment
160
158
  def given_configuration_keys
161
159
  parsed_options.keys.map(&:to_sym).to_set
162
160
  end
163
161
 
164
- # @return [String] - all keys from the code comment that look bad
162
+ # @return [String] all keys from the code comment that look bad
165
163
  def configuration_keys_difference
166
164
  given_configuration_keys.difference(valid_detector_keys).
167
165
  to_a.map { |key| "'#{key}'" }.
168
166
  join(', ')
169
167
  end
170
168
 
171
- # @return [Set] - all keys that are legit for the given detector
169
+ # @return [Set] all keys that are legit for the given detector
172
170
  def valid_detector_keys
173
171
  detector_class.configuration_keys
174
172
  end
@@ -15,20 +15,30 @@ module Reek
15
15
  # @public
16
16
  class AppConfiguration
17
17
  include ConfigurationValidator
18
- EXCLUDE_PATHS_KEY = 'exclude_paths'.freeze
18
+ EXCLUDE_PATHS_KEY = 'exclude_paths'
19
+ DIRECTORIES_KEY = 'directories'
20
+ DETECTORS_KEY = 'detectors'
19
21
 
20
- # Instantiate a configuration via given path, or the default path.
22
+ # Instantiate a configuration via the given path.
21
23
  #
22
- # @param path [Pathname] the path to the config file, or nil to use the
23
- # default path.
24
+ # @param path [Pathname] the path to the config file.
24
25
  #
25
26
  # @return [AppConfiguration]
26
27
  #
27
28
  # @public
28
- def self.from_path(path = nil)
29
- allocate.tap do |instance|
30
- instance.instance_eval { find_and_load(path: path) }
31
- end
29
+ def self.from_path(path)
30
+ values = ConfigurationFileFinder.find_and_load(path: path)
31
+ new(values: values)
32
+ end
33
+
34
+ # Instantiate a configuration via the default path.
35
+ #
36
+ # @return [AppConfiguration]
37
+ #
38
+ # @public
39
+ def self.from_default_path
40
+ values = ConfigurationFileFinder.find_and_load(path: nil)
41
+ new(values: values)
32
42
  end
33
43
 
34
44
  # Instantiate a configuration by passing everything in.
@@ -40,21 +50,17 @@ module Reek
40
50
  # @return [AppConfiguration]
41
51
  #
42
52
  # @public
43
- def self.from_hash(hash = {})
44
- allocate.tap do |instance|
45
- instance.instance_eval do
46
- load_values hash
47
- end
48
- end
53
+ def self.from_hash(hash)
54
+ new(values: hash)
49
55
  end
50
56
 
51
57
  def self.default
52
- new
58
+ new(values: {})
53
59
  end
54
60
 
55
61
  # Returns the directive for a given directory.
56
62
  #
57
- # @param source_via [String] - the source of the code inspected
63
+ # @param source_via [String] the source of the code inspected
58
64
  #
59
65
  # @return [Hash] the directory directive for the source with the default directive
60
66
  # reverse-merged into it.
@@ -67,18 +73,22 @@ module Reek
67
73
  excluded_paths.map(&:expand_path).include?(path.expand_path)
68
74
  end
69
75
 
70
- def load_values(configuration_hash)
71
- configuration_hash.each do |key, value|
76
+ def load_values(values)
77
+ values.each do |key, value|
72
78
  if key == EXCLUDE_PATHS_KEY
73
79
  excluded_paths.add value
74
- elsif smell_type?(key)
75
- default_directive.add key, value
76
- else
77
- directory_directives.add key, value
80
+ elsif key == DIRECTORIES_KEY
81
+ directory_directives.add value
82
+ elsif key == DETECTORS_KEY
83
+ default_directive.add value
78
84
  end
79
85
  end
80
86
  end
81
87
 
88
+ def initialize(values: {})
89
+ load_values(values)
90
+ end
91
+
82
92
  private
83
93
 
84
94
  attr_writer :directory_directives, :default_directive, :excluded_paths
@@ -94,12 +104,6 @@ module Reek
94
104
  def excluded_paths
95
105
  @excluded_paths ||= [].extend(ExcludedPaths)
96
106
  end
97
-
98
- def find_and_load(path: nil)
99
- configuration_hash = ConfigurationFileFinder.find_and_load(path: path)
100
-
101
- load_values(configuration_hash)
102
- end
103
107
  end
104
108
  end
105
109
  end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './configuration_validator'
4
+
5
+ module Reek
6
+ module Configuration
7
+ # Responsible for converting marked strings coming from the outside world
8
+ # into proper regexes.
9
+ class ConfigurationConverter
10
+ REGEXABLE_ATTRIBUTES = %w(accept reject exclude).freeze
11
+ include ConfigurationValidator
12
+ attr_reader :configuration
13
+
14
+ # @param configuration [Hash] e.g.
15
+ #
16
+ # detectors => {
17
+ # "UnusedPrivateMethod" => {"exclude"=>["/exclude regexp/"]},
18
+ # "UncommunicativeMethodName"=>{"reject"=>["reject name"], "accept"=>["accept name"]
19
+ # },
20
+ # directories => {
21
+ # "app/controllers" => {
22
+ # "UnusedPrivateMethod" => {"exclude"=>["/exclude regexp/"]},
23
+ # "UncommunicativeMethodName"=>{"reject"=>["reject name"], "accept"=>["accept name"]}
24
+ # }
25
+ # }
26
+ def initialize(configuration)
27
+ @configuration = configuration
28
+ end
29
+
30
+ # Converts all marked strings across the whole configuration to regexes.
31
+ # @return [Hash]
32
+ #
33
+ def convert
34
+ strings_to_regexes_for_detectors
35
+ strings_to_regexes_for_directories
36
+
37
+ configuration
38
+ end
39
+
40
+ private
41
+
42
+ # @param value [String] String that is potentially marked as regex, e.g. "/foobar/".
43
+ # @return [Bool] if the string in question is marked as regex.
44
+ #
45
+ # @quality :reek:UtilityFunction
46
+ def marked_as_regex?(value)
47
+ value.start_with?('/') && value.end_with?('/')
48
+ end
49
+
50
+ # @param value [value] String that is potentially marked as regex, e.g. "/foobar/".
51
+ # @return [Regexp] e.g. /foobar/.
52
+ #
53
+ def to_regex(value)
54
+ marked_as_regex?(value) ? Regexp.new(value[1..-2]) : value
55
+ end
56
+
57
+ # @param detector_configuration [Hash] e.g.
58
+ # { "UnusedPrivateMethod" => {"exclude"=>["/exclude regexp/"] }
59
+ # @return [Array] all the attributes from the detector configuration that potentially contain regexes.
60
+ # Using this example above this would just be "exclude".
61
+ #
62
+ # @quality :reek:UtilityFunction
63
+ def convertible_attributes(detector_configuration)
64
+ detector_configuration.keys & REGEXABLE_ATTRIBUTES
65
+ end
66
+
67
+ # Iterates over our detector configuration and converts all marked strings into regexes.
68
+ # @return nil
69
+ #
70
+ # @quality :reek:DuplicateMethodCall { max_calls: 3 }
71
+ # @quality :reek:NestedIterators { max_allowed_nesting: 3 }
72
+ # @quality :reek:TooManyStatements { max_statements: 6 }
73
+ def strings_to_regexes_for_detectors
74
+ return unless configuration[AppConfiguration::DETECTORS_KEY]
75
+
76
+ configuration[AppConfiguration::DETECTORS_KEY].tap do |detectors|
77
+ detectors.keys.each do |detector|
78
+ convertible_attributes(detectors[detector]).each do |attribute|
79
+ detectors[detector][attribute] = detectors[detector][attribute].map do |item|
80
+ to_regex item
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # Iterates over our directory configuration and converts all marked strings into regexes.
88
+ # @return nil
89
+ #
90
+ # @quality :reek:DuplicateMethodCall { max_calls: 3 }
91
+ # @quality :reek:NestedIterators { max_allowed_nesting: 4 }
92
+ # @quality :reek:TooManyStatements { max_statements: 7 }
93
+ def strings_to_regexes_for_directories
94
+ return unless configuration[AppConfiguration::DIRECTORIES_KEY]
95
+
96
+ configuration[AppConfiguration::DIRECTORIES_KEY].tap do |directories|
97
+ directories.keys.each do |directory|
98
+ directories[directory].each do |detector, configuration|
99
+ convertible_attributes(configuration).each do |attribute|
100
+ directories[directory][detector][attribute] = directories[directory][detector][attribute].map do |item|
101
+ to_regex item
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,35 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
+ require_relative './configuration_converter'
5
+ require_relative './schema_validator'
6
+ require_relative '../errors/config_file_error'
4
7
 
5
8
  module Reek
6
9
  module Configuration
7
- # Raised when config file is not properly readable.
8
- class ConfigFileException < StandardError; end
9
10
  #
10
11
  # ConfigurationFileFinder is responsible for finding Reek's configuration.
11
12
  #
12
13
  # There are 3 ways of passing `reek` a configuration file:
13
14
  # 1. Using the cli "-c" switch
14
- # 2. Having a file ending with .reek either in your current working
15
+ # 2. Having a file .reek.yml either in your current working
15
16
  # directory or in a parent directory
16
- # 3. Having a file ending with .reek in your HOME directory
17
+ # 3. Having a file .reek.yml in your HOME directory
17
18
  #
18
19
  # The order in which ConfigurationFileFinder tries to find such a
19
20
  # configuration file is exactly like above.
20
21
  module ConfigurationFileFinder
21
- TOO_MANY_CONFIGURATION_FILES_MESSAGE = <<-MESSAGE.freeze
22
-
23
- Error: Found multiple configuration files %s
24
- while scanning directory %s.
25
-
26
- Reek supports only one configuration file. You have 2 options now:
27
- 1) Remove all offending files.
28
- 2) Be specific about which one you want to load via the -c switch.
29
-
30
- MESSAGE
22
+ DEFAULT_FILE_NAME = '.reek.yml'
31
23
 
32
24
  class << self
25
+ include ConfigurationValidator
33
26
  #
34
27
  # Finds and loads a configuration file from a given path.
35
28
  #
@@ -47,7 +40,7 @@ module Reek
47
40
  #
48
41
  # @return [File|nil]
49
42
  #
50
- # :reek:ControlParameter
43
+ # @quality :reek:ControlParameter
51
44
  def find(path: nil, current: Pathname.pwd, home: Pathname.new(Dir.home))
52
45
  path || find_by_dir(current) || find_in_dir(home)
53
46
  end
@@ -59,19 +52,18 @@ module Reek
59
52
  # @param path [String]
60
53
  # @return [Hash]
61
54
  #
62
- # :reek:TooManyStatements: { max_statements: 6 }
55
+ # @quality :reek:TooManyStatements { max_statements: 6 }
63
56
  def load_from_file(path)
64
57
  return {} unless path
58
+
65
59
  begin
66
60
  configuration = YAML.load_file(path) || {}
67
61
  rescue StandardError => error
68
- raise ConfigFileException, "Invalid configuration file #{path}, error is #{error}"
62
+ raise Errors::ConfigFileException, "Invalid configuration file #{path}, error is #{error}"
69
63
  end
70
64
 
71
- unless configuration.is_a? Hash
72
- raise ConfigFileException, "Invalid configuration file \"#{path}\" -- Not a hash"
73
- end
74
- configuration
65
+ SchemaValidator.new(configuration).validate
66
+ ConfigurationConverter.new(configuration).convert
75
67
  end
76
68
 
77
69
  private
@@ -90,29 +82,12 @@ module Reek
90
82
 
91
83
  #
92
84
  # Checks a given directory for a configuration file and returns it.
93
- # Raises an exception if we find more than one.
94
85
  #
95
86
  # @return [File|nil]
96
87
  #
97
- # :reek:FeatureEnvy
88
+ # @quality :reek:FeatureEnvy
98
89
  def find_in_dir(dir)
99
- found = dir.children.select { |item| item.file? && item.to_s.end_with?('.reek') }.sort
100
- if found.size > 1
101
- escalate_too_many_configuration_files found, dir
102
- else
103
- found.first
104
- end
105
- end
106
-
107
- #
108
- # Writes a proper warning message to STDERR and then exits the program.
109
- #
110
- # @return [undefined]
111
- #
112
- def escalate_too_many_configuration_files(found, directory)
113
- offensive_files = found.map { |file| "'#{file.basename}'" }.join(', ')
114
- warn format(TOO_MANY_CONFIGURATION_FILES_MESSAGE, offensive_files, directory)
115
- exit 1
90
+ dir.children.detect { |item| item.file? && item.basename.to_s == DEFAULT_FILE_NAME }
116
91
  end
117
92
  end
118
93
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../errors/config_file_error'
4
+
3
5
  module Reek
4
6
  module Configuration
5
7
  #
@@ -8,37 +10,24 @@ module Reek
8
10
  module ConfigurationValidator
9
11
  private
10
12
 
11
- # :reek:UtilityFunction
13
+ # @quality :reek:UtilityFunction
12
14
  def smell_type?(key)
13
- case key
14
- when Class
15
- true
16
- when String
17
- begin
18
- Reek::SmellDetectors.const_defined? key
19
- rescue NameError
20
- false
21
- end
22
- end
15
+ Reek::SmellDetectors.const_defined? key
16
+ rescue NameError
17
+ false
23
18
  end
24
19
 
25
- # :reek:UtilityFunction
20
+ # @quality :reek:UtilityFunction
26
21
  def key_to_smell_detector(key)
27
- case key
28
- when Class
29
- key
30
- else
31
- Reek::SmellDetectors.const_get key
32
- end
33
- end
34
-
35
- def error_message_for_file_given(pathname)
36
- "Configuration error: `#{pathname}` is supposed to be a directory but is a file"
22
+ Reek::SmellDetectors.const_get key
37
23
  end
38
24
 
39
25
  def with_valid_directory(path)
40
26
  directory = Pathname.new path.to_s.chomp('/')
41
- abort(error_message_for_file_given(directory)) if directory.file?
27
+ if directory.file?
28
+ raise Errors::ConfigFileError,
29
+ "`#{directory}` is supposed to be a directory but is a file"
30
+ end
42
31
  yield directory if block_given?
43
32
  end
44
33
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './configuration_validator'
4
+
3
5
  module Reek
4
6
  module Configuration
5
7
  #
@@ -8,9 +10,21 @@ module Reek
8
10
  module DefaultDirective
9
11
  include ConfigurationValidator
10
12
 
11
- def add(key, config)
12
- detector = key_to_smell_detector(key)
13
- self[detector] = (self[detector] || {}).merge config
13
+ # Adds the configuration for detectors as default directive.
14
+ #
15
+ # @param detectors_configuration [Hash] the configuration e.g.:
16
+ # {
17
+ # :IrresponsibleModule => {:enabled=>false},
18
+ # :Attribute => {:enabled=>true}
19
+ # }
20
+ #
21
+ # @return [self]
22
+ def add(detectors_configuration)
23
+ detectors_configuration.each do |name, configuration|
24
+ detector = key_to_smell_detector(name)
25
+ self[detector] = (self[detector] || {}).merge configuration
26
+ end
27
+ self
14
28
  end
15
29
  end
16
30
  end
@@ -24,17 +24,23 @@ module Reek
24
24
 
25
25
  # Adds a directive and returns self.
26
26
  #
27
- # @param path [Pathname] the path
28
- # @param config [Hash] the configuration
27
+ # @param directory_config [Hash] the configuration e.g.:
28
+ # {
29
+ # "samples/two_smelly_files" => {:IrresponsibleModule=>{:enabled=>false}},
30
+ # "samples/three_clean_files" => {:Attribute=>{:enabled=>true}}
31
+ # }
29
32
  #
30
33
  # @return [self]
31
34
  #
32
- # :reek:NestedIterators: { max_allowed_nesting: 2 }
33
- def add(path, config)
34
- with_valid_directory(path) do |directory|
35
- self[directory] = config.each_with_object({}) do |(key, value), hash|
36
- abort(error_message_for_invalid_smell_type(key)) unless smell_type?(key)
37
- hash[key_to_smell_detector(key)] = value
35
+ # @quality :reek:NestedIterators { max_allowed_nesting: 3 }
36
+ # @quality :reek:TooManyStatements { max_statements: 6 }
37
+ def add(directory_config)
38
+ directory_config.each do |path, detector_config|
39
+ with_valid_directory(path) do |directory|
40
+ self[directory] = detector_config.each_with_object({}) do |(key, value), hash|
41
+ abort(error_message_for_invalid_smell_type(key)) unless smell_type?(key)
42
+ hash[key_to_smell_detector(key)] = value
43
+ end
38
44
  end
39
45
  end
40
46
  self
@@ -42,8 +48,8 @@ module Reek
42
48
 
43
49
  private
44
50
 
45
- # :reek:DuplicateMethodCall: { max_calls: 2 }
46
- # :reek:FeatureEnvy
51
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
52
+ # @quality :reek:FeatureEnvy
47
53
  def best_match_for(source_base_dir)
48
54
  keys.
49
55
  select { |pathname| source_base_dir.to_s.match(/#{Regexp.escape(pathname.to_s)}/) }.
@@ -52,7 +58,7 @@ module Reek
52
58
 
53
59
  def error_message_for_invalid_smell_type(klass)
54
60
  "You are trying to configure smell type #{klass} but we can't find one with that name.\n" \
55
- "Please make sure you spelled it right. (See 'defaults.reek' in the Reek\n" \
61
+ "Please make sure you spelled it right. (See 'docs/defaults.reek' in the Reek\n" \
56
62
  'repository for a list of all available smell types.)'
57
63
  end
58
64
  end
@@ -10,7 +10,7 @@ module Reek
10
10
  module ExcludedPaths
11
11
  include ConfigurationValidator
12
12
 
13
- # :reek:NestedIterators: { max_allowed_nesting: 2 }
13
+ # @quality :reek:NestedIterators { max_allowed_nesting: 2 }
14
14
  def add(paths)
15
15
  paths.each do |path|
16
16
  with_valid_directory(path) { |directory| self << directory }
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reek
4
+ module Configuration
5
+ # Responsible for converting configuration values coming from the outside world
6
+ # to whatever we want to use internally.
7
+ module RakeTaskConverter
8
+ class << self
9
+ REGEXABLE_ATTRIBUTES = %w(accept reject exclude).freeze
10
+
11
+ # Converts marked strings like "/foobar/" into regexes.
12
+ #
13
+ # @param configuration [Hash] e.g.
14
+ # {"enabled"=>true, "exclude"=>[], "reject"=>[/^[a-z]$/, /[0-9]$/, /[A-Z]/], "accept"=>[]}
15
+ # @return [Hash]
16
+ #
17
+ # @quality :reek:NestedIterators { max_allowed_nesting: 2 }
18
+ def convert(configuration)
19
+ (configuration.keys & REGEXABLE_ATTRIBUTES).each do |attribute|
20
+ configuration[attribute] = configuration[attribute].map do |item|
21
+ item.is_a?(Regexp) ? item.inspect : item
22
+ end
23
+ end
24
+ configuration
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end