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
@@ -20,8 +20,9 @@ module Reek
20
20
  # counting. Ideally `ContextBuilder` would only build up the context tree and leave the
21
21
  # statement and reference counting to the contexts.
22
22
  #
23
- # :reek:TooManyMethods: { max_methods: 31 }
24
- # :reek:UnusedPrivateMethod: { exclude: [ !ruby/regexp /process_/ ] }
23
+ # @quality :reek:TooManyMethods { max_methods: 31 }
24
+ # @quality :reek:UnusedPrivateMethod { exclude: [ !ruby/regexp /process_/ ] }
25
+ # @quality :reek:DataClump
25
26
  class ContextBuilder
26
27
  attr_reader :context_tree
27
28
 
@@ -58,10 +59,10 @@ module Reek
58
59
  # RootContext -> children: 1 ModuleContext -> children: 1 MethodContext
59
60
  #
60
61
  # @return [Reek::Context::RootContext] tree of nested contexts
61
- def build(exp)
62
+ def build(exp, parent_exp = nil)
62
63
  context_processor = "process_#{exp.type}"
63
64
  if context_processor_exists?(context_processor)
64
- send(context_processor, exp)
65
+ send(context_processor, exp, parent_exp)
65
66
  else
66
67
  process exp
67
68
  end
@@ -71,12 +72,12 @@ module Reek
71
72
  # Handles every node for which we have no context_processor.
72
73
  #
73
74
  def process(exp)
74
- exp.children.grep(AST::Node).each(&method(:build))
75
+ exp.children.grep(AST::Node).each { |child| build(child, exp) }
75
76
  end
76
77
 
77
78
  # Handles `module` and `class` nodes.
78
79
  #
79
- def process_module(exp)
80
+ def process_module(exp, _parent)
80
81
  inside_new_context(Context::ModuleContext, exp) do
81
82
  process(exp)
82
83
  end
@@ -91,7 +92,7 @@ module Reek
91
92
  # class << self
92
93
  # end
93
94
  #
94
- def process_sclass(exp)
95
+ def process_sclass(exp, _parent)
95
96
  inside_new_context(Context::GhostContext, exp) do
96
97
  process(exp)
97
98
  end
@@ -103,9 +104,9 @@ module Reek
103
104
  #
104
105
  # Foo = Class.new Bar
105
106
  #
106
- def process_casgn(exp)
107
+ def process_casgn(exp, parent)
107
108
  if exp.defines_module?
108
- process_module(exp)
109
+ process_module(exp, parent)
109
110
  else
110
111
  process(exp)
111
112
  end
@@ -119,8 +120,8 @@ module Reek
119
120
  #
120
121
  # Given the above example we would count 2 statements overall.
121
122
  #
122
- def process_def(exp)
123
- inside_new_context(current_context.method_context_class, exp) do
123
+ def process_def(exp, parent)
124
+ inside_new_context(current_context.method_context_class, exp, parent) do
124
125
  increase_statement_count_by(exp.body)
125
126
  process(exp)
126
127
  end
@@ -134,8 +135,8 @@ module Reek
134
135
  #
135
136
  # Given the above example we would count 2 statements overall.
136
137
  #
137
- def process_defs(exp)
138
- inside_new_context(Context::SingletonMethodContext, exp) do
138
+ def process_defs(exp, parent)
139
+ inside_new_context(Context::SingletonMethodContext, exp, parent) do
139
140
  increase_statement_count_by(exp.body)
140
141
  process(exp)
141
142
  end
@@ -151,14 +152,14 @@ module Reek
151
152
  # we also record to what the method call is referring to
152
153
  # which we later use for smell detectors like FeatureEnvy.
153
154
  #
154
- def process_send(exp)
155
+ def process_send(exp, _parent)
156
+ process(exp)
155
157
  case current_context
156
158
  when Context::ModuleContext
157
159
  handle_send_for_modules exp
158
160
  when Context::MethodContext
159
161
  handle_send_for_methods exp
160
162
  end
161
- process(exp)
162
163
  end
163
164
 
164
165
  # Handles `op_asgn` nodes a.k.a. Ruby's assignment operators.
