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
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -19,12 +20,12 @@ module Reek
19
20
 
20
21
  # The name of the config field that sets the maximum depth
21
22
  # of nested iterators to be permitted within any single method.
22
- MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'.freeze
23
+ MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'
23
24
  DEFAULT_MAX_ALLOWED_NESTING = 1
24
25
 
25
26
  # The name of the config field that sets the names of any
26
27
  # methods for which nesting should not be considered
27
- IGNORE_ITERATORS_KEY = 'ignore_iterators'.freeze
28
+ IGNORE_ITERATORS_KEY = 'ignore_iterators'
28
29
  DEFAULT_IGNORE_ITERATORS = ['tap'].freeze
29
30
 
30
31
  def self.default_config
@@ -40,14 +41,10 @@ module Reek
40
41
  #
41
42
  # @return [Array<SmellWarning>]
42
43
  #
43
- def sniff(ctx)
44
- configure_ignore_iterators ctx
45
- violations = find_violations ctx
46
-
47
- violations.group_by(&:depth).map do |depth, group|
44
+ def sniff
45
+ find_violations.group_by(&:depth).map do |depth, group|
48
46
  lines = group.map(&:line)
49
47
  smell_warning(
50
- context: ctx,
51
48
  lines: lines,
52
49
  message: "contains iterators nested #{depth} deep",
53
50
  parameters: { depth: depth })
@@ -56,8 +53,6 @@ module Reek
56
53
 
57
54
  private
58
55
 
59
- attr_accessor :ignore_iterators
60
-
61
56
  # Finds the set of independent most deeply nested iterators that are
62
57
  # nested more deeply than allowed.
63
58
  #
@@ -68,10 +63,8 @@ module Reek
68
63
  #
69
64
  # @return [Array<Iterator>]
70
65
  #
71
- def find_violations(ctx)
72
- candidates = find_candidates ctx
73
- max_allowed_nesting = max_nesting(ctx)
74
- candidates.select { |it| it.depth > max_allowed_nesting }
66
+ def find_violations
67
+ find_candidates.select { |it| it.depth > max_allowed_nesting }
75
68
  end
76
69
 
77
70
  # Finds the set of independent most deeply nested iterators regardless of
@@ -79,9 +72,8 @@ module Reek
79
72
  #
80
73
  # @return [Array<Iterator>]
81
74
  #
82
- def find_candidates(ctx)
83
- exp = ctx.exp
84
- scout(exp: exp, depth: 0)
75
+ def find_candidates
76
+ scout(exp: expression, depth: 0)
85
77
  end
86
78
 
87
79
  # A little digression into parser's sexp is necessary here:
@@ -101,10 +93,11 @@ module Reek
101
93
  #
102
94
  # @return [Array<Iterator>]
103
95
  #
104
- # :reek:TooManyStatements: { max_statements: 6 }
96
+ # @quality :reek:TooManyStatements { max_statements: 6 }
105
97
  def scout(exp:, depth:)
106
98
  return [] unless exp
107
- exp.find_nodes([:block]).flat_map do |iterator|
99
+ # Find all non-nested blocks in this expression
100
+ exp.each_node([:block], [:block]).flat_map do |iterator|
108
101
  new_depth = increment_depth(iterator, depth)
109
102
  # 1st case: we recurse down the given block of the iterator. In this case
110
103
  # we need to check if we should increment the depth.
@@ -120,19 +113,19 @@ module Reek
120
113
  end
121
114
  end
122
115
 
123
- def configure_ignore_iterators(ctx)
124
- self.ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx)
116
+ def ignore_iterators
117
+ @ignore_iterators ||= value(IGNORE_ITERATORS_KEY, context)
125
118
  end
126
119
 
127
120
  def increment_depth(iterator, depth)
128
121
  ignored_iterator?(iterator) ? depth : depth + 1
129
122
  end
130
123
 
