reek 4.7.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +18 -3
  4. data/.simplecov +1 -0
  5. data/.travis.yml +3 -4
  6. data/.yardopts +1 -1
  7. data/CHANGELOG.md +45 -0
  8. data/Gemfile +4 -4
  9. data/README.md +134 -104
  10. data/Rakefile +16 -3
  11. data/bin/reek +1 -3
  12. data/docs/API.md +2 -9
  13. data/docs/Basic-Smell-Options.md +51 -11
  14. data/docs/Code-Smells.md +1 -1
  15. data/docs/Command-Line-Options.md +14 -4
  16. data/docs/Duplicate-Method-Call.md +49 -1
  17. data/docs/Feature-Envy.md +44 -0
  18. data/docs/How-To-Write-New-Detectors.md +2 -3
  19. data/docs/Instance-Variable-Assumption.md +1 -1
  20. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  21. data/docs/Rake-Task.md +1 -1
  22. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  23. data/docs/Reek-Driven-Development.md +1 -1
  24. data/docs/Uncommunicative-Method-Name.md +45 -6
  25. data/docs/Uncommunicative-Module-Name.md +49 -7
  26. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  27. data/docs/Uncommunicative-Variable-Name.md +73 -2
  28. data/docs/Unused-Private-Method.md +2 -2
  29. data/docs/defaults.reek.yml +129 -0
  30. data/docs/yard_plugin.rb +1 -0
  31. data/features/command_line_interface/basic_usage.feature +2 -2
  32. data/features/command_line_interface/options.feature +46 -4
  33. data/features/command_line_interface/show_progress.feature +4 -4
  34. data/features/command_line_interface/smell_selection.feature +1 -1
  35. data/features/command_line_interface/smells_count.feature +6 -6
  36. data/features/command_line_interface/stdin.feature +30 -8
  37. data/features/configuration_files/accept_setting.feature +45 -28
  38. data/features/configuration_files/directory_specific_directives.feature +78 -73
  39. data/features/configuration_files/exclude_directives.feature +11 -10
  40. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  41. data/features/configuration_files/masking_smells.feature +38 -9
  42. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  43. data/features/configuration_files/reject_setting.feature +52 -41
  44. data/features/configuration_files/schema_validation.feature +59 -0
  45. data/features/configuration_files/unused_private_method.feature +18 -16
  46. data/features/configuration_loading.feature +53 -10
  47. data/features/configuration_via_source_comments/erroneous_source_comments.feature +2 -2
  48. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  49. data/features/locales.feature +2 -2
  50. data/features/rake_task/rake_task.feature +15 -15
  51. data/features/reports/json.feature +3 -3
  52. data/features/reports/reports.feature +34 -34
  53. data/features/reports/yaml.feature +3 -3
  54. data/features/rspec_matcher.feature +9 -1
  55. data/features/samples.feature +287 -287
  56. data/features/step_definitions/reek_steps.rb +4 -0
  57. data/features/step_definitions/sample_file_steps.rb +9 -4
  58. data/features/support/env.rb +2 -2
  59. data/features/todo_list.feature +26 -23
  60. data/lib/reek/ast/node.rb +40 -55
  61. data/lib/reek/ast/object_refs.rb +1 -1
  62. data/lib/reek/ast/reference_collector.rb +2 -4
  63. data/lib/reek/ast/sexp_extensions/case.rb +1 -1
  64. data/lib/reek/ast/sexp_extensions/if.rb +8 -1
  65. data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
  66. data/lib/reek/ast/sexp_extensions/methods.rb +4 -6
  67. data/lib/reek/cli/application.rb +4 -3
  68. data/lib/reek/cli/command/report_command.rb +1 -2
  69. data/lib/reek/cli/command/todo_list_command.rb +8 -8
  70. data/lib/reek/cli/options.rb +29 -14
  71. data/lib/reek/cli/silencer.rb +14 -3
  72. data/lib/reek/code_comment.rb +14 -16
  73. data/lib/reek/configuration/app_configuration.rb +32 -28
  74. data/lib/reek/configuration/configuration_converter.rb +110 -0
  75. data/lib/reek/configuration/configuration_file_finder.rb +15 -40
  76. data/lib/reek/configuration/configuration_validator.rb +12 -23
  77. data/lib/reek/configuration/default_directive.rb +17 -3
  78. data/lib/reek/configuration/directory_directives.rb +17 -11
  79. data/lib/reek/configuration/excluded_paths.rb +1 -1
  80. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  81. data/lib/reek/configuration/schema.yml +210 -0
  82. data/lib/reek/configuration/schema_validator.rb +38 -0
  83. data/lib/reek/context/attribute_context.rb +1 -1
  84. data/lib/reek/context/code_context.rb +8 -11
  85. data/lib/reek/context/method_context.rb +7 -12
  86. data/lib/reek/context/module_context.rb +4 -4
  87. data/lib/reek/context_builder.rb +11 -11
  88. data/lib/reek/detector_repository.rb +6 -0
  89. data/lib/reek/documentation_link.rb +28 -0
  90. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +13 -12
  91. data/lib/reek/errors/bad_detector_in_comment_error.rb +11 -10
  92. data/lib/reek/errors/base_error.rb +3 -0
  93. data/lib/reek/errors/config_file_error.rb +11 -0
  94. data/lib/reek/errors/encoding_error.rb +16 -11
  95. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +11 -10
  96. data/lib/reek/errors/incomprehensible_source_error.rb +20 -22
  97. data/lib/reek/errors/syntax_error.rb +41 -0
  98. data/lib/reek/examiner.rb +19 -25
  99. data/lib/reek/logging_error_handler.rb +7 -5
  100. data/lib/reek/rake/task.rb +3 -3
  101. data/lib/reek/report/base_report.rb +8 -12
  102. data/lib/reek/report/code_climate/code_climate_configuration.rb +1 -1
  103. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -10
  104. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  105. data/lib/reek/report/heading_formatter.rb +54 -0
  106. data/lib/reek/report/json_report.rb +1 -1
  107. data/lib/reek/report/location_formatter.rb +40 -0
  108. data/lib/reek/report/progress_formatter.rb +79 -0
  109. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  110. data/lib/reek/report/text_report.rb +1 -2
  111. data/lib/reek/report/xml_report.rb +3 -3
  112. data/lib/reek/report/yaml_report.rb +1 -1
  113. data/lib/reek/report.rb +15 -10
  114. data/lib/reek/smell_configuration.rb +2 -2
  115. data/lib/reek/smell_detectors/attribute.rb +0 -1
  116. data/lib/reek/smell_detectors/base_detector.rb +9 -12
  117. data/lib/reek/smell_detectors/boolean_parameter.rb +0 -1
  118. data/lib/reek/smell_detectors/class_variable.rb +3 -11
  119. data/lib/reek/smell_detectors/control_parameter.rb +17 -32
  120. data/lib/reek/smell_detectors/data_clump.rb +3 -4
  121. data/lib/reek/smell_detectors/duplicate_method_call.rb +6 -7
  122. data/lib/reek/smell_detectors/feature_envy.rb +1 -1
  123. data/lib/reek/smell_detectors/instance_variable_assumption.rb +1 -10
  124. data/lib/reek/smell_detectors/irresponsible_module.rb +0 -1
  125. data/lib/reek/smell_detectors/long_parameter_list.rb +1 -2
  126. data/lib/reek/smell_detectors/long_yield_list.rb +2 -3
  127. data/lib/reek/smell_detectors/manual_dispatch.rb +3 -3
  128. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +6 -7
  129. data/lib/reek/smell_detectors/module_initialize.rb +1 -2
  130. data/lib/reek/smell_detectors/nested_iterators.rb +6 -6
  131. data/lib/reek/smell_detectors/nil_check.rb +0 -1
  132. data/lib/reek/smell_detectors/repeated_conditional.rb +3 -4
  133. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +0 -1
  134. data/lib/reek/smell_detectors/too_many_constants.rb +2 -3
  135. data/lib/reek/smell_detectors/too_many_instance_variables.rb +1 -2
  136. data/lib/reek/smell_detectors/too_many_methods.rb +1 -2
  137. data/lib/reek/smell_detectors/too_many_statements.rb +1 -2
  138. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +2 -3
  139. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +2 -3
  140. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +2 -3
  141. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +6 -7
  142. data/lib/reek/smell_detectors/unused_parameters.rb +0 -1
  143. data/lib/reek/smell_detectors/unused_private_method.rb +0 -1
  144. data/lib/reek/smell_detectors/utility_function.rb +2 -3
  145. data/lib/reek/smell_detectors.rb +1 -2
  146. data/lib/reek/smell_warning.rb +15 -8
  147. data/lib/reek/source/source_code.rb +50 -72
  148. data/lib/reek/source/source_locator.rb +7 -7
  149. data/lib/reek/spec/should_reek.rb +2 -2
  150. data/lib/reek/spec/should_reek_of.rb +9 -16
  151. data/lib/reek/spec/should_reek_only_of.rb +4 -4
  152. data/lib/reek/spec.rb +6 -6
  153. data/lib/reek/tree_dresser.rb +5 -5
  154. data/lib/reek/version.rb +1 -1
  155. data/reek.gemspec +5 -5
  156. data/samples/checkstyle.xml +1 -1
  157. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  158. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  159. data/samples/configuration/full_configuration.reek +8 -4
  160. data/samples/configuration/full_mask.reek +5 -4
  161. data/samples/configuration/partial_mask.reek +3 -2
  162. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  163. data/samples/paths.rb +5 -4
  164. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  165. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  166. data/spec/factories/factories.rb +2 -13
  167. data/spec/reek/ast/node_spec.rb +103 -10
  168. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  169. data/spec/reek/ast/sexp_extensions_spec.rb +2 -2
  170. data/spec/reek/cli/application_spec.rb +50 -38
  171. data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
  172. data/spec/reek/cli/silencer_spec.rb +28 -0
  173. data/spec/reek/code_comment_spec.rb +31 -38
  174. data/spec/reek/configuration/app_configuration_spec.rb +46 -33
  175. data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -49
  176. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  177. data/spec/reek/configuration/directory_directives_spec.rb +6 -7
  178. data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
  179. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  180. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  181. data/spec/reek/context/code_context_spec.rb +60 -96
  182. data/spec/reek/context/ghost_context_spec.rb +1 -1
  183. data/spec/reek/context/root_context_spec.rb +1 -1
  184. data/spec/reek/documentation_link_spec.rb +20 -0
  185. data/spec/reek/errors/base_error_spec.rb +13 -0
  186. data/spec/reek/examiner_spec.rb +100 -30
  187. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -80
  188. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  189. data/spec/reek/report/json_report_spec.rb +13 -46
  190. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  191. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  192. data/spec/reek/report/text_report_spec.rb +4 -4
  193. data/spec/reek/report/xml_report_spec.rb +3 -3
  194. data/spec/reek/report/yaml_report_spec.rb +9 -38
  195. data/spec/reek/report_spec.rb +3 -3
  196. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  197. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  198. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  199. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  200. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  201. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +9 -9
  202. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  203. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  204. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  205. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  206. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  207. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  208. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +6 -4
  209. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
  210. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  211. data/spec/reek/smell_detectors/unused_private_method_spec.rb +10 -10
  212. data/spec/reek/smell_detectors/utility_function_spec.rb +5 -5
  213. data/spec/reek/smell_warning_spec.rb +12 -8
  214. data/spec/reek/source/source_code_spec.rb +17 -43
  215. data/spec/reek/source/source_locator_spec.rb +17 -17
  216. data/spec/reek/spec/should_reek_of_spec.rb +7 -11
  217. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  218. data/spec/reek/spec/should_reek_spec.rb +3 -3
  219. data/spec/reek/spec/smell_matcher_spec.rb +3 -3
  220. data/spec/reek/tree_dresser_spec.rb +12 -17
  221. data/spec/spec_helper.rb +6 -17
  222. data/tasks/configuration.rake +8 -5
  223. metadata +71 -41
  224. data/defaults.reek +0 -131
  225. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  226. data/lib/reek/report/formatter/heading_formatter.rb +0 -52
  227. data/lib/reek/report/formatter/location_formatter.rb +0 -42
  228. data/lib/reek/report/formatter/progress_formatter.rb +0 -81
  229. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  230. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -36
  231. data/lib/reek/report/formatter.rb +0 -33
  232. data/lib/reek/smell_detectors/syntax.rb +0 -37
  233. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  234. data/samples/smelly_with_inline_mask.rb +0 -8
  235. data/samples/smelly_with_modifiers.rb +0 -12
  236. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  237. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  238. data/spec/reek/smell_detectors/syntax_spec.rb +0 -17
  239. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  240. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  241. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  242. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  243. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  244. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  245. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  246. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  247. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  248. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -12,7 +12,7 @@ module Reek
