reek 4.6.0 → 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 (294) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +79 -26
  4. data/.simplecov +1 -0
  5. data/.travis.yml +3 -9
  6. data/.yardopts +1 -1
  7. data/CHANGELOG.md +76 -0
  8. data/CONTRIBUTING.md +1 -1
  9. data/Gemfile +5 -5
  10. data/README.md +138 -107
  11. data/Rakefile +16 -3
  12. data/bin/code_climate_reek +1 -0
  13. data/bin/reek +2 -3
  14. data/docs/API.md +2 -9
  15. data/docs/Basic-Smell-Options.md +51 -11
  16. data/docs/Code-Smells.md +1 -1
  17. data/docs/Command-Line-Options.md +14 -4
  18. data/docs/Duplicate-Method-Call.md +49 -1
  19. data/docs/Feature-Envy.md +44 -0
  20. data/docs/How-To-Write-New-Detectors.md +7 -7
  21. data/docs/Instance-Variable-Assumption.md +1 -1
  22. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  23. data/docs/Rake-Task.md +1 -1
  24. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  25. data/docs/Reek-Driven-Development.md +1 -1
  26. data/docs/Uncommunicative-Method-Name.md +45 -6
  27. data/docs/Uncommunicative-Module-Name.md +49 -7
  28. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  29. data/docs/Uncommunicative-Variable-Name.md +73 -2
  30. data/docs/Unused-Private-Method.md +3 -3
  31. data/docs/defaults.reek.yml +129 -0
  32. data/docs/yard_plugin.rb +1 -0
  33. data/features/command_line_interface/basic_usage.feature +2 -2
  34. data/features/command_line_interface/options.feature +46 -4
  35. data/features/command_line_interface/show_progress.feature +4 -4
  36. data/features/command_line_interface/smell_selection.feature +1 -1
  37. data/features/command_line_interface/smells_count.feature +6 -6
  38. data/features/command_line_interface/stdin.feature +31 -5
  39. data/features/configuration_files/accept_setting.feature +45 -28
  40. data/features/configuration_files/directory_specific_directives.feature +80 -75
  41. data/features/configuration_files/exclude_directives.feature +11 -10
  42. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  43. data/features/configuration_files/masking_smells.feature +38 -9
  44. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  45. data/features/configuration_files/reject_setting.feature +52 -41
  46. data/features/configuration_files/schema_validation.feature +59 -0
  47. data/features/configuration_files/unused_private_method.feature +18 -16
  48. data/features/configuration_loading.feature +53 -10
  49. data/features/configuration_via_source_comments/erroneous_source_comments.feature +3 -3
  50. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  51. data/features/locales.feature +32 -0
  52. data/features/rake_task/rake_task.feature +58 -18
  53. data/features/reports/codeclimate.feature +59 -0
  54. data/features/reports/json.feature +3 -3
  55. data/features/reports/reports.feature +34 -34
  56. data/features/reports/yaml.feature +3 -3
  57. data/features/rspec_matcher.feature +40 -0
  58. data/features/samples.feature +287 -287
  59. data/features/step_definitions/reek_steps.rb +14 -2
  60. data/features/step_definitions/sample_file_steps.rb +9 -4
  61. data/features/support/env.rb +2 -11
  62. data/features/todo_list.feature +26 -23
  63. data/lib/reek/ast/ast_node_class_map.rb +1 -0
  64. data/lib/reek/ast/builder.rb +16 -0
  65. data/lib/reek/ast/node.rb +50 -58
  66. data/lib/reek/ast/object_refs.rb +2 -1
  67. data/lib/reek/ast/reference_collector.rb +3 -4
  68. data/lib/reek/ast/sexp_extensions/arguments.rb +1 -0
  69. data/lib/reek/ast/sexp_extensions/attribute_assignments.rb +1 -0
  70. data/lib/reek/ast/sexp_extensions/begin.rb +17 -0
  71. data/lib/reek/ast/sexp_extensions/block.rb +1 -0
  72. data/lib/reek/ast/sexp_extensions/case.rb +2 -1
  73. data/lib/reek/ast/sexp_extensions/constant.rb +1 -0
  74. data/lib/reek/ast/sexp_extensions/if.rb +9 -1
  75. data/lib/reek/ast/sexp_extensions/lambda.rb +1 -0
  76. data/lib/reek/ast/sexp_extensions/literal.rb +1 -0
  77. data/lib/reek/ast/sexp_extensions/logical_operators.rb +2 -1
  78. data/lib/reek/ast/sexp_extensions/methods.rb +5 -6
  79. data/lib/reek/ast/sexp_extensions/module.rb +55 -8
  80. data/lib/reek/ast/sexp_extensions/nested_assignables.rb +1 -0
  81. data/lib/reek/ast/sexp_extensions/self.rb +1 -0
  82. data/lib/reek/ast/sexp_extensions/send.rb +1 -4
  83. data/lib/reek/ast/sexp_extensions/super.rb +1 -0
  84. data/lib/reek/ast/sexp_extensions/symbols.rb +1 -0
  85. data/lib/reek/ast/sexp_extensions/variables.rb +1 -0
  86. data/lib/reek/ast/sexp_extensions/when.rb +1 -0
  87. data/lib/reek/ast/sexp_extensions/yield.rb +1 -0
  88. data/lib/reek/ast/sexp_extensions.rb +2 -0
  89. data/lib/reek/cli/application.rb +5 -3
  90. data/lib/reek/cli/command/base_command.rb +1 -0
  91. data/lib/reek/cli/command/report_command.rb +2 -2
  92. data/lib/reek/cli/command/todo_list_command.rb +9 -8
  93. data/lib/reek/cli/options.rb +32 -16
  94. data/lib/reek/cli/silencer.rb +15 -3
  95. data/lib/reek/cli/status.rb +1 -0
  96. data/lib/reek/code_comment.rb +14 -16
  97. data/lib/reek/configuration/app_configuration.rb +34 -28
  98. data/lib/reek/configuration/configuration_converter.rb +110 -0
  99. data/lib/reek/configuration/configuration_file_finder.rb +17 -41
  100. data/lib/reek/configuration/configuration_validator.rb +13 -23
  101. data/lib/reek/configuration/default_directive.rb +18 -3
  102. data/lib/reek/configuration/directory_directives.rb +18 -11
  103. data/lib/reek/configuration/excluded_paths.rb +2 -1
  104. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  105. data/lib/reek/configuration/schema.yml +210 -0
  106. data/lib/reek/configuration/schema_validator.rb +38 -0
  107. data/lib/reek/context/attribute_context.rb +4 -3
  108. data/lib/reek/context/class_context.rb +1 -0
  109. data/lib/reek/context/code_context.rb +49 -44
  110. data/lib/reek/context/ghost_context.rb +1 -2
  111. data/lib/reek/context/method_context.rb +26 -18
  112. data/lib/reek/context/module_context.rb +11 -11
  113. data/lib/reek/context/root_context.rb +1 -4
  114. data/lib/reek/context/send_context.rb +3 -2
  115. data/lib/reek/context/singleton_attribute_context.rb +1 -0
  116. data/lib/reek/context/singleton_method_context.rb +1 -0
  117. data/lib/reek/context/statement_counter.rb +1 -0
  118. data/lib/reek/context/visibility_tracker.rb +1 -0
  119. data/lib/reek/context_builder.rb +44 -44
  120. data/lib/reek/detector_repository.rb +12 -11
  121. data/lib/reek/documentation_link.rb +28 -0
  122. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +15 -13
  123. data/lib/reek/errors/bad_detector_in_comment_error.rb +13 -11
  124. data/lib/reek/errors/base_error.rb +3 -0
  125. data/lib/reek/errors/config_file_error.rb +11 -0
  126. data/lib/reek/errors/encoding_error.rb +43 -0
  127. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +14 -12
  128. data/lib/reek/errors/incomprehensible_source_error.rb +23 -24
  129. data/lib/reek/errors/syntax_error.rb +41 -0
  130. data/lib/reek/examiner.rb +24 -22
  131. data/lib/reek/logging_error_handler.rb +8 -5
  132. data/lib/reek/rake/task.rb +8 -5
  133. data/lib/reek/report/base_report.rb +9 -12
  134. data/lib/reek/report/code_climate/code_climate_configuration.rb +2 -1
  135. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -6
  136. data/lib/reek/report/code_climate/code_climate_fingerprint.rb +1 -0
  137. data/lib/reek/report/code_climate/code_climate_formatter.rb +1 -0
  138. data/lib/reek/report/code_climate/code_climate_report.rb +1 -0
  139. data/lib/reek/report/code_climate.rb +1 -0
  140. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  141. data/lib/reek/report/heading_formatter.rb +54 -0
  142. data/lib/reek/report/html_report.rb +1 -0
  143. data/lib/reek/report/json_report.rb +2 -1
  144. data/lib/reek/report/location_formatter.rb +40 -0
  145. data/lib/reek/report/progress_formatter.rb +79 -0
  146. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  147. data/lib/reek/report/text_report.rb +2 -2
  148. data/lib/reek/report/xml_report.rb +4 -3
  149. data/lib/reek/report/yaml_report.rb +2 -1
  150. data/lib/reek/report.rb +16 -10
  151. data/lib/reek/smell_configuration.rb +3 -2
  152. data/lib/reek/smell_detectors/attribute.rb +6 -11
  153. data/lib/reek/smell_detectors/base_detector.rb +31 -26
  154. data/lib/reek/smell_detectors/boolean_parameter.rb +4 -5
  155. data/lib/reek/smell_detectors/class_variable.rb +6 -14
  156. data/lib/reek/smell_detectors/control_parameter.rb +19 -33
  157. data/lib/reek/smell_detectors/data_clump.rb +16 -9
  158. data/lib/reek/smell_detectors/duplicate_method_call.rb +24 -17
  159. data/lib/reek/smell_detectors/feature_envy.rb +10 -7
  160. data/lib/reek/smell_detectors/instance_variable_assumption.rb +15 -23
  161. data/lib/reek/smell_detectors/irresponsible_module.rb +6 -12
  162. data/lib/reek/smell_detectors/long_parameter_list.rb +11 -7
  163. data/lib/reek/smell_detectors/long_yield_list.rb +11 -7
  164. data/lib/reek/smell_detectors/manual_dispatch.rb +5 -5
  165. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +21 -20
  166. data/lib/reek/smell_detectors/module_initialize.rb +4 -5
  167. data/lib/reek/smell_detectors/nested_iterators.rb +17 -24
  168. data/lib/reek/smell_detectors/nil_check.rb +9 -15
  169. data/lib/reek/smell_detectors/repeated_conditional.rb +14 -11
  170. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +8 -8
  171. data/lib/reek/smell_detectors/too_many_constants.rb +11 -9
  172. data/lib/reek/smell_detectors/too_many_instance_variables.rb +11 -6
  173. data/lib/reek/smell_detectors/too_many_methods.rb +12 -7
  174. data/lib/reek/smell_detectors/too_many_statements.rb +11 -6
  175. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +11 -11
  176. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +15 -18
  177. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +18 -22
  178. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +27 -27
  179. data/lib/reek/smell_detectors/unused_parameters.rb +5 -6
  180. data/lib/reek/smell_detectors/unused_private_method.rb +14 -20
  181. data/lib/reek/smell_detectors/utility_function.rb +13 -16
  182. data/lib/reek/smell_detectors.rb +2 -1
  183. data/lib/reek/smell_warning.rb +16 -8
  184. data/lib/reek/source/source_code.rb +65 -46
  185. data/lib/reek/source/source_locator.rb +9 -8
  186. data/lib/reek/spec/should_reek.rb +3 -2
  187. data/lib/reek/spec/should_reek_of.rb +13 -26
  188. data/lib/reek/spec/should_reek_only_of.rb +5 -4
  189. data/lib/reek/spec/smell_matcher.rb +2 -1
  190. data/lib/reek/spec.rb +7 -6
  191. data/lib/reek/tree_dresser.rb +9 -8
  192. data/lib/reek/version.rb +2 -1
  193. data/lib/reek.rb +1 -0
  194. data/reek.gemspec +5 -6
  195. data/samples/checkstyle.xml +1 -1
  196. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  197. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  198. data/samples/configuration/full_configuration.reek +8 -4
  199. data/samples/configuration/full_mask.reek +5 -4
  200. data/samples/configuration/partial_mask.reek +3 -2
  201. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  202. data/samples/paths.rb +5 -4
  203. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  204. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  205. data/spec/factories/factories.rb +2 -13
  206. data/spec/reek/ast/node_spec.rb +103 -10
  207. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  208. data/spec/reek/ast/sexp_extensions_spec.rb +15 -34
  209. data/spec/reek/cli/application_spec.rb +52 -42
  210. data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
  211. data/spec/reek/cli/silencer_spec.rb +28 -0
  212. data/spec/reek/code_comment_spec.rb +31 -38
  213. data/spec/reek/configuration/app_configuration_spec.rb +46 -33
  214. data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -51
  215. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  216. data/spec/reek/configuration/directory_directives_spec.rb +6 -7
  217. data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
  218. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  219. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  220. data/spec/reek/context/code_context_spec.rb +70 -106
  221. data/spec/reek/context/ghost_context_spec.rb +9 -9
  222. data/spec/reek/context/method_context_spec.rb +2 -2
  223. data/spec/reek/context/module_context_spec.rb +3 -3
  224. data/spec/reek/context/root_context_spec.rb +1 -1
  225. data/spec/reek/context/statement_counter_spec.rb +1 -0
  226. data/spec/reek/context_builder_spec.rb +20 -0
  227. data/spec/reek/documentation_link_spec.rb +20 -0
  228. data/spec/reek/errors/base_error_spec.rb +13 -0
  229. data/spec/reek/examiner_spec.rb +137 -29
  230. data/spec/reek/rake/task_spec.rb +25 -2
  231. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -78
  232. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  233. data/spec/reek/report/code_climate/code_climate_report_spec.rb +22 -22
  234. data/spec/reek/report/json_report_spec.rb +13 -46
  235. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  236. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  237. data/spec/reek/report/text_report_spec.rb +4 -4
  238. data/spec/reek/report/xml_report_spec.rb +3 -3
  239. data/spec/reek/report/yaml_report_spec.rb +17 -46
  240. data/spec/reek/report_spec.rb +3 -3
  241. data/spec/reek/smell_detectors/base_detector_spec.rb +4 -5
  242. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  243. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  244. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  245. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  246. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  247. data/spec/reek/smell_detectors/irresponsible_module_spec.rb +59 -21
  248. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +10 -10
  249. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  250. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  251. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  252. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  253. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  254. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  255. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +36 -15
  256. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
  257. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  258. data/spec/reek/smell_detectors/unused_private_method_spec.rb +21 -10
  259. data/spec/reek/smell_detectors/utility_function_spec.rb +57 -5
  260. data/spec/reek/smell_warning_spec.rb +12 -8
  261. data/spec/reek/source/source_code_spec.rb +27 -38
  262. data/spec/reek/source/source_locator_spec.rb +42 -12
  263. data/spec/reek/spec/should_reek_of_spec.rb +25 -30
  264. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  265. data/spec/reek/spec/should_reek_spec.rb +8 -8
  266. data/spec/reek/spec/smell_matcher_spec.rb +23 -23
  267. data/spec/reek/tree_dresser_spec.rb +12 -17
  268. data/spec/spec_helper.rb +7 -17
  269. data/tasks/configuration.rake +8 -5
  270. metadata +77 -40
  271. data/defaults.reek +0 -128
  272. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  273. data/lib/reek/errors/parse_error.rb +0 -19
  274. data/lib/reek/report/formatter/heading_formatter.rb +0 -51
  275. data/lib/reek/report/formatter/location_formatter.rb +0 -41
  276. data/lib/reek/report/formatter/progress_formatter.rb +0 -80
  277. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  278. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -35
  279. data/lib/reek/report/formatter.rb +0 -32
  280. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  281. data/samples/smelly_with_inline_mask.rb +0 -8
  282. data/samples/smelly_with_modifiers.rb +0 -12
  283. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  284. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  285. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  286. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  287. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  288. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  289. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  290. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  291. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  292. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  293. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  294. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -22,10 +23,6 @@ module Reek