131
- def max_nesting(ctx)
132
- value(MAX_ALLOWED_NESTING_KEY, ctx)
124
+ def max_allowed_nesting
125
+ @max_allowed_nesting ||= value(MAX_ALLOWED_NESTING_KEY, context)
133
126
  end
134
127
 
135
- # :reek:FeatureEnvy
128
+ # @quality :reek:FeatureEnvy
136
129
  def ignored_iterator?(exp)
137
130
  ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.name } ||
138
131
  exp.without_block_arguments?
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -8,11 +9,10 @@ module Reek
8
9
  #
9
10
  # See {file:docs/Nil-Check.md} for details.
10
11
  class NilCheck < BaseDetector
11
- def sniff(ctx)
12
- lines = NodeDetector.new(ctx).detect.map(&:line)
12
+ def sniff
13
+ lines = detect_nodes.map(&:line)
13
14
  if lines.any?
14
15
  [smell_warning(
15
- context: ctx,
16
16
  lines: lines,
17
17
  message: 'performs a nil-check')]
18
18
  else
@@ -20,19 +20,13 @@ module Reek
20
20
  end
21
21
  end
22
22
 
23
- # Detect all nodes that smell of NilCheck
24
- class NodeDetector
25
- attr_reader :ctx
26
- def initialize(ctx)
27
- @ctx = ctx
28
- end
23
+ private
29
24
 
30
- def detect
31
- finders = [NodeFinder.new(ctx, :send, NilCallNodeDetector),
32
- NodeFinder.new(ctx, :when, NilWhenNodeDetector),
33
- NodeFinder.new(ctx, :csend, SafeNavigationNodeDetector)]
34
- finders.flat_map(&:smelly_nodes)
35
- end
25
+ def detect_nodes
26
+ finders = [NodeFinder.new(context, :send, NilCallNodeDetector),
27
+ NodeFinder.new(context, :when, NilWhenNodeDetector),
28
+ NodeFinder.new(context, :csend, SafeNavigationNodeDetector)]
29
+ finders.flat_map(&:smelly_nodes)
36
30
  end
37
31
 
38
32
  #
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative '../ast/node'
3
4
  require_relative 'base_detector'
4
5
 
@@ -27,7 +28,7 @@ module Reek
27
28
  class RepeatedConditional < BaseDetector
28
29
  # The name of the config field that sets the maximum number of
29
30
  # identical conditionals permitted within any single class.
30
- MAX_IDENTICAL_IFS_KEY = 'max_ifs'.freeze
31
+ MAX_IDENTICAL_IFS_KEY = 'max_ifs'
31
32
  DEFAULT_MAX_IFS = 2
32
33
 
33
34
  BLOCK_GIVEN_CONDITION = ::Parser::AST::Node.new(:send, [nil, :block_given?])
@@ -45,38 +46,40 @@ module Reek
45
46
  #
46
47
  # @return [Array<SmellWarning>]
47
48
  #
48
- # :reek:TooManyStatements: { max_statements: 6 }
49
- # :reek:DuplicateMethodCall: { max_calls: 2 }
50
- def sniff(ctx)
51
- max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx)
52
- conditional_counts(ctx).select do |_key, lines|
49
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
50
+ def sniff
51
+ conditional_counts.select do |_key, lines|
53
52
  lines.length > max_identical_ifs
54
53
  end.map do |key, lines|
55
54
  occurs = lines.length
56
55
  expression = key.format_to_ruby
57
56
  smell_warning(
58
- context: ctx,
59
57
  lines: lines,
60
58
  message: "tests '#{expression}' at least #{occurs} times",
61
59
  parameters: { name: expression, count: occurs })
62
60
  end
63
61
  end
64
62
 
63
+ private
64
+
65
+ def max_identical_ifs
66
+ @max_identical_ifs ||= value(MAX_IDENTICAL_IFS_KEY, context)
67
+ end
68
+
65
69
  #
66
70
  # Returns a Hash listing all of the conditional expressions in
