reek 4.6.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +79 -26
  4. data/.simplecov +1 -0
  5. data/.travis.yml +3 -9
  6. data/.yardopts +1 -1
  7. data/CHANGELOG.md +76 -0
  8. data/CONTRIBUTING.md +1 -1
  9. data/Gemfile +5 -5
  10. data/README.md +138 -107
  11. data/Rakefile +16 -3
  12. data/bin/code_climate_reek +1 -0
  13. data/bin/reek +2 -3
  14. data/docs/API.md +2 -9
  15. data/docs/Basic-Smell-Options.md +51 -11
  16. data/docs/Code-Smells.md +1 -1
  17. data/docs/Command-Line-Options.md +14 -4
  18. data/docs/Duplicate-Method-Call.md +49 -1
  19. data/docs/Feature-Envy.md +44 -0
  20. data/docs/How-To-Write-New-Detectors.md +7 -7
  21. data/docs/Instance-Variable-Assumption.md +1 -1
  22. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  23. data/docs/Rake-Task.md +1 -1
  24. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  25. data/docs/Reek-Driven-Development.md +1 -1
  26. data/docs/Uncommunicative-Method-Name.md +45 -6
  27. data/docs/Uncommunicative-Module-Name.md +49 -7
  28. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  29. data/docs/Uncommunicative-Variable-Name.md +73 -2
  30. data/docs/Unused-Private-Method.md +3 -3
  31. data/docs/defaults.reek.yml +129 -0
  32. data/docs/yard_plugin.rb +1 -0
  33. data/features/command_line_interface/basic_usage.feature +2 -2
  34. data/features/command_line_interface/options.feature +46 -4
  35. data/features/command_line_interface/show_progress.feature +4 -4
  36. data/features/command_line_interface/smell_selection.feature +1 -1
  37. data/features/command_line_interface/smells_count.feature +6 -6
  38. data/features/command_line_interface/stdin.feature +31 -5
  39. data/features/configuration_files/accept_setting.feature +45 -28
  40. data/features/configuration_files/directory_specific_directives.feature +80 -75
  41. data/features/configuration_files/exclude_directives.feature +11 -10
  42. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  43. data/features/configuration_files/masking_smells.feature +38 -9
  44. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  45. data/features/configuration_files/reject_setting.feature +52 -41
  46. data/features/configuration_files/schema_validation.feature +59 -0
  47. data/features/configuration_files/unused_private_method.feature +18 -16
  48. data/features/configuration_loading.feature +53 -10
  49. data/features/configuration_via_source_comments/erroneous_source_comments.feature +3 -3
  50. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  51. data/features/locales.feature +32 -0
  52. data/features/rake_task/rake_task.feature +58 -18
  53. data/features/reports/codeclimate.feature +59 -0
  54. data/features/reports/json.feature +3 -3
  55. data/features/reports/reports.feature +34 -34
  56. data/features/reports/yaml.feature +3 -3
  57. data/features/rspec_matcher.feature +40 -0
  58. data/features/samples.feature +287 -287
  59. data/features/step_definitions/reek_steps.rb +14 -2
  60. data/features/step_definitions/sample_file_steps.rb +9 -4
  61. data/features/support/env.rb +2 -11
  62. data/features/todo_list.feature +26 -23
  63. data/lib/reek/ast/ast_node_class_map.rb +1 -0
  64. data/lib/reek/ast/builder.rb +16 -0
  65. data/lib/reek/ast/node.rb +50 -58
  66. data/lib/reek/ast/object_refs.rb +2 -1
  67. data/lib/reek/ast/reference_collector.rb +3 -4
  68. data/lib/reek/ast/sexp_extensions/arguments.rb +1 -0
  69. data/lib/reek/ast/sexp_extensions/attribute_assignments.rb +1 -0
  70. data/lib/reek/ast/sexp_extensions/begin.rb +17 -0
  71. data/lib/reek/ast/sexp_extensions/block.rb +1 -0
  72. data/lib/reek/ast/sexp_extensions/case.rb +2 -1
  73. data/lib/reek/ast/sexp_extensions/constant.rb +1 -0
  74. data/lib/reek/ast/sexp_extensions/if.rb +9 -1
  75. data/lib/reek/ast/sexp_extensions/lambda.rb +1 -0
  76. data/lib/reek/ast/sexp_extensions/literal.rb +1 -0
  77. data/lib/reek/ast/sexp_extensions/logical_operators.rb +2 -1
  78. data/lib/reek/ast/sexp_extensions/methods.rb +5 -6
  79. data/lib/reek/ast/sexp_extensions/module.rb +55 -8
  80. data/lib/reek/ast/sexp_extensions/nested_assignables.rb +1 -0
  81. data/lib/reek/ast/sexp_extensions/self.rb +1 -0
  82. data/lib/reek/ast/sexp_extensions/send.rb +1 -4
  83. data/lib/reek/ast/sexp_extensions/super.rb +1 -0
  84. data/lib/reek/ast/sexp_extensions/symbols.rb +1 -0
  85. data/lib/reek/ast/sexp_extensions/variables.rb +1 -0
  86. data/lib/reek/ast/sexp_extensions/when.rb +1 -0
  87. data/lib/reek/ast/sexp_extensions/yield.rb +1 -0
  88. data/lib/reek/ast/sexp_extensions.rb +2 -0
  89. data/lib/reek/cli/application.rb +5 -3
  90. data/lib/reek/cli/command/base_command.rb +1 -0
  91. data/lib/reek/cli/command/report_command.rb +2 -2
  92. data/lib/reek/cli/command/todo_list_command.rb +9 -8
  93. data/lib/reek/cli/options.rb +32 -16
  94. data/lib/reek/cli/silencer.rb +15 -3
  95. data/lib/reek/cli/status.rb +1 -0
  96. data/lib/reek/code_comment.rb +14 -16
  97. data/lib/reek/configuration/app_configuration.rb +34 -28
  98. data/lib/reek/configuration/configuration_converter.rb +110 -0
  99. data/lib/reek/configuration/configuration_file_finder.rb +17 -41
  100. data/lib/reek/configuration/configuration_validator.rb +13 -23
  101. data/lib/reek/configuration/default_directive.rb +18 -3
  102. data/lib/reek/configuration/directory_directives.rb +18 -11
  103. data/lib/reek/configuration/excluded_paths.rb +2 -1
  104. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  105. data/lib/reek/configuration/schema.yml +210 -0
  106. data/lib/reek/configuration/schema_validator.rb +38 -0
  107. data/lib/reek/context/attribute_context.rb +4 -3
  108. data/lib/reek/context/class_context.rb +1 -0
  109. data/lib/reek/context/code_context.rb +49 -44
  110. data/lib/reek/context/ghost_context.rb +1 -2
  111. data/lib/reek/context/method_context.rb +26 -18
  112. data/lib/reek/context/module_context.rb +11 -11
  113. data/lib/reek/context/root_context.rb +1 -4
  114. data/lib/reek/context/send_context.rb +3 -2
  115. data/lib/reek/context/singleton_attribute_context.rb +1 -0
  116. data/lib/reek/context/singleton_method_context.rb +1 -0
  117. data/lib/reek/context/statement_counter.rb +1 -0
  118. data/lib/reek/context/visibility_tracker.rb +1 -0
  119. data/lib/reek/context_builder.rb +44 -44
  120. data/lib/reek/detector_repository.rb +12 -11
  121. data/lib/reek/documentation_link.rb +28 -0
  122. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +15 -13
  123. data/lib/reek/errors/bad_detector_in_comment_error.rb +13 -11
  124. data/lib/reek/errors/base_error.rb +3 -0
  125. data/lib/reek/errors/config_file_error.rb +11 -0
  126. data/lib/reek/errors/encoding_error.rb +43 -0
  127. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +14 -12
  128. data/lib/reek/errors/incomprehensible_source_error.rb +23 -24
  129. data/lib/reek/errors/syntax_error.rb +41 -0
  130. data/lib/reek/examiner.rb +24 -22
  131. data/lib/reek/logging_error_handler.rb +8 -5
  132. data/lib/reek/rake/task.rb +8 -5
  133. data/lib/reek/report/base_report.rb +9 -12
  134. data/lib/reek/report/code_climate/code_climate_configuration.rb +2 -1
  135. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -6
  136. data/lib/reek/report/code_climate/code_climate_fingerprint.rb +1 -0
  137. data/lib/reek/report/code_climate/code_climate_formatter.rb +1 -0
  138. data/lib/reek/report/code_climate/code_climate_report.rb +1 -0
  139. data/lib/reek/report/code_climate.rb +1 -0
  140. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  141. data/lib/reek/report/heading_formatter.rb +54 -0
  142. data/lib/reek/report/html_report.rb +1 -0
  143. data/lib/reek/report/json_report.rb +2 -1
  144. data/lib/reek/report/location_formatter.rb +40 -0
  145. data/lib/reek/report/progress_formatter.rb +79 -0
  146. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  147. data/lib/reek/report/text_report.rb +2 -2
  148. data/lib/reek/report/xml_report.rb +4 -3
  149. data/lib/reek/report/yaml_report.rb +2 -1
  150. data/lib/reek/report.rb +16 -10
  151. data/lib/reek/smell_configuration.rb +3 -2
  152. data/lib/reek/smell_detectors/attribute.rb +6 -11
  153. data/lib/reek/smell_detectors/base_detector.rb +31 -26
  154. data/lib/reek/smell_detectors/boolean_parameter.rb +4 -5
  155. data/lib/reek/smell_detectors/class_variable.rb +6 -14
  156. data/lib/reek/smell_detectors/control_parameter.rb +19 -33
  157. data/lib/reek/smell_detectors/data_clump.rb +16 -9
  158. data/lib/reek/smell_detectors/duplicate_method_call.rb +24 -17
  159. data/lib/reek/smell_detectors/feature_envy.rb +10 -7
  160. data/lib/reek/smell_detectors/instance_variable_assumption.rb +15 -23
  161. data/lib/reek/smell_detectors/irresponsible_module.rb +6 -12
  162. data/lib/reek/smell_detectors/long_parameter_list.rb +11 -7
  163. data/lib/reek/smell_detectors/long_yield_list.rb +11 -7
  164. data/lib/reek/smell_detectors/manual_dispatch.rb +5 -5
  165. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +21 -20
  166. data/lib/reek/smell_detectors/module_initialize.rb +4 -5
  167. data/lib/reek/smell_detectors/nested_iterators.rb +17 -24
  168. data/lib/reek/smell_detectors/nil_check.rb +9 -15
  169. data/lib/reek/smell_detectors/repeated_conditional.rb +14 -11
  170. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +8 -8
  171. data/lib/reek/smell_detectors/too_many_constants.rb +11 -9
  172. data/lib/reek/smell_detectors/too_many_instance_variables.rb +11 -6
  173. data/lib/reek/smell_detectors/too_many_methods.rb +12 -7
  174. data/lib/reek/smell_detectors/too_many_statements.rb +11 -6
  175. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +11 -11
  176. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +15 -18
  177. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +18 -22
  178. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +27 -27
  179. data/lib/reek/smell_detectors/unused_parameters.rb +5 -6
  180. data/lib/reek/smell_detectors/unused_private_method.rb +14 -20
  181. data/lib/reek/smell_detectors/utility_function.rb +13 -16
  182. data/lib/reek/smell_detectors.rb +2 -1
  183. data/lib/reek/smell_warning.rb +16 -8
  184. data/lib/reek/source/source_code.rb +65 -46
  185. data/lib/reek/source/source_locator.rb +9 -8
  186. data/lib/reek/spec/should_reek.rb +3 -2
  187. data/lib/reek/spec/should_reek_of.rb +13 -26
  188. data/lib/reek/spec/should_reek_only_of.rb +5 -4
  189. data/lib/reek/spec/smell_matcher.rb +2 -1
  190. data/lib/reek/spec.rb +7 -6
  191. data/lib/reek/tree_dresser.rb +9 -8
  192. data/lib/reek/version.rb +2 -1
  193. data/lib/reek.rb +1 -0
  194. data/reek.gemspec +5 -6
  195. data/samples/checkstyle.xml +1 -1
  196. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  197. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  198. data/samples/configuration/full_configuration.reek +8 -4
  199. data/samples/configuration/full_mask.reek +5 -4
  200. data/samples/configuration/partial_mask.reek +3 -2
  201. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  202. data/samples/paths.rb +5 -4
  203. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  204. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  205. data/spec/factories/factories.rb +2 -13
  206. data/spec/reek/ast/node_spec.rb +103 -10
  207. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  208. data/spec/reek/ast/sexp_extensions_spec.rb +15 -34
  209. data/spec/reek/cli/application_spec.rb +52 -42
  210. data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
  211. data/spec/reek/cli/silencer_spec.rb +28 -0
  212. data/spec/reek/code_comment_spec.rb +31 -38
  213. data/spec/reek/configuration/app_configuration_spec.rb +46 -33
  214. data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -51
  215. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  216. data/spec/reek/configuration/directory_directives_spec.rb +6 -7
  217. data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
  218. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  219. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  220. data/spec/reek/context/code_context_spec.rb +70 -106
  221. data/spec/reek/context/ghost_context_spec.rb +9 -9
  222. data/spec/reek/context/method_context_spec.rb +2 -2
  223. data/spec/reek/context/module_context_spec.rb +3 -3
  224. data/spec/reek/context/root_context_spec.rb +1 -1
  225. data/spec/reek/context/statement_counter_spec.rb +1 -0
  226. data/spec/reek/context_builder_spec.rb +20 -0
  227. data/spec/reek/documentation_link_spec.rb +20 -0
  228. data/spec/reek/errors/base_error_spec.rb +13 -0
  229. data/spec/reek/examiner_spec.rb +137 -29
  230. data/spec/reek/rake/task_spec.rb +25 -2
  231. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -78
  232. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  233. data/spec/reek/report/code_climate/code_climate_report_spec.rb +22 -22
  234. data/spec/reek/report/json_report_spec.rb +13 -46
  235. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  236. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  237. data/spec/reek/report/text_report_spec.rb +4 -4
  238. data/spec/reek/report/xml_report_spec.rb +3 -3
  239. data/spec/reek/report/yaml_report_spec.rb +17 -46
  240. data/spec/reek/report_spec.rb +3 -3
  241. data/spec/reek/smell_detectors/base_detector_spec.rb +4 -5
  242. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  243. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  244. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  245. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  246. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  247. data/spec/reek/smell_detectors/irresponsible_module_spec.rb +59 -21
  248. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +10 -10
  249. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  250. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  251. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  252. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  253. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  254. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  255. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +36 -15
  256. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
  257. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  258. data/spec/reek/smell_detectors/unused_private_method_spec.rb +21 -10
  259. data/spec/reek/smell_detectors/utility_function_spec.rb +57 -5
  260. data/spec/reek/smell_warning_spec.rb +12 -8
  261. data/spec/reek/source/source_code_spec.rb +27 -38
  262. data/spec/reek/source/source_locator_spec.rb +42 -12
  263. data/spec/reek/spec/should_reek_of_spec.rb +25 -30
  264. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  265. data/spec/reek/spec/should_reek_spec.rb +8 -8
  266. data/spec/reek/spec/smell_matcher_spec.rb +23 -23
  267. data/spec/reek/tree_dresser_spec.rb +12 -17
  268. data/spec/spec_helper.rb +7 -17
  269. data/tasks/configuration.rake +8 -5
  270. metadata +77 -40
  271. data/defaults.reek +0 -128
  272. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  273. data/lib/reek/errors/parse_error.rb +0 -19
  274. data/lib/reek/report/formatter/heading_formatter.rb +0 -51
  275. data/lib/reek/report/formatter/location_formatter.rb +0 -41
  276. data/lib/reek/report/formatter/progress_formatter.rb +0 -80
  277. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  278. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -35
  279. data/lib/reek/report/formatter.rb +0 -32
  280. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  281. data/samples/smelly_with_inline_mask.rb +0 -8
  282. data/samples/smelly_with_modifiers.rb +0 -12
  283. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  284. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  285. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  286. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  287. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  288. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  289. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  290. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  291. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  292. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  293. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  294. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -20,11 +21,10 @@ module Reek