22
23
  ([receiver] + args).compact
23
24
  end
24
25
 
25
- def arg_names
26
- args.map { |arg| arg.children.first }
27
- end
28
-
29
26
  def module_creation_call?
30
27
  object_creation_call? && module_creation_receiver?
31
28
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module AST
4
5
  module SexpExtensions
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'reference_collector'
3
4
 
4
5
  require_relative 'sexp_extensions/arguments'
5
6
  require_relative 'sexp_extensions/attribute_assignments'
7
+ require_relative 'sexp_extensions/begin'
6
8
  require_relative 'sexp_extensions/block'
7
9
  require_relative 'sexp_extensions/case'
8
10
  require_relative 'sexp_extensions/constant'
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'options'
3
4
  require_relative 'status'
4
5
  require_relative '../configuration/app_configuration'
5
6
  require_relative '../source/source_locator'
6
7
  require_relative 'command/report_command'
7
8
  require_relative 'command/todo_list_command'
9
+ require_relative '../errors/config_file_error'
8
10
 
9
11
  module Reek
10
12
  module CLI
@@ -41,7 +43,7 @@ module Reek
41
43
 
42
44
  def configure_app_configuration(config_file)
43
45
  Configuration::AppConfiguration.from_path(config_file)
44
- rescue Reek::Configuration::ConfigFileException => error
46
+ rescue Errors::ConfigFileError => error
45
47
  warn "Error: #{error}"