@@ -173,7 +174,7 @@ module Reek
173
174
  #
174
175
  # We record one reference to `x` given the example above.
175
176
  #
176
- def process_op_asgn(exp)
177
+ def process_op_asgn(exp, _parent)
177
178
  current_context.record_call_to(exp)
178
179
  process(exp)
179
180
  end
@@ -192,7 +193,7 @@ module Reek
192
193
  #
193
194
  # We record one reference to `self`.
194
195
  #
195
- def process_ivar(exp)
196
+ def process_ivar(exp, _parent)
196
197
  current_context.record_use_of_self
197
198
  process(exp)
198
199
  end
@@ -205,7 +206,7 @@ module Reek
205
206
  #
206
207
  # def self.foo; end
207
208
  #
208
- def process_self(_)
209
+ def process_self(_exp, _parent)
209
210
  current_context.record_use_of_self
210
211
  end
211
212
 
@@ -225,7 +226,7 @@ module Reek
225
226
  #
226
227
  # We record one reference to `self`.
227
228
  #
228
- def process_zsuper(_)
229
+ def process_zsuper(_exp, _parent)
229
230
  current_context.record_use_of_self
230
231
  end
231
232
 
@@ -249,7 +250,7 @@ module Reek
249
250
  #
250
251
  # We record one reference to `self`.
251
252
  #
252
- def process_super(exp)
253
+ def process_super(exp, _parent)
253
254
  current_context.record_use_of_self
254
255
  process(exp)
255
256
  end
@@ -262,7 +263,7 @@ module Reek
262
263
  #
263
264
  # Counts non-empty blocks as one statement.
264
265
  #
265
- def process_block(exp)
266
+ def process_block(exp, _parent)
266
267
  increase_statement_count_by(exp.block)
267
268
  process(exp)
268
269
  end
@@ -282,7 +283,7 @@ module Reek
282
283
  # At the end we subtract one statement because the surrounding context was already counted
283
284
  # as one (e.g. via `process_def`).
284
285
  #
285
- def process_begin(exp)
286
+ def process_begin(exp, _parent)
286
287
  increase_statement_count_by(exp.children)
287
288
  decrease_statement_count
288
289
  process(exp)
@@ -308,7 +309,7 @@ module Reek
308
309
  # `children[1]` refers to the `if` body (so `puts 'bingo'` from above) and
309
310
  # `children[2]` to the `else` body (so `3` from above), which might be nil.
310
311
  #
311
- def process_if(exp)
312
+ def process_if(exp, _parent)
312
313
  children = exp.children
313
314
  increase_statement_count_by(children[1])
314
315
  increase_statement_count_by(children[2])
@@ -331,7 +332,7 @@ module Reek
331
332
  #
332
333
  # `children[1]` below refers to the `while` body (so `puts 'bingo'` from above)
333
334
  #
334
- def process_while(exp)
335
+ def process_while(exp, _parent)
335
336
  increase_statement_count_by(exp.children[1])
336
337
  decrease_statement_count
337
338
  process(exp)
@@ -354,7 +355,7 @@ module Reek
354
355
  #
355
356
  # `children[2]` below refers to the `while` body (so `puts i` from above)
356
357
  #
357
- def process_for(exp)
358
+ def process_for(exp, _parent)
358
359
  increase_statement_count_by(exp.children[2])
359
360
  decrease_statement_count
360
361
  process(exp)
@@ -384,7 +385,7 @@ module Reek
384
385
  # `exp` would be the whole method body wrapped under a `rescue` node.
385
386
  # See `process_resbody` for additional reference.
386
387
  #
387
- def process_rescue(exp)
388
+ def process_rescue(exp, _parent)
388
389
  increase_statement_count_by(exp.children.first)
389
390
  decrease_statement_count
390
391
  process(exp)
@@ -412,7 +413,7 @@ module Reek
412
413
  # `exp` would be the whole `rescue` body.
413
414
  # See `process_rescue` for additional reference.
414
415
  #
415
- def process_resbody(exp)
416
+ def process_resbody(exp, _parent)
416
417
  increase_statement_count_by(exp.children[1..-1].compact)
417
418
  process(exp)
418
419
  end