20
21
  #
21
22
  # See {file:docs/Uncommunicative-Variable-Name.md} for details.
22
23
  #
23
- # :reek:DataClump: { max_copies: 4 }
24
24
  class UncommunicativeVariableName < BaseDetector
25
25
  # The name of the config field that lists the regexps of
26
26
  # smelly names to be reported.
27
- REJECT_KEY = 'reject'.freeze
27
+ REJECT_KEY = 'reject'
28
28
  DEFAULT_REJECT_SET = [
29
29
  /^.$/, # single-character names
30
30
  /[0-9]$/, # any name ending with a number
@@ -34,7 +34,7 @@ module Reek
34
34
  # The name of the config field that lists the specific names that are
35
35
  # to be treated as exceptions; these names will not be reported as
36
36
  # uncommunicative.
37
- ACCEPT_KEY = 'accept'.freeze
37
+ ACCEPT_KEY = 'accept'
38
38
  DEFAULT_ACCEPT_SET = [/^_$/].freeze
39
39
 
40
40
  def self.default_config
@@ -52,14 +52,11 @@ module Reek
52
52
  #
53
53
  # @return [Array<SmellWarning>]
54
54
  #
55
- def sniff(ctx)
56
- self.reject_names = value(REJECT_KEY, ctx)
57
- self.accept_names = value(ACCEPT_KEY, ctx)
58
- variable_names(ctx.exp).select do |name, _lines|
55
+ def sniff
56
+ variable_names.select do |name, _lines|
59
57
  uncommunicative_variable_name?(name)
60
58
  end.map do |name, lines|
61
59
  smell_warning(
62
- context: ctx,
63
60
  lines: lines,
64
61
  message: "has the variable name '#{name}'",
65
62
  parameters: { name: name.to_s })
@@ -68,6 +65,14 @@ module Reek
68
65
 
69
66
  private
70
67
 
68
+ def reject_names
69
+ @reject_names ||= value(REJECT_KEY, context)
70
+ end
71
+
72
+ def accept_names
73
+ @accept_names ||= value(ACCEPT_KEY, context)
74
+ end
75
+
71
76
  def uncommunicative_variable_name?(name)
72
77
  sanitized_name = name.to_s.gsub(/^[@\*\&]*/, '')
73
78
  !acceptable_name?(sanitized_name)
@@ -78,34 +83,31 @@ module Reek
78
83
  Array(reject_names).none? { |reject_pattern| name.match reject_pattern }
79
84
  end
80
85
 
81
- # :reek:TooManyStatements: { max_statements: 6 }
82
- def variable_names(exp)
86
+ def variable_names
83
87
  result = Hash.new { |hash, key| hash[key] = [] }
84
- find_assignment_variable_names(exp, result)
85
- find_block_argument_variable_names(exp, result)
86
- result.to_a.sort_by { |name, _| name.to_s }
88
+ find_assignment_variable_names(result)
89
+ find_block_argument_variable_names(result)
90
+ result
87
91
  end
88
92
 
89
- # :reek:UtilityFunction
90
- def find_assignment_variable_names(exp, accumulator)
91
- assignment_nodes = exp.each_node(:lvasgn, [:class, :module, :defs, :def])
93
+ def find_assignment_variable_names(accumulator)
94
+ assignment_nodes = expression.each_node(:lvasgn, [:class, :module, :defs, :def]).to_a
92
95
 
93
- case exp.type
96
+ case expression.type
94
97
  when :class, :module
95
- assignment_nodes += exp.each_node(:ivasgn, [:class, :module])
98
+ assignment_nodes += expression.each_node(:ivasgn, [:class, :module]).to_a
96
99
  end
97
100
 
98
101
  assignment_nodes.each { |asgn| accumulator[asgn.children.first].push(asgn.line) }
99
102
  end
100
103
 
101
- # :reek:FeatureEnvy
102
- # :reek:TooManyStatements: { max_statements: 6 }
103
- def find_block_argument_variable_names(exp, accumulator)
104
- arg_search_exp = case exp.type
104
+ # @quality :reek:TooManyStatements { max_statements: 6 }
105
+ def find_block_argument_variable_names(accumulator)
106
+ arg_search_exp = case expression.type
105
107
  when :class, :module
106
- exp
108
+ expression
107
109
  when :defs, :def
108
- exp.body
110
+ expression.body
109
111
  end
110
112
 
111
113
  return unless arg_search_exp
@@ -127,15 +129,13 @@ module Reek
127
129
  end
128
130
  end
129
131
 
130
- # :reek:UtilityFunction
132
+ # @quality :reek:UtilityFunction
131
133
  def record_variable_name(exp, symbol, accumulator)
132
134
  varname = symbol.to_s.sub(/^\*/, '')
133
135
  return if varname == ''
134
136
  var = varname.to_sym
135
137
  accumulator[var].push(exp.line)
136
138
  end
137
-
138
- attr_accessor :accept_names, :reject_names
139
139
  end
140
140
  end
141
141
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -13,14 +14,12 @@ module Reek
13
14
  #
14
15
  # @return [Array<SmellWarning>]
15
16
  #
16
- # :reek:FeatureEnvy
17
- def sniff(ctx)
18
- return [] if ctx.uses_super_with_implicit_arguments?
19
- ctx.unused_params.map do |param|
17
+ def sniff
18
+ return [] if context.uses_super_with_implicit_arguments?
19
+ context.unused_params.map do |param|
20
20
  name = param.name.to_s
21
21
  smell_warning(
22
- context: ctx,
23
- lines: [ctx.exp.line],
22
+ lines: [source_line],
24
23
  message: "has unused parameter '#{name}'",
25
24
  parameters: { name: name })
26
25
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_detector'
3
4
 
4
5
  module Reek
@@ -31,15 +32,12 @@ module Reek
31
32
  end
32
33
 
33
34
  #
34
- # @param ctx [Context::ClassContext]
35
35
  # @return [Array<SmellWarning>]
36
36
  #
37
- # :reek:FeatureEnvy
38
- def sniff(ctx)
39
- hits(ctx).map do |hit|
37
+ def sniff
38
+ hits.map do |hit|
40
39
  name = hit.name
41
40
  smell_warning(
42
- context: ctx,
43
41
  lines: [hit.line],
44
42
  message: "has the unused private instance method '#{name}'",
45
43
  parameters: { name: name.to_s })
@@ -49,37 +47,33 @@ module Reek
49
47
  private
50
48
 
51
49
  #
52
- # @param ctx [Context::ClassContext]
53
50
  # @return [Array<Hit>]
54
51
  #
55
- def hits(ctx)
56
- unused_private_methods(ctx).map do |defined_method|
57
- Hit.new(defined_method) unless ignore_method?(ctx, defined_method)
52
+ def hits
53
+ unused_private_methods.map do |defined_method|
54
+ Hit.new(defined_method) unless ignore_method?(defined_method)
58
55
  end.compact
59
56
  end
60
57
 
61
58
  #
62
- # @param ctx [Context::ClassContext]
63
59
  # @return [Array<Context::MethodContext]
64
60
  #
65
- # :reek:UtilityFunction
66
- def unused_private_methods(ctx)
67
- defined_private_methods = ctx.defined_instance_methods(visibility: :private)
68
- called_method_names = ctx.instance_method_calls.map(&:name)
61
+ def unused_private_methods
62
+ defined_private_methods = context.defined_instance_methods(visibility: :private)
63
+ called_method_names = context.instance_method_calls.map(&:name)
69
64
 
70
- defined_private_methods.select do |defined_method|
71
- !called_method_names.include?(defined_method.name)
65
+ defined_private_methods.reject do |defined_method|
66
+ called_method_names.include?(defined_method.name)
72
67
  end
73
68
  end
74
69
 
75
70
  #
76
- # @param ctx [Context::ClassContext]
71
+ # @param method [Context::MethodContext]
77
72
  # @return [Boolean]
78
73
  #
79
- # :reek:FeatureEnvy
80
- def ignore_method?(ctx, method)
74
+ def ignore_method?(method)
81
75
  # ignore_contexts will be e.g. ["Foo::Smelly#my_method", "..."]
82
- ignore_contexts = value(EXCLUDE_KEY, ctx)
76
+ ignore_contexts = value(EXCLUDE_KEY, context)
83
77
  ignore_contexts.any? do |ignore_context|
84
78
  full_name = "#{method.parent.full_name}##{method.name}"
85
79
  full_name[ignore_context]
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative '../ast/reference_collector'
3
4
  require_relative 'base_detector'
4
5
 
@@ -37,7 +38,7 @@ module Reek
37
38
  #
38
39
  # See {file:docs/Utility-Function.md} for details.
39
40
  class UtilityFunction < BaseDetector
40
- PUBLIC_METHODS_ONLY_KEY = 'public_methods_only'.freeze
41
+ PUBLIC_METHODS_ONLY_KEY = 'public_methods_only'
41
42
  PUBLIC_METHODS_ONLY_DEFAULT = false
42
43
 
43
44
  def self.default_config
@@ -55,30 +56,26 @@ module Reek
55
56
  #
56
57
  # @return [Array<SmellWarning>]
57
58
  #
58
- # :reek:FeatureEnvy
59
- # :reek:TooManyStatements: { max_statements: 6 }
60
- def sniff(ctx)
61
- return [] if ctx.singleton_method? || ctx.module_function?
62
- return [] if ctx.references_self?
63
- return [] if num_helper_methods(ctx).zero?
64
- return [] if ignore_method?(ctx)
59
+ def sniff
60
+ return [] if context.singleton_method? || context.module_function?
61
+ return [] if context.references_self?
62
+ return [] if num_helper_methods.zero?
63
+ return [] if ignore_method?
65
64
 
66
65
  [smell_warning(
67
- context: ctx,
68
- lines: [ctx.exp.line],
66
+ lines: [source_line],
69
67
  message: "doesn't depend on instance state (maybe move it to another class?)")]
70
68
  end
71
69
 
72
70
  private
73
71
 
74
- # :reek:UtilityFunction
75
- def num_helper_methods(method_ctx)
76
- method_ctx.local_nodes(:send).length
72
+ def num_helper_methods
73
+ context.local_nodes(:send).to_a.length
77
74
  end
78
75
 
79
- def ignore_method?(method_ctx)
80
- method_ctx.non_public_visibility? &&
81
- value(PUBLIC_METHODS_ONLY_KEY, method_ctx)
76
+ def ignore_method?
77
+ context.non_public_visibility? &&
78
+ value(PUBLIC_METHODS_ONLY_KEY, context)
82
79
  end
83
80
  end
84
81
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'smell_detectors/attribute'
3
4
  require_relative 'smell_detectors/boolean_parameter'
4
5
  require_relative 'smell_detectors/class_variable'
@@ -14,7 +15,7 @@ require_relative 'smell_detectors/manual_dispatch'
14
15
  require_relative 'smell_detectors/module_initialize'
15
16
  require_relative 'smell_detectors/nested_iterators'
16
17
  require_relative 'smell_detectors/nil_check'
17
- require_relative 'smell_detectors/prima_donna_method'
18
+ require_relative 'smell_detectors/missing_safe_method'
18
19
  require_relative 'smell_detectors/repeated_conditional'
19
20
  require_relative 'smell_detectors/subclassed_from_core_class'
20
21
  require_relative 'smell_detectors/too_many_instance_variables'
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'forwardable'
4
+ require_relative 'documentation_link'
3
5
 
4
6
  module Reek
5
7
  #
@@ -7,7 +9,7 @@ module Reek
7
9
  #
8
10
  # @public
9
11
  #
10
- # :reek:TooManyInstanceVariables: { max_instance_variables: 6 }
12
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 6 }
11
13
  class SmellWarning
