reek 4.7.1 → 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 (260) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +36 -7
  4. data/.simplecov +1 -0
  5. data/.travis.yml +3 -9
  6. data/.yardopts +1 -1
  7. data/CHANGELOG.md +59 -0
  8. data/CONTRIBUTING.md +1 -1
  9. data/Gemfile +5 -5
  10. data/README.md +134 -104
  11. data/Rakefile +16 -3
  12. data/bin/reek +1 -3
  13. data/docs/API.md +2 -9
  14. data/docs/Basic-Smell-Options.md +51 -11
  15. data/docs/Code-Smells.md +1 -1
  16. data/docs/Command-Line-Options.md +14 -4
  17. data/docs/Duplicate-Method-Call.md +49 -1
  18. data/docs/Feature-Envy.md +44 -0
  19. data/docs/How-To-Write-New-Detectors.md +7 -7
  20. data/docs/Instance-Variable-Assumption.md +1 -1
  21. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  22. data/docs/Rake-Task.md +1 -1
  23. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  24. data/docs/Reek-Driven-Development.md +1 -1
  25. data/docs/Uncommunicative-Method-Name.md +45 -6
  26. data/docs/Uncommunicative-Module-Name.md +49 -7
  27. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  28. data/docs/Uncommunicative-Variable-Name.md +73 -2
  29. data/docs/Unused-Private-Method.md +3 -3
  30. data/docs/defaults.reek.yml +129 -0
  31. data/docs/yard_plugin.rb +1 -0
  32. data/features/command_line_interface/basic_usage.feature +2 -2
  33. data/features/command_line_interface/options.feature +46 -4
  34. data/features/command_line_interface/show_progress.feature +4 -4
  35. data/features/command_line_interface/smell_selection.feature +1 -1
  36. data/features/command_line_interface/smells_count.feature +6 -6
  37. data/features/command_line_interface/stdin.feature +30 -8
  38. data/features/configuration_files/accept_setting.feature +45 -28
  39. data/features/configuration_files/directory_specific_directives.feature +80 -75
  40. data/features/configuration_files/exclude_directives.feature +11 -10
  41. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  42. data/features/configuration_files/masking_smells.feature +38 -9
  43. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  44. data/features/configuration_files/reject_setting.feature +52 -41
  45. data/features/configuration_files/schema_validation.feature +59 -0
  46. data/features/configuration_files/unused_private_method.feature +18 -16
  47. data/features/configuration_loading.feature +53 -10
  48. data/features/configuration_via_source_comments/erroneous_source_comments.feature +3 -3
  49. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  50. data/features/locales.feature +32 -0
  51. data/features/rake_task/rake_task.feature +58 -18
  52. data/features/reports/json.feature +3 -3
  53. data/features/reports/reports.feature +34 -34
  54. data/features/reports/yaml.feature +3 -3
  55. data/features/rspec_matcher.feature +40 -0
  56. data/features/samples.feature +287 -287
  57. data/features/step_definitions/reek_steps.rb +2 -2
  58. data/features/step_definitions/sample_file_steps.rb +9 -4
  59. data/features/support/env.rb +2 -11
  60. data/features/todo_list.feature +26 -23
  61. data/lib/reek/ast/builder.rb +1 -1
  62. data/lib/reek/ast/node.rb +40 -58
  63. data/lib/reek/ast/object_refs.rb +1 -1
  64. data/lib/reek/ast/reference_collector.rb +2 -4
  65. data/lib/reek/ast/sexp_extensions/case.rb +1 -1
  66. data/lib/reek/ast/sexp_extensions/if.rb +8 -1
  67. data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
  68. data/lib/reek/ast/sexp_extensions/methods.rb +4 -6
  69. data/lib/reek/ast/sexp_extensions/send.rb +0 -4
  70. data/lib/reek/cli/application.rb +4 -3
  71. data/lib/reek/cli/command/report_command.rb +1 -2
  72. data/lib/reek/cli/command/todo_list_command.rb +8 -8
  73. data/lib/reek/cli/options.rb +31 -16
  74. data/lib/reek/cli/silencer.rb +14 -3
  75. data/lib/reek/code_comment.rb +14 -16
  76. data/lib/reek/configuration/app_configuration.rb +32 -27
  77. data/lib/reek/configuration/configuration_converter.rb +110 -0
  78. data/lib/reek/configuration/configuration_file_finder.rb +16 -41
  79. data/lib/reek/configuration/configuration_validator.rb +12 -23
  80. data/lib/reek/configuration/default_directive.rb +17 -3
  81. data/lib/reek/configuration/directory_directives.rb +17 -11
  82. data/lib/reek/configuration/excluded_paths.rb +1 -1
  83. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  84. data/lib/reek/configuration/schema.yml +210 -0
  85. data/lib/reek/configuration/schema_validator.rb +38 -0
  86. data/lib/reek/context/attribute_context.rb +3 -3
  87. data/lib/reek/context/code_context.rb +47 -43
  88. data/lib/reek/context/ghost_context.rb +0 -2
  89. data/lib/reek/context/method_context.rb +22 -15
  90. data/lib/reek/context/module_context.rb +5 -9
  91. data/lib/reek/context/root_context.rb +0 -4
  92. data/lib/reek/context/send_context.rb +2 -2
  93. data/lib/reek/context_builder.rb +43 -44
  94. data/lib/reek/detector_repository.rb +11 -11
  95. data/lib/reek/documentation_link.rb +28 -0
  96. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +14 -13
  97. data/lib/reek/errors/bad_detector_in_comment_error.rb +12 -11
  98. data/lib/reek/errors/base_error.rb +3 -0
  99. data/lib/reek/errors/config_file_error.rb +11 -0
  100. data/lib/reek/errors/encoding_error.rb +43 -0
  101. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +13 -12
  102. data/lib/reek/errors/incomprehensible_source_error.rb +22 -24
  103. data/lib/reek/errors/syntax_error.rb +41 -0
  104. data/lib/reek/examiner.rb +24 -26
  105. data/lib/reek/logging_error_handler.rb +7 -5
  106. data/lib/reek/rake/task.rb +8 -4
  107. data/lib/reek/report/base_report.rb +8 -12
  108. data/lib/reek/report/code_climate/code_climate_configuration.rb +1 -1
  109. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -10
  110. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  111. data/lib/reek/report/heading_formatter.rb +54 -0
  112. data/lib/reek/report/json_report.rb +1 -1
  113. data/lib/reek/report/location_formatter.rb +40 -0
  114. data/lib/reek/report/progress_formatter.rb +79 -0
  115. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  116. data/lib/reek/report/text_report.rb +1 -2
  117. data/lib/reek/report/xml_report.rb +3 -3
  118. data/lib/reek/report/yaml_report.rb +1 -1
  119. data/lib/reek/report.rb +15 -10
  120. data/lib/reek/smell_configuration.rb +2 -2
  121. data/lib/reek/smell_detectors/attribute.rb +5 -11
  122. data/lib/reek/smell_detectors/base_detector.rb +30 -26
  123. data/lib/reek/smell_detectors/boolean_parameter.rb +3 -5
  124. data/lib/reek/smell_detectors/class_variable.rb +5 -14
  125. data/lib/reek/smell_detectors/control_parameter.rb +18 -33
  126. data/lib/reek/smell_detectors/data_clump.rb +15 -9
  127. data/lib/reek/smell_detectors/duplicate_method_call.rb +23 -17
  128. data/lib/reek/smell_detectors/feature_envy.rb +9 -7
  129. data/lib/reek/smell_detectors/instance_variable_assumption.rb +14 -23
  130. data/lib/reek/smell_detectors/irresponsible_module.rb +5 -12
  131. data/lib/reek/smell_detectors/long_parameter_list.rb +10 -7
  132. data/lib/reek/smell_detectors/long_yield_list.rb +10 -7
  133. data/lib/reek/smell_detectors/manual_dispatch.rb +4 -5
  134. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +20 -20
  135. data/lib/reek/smell_detectors/module_initialize.rb +3 -5
  136. data/lib/reek/smell_detectors/nested_iterators.rb +16 -24
  137. data/lib/reek/smell_detectors/nil_check.rb +8 -15
  138. data/lib/reek/smell_detectors/repeated_conditional.rb +13 -11
  139. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +7 -8
  140. data/lib/reek/smell_detectors/too_many_constants.rb +10 -9
  141. data/lib/reek/smell_detectors/too_many_instance_variables.rb +10 -6
  142. data/lib/reek/smell_detectors/too_many_methods.rb +11 -7
  143. data/lib/reek/smell_detectors/too_many_statements.rb +10 -6
  144. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +10 -11
  145. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +14 -18
  146. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +17 -22
  147. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +26 -27
  148. data/lib/reek/smell_detectors/unused_parameters.rb +4 -6
  149. data/lib/reek/smell_detectors/unused_private_method.rb +11 -18
  150. data/lib/reek/smell_detectors/utility_function.rb +12 -16
  151. data/lib/reek/smell_detectors.rb +1 -2
  152. data/lib/reek/smell_warning.rb +15 -8
  153. data/lib/reek/source/source_code.rb +57 -58
  154. data/lib/reek/source/source_locator.rb +8 -8
  155. data/lib/reek/spec/should_reek.rb +2 -2
  156. data/lib/reek/spec/should_reek_of.rb +9 -16
  157. data/lib/reek/spec/should_reek_only_of.rb +4 -4
  158. data/lib/reek/spec.rb +6 -6
  159. data/lib/reek/tree_dresser.rb +8 -8
  160. data/lib/reek/version.rb +1 -1
  161. data/reek.gemspec +5 -7
  162. data/samples/checkstyle.xml +1 -1
  163. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  164. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  165. data/samples/configuration/full_configuration.reek +8 -4
  166. data/samples/configuration/full_mask.reek +5 -4
  167. data/samples/configuration/partial_mask.reek +3 -2
  168. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  169. data/samples/paths.rb +5 -4
  170. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  171. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  172. data/spec/factories/factories.rb +2 -13
  173. data/spec/reek/ast/node_spec.rb +103 -10
  174. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  175. data/spec/reek/ast/sexp_extensions_spec.rb +14 -34
  176. data/spec/reek/cli/application_spec.rb +52 -42
  177. data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
  178. data/spec/reek/cli/silencer_spec.rb +28 -0
  179. data/spec/reek/code_comment_spec.rb +31 -38
  180. data/spec/reek/configuration/app_configuration_spec.rb +46 -33
  181. data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -51
  182. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  183. data/spec/reek/configuration/directory_directives_spec.rb +6 -7
  184. data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
  185. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  186. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  187. data/spec/reek/context/code_context_spec.rb +70 -106
  188. data/spec/reek/context/ghost_context_spec.rb +9 -9
  189. data/spec/reek/context/method_context_spec.rb +2 -2
  190. data/spec/reek/context/module_context_spec.rb +3 -3
  191. data/spec/reek/context/root_context_spec.rb +1 -1
  192. data/spec/reek/context_builder_spec.rb +20 -0
  193. data/spec/reek/documentation_link_spec.rb +20 -0
  194. data/spec/reek/errors/base_error_spec.rb +13 -0
  195. data/spec/reek/examiner_spec.rb +136 -29
  196. data/spec/reek/rake/task_spec.rb +25 -2
  197. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -80
  198. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  199. data/spec/reek/report/json_report_spec.rb +13 -46
  200. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  201. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  202. data/spec/reek/report/text_report_spec.rb +4 -4
  203. data/spec/reek/report/xml_report_spec.rb +3 -3
  204. data/spec/reek/report/yaml_report_spec.rb +9 -38
  205. data/spec/reek/report_spec.rb +3 -3
  206. data/spec/reek/smell_detectors/base_detector_spec.rb +4 -5
  207. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  208. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  209. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  210. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  211. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  212. data/spec/reek/smell_detectors/irresponsible_module_spec.rb +0 -11
  213. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +10 -10
  214. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  215. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  216. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  217. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  218. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  219. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  220. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +36 -15
  221. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
  222. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  223. data/spec/reek/smell_detectors/unused_private_method_spec.rb +21 -10
  224. data/spec/reek/smell_detectors/utility_function_spec.rb +57 -5
  225. data/spec/reek/smell_warning_spec.rb +12 -8
  226. data/spec/reek/source/source_code_spec.rb +22 -21
  227. data/spec/reek/source/source_locator_spec.rb +17 -17
  228. data/spec/reek/spec/should_reek_of_spec.rb +25 -29
  229. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  230. data/spec/reek/spec/should_reek_spec.rb +8 -8
  231. data/spec/reek/spec/smell_matcher_spec.rb +23 -23
  232. data/spec/reek/tree_dresser_spec.rb +12 -17
  233. data/spec/spec_helper.rb +6 -17
  234. data/tasks/configuration.rake +8 -5
  235. metadata +74 -41
  236. data/defaults.reek +0 -131
  237. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  238. data/lib/reek/report/formatter/heading_formatter.rb +0 -52
  239. data/lib/reek/report/formatter/location_formatter.rb +0 -42
  240. data/lib/reek/report/formatter/progress_formatter.rb +0 -81
  241. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  242. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -36
  243. data/lib/reek/report/formatter.rb +0 -33
  244. data/lib/reek/smell_detectors/syntax.rb +0 -37
  245. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  246. data/samples/smelly_with_inline_mask.rb +0 -8
  247. data/samples/smelly_with_modifiers.rb +0 -12
  248. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  249. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  250. data/spec/reek/smell_detectors/syntax_spec.rb +0 -17
  251. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  252. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  253. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  254. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  255. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  256. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  257. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  258. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  259. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  260. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -21,16 +21,15 @@ module Reek
