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
@@ -15,40 +15,33 @@ module Reek
15
15
  # - {file:README.md}
16
16
  # for details.
17
17
  #
18
- # :reek:UnusedPrivateMethod: { exclude: [ smell_warning ] }
19
- # :reek:TooManyMethods: { max_methods: 18 }
18
+ # @quality :reek:UnusedPrivateMethod { exclude: [ smell_warning ] }
19
+ # @quality :reek:TooManyMethods { max_methods: 18 }
20
20
  class BaseDetector
21
21
  attr_reader :config
22
22
  # The name of the config field that lists the names of code contexts
23
23
  # that should not be checked. Add this field to the config for each
24
24
  # smell that should ignore this code element.
25
- EXCLUDE_KEY = 'exclude'.freeze
25
+ EXCLUDE_KEY = 'exclude'
26
26
 
27
27
  # The default value for the +EXCLUDE_KEY+ if it isn't specified
28
28
  # in any configuration file.
29
29
  DEFAULT_EXCLUDE_SET = [].freeze
30
30
 
31
- def initialize(config = {})
32
- @config = SmellConfiguration.new self.class.default_config.merge(config)
31
+ def initialize(configuration: {}, context: nil)
32
+ @config = SmellConfiguration.new self.class.default_config.merge(configuration)
33
+ @context = context
33
34
  end
34
35
 
35
36
  def smell_type
36
37
  self.class.smell_type
37
38
  end
38
39
 
39
- def contexts
40
- self.class.contexts
41
- end
42
-
43
- def run_for(context)
44
- return [] unless enabled_for?(context)
45
- return [] if exception?(context)
40
+ def run
41
+ return [] unless enabled?
42
+ return [] if exception?
46
43
 
47
- sniff(context)
48
- end
49
-
50
- def exception?(context)
51
- context.matches?(value(EXCLUDE_KEY, context))
44
+ sniff
52
45
  end
53
46
 
54
47
  def self.todo_configuration_for(smells)
@@ -59,7 +52,21 @@ module Reek
59
52
 
60
53
  private
61
54
 
62
- def enabled_for?(context)
55
+ attr_reader :context
56
+
57
+ def expression
58
+ @expression ||= context.exp
59
+ end
60
+
61
+ def source_line
62
+ @source_line ||= expression.line
63
+ end
64
+
65
+ def exception?
66
+ context.matches?(value(EXCLUDE_KEY, context))
67
+ end
68
+
69
+ def enabled?
63
70
  config.enabled? && config_for(context)[SmellConfiguration::ENABLED_KEY] != false
64
71
  end
65
72
 
@@ -71,12 +78,9 @@ module Reek
71
78
  ctx.config_for(self.class)
72
79
  end
73
80
 