12
14
  include Comparable
13
15
  extend Forwardable
@@ -20,7 +22,7 @@ module Reek
20
22
  # objects yourself. This is why the initializer is not part of the
21
23
  # public API.
22
24
  #
23
- # :reek:LongParameterList: { max_params: 6 }
25
+ # @quality :reek:LongParameterList { max_params: 6 }
24
26
  def initialize(smell_detector, context: '', lines:, message:,
25
27
  source:, parameters: {})
26
28
  @smell_detector = smell_detector
@@ -54,7 +56,9 @@ module Reek
54
56
  base_hash.merge(stringified_params)
55
57
  end
56
58
 
57
- alias yaml_hash to_hash
59
+ def yaml_hash
60
+ to_hash.merge('documentation_link' => explanatory_link)
61
+ end
58
62
 
59
63
  def base_message
60
64
  "#{smell_type}: #{context} #{message}"
@@ -64,6 +68,10 @@ module Reek
64
68
  smell_detector.class
65
69
  end
66
70
 
71
+ def explanatory_link
72
+ DocumentationLink.build(smell_type)
73
+ end
74
+
67
75
  protected
68
76
 
69
77
  def identifying_values
@@ -74,11 +82,11 @@ module Reek
74
82
 
75
83
  def base_hash
76
84
  {
77
- 'context' => context,
78
- 'lines' => lines,
79
- 'message' => message,
80
- 'smell_type' => smell_type,
81
- 'source' => source
85
+ 'context' => context,
86
+ 'lines' => lines,
87
+ 'message' => message,
88
+ 'smell_type' => smell_type,
89
+ 'source' => source
82
90
  }