46
48
  exit Status::DEFAULT_ERROR_EXIT_CODE
47
49
  end
@@ -67,7 +69,7 @@ module Reek
67
69
  options.argv
68
70
  end
69
71
 
70
- # :reek:UtilityFunction
72
+ # @quality :reek:UtilityFunction
71
73
  def input_was_piped?
72
74
  !$stdin.tty?
73
75
  end
@@ -87,7 +89,7 @@ module Reek
87
89
  end
88
90
 
89
91
  def source_from_pipe
90
- [$stdin]
92
+ [Source::SourceCode.from($stdin, origin: options.stdin_filename)]
91
93
  end
92
94
 
93
95
  def disable_progress_output_unless_verbose
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module CLI
4
5
  module Command
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_command'
3
4
  require_relative '../../examiner'
4
5
  require_relative '../../logging_error_handler'
@@ -37,7 +38,6 @@ module Reek
37
38
  @reporter ||=
38
39
  report_class.new(
39
40
  warning_formatter: warning_formatter,
40
- report_formatter: Report::Formatter,
41
41
  sort_by_issue_count: sort_by_issue_count,
42
42
  heading_formatter: heading_formatter,
43
43
  progress_formatter: progress_formatter.new(sources.length))
@@ -52,7 +52,7 @@ module Reek
52
52
  end