21
21
  # gets distorted and diluted, and ! ceases to convey any information
22
22
  # whatsoever.
23
23
  #
24
- # Such a method is called PrimaDonnaMethod and is reported as a smell.
24
+ # Such a method is called MissingSafeMethod and is reported as a smell.
25
25
  #
26
- # See {file:docs/Prima-Donna-Method.md} for details.
27
- class PrimaDonnaMethod < BaseDetector
26
+ # See {file:docs/Missing-Safe-Method.md} for details.
27
+ class MissingSafeMethod < BaseDetector
28
28
  def self.contexts # :nodoc:
29
29
  [:class]
30
30
  end
31
31
 
32
32
  #
33
- # @param ctx [Context::ModuleContext]
34
33
  # @return [Array<SmellWarning>]
35
34
  #
36
35
  # Given this code:
@@ -40,38 +39,36 @@ module Reek
40
39
  # end
41
40
  # end
42
41
  #
43
- # An example `ctx` could look like this:
42
+ # An example context could look like this:
44
43
  #
45
44
  # s(:class,
46
45
  # s(:const, nil, :Alfa), nil,
47
46
  # s(:def, :bravo!,
48
47
  # s(:args), nil))
49
48
  #
50
- def sniff(ctx)
51
- ctx.node_instance_methods.select do |method_sexp|
52
- prima_donna_method?(method_sexp, ctx)
49
+ def sniff
50
+ context.node_instance_methods.select do |method_sexp|
51
+ missing_safe_method?(method_sexp)
53
52
  end.map do |method_sexp|