74
- # :reek:FeatureEnvy
75
- def smell_warning(options = {})
76
- context = options.fetch(:context)
77
- exp = context.exp
81
+ def smell_warning(**options)
78
82
  SmellWarning.new(self,
79
- source: exp.source,
83
+ source: expression.source,
80
84
  context: context.full_name,
81
85
  lines: options.fetch(:lines),
82
86
  message: options.fetch(:message),
@@ -92,7 +96,7 @@ module Reek
92
96
  [:def, :defs]
93
97
  end
94
98
 
95
- # :reek:UtilityFunction
99
+ # @quality :reek:UtilityFunction
96
100
  def default_config
97
101
  {
98
102
  SmellConfiguration::ENABLED_KEY => true,
@@ -131,14 +135,14 @@ module Reek
131
135
  # Note that we assume a valid name - exceptions are not handled here.
132
136
  #
133
137
  # @param detector_name [String] the detector in question, e.g. 'DuplicateMethodCall'
134
- # @return [SmellDetector] - this will return the class, not an instance
138
+ # @return [SmellDetector] this will return the class, not an instance
135
139
  #
136
140
  def to_detector(detector_name)
137
141
  SmellDetectors.const_get detector_name
138
142
  end
139
143
 
140
144
  #
141
- # @return [Set<Symbol>] - all configuration keys that are available for this detector
145
+ # @return [Set<Symbol>] all configuration keys that are available for this detector
142
146
  #
143
147
  def configuration_keys
144
148
  Set.new(default_config.keys.map(&:to_sym))
@@ -19,14 +19,12 @@ module Reek
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- # :reek:FeatureEnvy
23
- def sniff(ctx)
24
- ctx.default_assignments.select do |_parameter, value|
22
+ def sniff
23
+ context.default_assignments.select do |_parameter, value|
25
24
  [:true, :false].include?(value.type)
26
25
  end.map do |parameter, _value|
27
26
  smell_warning(
28
- context: ctx,
29
- lines: [ctx.exp.line],
27
+ lines: [source_line],
30
28
  message: "has boolean parameter '#{parameter}'",
31
29
  parameters: { parameter: parameter.to_s })
32
30
  end
@@ -24,10 +24,10 @@ module Reek
24
24
  #
25
25
  # @return [Array<SmellWarning>]
26
26
  #
27
- def sniff(ctx)
28
- class_variables_in(ctx.exp).map do |variable, lines|
27
+ def sniff
28
+ class_variables_in_context.map do |variable, occurences|
29
+ lines = occurences.map(&:line)
29
30
  smell_warning(
30
- context: ctx,
31
31
  lines: lines,
32
32
  message: "declares the class variable '#{variable}'",
33
33
  parameters: { name: variable.to_s })
@@ -38,17 +38,8 @@ module Reek
38
38
  # Collects the names of the class variables declared and/or used
39
39
  # in the given module.
40
40
  #
41
- # :reek:TooManyStatements: { max_statements: 7 }
42
- # :reek:FeatureEnvy
43
- def class_variables_in(exp)
44
- result = Hash.new { |hash, key| hash[key] = [] }
45
- collector = proc do |cvar_node|
46
- result[cvar_node.name].push(cvar_node.line)
47
- end
48
- [:cvar, :cvasgn, :cvdecl].each do |stmt_type|
49
- exp.each_node(stmt_type, [:class, :module], &collector)
50
- end
51
- result
41
+ def class_variables_in_context
42
+ context.local_nodes([:cvar, :cvasgn, :cvdecl]).group_by(&:name)
52
43
  end
53
44
  end
54
45
  end
@@ -49,18 +49,32 @@ module Reek
49
49
  #
50
50
  # @return [Array<SmellWarning>]
51
51
  #
52
- # :reek:FeatureEnvy
53
- def sniff(ctx)
54
- ControlParameterCollector.new(ctx).control_parameters.map do |control_parameter|
52
+ def sniff
53
+ control_parameters.map do |control_parameter|
55
54
  argument = control_parameter.name.to_s
56
55
  smell_warning(
57
- context: ctx,
58
56
  lines: control_parameter.lines,
59
57
  message: "is controlled by argument '#{argument}'",
60
58
  parameters: { argument: argument })
61
59
  end
62
60
  end
63
61
 
62
+ private
63
+
64
+ def control_parameters
65
+ potential_parameters.
66
+ map { |param| FoundControlParameter.new(param, find_matches(param)) }.
67
+ select(&:smells?)
68
+ end
69
+
70
+ def potential_parameters
71
+ expression.parameter_names
72
+ end
73
+
74
+ def find_matches(param)
75
+ ControlParameterFinder.new(expression, param).find_matches
76
+ end
77
+
64
78
  #
65
79
  # Collects information about a single control parameter.
66
80
  #
@@ -166,35 +180,6 @@ module Reek
166
180
  end
167
181
 
168
182
  private_constant :ControlParameterFinder
169
-
170
- #
171
- # Collects all control parameters in a given context.
172
- #
173
- class ControlParameterCollector
174
- def initialize(context)
175
- @context = context
176
- end
177
-
178
- def control_parameters
179
- potential_parameters.
180
- map { |param| FoundControlParameter.new(param, find_matches(param)) }.
181
- select(&:smells?)
182
- end
183
-
184
- private
185
-
186
- attr_reader :context
187
-
188
- def potential_parameters
189
- context.exp.parameter_names
190
- end
191
-
192
- def find_matches(param)
193
- ControlParameterFinder.new(context.exp, param).find_matches
194
- end
195
- end
196
-
197
- private_constant :ControlParameterCollector
198
183
  end
199
184
  end
200
185
  end
@@ -24,7 +24,7 @@ module Reek
24
24
  # reported as a DataClump unless there are more than this many
25
25
  # methods containing those parameters.
26
26
  #
27
- MAX_COPIES_KEY = 'max_copies'.freeze
27
+ MAX_COPIES_KEY = 'max_copies'
28
28
  DEFAULT_MAX_COPIES = 2
29
29
 
30
30
  #
@@ -32,7 +32,7 @@ module Reek
32
32
  # size. No group of common parameters will be reported as
33
33
  # a DataClump unless it contains at least this many parameters.
34
34
  #
35
- MIN_CLUMP_SIZE_KEY = 'min_clump_size'.freeze
35
+ MIN_CLUMP_SIZE_KEY = 'min_clump_size'
36
36
  DEFAULT_MIN_CLUMP_SIZE = 2
37
37
 
38
38
  def self.contexts # :nodoc:
@@ -50,14 +50,10 @@ module Reek
50
50
  #
51
51
  # @return [Array<SmellWarning>]
52
52
  #
53
- # :reek:FeatureEnvy
54
- def sniff(ctx)
55
- max_copies = value(MAX_COPIES_KEY, ctx)
56
- min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx)
57
- MethodGroup.new(ctx, min_clump_size, max_copies).clumps.map do |clump, methods|
53
+ def sniff
54
+ MethodGroup.new(context, min_clump_size, max_copies).clumps.map do |clump, methods|
58
55
  methods_length = methods.length
59
56
  smell_warning(
60
- context: ctx,
61
57
  lines: methods.map(&:line),
62
58
  message: "takes parameters #{DataClump.print_clump(clump)} " \
63
59
  "to #{methods_length} methods",
@@ -72,6 +68,16 @@ module Reek
72
68
  def self.print_clump(clump)
73
69
  "[#{clump.map { |parameter| "'#{parameter}'" }.join(', ')}]"
74
70
  end
71
+
72
+ private
73
+
74
+ def max_copies
75
+ value(MAX_COPIES_KEY, context)
76
+ end
77
+
78
+ def min_clump_size
79
+ value(MIN_CLUMP_SIZE_KEY, context)
80
+ end
75
81
  end
76
82
  end
77
83
 
@@ -94,7 +100,7 @@ module Reek
94
100
  end.uniq
95
101
  end
96
102
 
97
- # :reek:UtilityFunction
103
+ # @quality :reek:UtilityFunction
98
104
  def common_argument_names_for(methods)
99
105
  methods.map(&:arg_names).inject(:&)
100
106
  end
@@ -21,13 +21,13 @@ module Reek
21
21
  class DuplicateMethodCall < BaseDetector
22
22
  # The name of the config field that sets the maximum number of
23
23
  # identical calls to be permitted within any single method.
24
- MAX_ALLOWED_CALLS_KEY = 'max_calls'.freeze
24
+ MAX_ALLOWED_CALLS_KEY = 'max_calls'
25
25
  DEFAULT_MAX_CALLS = 1
26
26
 
27
27
  # The name of the config field that sets the names of any
28
28
  # methods for which identical calls should be to be permitted
29
29
  # within any single method.
30
- ALLOW_CALLS_KEY = 'allow_calls'.freeze
30
+ ALLOW_CALLS_KEY = 'allow_calls'
31
31
  DEFAULT_ALLOW_CALLS = [].freeze
32
32
 
33
33
  def self.default_config
@@ -37,26 +37,32 @@ module Reek
37
37
  end
38
38
 
39
39
  #
40
- # Looks for duplicate calls within the body of the method +ctx+.
40
+ # Looks for duplicate calls within the body of the method context.
41
41
  #
42
42
  # @return [Array<SmellWarning>]
43
43
  #
44
- # :reek:FeatureEnvy
45
- # :reek:DuplicateMethodCall: { max_calls: 2 }
46
- def sniff(ctx)
47
- max_allowed_calls = value(MAX_ALLOWED_CALLS_KEY, ctx)
48
- allow_calls = value(ALLOW_CALLS_KEY, ctx)
49
-
50
- collector = CallCollector.new(ctx, max_allowed_calls, allow_calls)
44
+ def sniff
45
+ collector = CallCollector.new(context, max_allowed_calls, allow_calls)
51
46
  collector.smelly_calls.map do |found_call|
47
+ call = found_call.call
48
+ occurs = found_call.occurs
52
49
  smell_warning(
53
- context: ctx,
54
50
  lines: found_call.lines,
55
- message: "calls '#{found_call.call}' #{found_call.occurs} times",
56
- parameters: { name: found_call.call, count: found_call.occurs })
51
+ message: "calls '#{call}' #{occurs} times",
52
+ parameters: { name: call, count: occurs })
57
53
  end
58
54
  end
59
55
 
56
+ private
57
+
58
+ def max_allowed_calls
59
+ value(MAX_ALLOWED_CALLS_KEY, context)
60
+ end
61
+
62
+ def allow_calls
63
+ value(ALLOW_CALLS_KEY, context)
64
+ end
65
+
60
66
  # Collects information about a single found call
61
67
  class FoundCall
62
68
  def initialize(call_node)
@@ -111,10 +117,10 @@ module Reek
111
117
 
112
118
  attr_reader :allow_calls, :max_allowed_calls
113
119
 
114
- # :reek:TooManyStatements: { max_statements: 6 }
115
- # :reek:DuplicateMethodCall: { max_calls: 2 }
120
+ # @quality :reek:TooManyStatements { max_statements: 6 }
121
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
116
122
  def collect_calls(result)
117
- context.each_node(:send, [:mlhs]) do |call_node|
123
+ context.local_nodes(:send, [:mlhs]) do |call_node|
118
124
  next if call_node.object_creation_call?
119
125
  next if simple_method_call? call_node
120
126
  result[call_node].record(call_node)
@@ -128,7 +134,7 @@ module Reek
128
134
  found_call.occurs > max_allowed_calls && !allow_calls?(found_call.call)
129
135
  end
130
136
 
131
- # :reek:UtilityFunction
137
+ # @quality :reek:UtilityFunction
132
138
  def simple_method_call?(call_node)
133
139
  !call_node.receiver && call_node.args.empty?
134
140
  end
@@ -42,11 +42,11 @@ module Reek
42
42
  #
43
43
  # @return [Array<SmellWarning>]
44
44
  #
45
- def sniff(ctx)
46
- return [] unless ctx.references_self?
47
- envious_receivers(ctx).map do |name, lines|
45
+ def sniff
46
+ return [] if context.singleton_method? || context.module_function?
47
+ return [] unless context.references_self?
48
+ envious_receivers.map do |name, lines|
48
49
  smell_warning(
49
- context: ctx,
50
50
  lines: lines,
51
51
  message: "refers to '#{name}' more than self (maybe move it to another class?)",
52
52
  parameters: { name: name.to_s })
@@ -55,9 +55,11 @@ module Reek
55
55
 
56
56
  private
57
57
 
58
- # :reek:UtilityFunction
59
- def envious_receivers(ctx)
60
- refs = ctx.refs
58
+ def refs
59
+ @refs ||= context.refs
60
+ end
61
+
62
+ def envious_receivers
61
63
  return {} if refs.self_is_max?
62
64
  refs.most_popular
63
65
  end
@@ -19,32 +19,31 @@ module Reek
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- def sniff(ctx)
23
- method_expressions = ctx.node_instance_methods
24
-
25
- assumptions = (variables_from_context(method_expressions) -
26
- variables_from_initialize(method_expressions)).uniq
22
+ def sniff
23
+ assumptions = (variables_from_context - variables_from_initialize).uniq
27
24
 
28
25
  assumptions.map do |assumption|
29
- build_smell_warning(ctx, assumption)
26
+ build_smell_warning(assumption)
30
27
  end
31
28
  end
32
29
 
33
30
  private
34
31
 
35
- def build_smell_warning(ctx, assumption)
32
+ def method_expressions
33
+ @method_expressions ||= context.node_instance_methods
34
+ end
35
+
36
+ def build_smell_warning(assumption)
36
37
  message = "assumes too much for instance variable '#{assumption}'"
37
38
 
38
39
  smell_warning(
39
- context: ctx,
40
- lines: [ctx.exp.line],
40
+ lines: [source_line],
41
41
  message: message,
42
42
  parameters: { assumption: assumption.to_s })
43
43
  end
44
44
 
45
- # :reek:UtilityFunction
46
- def variables_from_initialize(instance_methods)
47
- initialize_exp = instance_methods.detect do |method|
45
+ def variables_from_initialize
46
+ initialize_exp = method_expressions.detect do |method|
48
47
  method.name == :initialize
49
48
  end
50
49
 
@@ -53,19 +52,11 @@ module Reek
53
52
  initialize_exp.each_node(:ivasgn).map(&:name)
54
53
  end
55
54
 
56
- def variables_from_context(instance_methods)
57
- instance_methods.map do |method|
58
- method.find_nodes(assumption_nodes, ignored_nodes).map(&:name)
55
+ def variables_from_context
56
+ method_expressions.map do |method|
57
+ method.each_node(:ivar, [:or_asgn]).map(&:name)
59
58
  end.flatten
60
59
  end
61
-
62
- def assumption_nodes
63
- [:ivar]
64
- end
65
-
66
- def ignored_nodes
67
- [:or_asgn]
68
- end
69
60
  end
70
61
  end
71
62
  end
@@ -19,24 +19,17 @@ module Reek
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- def sniff(ctx)
23
- return [] if descriptive?(ctx) || ctx.namespace_module?
24
- expression = ctx.exp
22
+ def sniff
23
+ return [] if descriptive_context? || context.namespace_module?
25
24
  [smell_warning(
26
- context: ctx,
27
- lines: [expression.line],
25
+ lines: [source_line],
28
26
  message: 'has no descriptive comment')]
29
27
  end
30
28
 
31
29
  private
32
30
 
33
- def descriptive
34
- @descriptive ||= {}
35
- end
36
-
37
- # :reek:FeatureEnvy
38
- def descriptive?(ctx)
39
- descriptive[ctx.full_name] ||= ctx.descriptively_commented?
31
+ def descriptive_context?
32
+ context.descriptively_commented?
40
33
  end
41
34
  end
42
35
  end
@@ -16,7 +16,7 @@ module Reek
16
16
  class LongParameterList < BaseDetector
17
17
  # The name of the config field that sets the maximum number of
18
18
  # parameters permitted in any method or block.
19
- MAX_ALLOWED_PARAMS_KEY = 'max_params'.freeze
19
+ MAX_ALLOWED_PARAMS_KEY = 'max_params'
20
20
  DEFAULT_MAX_ALLOWED_PARAMS = 3
21
21
 
22
22
  def self.default_config
@@ -32,17 +32,20 @@ module Reek
32
32
  #
33
33
  # @return [Array<SmellWarning>]
34
34
  #
35
- def sniff(ctx)
36
- max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx)
37
- exp = ctx.exp
38
- count = exp.arg_names.length
35
+ def sniff
36
+ count = expression.arg_names.length
39
37
  return [] if count <= max_allowed_params