53
53
 
54
54
  def warning_formatter_class
55
- Report.warning_formatter_class(options.show_links ? :wiki_links : :simple)
55
+ Report.warning_formatter_class(options.show_links ? :documentation_links : :simple)
56
56
  end
57
57
 
58
58
  def location_formatter
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_command'
3
4
  require_relative '../../examiner'
5
+ require_relative '../../configuration/app_configuration'
4
6
 
5
7
  module Reek
6
8
  module CLI
@@ -10,15 +12,15 @@ module Reek
10
12
  # file that can serve as a todo list.
11
13
  #
12
14
  class TodoListCommand < BaseCommand
13
- FILE_NAME = '.todo.reek'.freeze
15
+ FILE_NAME = '.todo.reek'
14
16
 
15
17
  def execute
16
- smells = scan_for_smells
17
18
  if smells.empty?
18
19
  puts "\n'.todo.reek' not generated because "\
19
20
  'there were no smells found!'
20
21
  else
21
- File.write FILE_NAME, groups_for(smells).to_yaml
22
+ File.write FILE_NAME,
23
+ { Configuration::AppConfiguration::DETECTORS_KEY => groups }.to_yaml
22
24
  puts "\n'.todo.reek' generated! You can now use "\
23
25
  'this as a starting point for your configuration.'