54
53
  name = method_sexp.name
55
54
  smell_warning(
56
- context: ctx,
57
- lines: [ctx.exp.line],
58
- message: "has prima donna method '#{name}'",
55
+ lines: [method_sexp.line],
56
+ message: "has missing safe method '#{name}'",
59
57
  parameters: { name: name.to_s })
60
58
  end
61
59
  end
62
60
 
63
61
  private
64
62
 
65
- def prima_donna_method?(method_sexp, ctx)
63
+ def missing_safe_method?(method_sexp)
66
64
  return false unless method_sexp.ends_with_bang?
67
- return false if ignore_method?(method_sexp, ctx)
68
- return false if version_without_bang_exists?(method_sexp, ctx)
65
+ return false if ignore_method? method_sexp
66
+ return false if version_without_bang_exists? method_sexp
69
67
  true
70
68
  end
71
69
 
72
- # :reek:UtilityFunction
73
- def version_without_bang_exists?(method_sexp, ctx)
74
- ctx.node_instance_methods.find do |sexp_item|
70
+ def version_without_bang_exists?(method_sexp)
71
+ context.node_instance_methods.find do |sexp_item|
75
72
  sexp_item.name.to_s == method_sexp.name_without_bang
76
73
  end
77
74
  end
@@ -79,13 +76,16 @@ module Reek
79
76
  #