@@ -434,7 +435,7 @@ module Reek
434
435
  # At the end we subtract one statement because the surrounding context was already counted
435
436
  # as one (e.g. via `process_def`).
436
437
  #
437
- def process_case(exp)
438
+ def process_case(exp, _parent)
438
439
  increase_statement_count_by(exp.else_body)
439
440
  decrease_statement_count
440
441
  process(exp)
@@ -460,7 +461,7 @@ module Reek
460
461
  #
461
462
  # Counts the `when` body.
462
463
  #
463
- def process_when(exp)
464
+ def process_when(exp, _parent)
464
465
  increase_statement_count_by(exp.body)
465
466
  process(exp)
466
467
  end
@@ -469,7 +470,7 @@ module Reek
469
470
  self.class.private_method_defined?(name)
470
471
  end
471
472
 
472
- # :reek:ControlParameter
473
+ # @quality :reek:ControlParameter
473
474
  def increase_statement_count_by(sexp)
474
475
  current_context.statement_counter.increase_by sexp
475
476
  end
@@ -481,37 +482,35 @@ module Reek
481
482
  # Stores a reference to the current context, creates a nested new one,
482
483
  # yields to the given block and then restores the previous context.
483
484
  #
484
- # @param klass [Context::*Context] - context class
485
- # @param exp - current expression
485
+ # @param klass [Context::*Context] context class
486
+ # @param args arguments for the class initializer
486
487
  # @yield block
487
488
  #
488
- def inside_new_context(klass, exp)
489
- new_context = append_new_context(klass, exp)
489
+ def inside_new_context(klass, *args)
490
+ new_context = append_new_context(klass, *args)
490
491
 
491
492
  orig, self.current_context = current_context, new_context
492
493
  yield
493
494
  self.current_context = orig
494
495
  end
495
496
 
496
- # Append a new child context to the current context but does not change the
497
- # current context.
497
+ # Appends a new child context to the current context but does not change
498
+ # the current context.
498
499
  #
499
- # @param klass [Context::*Context] - context class
500
- # @param args - arguments for the class initializer
500
+ # @param klass [Context::*Context] context class
501
+ # @param args arguments for the class initializer
501
502
  #
502
- # @return [Context::*Context] - the context that was appended
503
+ # @return [Context::*Context] the context that was appended
503
504
  #
504
505
  def append_new_context(klass, *args)
505
- klass.new(current_context, *args).tap do |new_context|
506
+ klass.new(*args).tap do |new_context|
506
507
  new_context.register_with_parent(current_context)
507
508
  end
508
509
  end
509
510
 
510
511
  def handle_send_for_modules(exp)
511
- method_name = exp.name
512
- arg_names = exp.arg_names
513
- current_context.track_visibility(method_name, arg_names)
514
- current_context.track_singleton_visibility(method_name, arg_names)
512
+ arg_names = exp.args.map { |arg| arg.children.first }
513
+ current_context.track_visibility(exp.name, arg_names)
515
514
  register_attributes(exp)
516
515
  end
517
516
 
@@ -35,31 +35,31 @@ module Reek
35
35
  configuration: {})
36
36
  @configuration = configuration
37
37
  @smell_types = smell_types
38
- @detectors = smell_types.map { |klass| klass.new configuration_for(klass) }
39
38
  end
40
39
 
41
40
  def examine(context)
42
- smell_detectors_for(context.type).flat_map do |detector|
43
- detector.run_for(context)
41
+ smell_detectors_for(context.type).flat_map do |klass|
42
+ detector = klass.new configuration: configuration_for(klass), context: context
43
+ detector.run
44
44
  end
45
45
  end
46
46
 
47
+ # @return [Array<String>] The names of all known SmellDetectors
48
+ # e.g. ["BooleanParameter", "ClassVariable"].
49
+ def self.available_detector_names
50
+ smell_types.map(&:smell_type)
51
+ end
52
+
47
53
  private
48
54
 
49
- attr_reader :configuration, :smell_types, :detectors
55
+ attr_reader :configuration, :smell_types
50
56
 
51
57
  def configuration_for(klass)
52
58
  configuration.fetch klass, {}