83
91
  end
84
92
  end
@@ -1,65 +1,94 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative '../cli/silencer'
3
- Reek::CLI::Silencer.silently do
4
- require 'parser/ruby23'
5
- end
4
+ Reek::CLI::Silencer.without_warnings { require 'parser/ruby25' }
6
5
  require_relative '../tree_dresser'
7
6
  require_relative '../ast/node'
8
- require_relative '../errors/parse_error'
7
+ require_relative '../ast/builder'
9
8
 
10
9
  # Opt in to new way of representing lambdas
11
- Parser::Builders::Default.emit_lambda = true
10
+ Reek::AST::Builder.emit_lambda = true
12
11
 
13
12
  module Reek
14
13
  module Source
15
14
  #
16
- # A +Source+ object represents a chunk of Ruby source code.
15
+ # A +SourceCode+ object represents a chunk of Ruby source code.
17
16
  #
18
17
  class SourceCode
19
- IO_IDENTIFIER = 'STDIN'.freeze
20
- STRING_IDENTIFIER = 'string'.freeze
21
-
22
- attr_reader :origin
18
+ IO_IDENTIFIER = 'STDIN'
19
+ STRING_IDENTIFIER = 'string'
23
20
 
24
21
  # Initializer.
25
22
  #
26
- # code - Ruby code as String
27
- # origin - 'STDIN', 'string' or a filepath as String
28
- # parser - the parser to use for generating AST's out of the given source
29
- def initialize(code:, origin:, parser: Parser::Ruby23)
30
- @source = code
23
+ # @param source [File|Pathname|IO|String] Ruby source code
24
+ # @param origin [String] Origin of the source code. Will be determined
25
+ # automatically if left blank.
26
+ # @param parser the parser to use for generating AST's out of the given code
27
+ def initialize(source:, origin: nil, parser: self.class.default_parser)
31
28
  @origin = origin