67
71
  # the given syntax tree together with the number of times each
68
72
  # occurs. Ignores nested classes and modules.
69
73
  #
70
- # :reek:TooManyStatements: { max_statements: 9 }
71
- # :reek:FeatureEnvy
72
- def conditional_counts(sexp)
74
+ # @quality :reek:TooManyStatements { max_statements: 9 }
75
+ def conditional_counts
73
76
  result = Hash.new { |hash, key| hash[key] = [] }
74
77
  collector = proc do |node|
75
78
  next unless (condition = node.condition)
76
79
  next if condition == BLOCK_GIVEN_CONDITION
77
80
  result[condition].push(condition.line)
78
81
  end
79
- [:if, :case].each { |stmt| sexp.local_nodes(stmt, &collector) }
82
+ [:if, :case].each { |stmt| context.local_nodes(stmt, &collector) }
80
83
  result
81
84
  end
82
85
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -25,26 +26,25 @@ module Reek
25
26
  # class Foo < Bar; end;
26
27
  #
27
28
  # @return [Array<SmellWarning>]
28
- def sniff(ctx)
29
- superclass = ctx.exp.superclass
29
+ def sniff
30
+ superclass = expression.superclass
30
31
 
31
32
  return [] unless superclass
32
33
 
33
- sniff_superclass(ctx, superclass.name)
34
+ sniff_superclass superclass.name
34
35
  end
35
36
 
36
37
  private
37
38
 
38
- def sniff_superclass(ctx, superclass_name)
39
+ def sniff_superclass(superclass_name)
39
40
  return [] unless CORE_CLASSES.include?(superclass_name)
40
41
 
41
- [build_smell_warning(ctx, superclass_name)]
42
+ [build_smell_warning(superclass_name)]
42
43
  end
43
44
 
44
- def build_smell_warning(ctx, ancestor_name)
45
+ def build_smell_warning(ancestor_name)
45
46
  smell_attributes = {
46
- context: ctx,
47
- lines: [ctx.exp.line],
47
+ lines: [source_line],
48
48
  message: "inherits from core class '#{ancestor_name}'",
49
49
  parameters: { ancestor: ancestor_name }
50
50
  }
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -14,7 +15,7 @@ module Reek
14
15
  class TooManyConstants < BaseDetector
15
16
  # The name of the config field that sets the maximum number
16
17
  # of constants permitted in a class.
17
- MAX_ALLOWED_CONSTANTS_KEY = 'max_constants'.freeze
18
+ MAX_ALLOWED_CONSTANTS_KEY = 'max_constants'
18
19
  DEFAULT_MAX_CONSTANTS = 5
19
20
  IGNORED_NODES = [:module, :class].freeze
20
21
 
@@ -33,22 +34,23 @@ module Reek
33
34
  #
34
35
  # @return [Array<SmellWarning>]
35
36
  #
36
- def sniff(ctx)
37
- max_allowed_constants = value(MAX_ALLOWED_CONSTANTS_KEY, ctx)
38
-
39
- count = ctx.each_node(:casgn, IGNORED_NODES).delete_if(&:defines_module?).length
37
+ def sniff
38
+ count = context.local_nodes(:casgn).reject(&:defines_module?).length
40
39
 
41
40
  return [] if count <= max_allowed_constants
42
41
 
43
- build_smell_warning(ctx, count)
42
+ build_smell_warning(count)
44
43
  end
45
44
 
46
45
  private
47
46
 
48
- def build_smell_warning(ctx, count)
47
+ def max_allowed_constants
48
+ value(MAX_ALLOWED_CONSTANTS_KEY, context)
49
+ end
50
+
51
+ def build_smell_warning(count)
49
52
  [smell_warning(
50
- context: ctx,
51
- lines: [ctx.exp.line],
53
+ lines: [source_line],
52
54
  message: "has #{count} constants",
53
55
  parameters: { count: count })]