53
59
  end
54
60
 
55
61
  def smell_detectors_for(type)
56
- enabled_detectors.select do |detector|
57
- detector.contexts.include? type
58
- end
59
- end
60
-
61
- def enabled_detectors
62
- detectors.select { |detector| detector.config.enabled? }
62
+ smell_types.select { |detector| detector.contexts.include? type }
63
63
  end
64
64
  end
65
65
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reek
4
+ # Generate versioned links to our documentation
5
+ module DocumentationLink
6
+ HELP_LINK_TEMPLATE = 'https://github.com/troessner/reek/blob/v%<version>s/docs/%<item>s.md'
7
+
8
+ module_function
9
+
10
+ # Build link to the documentation about the given subject for the current
11
+ # version of Reek. The subject can be either a smell type like
12
+ # 'FeatureEnvy' or a general subject like 'Rake Task'.
13
+ #
14
+ # @param subject [String]
15
+ # @return [String] the full URL for the relevant documentation
16
+ def build(subject)
17
+ Kernel.format(HELP_LINK_TEMPLATE, version: Version::STRING, item: name_to_param(subject))
18
+ end
19
+
20
+ # Convert the given subject name to a form that is acceptable in a URL.
21
+ def name_to_param(name)
22
+ # Splits the subject on the start of capitalized words, optionally
23
+ # preceded by a space. The space is discarded, the start of the word is
24
+ # not.
25
+ name.split(/ *(?=[A-Z][a-z])/).join('-')
26
+ end
27
+ end
28
+ end
@@ -1,36 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base_error'
4
+ require_relative '../documentation_link'
4
5
 
5
6
  module Reek
6
7
  module Errors
7
8
  # Gets raised when trying to configure a detector with an option
8
9
  # which is unknown to it.
9
10
  class BadDetectorConfigurationKeyInCommentError < BaseError
10
- UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
11
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE
11
12
 
12
- Error: You are trying to configure the smell detector '%s'
13
- in one of your source code comments with the unknown option %s.
14
- The source is '%s' and the comment belongs to the expression starting in line %d.
13
+ Error: You are trying to configure the smell detector '%<detector>s'
14
+ in one of your source code comments with the unknown option %<option>s.
15
+ The source is '%<source>s' and the comment belongs to the expression starting in line %<line>d.
15
16
  Here's the original comment:
16
17
 
17
- %s
18
+ %<comment>s
18
19
 
19
20
  Please see the Reek docs for:
20
- * how to configure Reek via source code comments: https://github.com/troessner/reek/blob/master/docs/Smell-Suppression.md
21
- * what basic options are available: https://github.com/troessner/reek/blob/master/docs/Basic-Smell-Options.md
21
+ * how to configure Reek via source code comments: #{DocumentationLink.build('Smell Suppression')}
22
+ * what basic options are available: #{DocumentationLink.build('Basic Smell Options')}
22
23
  * what custom options are available by checking the detector specific documentation in /docs
23
24
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
24
25
 
25
- EOS
26
+ MESSAGE
26
27
 
27
28
  def initialize(detector_name:, offensive_keys:, source:, line:, original_comment:)
28
29
  message = format(UNKNOWN_SMELL_DETECTOR_MESSAGE,
29
- detector_name,
30
- offensive_keys,
31
- source,
32
- line,
33
- original_comment)
30
+ detector: detector_name,
31
+ option: offensive_keys,
32
+ source: source,
33
+ line: line,
34
+ comment: original_comment)
34
35
  super message
35
36
  end
36
37
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base_error'
4
+ require_relative '../documentation_link'
4
5
 
5
6
  module Reek
6
7
  module Errors
@@ -8,28 +9,28 @@ module Reek
8
9
  # This might happen for multiple reasons. The users might have a typo in
9
10
  # his comment or he might use a detector that does not exist anymore.
10
11
  class BadDetectorInCommentError < BaseError
11
- UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
12
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE
12
13
 
13
- Error: You are trying to configure an unknown smell detector '%s' in one
14
+ Error: You are trying to configure an unknown smell detector '%<detector>s' in one
14
15
  of your source code comments.