32
29
  @parser = parser
30
+ @source = source
33
31
  end
34
32
 
35
33
  # Initializes an instance of SourceCode given a source.
36
- # This source can come via 4 different ways:
34
+ # This source can come via several different ways:
37
35
  # - from Files or Pathnames a la `reek lib/reek/`
38
36
  # - from IO (STDIN) a la `echo "class Foo; end" | reek`
39
37
  # - from String via our rspec matchers a la `expect("class Foo; end").to reek`
38
+ # - from an existing SourceCode object. This is passed through unchanged
40
39
  #
41
- # @param source [File|IO|String] - the given source
40
+ # @param source [SourceCode|File|Pathname|IO|String] the given source
41
+ # @param origin [String|nil]
42
42
  #
43
43
  # @return an instance of SourceCode
44
- # :reek:DuplicateMethodCall: { max_calls: 2 }
45
- def self.from(source)
44
+ def self.from(source, origin: nil)
46
45
  case source
47
- when File then new(code: source.read, origin: source.path)
48
- when IO then new(code: source.readlines.join, origin: IO_IDENTIFIER)
49
- when Pathname then new(code: source.read, origin: source.to_s)
50
- when String then new(code: source, origin: STRING_IDENTIFIER)
46
+ when self then source
47
+ else new(source: source, origin: origin)
51
48
  end