80
77
  # @param method_node [Reek::AST::Node],
81
78
  # e.g. s(:def, :bravo!, s(:args), nil)
82
- # @param ctx [Context::ModuleContext]
83
79
  # @return [Boolean]
84
80
  #
85
- def ignore_method?(method_node, ctx)
86
- ignore_method_names = value(EXCLUDE_KEY, ctx) # e.g. ["bravo!"]
81
+ def ignore_method?(method_node)
87
82
  ignore_method_names.include? method_node.name.to_s # method_node.name is e.g.: :bravo!
88
83
  end
84
+
85
+ # e.g. ["bravo!"]
86
+ def ignore_method_names
87
+ @ignore_method_names ||= value(EXCLUDE_KEY, context)
88
+ end
89
89
  end
90
90
  end
91
91
  end
@@ -20,13 +20,11 @@ module Reek
20
20
  #
21
21
  # @return [Array<SmellWarning>]
22
22
  #
23
- # :reek:FeatureEnvy
24
- def sniff(ctx)
25
- ctx.local_nodes(:def) do |node|
23
+ def sniff
24
+ context.defined_instance_methods.each do |node|
26
25
  if node.name == :initialize
27
26
  return smell_warning(
28
- context: ctx,
29
- lines: [ctx.exp.line],
27
+ lines: [source_line],
30
28
  message: 'has initialize method')
31
29
  end
32
30
  end
@@ -20,12 +20,12 @@ module Reek
20
20
 
21
21
  # The name of the config field that sets the maximum depth
22
22
  # of nested iterators to be permitted within any single method.
23
- MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'.freeze
23
+ MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'
24
24
  DEFAULT_MAX_ALLOWED_NESTING = 1
25
25
 
26
26
  # The name of the config field that sets the names of any