12
12
  #
13
13
  class YAMLReport < BaseReport
14
14
  def show(out = $stdout)
15
- out.print smells.map { |smell| warning_formatter.format_hash(smell) }.to_yaml
15
+ out.print smells.map(&:yaml_hash).to_yaml
16
16
  end
17
17
  end
18
18
  end
data/lib/reek/report.rb CHANGED
@@ -6,7 +6,12 @@ require_relative 'report/json_report'
6
6
  require_relative 'report/text_report'
7
7
  require_relative 'report/xml_report'
8
8
  require_relative 'report/yaml_report'
9
- require_relative 'report/formatter'
9
+
10
+ require_relative 'report/heading_formatter'
11
+ require_relative 'report/location_formatter'
12
+ require_relative 'report/progress_formatter'
13
+ require_relative 'report/simple_warning_formatter'
14
+ require_relative 'report/documentation_link_warning_formatter'
10
15
 
11
16
  module Reek
12
17
  # Reek reporting functionality.
@@ -21,24 +26,24 @@ module Reek
21
26
  }.freeze
22
27
 
23
28
  LOCATION_FORMATTERS = {
24
- single_line: Formatter::SingleLineLocationFormatter,
25
- plain: Formatter::BlankLocationFormatter,
26
- numbers: Formatter::DefaultLocationFormatter
29
+ single_line: SingleLineLocationFormatter,
30
+ plain: BlankLocationFormatter,
31
+ numbers: DefaultLocationFormatter
27
32
  }.freeze