24
26
  end
@@ -27,14 +29,13 @@ module Reek
27
29
 
28
30
  private
29
31
 
30
- def scan_for_smells
31
- sources.map do |source|
32
- Examiner.new(source,
33
- filter_by_smells: smell_names)
32
+ def smells
33
+ @smells ||= sources.map do |source|
34
+ Examiner.new(source, filter_by_smells: smell_names)
34
35
  end.map(&:smells).flatten
35
36
  end
36
37
 
37
- def groups_for(smells)
38
+ def groups
38
39
  @groups ||=
39
40
  begin
40
41
  todos = smells.group_by(&:smell_class).map do |smell_class, smells_for_class|
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'optparse'
3
4
  require 'rainbow'
4
5
  require_relative '../version'
5
6
  require_relative 'status'
7
+ require_relative '../detector_repository'
8
+ require_relative '../documentation_link'
6
9
 
7
10
  module Reek
8
11
  module CLI
@@ -11,9 +14,9 @@ module Reek
11
14
  #
12
15
  # See {file:docs/Command-Line-Options.md} for details.
13
16
  #
14
- # :reek:TooManyInstanceVariables: { max_instance_variables: 12 }
15
- # :reek:TooManyMethods: { max_methods: 18 }
16
- # :reek:Attribute: { enabled: false }
17
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 12 }
18
+ # @quality :reek:TooManyMethods { max_methods: 18 }
19
+ # @quality :reek:Attribute { enabled: false }
17
20
  #