27
27
  # methods for which nesting should not be considered
28
- IGNORE_ITERATORS_KEY = 'ignore_iterators'.freeze
28
+ IGNORE_ITERATORS_KEY = 'ignore_iterators'
29
29
  DEFAULT_IGNORE_ITERATORS = ['tap'].freeze
30
30
 
31
31
  def self.default_config
@@ -41,14 +41,10 @@ module Reek
41
41
  #
42
42
  # @return [Array<SmellWarning>]
43
43
  #
44
- def sniff(ctx)
45
- configure_ignore_iterators ctx
46
- violations = find_violations ctx
47
-
48
- violations.group_by(&:depth).map do |depth, group|
44
+ def sniff
45
+ find_violations.group_by(&:depth).map do |depth, group|
49
46
  lines = group.map(&:line)
50
47
  smell_warning(
51
- context: ctx,
52
48
  lines: lines,
53
49
  message: "contains iterators nested #{depth} deep",
54
50
  parameters: { depth: depth })
@@ -57,8 +53,6 @@ module Reek
57
53
 
58
54
  private
59
55
 
60
- attr_accessor :ignore_iterators
61
-
62
56
  # Finds the set of independent most deeply nested iterators that are
63
57
  # nested more deeply than allowed.
64
58
  #
@@ -69,10 +63,8 @@ module Reek
69
63
  #
70
64
  # @return [Array<Iterator>]
71
65
  #
72
- def find_violations(ctx)
73
- candidates = find_candidates ctx
74
- max_allowed_nesting = max_nesting(ctx)
75
- candidates.select { |it| it.depth > max_allowed_nesting }
66
+ def find_violations
67
+ find_candidates.select { |it| it.depth > max_allowed_nesting }
76
68
  end
77
69
 
78
70
  # Finds the set of independent most deeply nested iterators regardless of
@@ -80,9 +72,8 @@ module Reek
80
72
  #
81
73
  # @return [Array<Iterator>]
82
74
  #
83
- def find_candidates(ctx)
84
- exp = ctx.exp
85
- scout(exp: exp, depth: 0)
75
+ def find_candidates
76
+ scout(exp: expression, depth: 0)
86
77
  end
87
78
 
88
79
  # A little digression into parser's sexp is necessary here:
@@ -102,10 +93,11 @@ module Reek
102
93
  #
103
94
  # @return [Array<Iterator>]
104
95
  #
105
- # :reek:TooManyStatements: { max_statements: 6 }
96
+ # @quality :reek:TooManyStatements { max_statements: 6 }
106
97
  def scout(exp:, depth:)
107
98
  return [] unless exp
108
- 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|
109
101
  new_depth = increment_depth(iterator, depth)
110
102
  # 1st case: we recurse down the given block of the iterator. In this case
111
103
  # we need to check if we should increment the depth.
@@ -121,19 +113,19 @@ module Reek
121
113
  end
122
114
  end
123
115
 
124
- def configure_ignore_iterators(ctx)
125
- self.ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx)
116
+ def ignore_iterators
117
+ @ignore_iterators ||= value(IGNORE_ITERATORS_KEY, context)
126
118
  end
127
119
 
128
120
  def increment_depth(iterator, depth)
129
121
  ignored_iterator?(iterator) ? depth : depth + 1
130
122
  end
131
123
 
132
- def max_nesting(ctx)
133
- value(MAX_ALLOWED_NESTING_KEY, ctx)
124
+ def max_allowed_nesting
125
+ @max_allowed_nesting ||= value(MAX_ALLOWED_NESTING_KEY, context)
134
126
  end
135
127
 
136
- # :reek:FeatureEnvy
128
+ # @quality :reek:FeatureEnvy
137
129
  def ignored_iterator?(exp)
138
130
  ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.name } ||
139
131
  exp.without_block_arguments?
@@ -9,11 +9,10 @@ module Reek
9
9
  #
10
10
  # See {file:docs/Nil-Check.md} for details.
11
11
  class NilCheck < BaseDetector
12
- def sniff(ctx)
13
- lines = NodeDetector.new(ctx).detect.map(&:line)
12
+ def sniff
13
+ lines = detect_nodes.map(&:line)
14
14
  if lines.any?
15
15
  [smell_warning(
16
- context: ctx,
17
16
  lines: lines,
18
17
  message: 'performs a nil-check')]
19
18
  else
@@ -21,19 +20,13 @@ module Reek
21
20
  end
22
21
  end
23
22
 
24
- # Detect all nodes that smell of NilCheck
25
- class NodeDetector
26
- attr_reader :ctx
27
- def initialize(ctx)
28
- @ctx = ctx
29
- end
23
+ private
30
24
 