28
33
 
29
34
  HEADING_FORMATTERS = {
30
- verbose: Formatter::VerboseHeadingFormatter,
31
- quiet: Formatter::QuietHeadingFormatter
35
+ verbose: VerboseHeadingFormatter,
36
+ quiet: QuietHeadingFormatter
32
37
  }.freeze
33
38
 
34
39
  PROGRESS_FORMATTERS = {
35
- dots: Formatter::ProgressFormatter::Dots,
36
- quiet: Formatter::ProgressFormatter::Quiet
40
+ dots: ProgressFormatter::Dots,
41
+ quiet: ProgressFormatter::Quiet
37
42
  }.freeze
38
43
 
39
44
  WARNING_FORMATTER_CLASSES = {
40
- wiki_links: Formatter::WikiLinkWarningFormatter,
41
- simple: Formatter::SimpleWarningFormatter
45
+ documentation_links: DocumentationLinkWarningFormatter,
46
+ simple: SimpleWarningFormatter
42
47
  }.freeze
43
48
 
44
49
  # Map report format symbol to a report class.
@@ -7,11 +7,11 @@ module Reek
7
7
  class SmellConfiguration
8
8
  # The name of the config field that specifies whether a smell is
9
9
  # enabled. Set to +true+ or +false+.
10
- ENABLED_KEY = 'enabled'.freeze
10
+ ENABLED_KEY = 'enabled'
11
11
 