18
21
  class Options
19
22
  attr_reader :argv, :parser, :smells_to_detect
@@ -25,6 +28,7 @@ module Reek
25
28
  :show_empty,
26
29
  :show_links,
27
30
  :sorting,
31
+ :stdin_filename,
28
32
  :success_exit_code,
29
33
  :failure_exit_code,
30
34
  :generate_todo_list,
@@ -63,12 +67,12 @@ module Reek
63
67
  # processing by a machine, but will be viewed by a human. This means
64
68
  # features like coloring can be safely enabled by default.
65
69
  #
66
- # :reek:UtilityFunction
70
+ # @quality :reek:UtilityFunction
67
71
  def tty_output?
68
72
  $stdout.tty?
69
73
  end
70
74
 
71
- # :reek:TooManyStatements: { max_statements: 7 }
75
+ # @quality :reek:TooManyStatements { max_statements: 7 }
72
76
  def set_up_parser
73
77
  set_banner
74
78
  set_configuration_options
@@ -81,7 +85,7 @@ module Reek
81
85
 
82
86
  def set_banner
83
87
  program_name = parser.program_name
84
- parser.banner = <<-EOB.gsub(/^[ ]+/, '')
88
+ parser.banner = <<-BANNER.gsub(/^[ ]+/, '')
85
89
  Usage: #{program_name} [options] [files]
86
90
 
87
91
  Examples:
@@ -90,12 +94,12 @@ module Reek
90
94
  #{program_name} -s lib
91
95
  cat my_class.rb | #{program_name}