31
- def detect
32
- finders = [NodeFinder.new(ctx, :send, NilCallNodeDetector),
33
- NodeFinder.new(ctx, :when, NilWhenNodeDetector),
34
- NodeFinder.new(ctx, :csend, SafeNavigationNodeDetector)]
35
- finders.flat_map(&:smelly_nodes)
36
- 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)
37
30
  end
38
31
 
39
32
  #
@@ -28,7 +28,7 @@ module Reek
28
28
  class RepeatedConditional < BaseDetector
29
29
  # The name of the config field that sets the maximum number of
30
30
  # identical conditionals permitted within any single class.
31
- MAX_IDENTICAL_IFS_KEY = 'max_ifs'.freeze
31
+ MAX_IDENTICAL_IFS_KEY = 'max_ifs'
32
32
  DEFAULT_MAX_IFS = 2
33
33
 
34
34
  BLOCK_GIVEN_CONDITION = ::Parser::AST::Node.new(:send, [nil, :block_given?])
@@ -46,38 +46,40 @@ module Reek
46
46
  #
47
47
  # @return [Array<SmellWarning>]
48
48
  #
49
- # :reek:TooManyStatements: { max_statements: 6 }
50
- # :reek:DuplicateMethodCall: { max_calls: 2 }
51
- def sniff(ctx)
52
- max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx)
53
- conditional_counts(ctx).select do |_key, lines|
49
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
50
+ def sniff
51
+ conditional_counts.select do |_key, lines|
54
52
  lines.length > max_identical_ifs
55
53
  end.map do |key, lines|
56
54
  occurs = lines.length
57
55
  expression = key.format_to_ruby
58
56
  smell_warning(
59
- context: ctx,
60
57
  lines: lines,
61
58
  message: "tests '#{expression}' at least #{occurs} times",
62
59
  parameters: { name: expression, count: occurs })
63
60
  end
64
61
  end
65
62
 
63
+ private
64
+
65
+ def max_identical_ifs
66
+ @max_identical_ifs ||= value(MAX_IDENTICAL_IFS_KEY, context)
67
+ end
68
+
66
69
  #
67
70
  # Returns a Hash listing all of the conditional expressions in
68
71
  # the given syntax tree together with the number of times each
69
72
  # occurs. Ignores nested classes and modules.
70
73
  #
71
- # :reek:TooManyStatements: { max_statements: 9 }
72
- # :reek:FeatureEnvy
73
- def conditional_counts(sexp)
74
+ # @quality :reek:TooManyStatements { max_statements: 9 }
75
+ def conditional_counts
74
76
  result = Hash.new { |hash, key| hash[key] = [] }
75
77
  collector = proc do |node|
76
78
  next unless (condition = node.condition)
77
79
  next if condition == BLOCK_GIVEN_CONDITION
78
80
  result[condition].push(condition.line)
79
81
  end
80
- [:if, :case].each { |stmt| sexp.local_nodes(stmt, &collector) }
82
+ [:if, :case].each { |stmt| context.local_nodes(stmt, &collector) }
81
83
  result
82
84
  end
83
85
  end
@@ -26,26 +26,25 @@ module Reek
26
26
  # class Foo < Bar; end;
27
27
  #
28
28
  # @return [Array<SmellWarning>]
29
- def sniff(ctx)
30
- superclass = ctx.exp.superclass
29
+ def sniff
30
+ superclass = expression.superclass
31
31
 
32
32
  return [] unless superclass
33
33
 
34
- sniff_superclass(ctx, superclass.name)
34
+ sniff_superclass superclass.name
35
35
  end
36
36
 
37
37
  private
38
38
 
39
- def sniff_superclass(ctx, superclass_name)
39
+ def sniff_superclass(superclass_name)
40
40
  return [] unless CORE_CLASSES.include?(superclass_name)
41
41
 
42
- [build_smell_warning(ctx, superclass_name)]
42
+ [build_smell_warning(superclass_name)]
43
43
  end
44
44
 
45
- def build_smell_warning(ctx, ancestor_name)
45
+ def build_smell_warning(ancestor_name)
46
46
  smell_attributes = {
47
- context: ctx,
48
- lines: [ctx.exp.line],
47
+ lines: [source_line],
49
48
  message: "inherits from core class '#{ancestor_name}'",
50
49
  parameters: { ancestor: ancestor_name }
51
50
  }
@@ -15,7 +15,7 @@ module Reek
15
15
  class TooManyConstants < BaseDetector
16
16
  # The name of the config field that sets the maximum number
17
17
  # of constants permitted in a class.