52
49
  end
53
50
 
54
- # Parses the given source into an AST and associates the source code comments with it.
51
+ def syntax_tree
52
+ @syntax_tree ||= parse
53
+ end
54
+
55
+ def self.default_parser
56
+ Parser::Ruby25.new(AST::Builder.new).tap do |parser|
57
+ diagnostics = parser.diagnostics
58
+ diagnostics.all_errors_are_fatal = true
59
+ diagnostics.ignore_warnings = true
60
+ end
61
+ end
62
+
63
+ def origin
64
+ @origin ||=
65
+ case source
66
+ when File then source.path
67
+ when IO then IO_IDENTIFIER
68
+ when Pathname then source.to_s
69
+ when String then STRING_IDENTIFIER
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def code
76
+ @code ||=
77
+ case source
78
+ when File, Pathname then source.read
79
+ when IO then source.readlines.join
80
+ when String then source
81
+ end.force_encoding(Encoding::UTF_8)
82
+ end
83
+
84
+ attr_reader :parser, :source
85
+
86
+ # Parses the given code into an AST and associates the source code comments with it.
55
87
  # This AST is then traversed by a TreeDresser which adorns the nodes in the AST
56
88
  # with our SexpExtensions.
57
89
  # Finally this AST is returned where each node is an anonymous subclass of Reek::AST::Node