92
96
 
93
- See https://wiki.github.com/troessner/reek for detailed help.
97
+ See https://github.com/troessner/reek for detailed help.
94
98
 
95
- EOB
99
+ BANNER
96
100
  end
97
101
 
98
- # :reek:TooManyStatements: { max_statements: 6 }
102
+ # @quality :reek:TooManyStatements { max_statements: 7 }
99
103
  def set_configuration_options
100
104
  parser.separator 'Configuration:'
101
105
  parser.on('-c', '--config FILE', 'Read configuration options from FILE') do |file|
@@ -103,11 +107,16 @@ module Reek
103
107
  end
104
108
  parser.on('--smell SMELL',
105
109
  '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 '\
110
+ 'Call it like this: reek --smell MissingSafeMethod source.rb',
111
+ "Check out #{DocumentationLink.build('Code Smells')} "\
108
112
  'for a list of smells') do |smell|
109
113
  smells_to_detect << smell
110
114
  end
115
+ parser.on('--stdin-filename FILE',
116
+ 'When passing code in via pipe, assume this filename when '\
117
+ 'checking file or directory rules in the config.') do |file|
118
+ self.stdin_filename = file
119
+ end
111
120
  end
112
121
 
113
122
  def set_generate_todo_list_options
@@ -127,7 +136,7 @@ module Reek
127
136
  end
128
137
  end
129
138
 
130
- # :reek:TooManyStatements: { max_statements: 7 }
139
+ # @quality :reek:TooManyStatements { max_statements: 7 }
131
140
  def set_report_formatting_options
132
141
  parser.separator "\nText format options:"
133
142
  set_up_color_option
@@ -149,8 +158,8 @@ module Reek
149
158
  'Show headings for smell-free source files (default: false)') do |show_empty|
150
159
  self.show_empty = show_empty
151
160
  end
152
- parser.on('-U', '--[no-]wiki-links',
153
- 'Show link to related wiki page for each smell (default: true)') do |show_links|
161
+ parser.on('-U', '--[no-]documentation',
162
+ 'Show link to related documentation page for each smell (default: true)') do |show_links|
154
163
  self.show_links = show_links
155
164
  end
156
165
  end
@@ -190,7 +199,7 @@ module Reek
190
199
  end
191
200
  end
192
201
 
193
- # :reek:DuplicateMethodCall: { max_calls: 2 }
202
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
194
203
  def set_exit_codes
195
204
  parser.separator "\nExit codes:"