12
12
  # The name of the config field that sets scope-specific overrides
13
13
  # for other values in the current smell detector's configuration.
14
- OVERRIDES_KEY = 'overrides'.freeze
14
+ OVERRIDES_KEY = 'overrides'
15
15
 
16
16
  def initialize(hash)
17
17
  @options = hash
@@ -28,7 +28,6 @@ module Reek
28
28
  def sniff
29
29
  attributes_in_context.map do |_attribute, line|
30
30
  smell_warning(
31
- context: context,
32
31
  lines: [line],
33
32
  message: 'is a writable attribute')
34
33
  end
@@ -15,14 +15,14 @@ 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.
@@ -59,7 +59,7 @@ module Reek
59
59
  end
60
60
 
61
61
  def source_line
62
- @line ||= expression.line
62
+ @source_line ||= expression.line
63
63
  end
64
64
 
65
65
  def exception?
@@ -78,12 +78,9 @@ module Reek
78
78
  ctx.config_for(self.class)
79
79
  end
80
80
 
81
- # :reek:FeatureEnvy
82
- def smell_warning(options = {})
83
- context = options.fetch(:context)
84
- exp = context.exp
81
+ def smell_warning(**options)
85
82
  SmellWarning.new(self,
86
- source: exp.source,
83
+ source: expression.source,
87
84
  context: context.full_name,
88
85
  lines: options.fetch(:lines),
89
86
  message: options.fetch(:message),
@@ -99,7 +96,7 @@ module Reek
99
96
  [:def, :defs]
100
97
  end
101
98
 
102
- # :reek:UtilityFunction
99
+ # @quality :reek:UtilityFunction
103
100
  def default_config
104
101
  {
105
102
  SmellConfiguration::ENABLED_KEY => true,
@@ -138,14 +135,14 @@ module Reek
138
135
  # Note that we assume a valid name - exceptions are not handled here.
139
136
  #
140
137
  # @param detector_name [String] the detector in question, e.g. 'DuplicateMethodCall'
141
- # @return [SmellDetector] - this will return the class, not an instance
138
+ # @return [SmellDetector] this will return the class, not an instance
142
139
  #
143
140
  def to_detector(detector_name)
144
141
  SmellDetectors.const_get detector_name
145
142
  end
146
143
 
147
144
  #
148
- # @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
149
146
  #
150
147
  def configuration_keys
151
148
  Set.new(default_config.keys.map(&:to_sym))
@@ -24,7 +24,6 @@ module Reek
24
24
  [:true, :false].include?(value.type)
25
25
  end.map do |parameter, _value|
26
26
  smell_warning(
27
- context: context,
28
27
  lines: [source_line],
29
28
  message: "has boolean parameter '#{parameter}'",
30
29
  parameters: { parameter: parameter.to_s })
@@ -25,9 +25,9 @@ module Reek
25
25
  # @return [Array<SmellWarning>]
26
26
  #
27
27
  def sniff
28
- class_variables_in_context.map do |variable, lines|
28
+ class_variables_in_context.map do |variable, occurences|
29
+ lines = occurences.map(&:line)
29
30
  smell_warning(
30
- context: context,
31
31
  lines: lines,
32
32
  message: "declares the class variable '#{variable}'",
33
33
  parameters: { name: variable.to_s })
@@ -38,16 +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
41
  def class_variables_in_context
43
- result = Hash.new { |hash, key| hash[key] = [] }
44
- collector = proc do |cvar_node|
45
- result[cvar_node.name].push(cvar_node.line)
46
- end
47
- [:cvar, :cvasgn, :cvdecl].each do |stmt_type|
48
- expression.each_node(stmt_type, [:class, :module], &collector)
49
- end
50
- result
42
+ context.local_nodes([:cvar, :cvasgn, :cvdecl]).group_by(&:name)
51
43
  end
52
44
  end
53
45
  end
@@ -49,18 +49,32 @@ module Reek
49
49
  #
50
50
  # @return [Array<SmellWarning>]
51
51
  #
52
- # :reek:FeatureEnvy
53
52
  def sniff
54
- ControlParameterCollector.new(context).control_parameters.map do |control_parameter|
53
+ control_parameters.map do |control_parameter|
55
54
  argument = control_parameter.name.to_s
56
55
  smell_warning(
57
- context: context,
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:
@@ -54,7 +54,6 @@ module Reek
54
54
  MethodGroup.new(context, min_clump_size, max_copies).clumps.map do |clump, methods|
55
55
  methods_length = methods.length
56
56
  smell_warning(
57
- context: context,
58
57
  lines: methods.map(&:line),
59
58
  message: "takes parameters #{DataClump.print_clump(clump)} " \
60
59
  "to #{methods_length} methods",
@@ -101,7 +100,7 @@ module Reek
101
100
  end.uniq
102
101
  end
103
102
 
104
- # :reek:UtilityFunction
103
+ # @quality :reek:UtilityFunction
105
104
  def common_argument_names_for(methods)
106
105
  methods.map(&:arg_names).inject(:&)
107
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
@@ -47,7 +47,6 @@ module Reek
47
47
  call = found_call.call
48
48
  occurs = found_call.occurs
49
49
  smell_warning(
50
- context: context,
51
50
  lines: found_call.lines,
52
51
  message: "calls '#{call}' #{occurs} times",
53
52
  parameters: { name: call, count: occurs })
@@ -118,10 +117,10 @@ module Reek
118
117
 
119
118
  attr_reader :allow_calls, :max_allowed_calls
120
119
 
121
- # :reek:TooManyStatements: { max_statements: 6 }
122
- # :reek:DuplicateMethodCall: { max_calls: 2 }
120
+ # @quality :reek:TooManyStatements { max_statements: 6 }
121
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
123
122
  def collect_calls(result)
124
- context.each_node(:send, [:mlhs]) do |call_node|
123
+ context.local_nodes(:send, [:mlhs]) do |call_node|
125
124
  next if call_node.object_creation_call?
126
125
  next if simple_method_call? call_node
127
126
  result[call_node].record(call_node)
@@ -135,7 +134,7 @@ module Reek
135
134
  found_call.occurs > max_allowed_calls && !allow_calls?(found_call.call)
136
135
  end
137
136
 
138
- # :reek:UtilityFunction
137
+ # @quality :reek:UtilityFunction
139
138
  def simple_method_call?(call_node)
140
139
  !call_node.receiver && call_node.args.empty?
141
140
  end
@@ -43,10 +43,10 @@ module Reek
43
43
  # @return [Array<SmellWarning>]
44
44
  #
45
45
  def sniff
46
+ return [] if context.singleton_method? || context.module_function?
46
47
  return [] unless context.references_self?
47
48
  envious_receivers.map do |name, lines|
48
49
  smell_warning(
49
- context: context,
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 })
@@ -37,7 +37,6 @@ module Reek
37
37
  message = "assumes too much for instance variable '#{assumption}'"
38
38
 
39
39
  smell_warning(
40
- context: context,
41
40
  lines: [source_line],
42
41
  message: message,
43
42
  parameters: { assumption: assumption.to_s })
@@ -55,17 +54,9 @@ module Reek
55
54
 
56
55
  def variables_from_context
57
56
  method_expressions.map do |method|
58
- method.find_nodes(assumption_nodes, ignored_nodes).map(&:name)
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
@@ -22,7 +22,6 @@ module Reek
22
22
  def sniff
23
23
  return [] if descriptive_context? || context.namespace_module?
24
24
  [smell_warning(
25
- context: context,
26
25
  lines: [source_line],
27
26
  message: 'has no descriptive comment')]
28
27
  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
@@ -36,7 +36,6 @@ module Reek
36
36
  count = expression.arg_names.length
37
37
  return [] if count <= max_allowed_params
38
38
  [smell_warning(
39
- context: context,
40
39
  lines: [source_line],
41
40
  message: "has #{count} parameters",
42
41
  parameters: { count: count })]
@@ -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,14 +24,13 @@ module Reek
24
24
  #
25
25
  # @return [Array<SmellWarning>]
26
26
  #
27
- # :reek:DuplicateMethodCall: { max_calls: 2 }
27
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
28
28
  def sniff
29
29
  context.local_nodes(:yield).select do |yield_node|
30
30
  yield_node.args.length > max_allowed_params
31
31
  end.map do |yield_node|
32
32
  count = yield_node.args.length
33
33
  smell_warning(
34
- context: context,
35
34
  lines: [yield_node.line],
36
35
  message: "yields #{count} parameters",
37
36
  parameters: { count: count })
@@ -12,7 +12,7 @@ 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
@@ -20,10 +20,10 @@ module Reek
20
20
  # @return [Array<SmellWarning>]
21
21
  #
22
22
  def sniff
23
- smelly_nodes = context.each_node(:send).select { |node| node.name == :respond_to? }
23
+ smelly_nodes = context.local_nodes(:send).select { |node| node.name == :respond_to? }
24
24
  return [] if smelly_nodes.empty?
25
25
  lines = smelly_nodes.map(&:line)
26
- [smell_warning(context: context, lines: lines, message: MESSAGE)]
26
+ [smell_warning(lines: lines, message: MESSAGE)]
27
27
  end
28
28
  end
29
29
  end
@@ -21,10 +21,10 @@ 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
@@ -48,20 +48,19 @@ module Reek
48
48
  #
49
49
  def sniff
50
50
  context.node_instance_methods.select do |method_sexp|
51
- prima_donna_method?(method_sexp)
51
+ missing_safe_method?(method_sexp)
52
52
  end.map do |method_sexp|
53
53
  name = method_sexp.name
54
54
  smell_warning(
55
- context: context,
56
55
  lines: [method_sexp.line],
57
- message: "has prima donna method '#{name}'",
56
+ message: "has missing safe method '#{name}'",
58
57
  parameters: { name: name.to_s })
59
58
  end
60
59
  end
61
60
 
62
61
  private
63
62
 
64
- def prima_donna_method?(method_sexp)
63
+ def missing_safe_method?(method_sexp)
65
64
  return false unless method_sexp.ends_with_bang?
66
65
  return false if ignore_method? method_sexp
67
66
  return false if version_without_bang_exists? method_sexp
@@ -21,10 +21,9 @@ module Reek
21
21
  # @return [Array<SmellWarning>]
22
22
  #
23
23
  def sniff
24
- context.local_nodes(:def) do |node|
24
+ context.defined_instance_methods.each do |node|
25
25
  if node.name == :initialize
26
26
  return smell_warning(
27
- context: context,
28
27
  lines: [source_line],
29
28
  message: 'has initialize method')
30
29
  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
@@ -45,7 +45,6 @@ module Reek
45
45
  find_violations.group_by(&:depth).map do |depth, group|
46
46
  lines = group.map(&:line)
47
47
  smell_warning(
48
- context: context,
49
48
  lines: lines,
50
49
  message: "contains iterators nested #{depth} deep",
51
50
  parameters: { depth: depth })
@@ -94,10 +93,11 @@ module Reek
94
93
  #
95
94
  # @return [Array<Iterator>]
96
95
  #
97
- # :reek:TooManyStatements: { max_statements: 6 }
96
+ # @quality :reek:TooManyStatements { max_statements: 6 }
98
97
  def scout(exp:, depth:)
99
98
  return [] unless exp
100
- 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|
101
101
  new_depth = increment_depth(iterator, depth)
102
102
  # 1st case: we recurse down the given block of the iterator. In this case
103
103
  # we need to check if we should increment the depth.
@@ -125,7 +125,7 @@ module Reek
125
125
  @max_allowed_nesting ||= value(MAX_ALLOWED_NESTING_KEY, context)
126
126
  end
127
127
 
128
- # :reek:FeatureEnvy
128
+ # @quality :reek:FeatureEnvy
129
129
  def ignored_iterator?(exp)
130
130
  ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.name } ||
131
131
  exp.without_block_arguments?
@@ -13,7 +13,6 @@ module Reek
13
13
  lines = detect_nodes.map(&:line)
14
14
  if lines.any?
15
15
  [smell_warning(
16
- context: context,
17
16
  lines: lines,
18
17
  message: 'performs a nil-check')]
19
18
  else
@@ -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,7 +46,7 @@ module Reek
46
46
  #
47
47
  # @return [Array<SmellWarning>]
48
48
  #
49
- # :reek:DuplicateMethodCall: { max_calls: 2 }
49
+ # @quality :reek:DuplicateMethodCall { max_calls: 2 }
50
50
  def sniff
51
51
  conditional_counts.select do |_key, lines|
52
52
  lines.length > max_identical_ifs
@@ -54,7 +54,6 @@ module Reek
54
54
  occurs = lines.length
55
55
  expression = key.format_to_ruby
56
56
  smell_warning(
57
- context: context,
58
57
  lines: lines,
59
58
  message: "tests '#{expression}' at least #{occurs} times",
60
59
  parameters: { name: expression, count: occurs })
@@ -72,7 +71,7 @@ module Reek
72
71
  # the given syntax tree together with the number of times each
73
72
  # occurs. Ignores nested classes and modules.
74
73
  #
75
- # :reek:TooManyStatements: { max_statements: 9 }
74
+ # @quality :reek:TooManyStatements { max_statements: 9 }
76
75
  def conditional_counts
77
76
  result = Hash.new { |hash, key| hash[key] = [] }
78
77
  collector = proc do |node|
@@ -44,7 +44,6 @@ module Reek
44
44
 
45
45
  def build_smell_warning(ancestor_name)
46
46
  smell_attributes = {
47
- context: context,
48
47
  lines: [source_line],
49
48
  message: "inherits from core class '#{ancestor_name}'",
50
49
  parameters: { ancestor: ancestor_name }
@@ -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
 
@@ -35,7 +35,7 @@ module Reek
35
35
  # @return [Array<SmellWarning>]
36
36
  #
37
37
  def sniff
38
- count = context.each_node(:casgn, IGNORED_NODES).delete_if(&:defines_module?).length
38
+ count = context.local_nodes(:casgn).reject(&:defines_module?).length
39
39
 
40
40
  return [] if count <= max_allowed_constants
41
41
 
@@ -50,7 +50,6 @@ module Reek
50
50
 
51
51
  def build_smell_warning(count)
52
52
  [smell_warning(
53
- context: context,
54
53
  lines: [source_line],
55
54
  message: "has #{count} constants",
56
55
  parameters: { count: count })]
@@ -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
@@ -38,7 +38,6 @@ module Reek
38
38
  count = variables.uniq.size
39
39
  return [] if count <= max_allowed_ivars
40
40
  [smell_warning(
41
- context: context,
42
41
  lines: [source_line],
43
42
  message: "has at least #{count} instance variables",
44
43
  parameters: { count: count })]
@@ -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
@@ -40,7 +40,6 @@ module Reek
40
40
  actual = context.node_instance_methods.length
41
41
  return [] if actual <= max_allowed_methods
42
42
  [smell_warning(
43
- context: context,
44
43
  lines: [source_line],
45
44
  message: "has at least #{actual} methods",
46
45
  parameters: { count: actual })]