58
90
  #
59
- # Important to note is that Reek will not fail on unparseable files but rather print out
60
- # a warning and then just continue.
61
- #
62
- # Given this @source:
91
+ # Given this @code:
63
92
  #
64
93
  # # comment about C
65
94
  # class C
@@ -80,26 +109,16 @@ module Reek
80
109
  # where each node is possibly adorned with our SexpExtensions (see ast/ast_node_class_map
81
110
  # and ast/sexp_extensions for details).
82
111
  #
83
- # @return [Anonymous subclass of Reek::AST::Node] the AST presentation
84
- # for the given source
85
- def syntax_tree
86
- @syntax_tree ||=
87
- begin
88
- begin
89
- ast, comments = parser.parse_with_comments(source, origin)
90
- rescue Racc::ParseError, Parser::SyntaxError => error
91
- raise Errors::ParseError, origin: origin, original_exception: error
92
- end
112
+ # @return Reek::AST::Node the AST presentation for the given code
113
+ def parse
114
+ buffer = Parser::Source::Buffer.new(origin, 1)
115
+ buffer.source = code
116
+ ast, comments = parser.parse_with_comments(buffer)
93
117
 
94
- # See https://whitequark.github.io/parser/Parser/Source/Comment/Associator.html
95
- comment_map = Parser::Source::Comment.associate(ast, comments) if ast
96
- TreeDresser.new.dress(ast, comment_map)
97
- end
118
+ # See https://whitequark.github.io/parser/Parser/Source/Comment/Associator.html
119
+ comment_map = Parser::Source::Comment.associate(ast, comments)
120
+ TreeDresser.new.dress(ast, comment_map) || AST::Node.new(:empty)
98
121
  end
99
-
100
- private
101
-
102
- attr_reader :parser, :source
103
122
  end
104
123
  end
105
124
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'find'
3
4
  require 'pathname'
4
5
 
@@ -23,7 +24,7 @@ module Reek
23
24
  # Traverses all paths we initialized the SourceLocator with, finds
24
25
  # all relevant Ruby files and returns them as a list.
25
26
  #
26
- # @return [Array<Pathname>] - Ruby paths found
27
+ # @return [Array<Pathname>] Ruby paths found
27
28
  def sources
28
29
  source_paths
29
30
  end
@@ -32,8 +33,8 @@ module Reek
32
33
 
33
34
  attr_reader :configuration, :paths, :options