196
205
  parser.on('--success-exit-code CODE',
@@ -205,13 +214,20 @@ module Reek
205
214
  end
206
215
  end
207
216
 
208
- # :reek:TooManyStatements: { max_statements: 7 }
217
+ # @quality :reek:TooManyStatements { max_statements: 12 }
209
218
  def set_utility_options
210
219
  parser.separator "\nUtility options:"
211
220
  parser.on_tail('-h', '--help', 'Show this message') do
212
221
  puts parser
213
222
  exit
214
223
  end
224
+ parser.on_tail('-l', '--list', 'List all available smell detectors') do
225
+ puts "All available smell detectors:\n\n"
226
+ puts DetectorRepository.available_detector_names
227
+ puts "\nCheck out #{DocumentationLink.build('Code Smells')} "\
228
+ 'for a details on each detector'
229
+ exit
230
+ end
215
231
  parser.on_tail('-v', '--version', 'Show version') do
216
232
  puts "#{parser.program_name} #{Reek::Version::STRING}\n"
217
233
  exit
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'stringio'
3
4
 
4
5
  module Reek
@@ -7,17 +8,28 @@ module Reek
7
8
  module Silencer
8
9
  module_function
9
10
 
10
- # :reek:TooManyStatements: { max_statements: 7 }
11
+ # @quality :reek:TooManyStatements { max_statements: 9 }
11
12
  def silently
12
13
  old_verbose = $VERBOSE
14
+ old_stderr = $stderr
15
+ old_stdout = $stdout
16
+
13
17
  $VERBOSE = false
14
18
  $stderr = StringIO.new
15
19
  $stdout = StringIO.new
16
20
  yield
17
21
  ensure
18
22
  $VERBOSE = old_verbose
19
- $stderr = STDERR
20
- $stdout = STDOUT
23
+ $stderr = old_stderr
24
+ $stdout = old_stdout
25
+ end
26
+
27
+ def without_warnings
28
+ old_verbose = $VERBOSE
29
+ $VERBOSE = false
30
+ yield
31
+ ensure
32
+ $VERBOSE = old_verbose
21
33
  end
22
34
  end
23
35
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module CLI
4
5
  module Status
@@ -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
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'pathname'
3
4
  require_relative './configuration_file_finder'
4
5
  require_relative './configuration_validator'
@@ -14,19 +15,30 @@ module Reek
14
15
  # @public
15
16
  class AppConfiguration
16
17
  include ConfigurationValidator
17
- EXCLUDE_PATHS_KEY = 'exclude_paths'.freeze
18
+ EXCLUDE_PATHS_KEY = 'exclude_paths'
19
+ DIRECTORIES_KEY = 'directories'
20
+ DETECTORS_KEY = 'detectors'
18
21
 
19
- # Instantiate a configuration via given path.
22
+ # Instantiate a configuration via the given path.
20
23
  #
21
- # @param path [Pathname] the path to the config file
24
+ # @param path [Pathname] the path to the config file.
22
25
  #
23
26
  # @return [AppConfiguration]
24
27
  #
25
28
  # @public
26
- def self.from_path(path = nil)
27
- allocate.tap do |instance|
28
- instance.instance_eval { find_and_load(path: path) }
29
- 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)
30
42
  end
31
43
 
32
44
  # Instantiate a configuration by passing everything in.
@@ -38,21 +50,17 @@ module Reek
38
50
  # @return [AppConfiguration]
39
51
  #
40
52
  # @public
41
- def self.from_hash(hash = {})
42
- allocate.tap do |instance|
43
- instance.instance_eval do
44
- load_values hash
45
- end
46
- end
53
+ def self.from_hash(hash)
54
+ new(values: hash)
47
55
  end
48
56
 
49
57
  def self.default
50
- new
58
+ new(values: {})
51
59
  end
52
60
 
53
61
  # Returns the directive for a given directory.
54
62
  #
55
- # @param source_via [String] - the source of the code inspected
63
+ # @param source_via [String] the source of the code inspected
56
64
  #
57
65
  # @return [Hash] the directory directive for the source with the default directive
58
66
  # reverse-merged into it.
@@ -62,21 +70,25 @@ module Reek
62
70
  end
63
71
 
64
72
  def path_excluded?(path)
65
- excluded_paths.include?(path)
73
+ excluded_paths.map(&:expand_path).include?(path.expand_path)
66
74
  end
67
75
 
68
- def load_values(configuration_hash)
69
- configuration_hash.each do |key, value|
76
+ def load_values(values)
77
+ values.each do |key, value|
70
78
  if key == EXCLUDE_PATHS_KEY
71
79
  excluded_paths.add value
72
- elsif smell_type?(key)
73
- default_directive.add key, value
74
- else
75
- 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
76
84
  end
77
85
  end
78
86
  end
79
87
 
88
+ def initialize(values: {})
89
+ load_values(values)
90
+ end
91
+
80
92
  private
81
93
 
82
94
  attr_writer :directory_directives, :default_directive, :excluded_paths
@@ -92,12 +104,6 @@ module Reek
92
104
  def excluded_paths
93
105
  @excluded_paths ||= [].extend(ExcludedPaths)
94
106
  end
95
-
96
- def find_and_load(path: nil)
97
- configuration_hash = ConfigurationFileFinder.find_and_load(path: path)
98
-
99
- load_values(configuration_hash)
100
- end
101
107
  end
102
108
  end
103
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