40
38
  [smell_warning(
41
- context: ctx,
42
- lines: [exp.line],
39
+ lines: [source_line],
43
40
  message: "has #{count} parameters",
44
41
  parameters: { count: count })]
45
42
  end
43
+
44
+ private
45
+
46
+ def max_allowed_params
47
+ value(MAX_ALLOWED_PARAMS_KEY, context)
48
+ end
46
49
  end
47
50
  end
48
51
  end
@@ -12,7 +12,7 @@ module Reek
12
12
  class LongYieldList < BaseDetector
13
13
  # The name of the config field that sets the maximum number of
14
14
  # parameters permitted in any method or block.
15
- MAX_ALLOWED_PARAMS_KEY = 'max_params'.freeze
15
+ MAX_ALLOWED_PARAMS_KEY = 'max_params'
16
16
  DEFAULT_MAX_ALLOWED_PARAMS = 3
17
17
 
18
18
  def self.default_config
@@ -24,21 +24,24 @@ module Reek
24
24
  #
25
25
  # @return [Array<SmellWarning>]
26
26
  #
27
- # :reek:FeatureEnvy
28
- # :reek:DuplicateMethodCall: { max_calls: 2 }
29
- def sniff(ctx)
30
- max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx)
31
- ctx.local_nodes(:yield).select do |yield_node|
27
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
28
+ def sniff
29
+ context.local_nodes(:yield).select do |yield_node|
32
30
  yield_node.args.length > max_allowed_params