54
56
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -14,7 +15,7 @@ module Reek
14
15
  class TooManyInstanceVariables < BaseDetector
15
16
  # The name of the config field that sets the maximum number of instance
16
17
  # variables permitted in a class.
17
- MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'.freeze
18
+ MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
18
19
  DEFAULT_MAX_IVARS = 4
19
20
 
20
21
  def self.contexts
@@ -32,17 +33,21 @@ module Reek
32
33
  #
33
34
  # @return [Array<SmellWarning>]
34
35
  #
35
- def sniff(ctx)
36
- max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx)
37
- variables = ctx.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
36
+ def sniff
37
+ variables = context.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
38
38
  count = variables.uniq.size
39
39
  return [] if count <= max_allowed_ivars
40
40
  [smell_warning(
41
- context: ctx,
42
- lines: [ctx.exp.line],
41
+ lines: [source_line],
43
42
  message: "has at least #{count} instance variables",
44
43
  parameters: { count: count })]
45
44
  end
45
+
46
+ private
47
+
48
+ def max_allowed_ivars
49
+ value(MAX_ALLOWED_IVARS_KEY, context)
50
+ end
46
51
  end
47
52
  end
48
53
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -16,7 +17,7 @@ module Reek
16
17
  class TooManyMethods < BaseDetector
17
18
  # The name of the config field that sets the maximum number of methods
18
19
  # permitted in a class.
19
- MAX_ALLOWED_METHODS_KEY = 'max_methods'.freeze
20
+ MAX_ALLOWED_METHODS_KEY = 'max_methods'
20
21
  DEFAULT_MAX_METHODS = 15
21
22
 
22
23
  def self.contexts
@@ -30,21 +31,25 @@ module Reek
30
31
  end
31
32
 
32
33
  #
33
- # Checks +ctx+ for too many methods
34
+ # Checks context for too many methods
34
35
  #
35
36
  # @return [Array<SmellWarning>]
36
37
  #
37
- def sniff(ctx)
38
- max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx)
38
+ def sniff
39
39
  # TODO: Only checks instance methods!
40
- actual = ctx.node_instance_methods.length
40
+ actual = context.node_instance_methods.length
41
41
  return [] if actual <= max_allowed_methods
42
42
  [smell_warning(
43
- context: ctx,
44
- lines: [ctx.exp.line],
43
+ lines: [source_line],
45
44
  message: "has at least #{actual} methods",
46
45
  parameters: { count: actual })]
47
46
  end
47
+
48
+ private
49
+
50
+ def max_allowed_methods
51
+ value(MAX_ALLOWED_METHODS_KEY, context)
52
+ end
48
53
  end
49
54
  end
50
55
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -12,7 +13,7 @@ module Reek
12
13
  class TooManyStatements < BaseDetector
13
14
  # The name of the config field that sets the maximum number of
14
15
  # statements permitted in any method.
15
- MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'.freeze
16
+ MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
16
17
  DEFAULT_MAX_STATEMENTS = 5
17
18
 
18
19
  def self.default_config
@@ -26,16 +27,20 @@ module Reek
26
27
  #
27
28
  # @return [Array<SmellWarning>]
28
29
  #
29
- def sniff(ctx)
30
- max_allowed_statements = value(MAX_ALLOWED_STATEMENTS_KEY, ctx)
31
- count = ctx.number_of_statements
30
+ def sniff
31
+ count = context.number_of_statements
32
32
  return [] if count <= max_allowed_statements
33
33
  [smell_warning(
34
- context: ctx,
35
- lines: [ctx.exp.line],
34
+ lines: [source_line],
36
35
  message: "has approx #{count} statements",
37
36
  parameters: { count: count })]
38
37
  end
38
+
39
+ private
40
+
41
+ def max_allowed_statements
42
+ value(MAX_ALLOWED_STATEMENTS_KEY, context)
43
+ end
39
44
  end
40
45
  end