18
- MAX_ALLOWED_CONSTANTS_KEY = 'max_constants'.freeze
18
+ MAX_ALLOWED_CONSTANTS_KEY = 'max_constants'
19
19
  DEFAULT_MAX_CONSTANTS = 5
20
20
  IGNORED_NODES = [:module, :class].freeze
21
21
 
@@ -34,22 +34,23 @@ module Reek
34
34
  #
35
35
  # @return [Array<SmellWarning>]
36
36
  #
37
- def sniff(ctx)
38
- max_allowed_constants = value(MAX_ALLOWED_CONSTANTS_KEY, ctx)
39
-
40
- 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
41
39
 
42
40
  return [] if count <= max_allowed_constants
43
41
 
44
- build_smell_warning(ctx, count)
42
+ build_smell_warning(count)
45
43
  end
46
44
 
47
45
  private
48
46
 
49
- 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)
50
52
  [smell_warning(
51
- context: ctx,
52
- lines: [ctx.exp.line],
53
+ lines: [source_line],
53
54
  message: "has #{count} constants",
54
55
  parameters: { count: count })]
55
56
  end
@@ -15,7 +15,7 @@ module Reek
15
15
  class TooManyInstanceVariables < BaseDetector
16
16
  # The name of the config field that sets the maximum number of instance
17
17
  # variables permitted in a class.
18
- MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'.freeze
18
+ MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
19
19
  DEFAULT_MAX_IVARS = 4
20
20
 
21
21
  def self.contexts
@@ -33,17 +33,21 @@ module Reek
33
33
  #
34
34
  # @return [Array<SmellWarning>]
35
35
  #
36
- def sniff(ctx)
37
- max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx)
38
- variables = ctx.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
36
+ def sniff
37
+ variables = context.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
39
38
  count = variables.uniq.size
40
39
  return [] if count <= max_allowed_ivars
41
40
  [smell_warning(
42
- context: ctx,
43
- lines: [ctx.exp.line],
41
+ lines: [source_line],
44
42
  message: "has at least #{count} instance variables",
45
43
  parameters: { count: count })]
46
44
  end
45
+
46
+ private
47
+
48
+ def max_allowed_ivars
49
+ value(MAX_ALLOWED_IVARS_KEY, context)
50
+ end
47
51
  end
48
52
  end
49
53
  end
@@ -17,7 +17,7 @@ module Reek
17
17
  class TooManyMethods < BaseDetector
18
18
  # The name of the config field that sets the maximum number of methods
19
19
  # permitted in a class.
20
- MAX_ALLOWED_METHODS_KEY = 'max_methods'.freeze
20
+ MAX_ALLOWED_METHODS_KEY = 'max_methods'
21
21
  DEFAULT_MAX_METHODS = 15
22
22
 
23
23
  def self.contexts
@@ -31,21 +31,25 @@ module Reek
31
31
  end
32
32
 
33
33
  #
34
- # Checks +ctx+ for too many methods
34
+ # Checks context for too many methods
35
35
  #
36
36
  # @return [Array<SmellWarning>]
37
37
  #
38
- def sniff(ctx)
39
- max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx)
38
+ def sniff
40
39
  # TODO: Only checks instance methods!
41
- actual = ctx.node_instance_methods.length
40
+ actual = context.node_instance_methods.length
42
41
  return [] if actual <= max_allowed_methods
43
42
  [smell_warning(
44
- context: ctx,
45
- lines: [ctx.exp.line],
43
+ lines: [source_line],
46
44
  message: "has at least #{actual} methods",
47
45
  parameters: { count: actual })]
48
46
  end
47
+
48
+ private
49
+
50
+ def max_allowed_methods
51
+ value(MAX_ALLOWED_METHODS_KEY, context)
52
+ end
49
53
  end
50
54
  end
51
55
  end
@@ -13,7 +13,7 @@ module Reek
13
13
  class TooManyStatements < BaseDetector
14
14
  # The name of the config field that sets the maximum number of
15
15
  # statements permitted in any method.
16
- MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'.freeze
16
+ MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
17
17
  DEFAULT_MAX_STATEMENTS = 5
18
18
 
19
19
  def self.default_config
@@ -27,16 +27,20 @@ module Reek
27
27
  #
28
28
  # @return [Array<SmellWarning>]
29
29
  #
30
- def sniff(ctx)
31
- max_allowed_statements = value(MAX_ALLOWED_STATEMENTS_KEY, ctx)
32
- count = ctx.number_of_statements
30
+ def sniff
31
+ count = context.number_of_statements
33
32
  return [] if count <= max_allowed_statements
34
33
  [smell_warning(
35
- context: ctx,
36
- lines: [ctx.exp.line],
34
+ lines: [source_line],
37
35
  message: "has approx #{count} statements",
38
36
  parameters: { count: count })]