34
35
 
35
- # :reek:TooManyStatements: { max_statements: 7 }
36
- # :reek:NestedIterators: { max_allowed_nesting: 2 }
36
+ # @quality :reek:TooManyStatements { max_statements: 7 }
37
+ # @quality :reek:NestedIterators { max_allowed_nesting: 2 }
37
38
  def source_paths
38
39
  paths.each_with_object([]) do |given_path, relevant_paths|
39
40
  unless given_path.exist?
@@ -66,12 +67,12 @@ module Reek
66
67
  configuration.path_excluded?(path)
67
68
  end
68
69
 
69
- # :reek:UtilityFunction
70
+ # @quality :reek:UtilityFunction
70
71
  def print_no_such_file_error(path)
71
- $stderr.puts "Error: No such file - #{path}"
72
+ warn "Error: No such file - #{path}"
72
73
  end
73
74
 
74
- # :reek:UtilityFunction
75
+ # @quality :reek:UtilityFunction
75
76
  def hidden_directory?(path)
76
77
  path.basename.to_s.start_with? '.'
77
78
  end
@@ -80,12 +81,12 @@ module Reek
80
81
  path_excluded?(path) || hidden_directory?(path)
81
82
  end
82
83
 
83
- # :reek:UtilityFunction
84
+ # @quality :reek:UtilityFunction
84
85
  def ruby_file?(path)
85
86
  path.extname == '.rb'
86
87
  end
87
88
 
88
- # :reek:UtilityFunction
89
+ # @quality :reek:UtilityFunction
89
90
  def current_directory?(path)
90
91
  [Pathname.new('.'), Pathname.new('./')].include?(path)
91
92
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative '../examiner'
3
- require_relative '../report/formatter'
4
+ require_relative '../report/simple_warning_formatter'
4
5
 
5
6
  module Reek
6
7
  module Spec
@@ -22,7 +23,7 @@ module Reek
22
23
  end
23
24
 
24
25
  def failure_message_when_negated
25
- rpt = Report::Formatter.format_list(examiner.smells)
26
+ rpt = Report::SimpleWarningFormatter.new.format_list(examiner.smells)
26
27
  "Expected no smells, but got:\n#{rpt}"
27
28
  end
28
29
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative '../examiner'
3
4
  require_relative 'smell_matcher'
5
+ require_relative '../configuration/app_configuration'
4
6
 
5
7
  module Reek
6
8
  module Spec
@@ -11,36 +13,31 @@ module Reek
11
13
  class ShouldReekOf
12
14
  include RSpec::Matchers::Composable
13
15
 
14
- # Variant of Examiner that doesn't swallow exceptions
15
- class UnsafeExaminer < Examiner
16
- def run
17
- examine_tree
18
- end
19
- end
20
-
21
16
  attr_reader :failure_message, :failure_message_when_negated
22
17
 
23
- def initialize(smell_type_or_class,
18
+ def initialize(smell_type,
24
19
  smell_details = {},
25
20
  configuration = Configuration::AppConfiguration.default)
26
- @smell_type = normalize smell_type_or_class
21
+ @smell_type = smell_type.to_s
27
22
  @smell_details = smell_details
28
- configuration.load_values(smell_type => { SmellConfiguration::ENABLED_KEY => true })
23
+ configuration.load_values(Configuration::AppConfiguration::DETECTORS_KEY =>
24
+ { smell_type => { SmellConfiguration::ENABLED_KEY => true } })
29
25
  @configuration = configuration
30
26
  end
31
27
 
32
28
  def matches?(source)
33
29
  @matching_smell_types = nil
34
- self.examiner = UnsafeExaminer.new(source,
35
- filter_by_smells: [smell_type],
36
- configuration: configuration)
30
+ self.examiner = Examiner.new(source,
31
+ filter_by_smells: [smell_type],
32
+ configuration: configuration)
37
33
  set_failure_messages
38
34
  matching_smell_details?
39
35
  end
40
36
 
41
37
  def with_config(config_hash)
42
38
  new_configuration = Configuration::AppConfiguration.default
43
- new_configuration.load_values(smell_type => config_hash)
39
+ new_configuration.load_values(Configuration::AppConfiguration::DETECTORS_KEY =>
40
+ { smell_type => config_hash })
44
41
  self.class.new(smell_type, smell_details, new_configuration)
45
42
  end
46
43
 
@@ -88,7 +85,7 @@ module Reek
88
85
  "#{smell_type} with smell details #{smell_details}, but it did"
89
86
  end
90
87
 
91
- # :reek:FeatureEnvy
88
+ # @quality :reek:FeatureEnvy
92
89
  def all_relevant_smell_details_formatted
93
90
  matching_smell_types.each_with_object([]).with_index do |(smell, accumulator), index|
94
91
  accumulator << "#{index + 1}.)\n"
@@ -99,17 +96,7 @@ module Reek
99
96
  end
100
97
 
101
98
  def origin
102
- examiner.description
103
- end
104
-
105
- # :reek:UtilityFunction
106
- def normalize(smell_type_or_class)
107
- case smell_type_or_class
108
- when Class
109
- smell_type_or_class.smell_type
110
- else
111
- smell_type_or_class.to_s
112
- end
99
+ examiner.origin
113
100
  end
114
101
  end
115
102
  end