33
31
  end.map do |yield_node|
34
32
  count = yield_node.args.length
35
33
  smell_warning(
36
- context: ctx,
37
34
  lines: [yield_node.line],
38
35
  message: "yields #{count} parameters",
39
36
  parameters: { count: count })
40
37
  end
41
38
  end
39
+
40
+ private
41
+
42
+ def max_allowed_params
43
+ value(MAX_ALLOWED_PARAMS_KEY, context)
44
+ end
42
45
  end
43
46
  end
44
47
  end
@@ -12,19 +12,18 @@ module Reek
12
12
  #
13
13
  # See {file:docs/Manual-Dispatch.md} for details.
14
14
  class ManualDispatch < BaseDetector
15
- MESSAGE = 'manually dispatches method call'.freeze
15
+ MESSAGE = 'manually dispatches method call'
16
16
 
17
17
  #
18
18
  # Checks for +respond_to?+ usage within the given method
19
19
  #
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
- # :reek:FeatureEnvy
23
- def sniff(ctx)
24
- smelly_nodes = ctx.each_node(:send).select { |node| node.name == :respond_to? }
22
+ def sniff
23
+ smelly_nodes = context.local_nodes(:send).select { |node| node.name == :respond_to? }
25
24
  return [] if smelly_nodes.empty?
26
25
  lines = smelly_nodes.map(&:line)
27
- [smell_warning(context: ctx, lines: lines, message: MESSAGE)]
26
+ [smell_warning(lines: lines, message: MESSAGE)]
28
27
  end
29
28
  end
30
29
  end