41
46
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -19,8 +20,8 @@ module Reek
19
20
  #
20
21
  # See {file:docs/Uncommunicative-Method-Name.md} for details.
21
22
  class UncommunicativeMethodName < BaseDetector
22
- REJECT_KEY = 'reject'.freeze
23
- ACCEPT_KEY = 'accept'.freeze
23
+ REJECT_KEY = 'reject'
24
+ ACCEPT_KEY = 'accept'
24
25
  DEFAULT_REJECT_PATTERNS = [/^[a-z]$/, /[0-9]$/, /[A-Z]/].freeze
25
26
  DEFAULT_ACCEPT_PATTERNS = [].freeze
26
27
 
@@ -35,29 +36,28 @@ module Reek
35
36
  #
36
37
  # @return [Array<SmellWarning>]
37
38
  #
38
- def sniff(context)
39
+ def sniff
39
40
  name = context.name.to_s
40
- return [] if acceptable_name?(name: name, context: context)
41
+ return [] if acceptable_name?(name)
41
42
 
42
43
  [smell_warning(
43
- context: context,
44
- lines: [context.exp.line],
44
+ lines: [source_line],
45
45
  message: "has the name '#{name}'",
46
46
  parameters: { name: name })]
47
47
  end
48
48
 
49
49
  private
50
50
 
51
- def acceptable_name?(name:, context:)
52
- accept_patterns(context).any? { |accept_pattern| name.match accept_pattern } ||
53
- reject_patterns(context).none? { |reject_pattern| name.match reject_pattern }
51
+ def acceptable_name?(name)
52
+ accept_patterns.any? { |accept_pattern| name.match accept_pattern } ||
53
+ reject_patterns.none? { |reject_pattern| name.match reject_pattern }
54
54
  end
55
55
 
56
- def reject_patterns(context)
56
+ def reject_patterns
57
57
  Array value(REJECT_KEY, context)
58
58
  end
59
59
 
60
- def accept_patterns(context)
60
+ def accept_patterns
61
61
  Array value(ACCEPT_KEY, context)
62
62
  end
63
63
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -20,13 +21,13 @@ module Reek
20
21
  class UncommunicativeModuleName < BaseDetector
21
22
  # The name of the config field that lists the regexps of
22
23
  # smelly names to be reported.
23
- REJECT_KEY = 'reject'.freeze
24
+ REJECT_KEY = 'reject'
24
25
  DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/].freeze
25
26
 
26
27
  # The name of the config field that lists the specific names that are
27
28
  # to be treated as exceptions; these names will not be reported as
28
29
  # uncommunicative.
29
- ACCEPT_KEY = 'accept'.freeze
30
+ ACCEPT_KEY = 'accept'
30
31
  DEFAULT_ACCEPT_PATTERNS = [].freeze
31
32
 
32
33
  def self.default_config
@@ -40,40 +41,36 @@ module Reek
40
41
  end
41
42
 
42
43
  #
43
- # Checks the given +context+ for uncommunicative names.
44
+ # Checks the detector's context for uncommunicative names.
44
45
  #
45
46
  # @return [Array<SmellWarning>]
46
47
  #
47
- def sniff(context)
48
+ def sniff
48
49
  fully_qualified_name = context.full_name
49
- exp = context.exp
50
- module_name = exp.simple_name
50
+ module_name = expression.simple_name
51
51
 