15
- The source is '%s' and the comment belongs to the expression starting in line %d.
16
+ The source is '%<source>s' and the comment belongs to the expression starting in line %<line>d.
16
17
  Here's the original comment:
17
18
 
18
- %s
19
+ %<comment>s
19
20
 
20
21
  Please see the Reek docs for:
21
- * how to configure Reek via source code comments: https://github.com/troessner/reek/blob/master/docs/Smell-Suppression.md
22
- * what smell detectors are available: https://github.com/troessner/reek/blob/master/docs/Code-Smells.md
22
+ * how to configure Reek via source code comments: #{DocumentationLink.build('Smell Suppression')}
23
+ * what smell detectors are available: #{DocumentationLink.build('Code Smells')}
23
24
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
24
25
 
25
- EOS
26
+ MESSAGE
26
27
 
27
28
  def initialize(detector_name:, source:, line:, original_comment:)
28
29
  message = format(UNKNOWN_SMELL_DETECTOR_MESSAGE,
29
- detector_name,
30
- source,
31
- line,
32
- original_comment)
30
+ detector: detector_name,
31
+ source: source,
32
+ line: line,
33
+ comment: original_comment)
33
34
  super message
34
35
  end
35
36
  end
@@ -4,6 +4,9 @@ module Reek
4
4
  module Errors
5
5
  # Base class for all runtime Reek errors
6
6
  class BaseError < ::RuntimeError
7
+ def long_message
8
+ message
9
+ end
7
10
  end
8
11
  end
9
12
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_error'
4
+
5
+ module Reek
6
+ module Errors
7
+ # Gets raised when Reek is unable to process the source due to bad config file
8
+ class ConfigFileError < BaseError
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_error'
4
+
5
+ module Reek
6
+ module Errors
7
+ # Gets raised when Reek is unable to process the source due to an EncodingError
8
+ class EncodingError < BaseError
9
+ TEMPLATE = "Source '%<source>s' cannot be processed by Reek due to an encoding error in the source file."
10
+
11
+ LONG_TEMPLATE = <<-MESSAGE
12
+ !!!
13
+ %<message>s
14
+
15
+ This is a problem that is outside of Reek's scope and should be fixed by you, the
16
+ user, in order for Reek being able to continue.
17
+ Check out this article for an idea on how to get started:
18
+ https://www.justinweiss.com/articles/3-steps-to-fix-encoding-problems-in-ruby/
19
+
20
+ Exception message:
21
+
22
+ %<exception>s
23
+
24
+ Original backtrace:
25
+
26
+ %<original>s
27
+
28
+ !!!
29
+ MESSAGE
30
+
31
+ def initialize(origin:)
32
+ super format(TEMPLATE, source: origin)
33
+ end
34
+
35
+ def long_message
36
+ format(LONG_TEMPLATE,
37
+ message: message,
38
+ exception: cause.inspect,
39
+ original: cause.backtrace.join("\n\t"))
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,34 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'base_error'
4
+ require_relative '../documentation_link'
4
5
 
5
6
  module Reek
6
7
  module Errors
7
8
  # Gets raised when trying to use a configuration for a detector
8
9
  # that can't be parsed into a hash.
9
10
  class GarbageDetectorConfigurationInCommentError < BaseError
10
- BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-EOS.freeze
11
+ BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-MESSAGE
11
12
 
12
- Error: You are trying to configure the smell detector '%s'.
13
- Unfortunately we can not parse the configuration you have given.
14
- The source is '%s' and the comment belongs to the expression starting in line %d.
13
+ Error: You are trying to configure the smell detector '%<detector>s'.
14
+ Unfortunately we cannot parse the configuration you have given.
15
+ The source is '%<source>s' and the comment belongs to the expression starting in line %<line>d.
15
16
  Here's the original comment:
16
17
 
17
- %s
18
+ %<comment>s
18
19
 
19
20
  Please see the Reek docs for:
20
- * how to configure Reek via source code comments: https://github.com/troessner/reek/blob/master/docs/Smell-Suppression.md
21
- * what smell detectors are available: https://github.com/troessner/reek/blob/master/docs/Code-Smells.md
21
+ * how to configure Reek via source code comments: #{DocumentationLink.build('Smell Suppression')}
22
+ * what smell detectors are available: #{DocumentationLink.build('Code Smells')}
22
23
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
23
24
 