39
37
  end
38
+
39
+ private
40
+
41
+ def max_allowed_statements
42
+ value(MAX_ALLOWED_STATEMENTS_KEY, context)
43
+ end
40
44
  end
41
45
  end
42
46
  end
@@ -20,8 +20,8 @@ module Reek
20
20
  #
21
21
  # See {file:docs/Uncommunicative-Method-Name.md} for details.
22
22
  class UncommunicativeMethodName < BaseDetector
23
- REJECT_KEY = 'reject'.freeze
24
- ACCEPT_KEY = 'accept'.freeze
23
+ REJECT_KEY = 'reject'
24
+ ACCEPT_KEY = 'accept'
25
25
  DEFAULT_REJECT_PATTERNS = [/^[a-z]$/, /[0-9]$/, /[A-Z]/].freeze
26
26
  DEFAULT_ACCEPT_PATTERNS = [].freeze
27
27
 
@@ -36,29 +36,28 @@ module Reek
36
36
  #
37
37
  # @return [Array<SmellWarning>]
38
38
  #
39
- def sniff(context)
39
+ def sniff
40
40
  name = context.name.to_s
41
- return [] if acceptable_name?(name: name, context: context)
41
+ return [] if acceptable_name?(name)
42
42
 
43
43
  [smell_warning(
44
- context: context,
45
- lines: [context.exp.line],
44
+ lines: [source_line],
46
45
  message: "has the name '#{name}'",
47
46
  parameters: { name: name })]
48
47
  end
49
48
 
50
49
  private
51
50
 
52
- def acceptable_name?(name:, context:)
53
- accept_patterns(context).any? { |accept_pattern| name.match accept_pattern } ||
54
- 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 }
55
54
  end
56
55
 
57
- def reject_patterns(context)
56
+ def reject_patterns
58
57
  Array value(REJECT_KEY, context)
59
58
  end
60
59
 
61
- def accept_patterns(context)
60
+ def accept_patterns
62
61
  Array value(ACCEPT_KEY, context)
63
62
  end
64
63
  end
@@ -21,13 +21,13 @@ module Reek
21
21
  class UncommunicativeModuleName < BaseDetector
22
22
  # The name of the config field that lists the regexps of
23
23
  # smelly names to be reported.
24
- REJECT_KEY = 'reject'.freeze
24
+ REJECT_KEY = 'reject'
25
25
  DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/].freeze
26
26
 
27
27
  # The name of the config field that lists the specific names that are
28
28
  # to be treated as exceptions; these names will not be reported as
29
29
  # uncommunicative.
30
- ACCEPT_KEY = 'accept'.freeze
30
+ ACCEPT_KEY = 'accept'
31
31
  DEFAULT_ACCEPT_PATTERNS = [].freeze
32
32
 
33
33
  def self.default_config
@@ -41,40 +41,36 @@ module Reek
41
41
  end
42
42
 
43
43
  #
44
- # Checks the given +context+ for uncommunicative names.
44
+ # Checks the detector's context for uncommunicative names.
45
45
  #
46
46
  # @return [Array<SmellWarning>]
47
47
  #
48
- def sniff(context)
48
+ def sniff
49
49
  fully_qualified_name = context.full_name
50
- exp = context.exp
51
- module_name = exp.simple_name
50
+ module_name = expression.simple_name
52
51
 
53
- return [] if acceptable_name?(context: context,
54
- module_name: module_name,
52
+ return [] if acceptable_name?(module_name: module_name,
55
53
  fully_qualified_name: fully_qualified_name)
56
54
 
57
55
  [smell_warning(
58
- context: context,
59
- lines: [exp.line],
56
+ lines: [source_line],
60
57
  message: "has the name '#{module_name}'",
61
58
  parameters: { name: module_name })]
62
59
  end
63
60
 
64
61
  private
65
62
 
66
- # :reek:ControlParameter
67
- def acceptable_name?(context:, module_name:, fully_qualified_name:)
68
- accept_patterns(context).any? { |accept_pattern| fully_qualified_name.match accept_pattern } ||
69
- 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 }
70
66
  end
71
67
 
72
- def reject_patterns(context)
73
- Array value(REJECT_KEY, context)
68
+ def reject_patterns
69
+ @reject_patterns ||= Array value(REJECT_KEY, context)
74
70
  end
75
71
 
76
- def accept_patterns(context)
77
- Array value(ACCEPT_KEY, context)
72
+ def accept_patterns
73
+ @accept_patterns ||= Array value(ACCEPT_KEY, context)
78
74
  end
79
75
  end
80
76
  end