52
- return [] if acceptable_name?(context: context,
53
- module_name: module_name,
52
+ return [] if acceptable_name?(module_name: module_name,
54
53
  fully_qualified_name: fully_qualified_name)
55
54
 
56
55
  [smell_warning(
57
- context: context,
58
- lines: [exp.line],
56
+ lines: [source_line],
59
57
  message: "has the name '#{module_name}'",
60
58
  parameters: { name: module_name })]
61
59
  end
62
60
 
63
61
  private
64
62
 
65
- # :reek:ControlParameter
66
- def acceptable_name?(context:, module_name:, fully_qualified_name:)
67
- accept_patterns(context).any? { |accept_pattern| fully_qualified_name.match accept_pattern } ||
68
- reject_patterns(context).none? { |reject_pattern| module_name.match reject_pattern }
63
+ def acceptable_name?(module_name:, fully_qualified_name:)
64
+ accept_patterns.any? { |accept_pattern| fully_qualified_name.match accept_pattern } ||
65
+ reject_patterns.none? { |reject_pattern| module_name.match reject_pattern }
69
66
  end
70
67
 
71
- def reject_patterns(context)
72
- Array value(REJECT_KEY, context)
68
+ def reject_patterns
69
+ @reject_patterns ||= Array value(REJECT_KEY, context)
73
70
  end
74
71
 
75
- def accept_patterns(context)
76
- Array value(ACCEPT_KEY, context)
72
+ def accept_patterns
73
+ @accept_patterns ||= Array value(ACCEPT_KEY, context)
77
74
  end
78
75
  end
79
76
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -20,10 +21,10 @@ module Reek
20
21
  #
21
22
  # See {file:docs/Uncommunicative-Parameter-Name.md} for details.
22
23
  class UncommunicativeParameterName < BaseDetector
23
- REJECT_KEY = 'reject'.freeze
24
+ REJECT_KEY = 'reject'
24
25
  DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/, /[A-Z]/, /^_/].freeze
25
26
 
26
- ACCEPT_KEY = 'accept'.freeze
27
+ ACCEPT_KEY = 'accept'
27
28
  DEFAULT_ACCEPT_PATTERNS = [].freeze
28
29
 
29
30
  def self.default_config
@@ -37,15 +38,14 @@ module Reek
37
38
  #
38
39
  # @return [Array<SmellWarning>]
39
40
  #
40
- def sniff(context)
41
- expression = context.exp
42
- expression.parameter_names.select do |name|
43
- sanitized_name = sanitize name
44
- uncommunicative_parameter_name?(name: sanitized_name, context: context)
45
- end.map do |name|
41
+ def sniff
42
+ params = expression.parameters.select do |param|
43
+ uncommunicative_parameter?(param)
44
+ end
45
+
46
+ params.map(&:name).map do |name|
46
47
  smell_warning(
47
- context: context,
48
- lines: [expression.line],
48
+ lines: [source_line],
49
49
  message: "has the parameter name '#{name}'",
50
50
  parameters: { name: name.to_s })
51
51
  end
@@ -53,27 +53,23 @@ module Reek
53
53
 
54
54
  private
55
55
 
56
- def uncommunicative_parameter_name?(name:, context:)
57
- !acceptable_name?(name: name, context: context) && context.uses_param?(name)
56
+ def uncommunicative_parameter?(parameter)
57
+ !acceptable_name?(parameter.plain_name) &&
58
+ (context.uses_param?(parameter) || !parameter.marked_unused?)
58
59
  end
59
60
 
60
- def acceptable_name?(name:, context:)
61
- accept_patterns(context).any? { |accept_pattern| name.match accept_pattern } ||
62
- reject_patterns(context).none? { |reject_pattern| name.match reject_pattern }
61
+ def acceptable_name?(name)
62
+ accept_patterns.any? { |accept_pattern| name.match accept_pattern } ||
63
+ reject_patterns.none? { |reject_pattern| name.match reject_pattern }
63
64
  end
64
65
 
65
- def reject_patterns(context)
66
+ def reject_patterns
66
67
  Array value(REJECT_KEY, context)
67
68
  end
68
69
 
69
- def accept_patterns(context)
70
+ def accept_patterns
70
71
  Array value(ACCEPT_KEY, context)
71
72
  end
72
-
73
- # :reek:UtilityFunction
74
- def sanitize(name)
75
- name.to_s.gsub(/^[@\*\&]*/, '')
76
- end
77
73
  end
78
74
  end
79
75
  end