24
- EOS
25
+ MESSAGE
25
26
 
26
27
  def initialize(detector_name:, source:, line:, original_comment:)
27
28
  message = format(BAD_DETECTOR_CONFIGURATION_MESSAGE,
28
- detector_name,
29
- source,
30
- line,
31
- original_comment)
29
+ detector: detector_name,
30
+ source: source,
31
+ line: line,
32
+ comment: original_comment)
32
33
  super message
33
34
  end
34
35
  end
@@ -6,42 +6,40 @@ module Reek
6
6
  module Errors
7
7
  # Gets raised when Reek is unable to process the source
8
8
  class IncomprehensibleSourceError < BaseError
9
- INCOMPREHENSIBLE_SOURCE_TEMPLATE = <<-EOS.freeze
10
- !!!
11
- Source %s can not be processed by Reek.
9
+ TEMPLATE = 'Source %<source>s cannot be processed by Reek.'
12
10
 
13
- This is most likely either a bug in your Reek configuration (config file or
14
- source code comments) or a Reek bug.
11
+ LONG_TEMPLATE = <<-MESSAGE
12
+ !!!
13
+ %<message>s
15
14
 
16
- Please double check your Reek configuration taking the original exception
17
- below into account - you might have misspelled a smell detector for instance.
18
- (In the future Reek will handle configuration errors more gracefully, something
19
- we are working on already).
15
+ This is most likely a Reek bug.
20
16
 
21
- If you feel that this is not a problem with your Reek configuration but with
22
- Reek itself it would be great if you could report this back to the Reek
23
- team by opening up a corresponding issue at https://github.com/troessner/reek/issues.
17
+ It would be great if you could report this back to the Reek team by opening a
18
+ corresponding issue at https://github.com/troessner/reek/issues.
24
19
 
25
- Please make sure to include the source in question, the Reek version
26
- and the original exception below.
20
+ Please make sure to include the source in question, the Reek version and the
21
+ original exception below.
27
22
 
28
23
  Exception message:
29
24
 
30
- %s
25
+ %<exception>s
31
26
 
32
27
  Original exception:
33
28
 
34
- %s
29
+ %<original>s
35
30
 
36
31
  !!!
37
- EOS
38
-
39
- def initialize(origin:, original_exception:)
40
- message = format(INCOMPREHENSIBLE_SOURCE_TEMPLATE,
41
- origin,
42
- original_exception.message,
43
- original_exception.backtrace.join("\n\t"))
44
- super message
32
+ MESSAGE
33
+
34
+ def initialize(origin:)
35
+ super format(TEMPLATE, source: origin)
36
+ end
37
+
38
+ def long_message
39
+ format(LONG_TEMPLATE,
40
+ message: message,
41
+ exception: cause.inspect,
42
+ original: cause.backtrace.join("\n\t"))
45
43
  end
46
44
  end
47
45
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_error'
4
+
5
+ module Reek
6
+ module Errors
7
+ # Gets raised when Reek is unable to process the source due to a SyntaxError
8
+ class SyntaxError < BaseError
9
+ TEMPLATE = "Source '%<source>s' cannot be processed by Reek due to a syntax error in the source file."
10
+
11
+ LONG_TEMPLATE = <<-MESSAGE
12
+ !!!
13
+ %<message>s
14
+
15
+ This is a problem that is outside of Reek's scope and should be fixed by you, the
16
+ user, in order for Reek being able to continue.
17
+
18
+ Exception message:
19
+
20
+ %<exception>s
21
+
22
+ Original backtrace:
23
+
24
+ %<original>s
25
+
26
+ !!!
27
+ MESSAGE
28
+
29
+ def initialize(origin:)
30
+ super format(TEMPLATE, source: origin)
31
+ end
32
+
33
+ def long_message
34
+ format(LONG_TEMPLATE,
35
+ message: message,
36
+ exception: cause.inspect,
37
+ original: cause.backtrace.join("\n\t"))
38
+ end
39
+ end
40
+ end
41
+ end