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 'smell_detectors'
3
4
  require_relative 'smell_detectors/base_detector'
4
5
  require_relative 'configuration/app_configuration'
@@ -34,31 +35,31 @@ module Reek
34
35
  configuration: {})
35
36
  @configuration = configuration
36
37
  @smell_types = smell_types
37
- @detectors = smell_types.map { |klass| klass.new configuration_for(klass) }
38
38
  end
39
39
 
40
40
  def examine(context)
41
- smell_detectors_for(context.type).flat_map do |detector|
42
- 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
43
44
  end
44
45
  end
45
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
+
46
53
  private
47
54
 
48
- attr_reader :configuration, :smell_types, :detectors
55
+ attr_reader :configuration, :smell_types
49
56
 
50
57
  def configuration_for(klass)
51
58
  configuration.fetch klass, {}
52
59
  end
53
60
 
54
61
  def smell_detectors_for(type)
55
- enabled_detectors.select do |detector|
56
- detector.contexts.include? type
57
- end
58
- end
59
-
60
- def enabled_detectors
61
- detectors.select { |detector| detector.config.enabled? }
62
+ smell_types.select { |detector| detector.contexts.include? type }
62
63
  end
63
64
  end
64
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,35 +1,37 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_error'
4
+ require_relative '../documentation_link'
3
5
 
4
6
  module Reek
5
7
  module Errors
6
8
  # Gets raised when trying to configure a detector with an option
7
9
  # which is unknown to it.
8
10
  class BadDetectorConfigurationKeyInCommentError < BaseError
9
- UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
11
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE
10
12
 
11
- Error: You are trying to configure the smell detector '%s'
12
- in one of your source code comments with the unknown option %s.
13
- 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.
14
16
  Here's the original comment:
15
17
 
16
- %s
18
+ %<comment>s
17
19
 
18
20
  Please see the Reek docs for:
19
- * how to configure Reek via source code comments: https://github.com/troessner/reek/blob/master/docs/Smell-Suppression.md
20
- * 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')}
21
23
  * what custom options are available by checking the detector specific documentation in /docs
22
24
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
23
25
 
24
- EOS
26
+ MESSAGE
25
27
 
26
28
  def initialize(detector_name:, offensive_keys:, source:, line:, original_comment:)
27
29
  message = format(UNKNOWN_SMELL_DETECTOR_MESSAGE,
28
- detector_name,
29
- offensive_keys,
30
- source,
31
- line,
32
- original_comment)
30
+ detector: detector_name,
31
+ option: offensive_keys,
32
+ source: source,
33
+ line: line,
34
+ comment: original_comment)
33
35
  super message
34
36
  end
35
37
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_error'
4
+ require_relative '../documentation_link'
3
5
 
4
6
  module Reek
5
7
  module Errors
@@ -7,28 +9,28 @@ module Reek
7
9
  # This might happen for multiple reasons. The users might have a typo in
8
10
  # his comment or he might use a detector that does not exist anymore.
9
11
  class BadDetectorInCommentError < BaseError
10
- UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
12
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE
11
13
 
12
- 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
13
15
  of your source code comments.
14
- 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.
15
17
  Here's the original comment:
16
18
 
17
- %s
19
+ %<comment>s
18
20
 
19
21
  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
22
+ * how to configure Reek via source code comments: #{DocumentationLink.build('Smell Suppression')}
23
+ * what smell detectors are available: #{DocumentationLink.build('Code Smells')}
22
24
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
23
25
 
24
- EOS
26
+ MESSAGE
25
27
 
26
28
  def initialize(detector_name:, source:, line:, original_comment:)
27
29
  message = format(UNKNOWN_SMELL_DETECTOR_MESSAGE,
28
- detector_name,
29
- source,
30
- line,
31
- original_comment)
30
+ detector: detector_name,
31
+ source: source,
32
+ line: line,
33
+ comment: original_comment)
32
34
  super message
33
35
  end
34
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,33 +1,35 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_error'
4
+ require_relative '../documentation_link'
3
5
 
4
6
  module Reek
5
7
  module Errors
6
8
  # Gets raised when trying to use a configuration for a detector
7
9
  # that can't be parsed into a hash.
8
10
  class GarbageDetectorConfigurationInCommentError < BaseError
9
- BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-EOS.freeze
11
+ BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-MESSAGE
10
12
 
11
- Error: You are trying to configure the smell detector '%s'.
12
- Unfortunately we can not parse the configuration you have given.
13
- 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.
14
16
  Here's the original comment:
15
17
 
16
- %s
18
+ %<comment>s
17
19
 
18
20
  Please see the Reek docs for:
19
- * how to configure Reek via source code comments: https://github.com/troessner/reek/blob/master/docs/Smell-Suppression.md
20
- * 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')}
21
23
  Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
22
24
 
23
- EOS
25
+ MESSAGE
24
26
 
25
27
  def initialize(detector_name:, source:, line:, original_comment:)
26
28
  message = format(BAD_DETECTOR_CONFIGURATION_MESSAGE,
27
- detector_name,
28
- source,
29
- line,
30
- original_comment)
29
+ detector: detector_name,
30
+ source: source,
31
+ line: line,
32
+ comment: original_comment)
31
33
  super message
32
34
  end
33
35
  end
@@ -1,46 +1,45 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'base_error'
3
4
 
4
5
  module Reek
5
6
  module Errors
6
7
  # Gets raised when Reek is unable to process the source
7
8
  class IncomprehensibleSourceError < BaseError
8
- INCOMPREHENSIBLE_SOURCE_TEMPLATE = <<-EOS.freeze
9
- !!!
10
- Source %s can not be processed by Reek.
9
+ TEMPLATE = 'Source %<source>s cannot be processed by Reek.'
11
10
 
12
- This is most likely either a bug in your Reek configuration (config file or
13
- source code comments) or a Reek bug.
11
+ LONG_TEMPLATE = <<-MESSAGE
12
+ !!!
13
+ %<message>s
14
14
 
15
- Please double check your Reek configuration taking the original exception
16
- below into account - you might have misspelled a smell detector for instance.
17
- (In the future Reek will handle configuration errors more gracefully, something
18
- we are working on already).
15
+ This is most likely a Reek bug.
19
16
 
20
- If you feel that this is not a problem with your Reek configuration but with
21
- Reek itself it would be great if you could report this back to the Reek
22
- 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.
23
19
 
24
- Please make sure to include the source in question, the Reek version
25
- and the original exception below.
20
+ Please make sure to include the source in question, the Reek version and the
21
+ original exception below.
26
22
 
27
23
  Exception message:
28
24
 
29
- %s
25
+ %<exception>s
30
26
 
31
27
  Original exception:
32
28
 
33
- %s
29
+ %<original>s
34
30
 
35
31
  !!!
36
- EOS
37
-
38
- def initialize(origin:, original_exception:)
39
- message = format(INCOMPREHENSIBLE_SOURCE_TEMPLATE,
40
- origin,
41
- original_exception.message,
42
- original_exception.backtrace.join("\n\t"))
43
- 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"))
44
43
  end
45
44
  end
46
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
data/lib/reek/examiner.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'context_builder'
3
4
  require_relative 'detector_repository'
4
5
  require_relative 'errors/incomprehensible_source_error'
6
+ require_relative 'errors/encoding_error'
7
+ require_relative 'errors/syntax_error'
5
8
  require_relative 'source/source_code'
6
9
 
7
10
  module Reek
@@ -9,6 +12,7 @@ module Reek
9
12
  # Applies all available smell detectors to a source.
10
13
  #
11
14
  # @public
15
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 5 }
12
16
  class Examiner
13
17
  # Handles no errors
14
18
  class NullHandler
@@ -37,26 +41,17 @@ module Reek
37
41
  detector_repository_class: DetectorRepository,
38
42
  error_handler: NullHandler.new)
39
43
  @source = Source::SourceCode.from(source)
44
+ @origin = @source.origin
40
45
  @smell_types = detector_repository_class.eligible_smell_types(filter_by_smells)
41
46
  @detector_repository = detector_repository_class.new(smell_types: @smell_types,
42
- configuration: configuration.directive_for(description))
47
+ configuration: configuration.directive_for(@origin))
43
48
  @error_handler = error_handler
44
49
  end
45
50
 
46
51
  # @return [String] origin of the source being analysed
47
52
  #
48
53
  # @public
49
- def origin
50
- @origin ||= source.origin
51
- end
52
-
53
- # @return [String] description of the source being analysed
54
- #
55
- # @public
56
- # @deprecated Use origin
57
- def description
58
- origin
59
- end
54
+ attr_reader :origin
60
55
 
61
56
  #
62
57
  # @return [Array<SmellWarning>] the smells found in the source
@@ -94,15 +89,26 @@ module Reek
94
89
  #
95
90
  # @return [Array<SmellWarning>] the smells found in the source
96
91
  #
97
- # :reek:TooManyStatements { max_statements: 8 }
98
92
  def run
99
- return [] unless syntax_tree
100
- begin
93
+ wrap_exceptions do
101
94
  examine_tree
102
- rescue Errors::BaseError => exception
103
- raise unless @error_handler.handle exception
104
- []
105
95
  end
96
+ rescue StandardError => exception
97
+ raise unless @error_handler.handle exception
98
+ []
99
+ end
100
+
101
+ # @quality :reek:TooManyStatements { max_statements: 6 }
102
+ def wrap_exceptions
103
+ yield
104
+ rescue Errors::BaseError
105
+ raise
106
+ rescue EncodingError
107
+ raise Errors::EncodingError, origin: origin
108
+ rescue Parser::SyntaxError
109
+ raise Errors::SyntaxError, origin: origin
110
+ rescue StandardError
111
+ raise Errors::IncomprehensibleSourceError, origin: origin
106
112
  end
107
113
 
108
114
  def syntax_tree
@@ -113,10 +119,6 @@ module Reek
113
119
  ContextBuilder.new(syntax_tree).context_tree.flat_map do |element|
114
120
  detector_repository.examine(element)
115
121
  end
116
- rescue Errors::BaseError
117
- raise
118
- rescue StandardError => exception
119
- raise Errors::IncomprehensibleSourceError, origin: origin, original_exception: exception
120
122
  end
121
123
  end
122
124
  end
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
- require_relative 'errors/bad_detector_configuration_key_in_comment_error'
3
- require_relative 'errors/bad_detector_in_comment_error'
4
- require_relative 'errors/garbage_detector_configuration_in_comment_error'
5
- require_relative 'errors/incomprehensible_source_error'
2
+
3
+ require_relative 'errors/base_error'
6
4
 
7
5
  module Reek
8
6
  # Handles errors by logging to stderr
9
7
  class LoggingErrorHandler
10
8
  def handle(exception)
11
- warn exception
9
+ case exception
10
+ when Errors::BaseError
11
+ warn exception.long_message
12
+ else
13
+ warn exception
14
+ end
12
15
  true
13
16
  end
14
17
  end
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env ruby
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'rake'
@@ -34,8 +33,8 @@ module Reek
34
33
  #
35
34
  # @public
36
35
  #
37
- # :reek:TooManyInstanceVariables: { max_instance_variables: 6 }
38
- # :reek:Attribute
36
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 6 }
37
+ # @quality :reek:Attribute
39
38
  class Task < ::Rake::TaskLib
40
39
  # Name of Reek task. Defaults to :reek.
41
40
  # @public
@@ -74,10 +73,14 @@ module Reek
74
73
  @name = name
75
74
  @reek_opts = ENV['REEK_OPTS'] || ''
76
75
  @fail_on_error = true
77
- @source_files = FileList[ENV['REEK_SRC'] || 'lib/**/*.rb']
78
76
  @verbose = false
79
77
 
80
78
  yield self if block_given?
79
+
80
+ if (reek_src = ENV['REEK_SRC'])
81
+ @source_files = FileList[reek_src]
82
+ end
83
+ @source_files ||= FileList['lib/**/*.rb']
81
84
  define_task
82
85
  end
83
86
 
@@ -111,7 +114,7 @@ module Reek
111
114
  reject(&:empty?)
112
115
  end
113
116
 
114
- # :reek:UtilityFunction
117
+ # @quality :reek:UtilityFunction
115
118
  def sys_call_failed?
116
119
  !$CHILD_STATUS.success?
117
120
  end
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'json'
3
4
  require 'pathname'
4
5
  require 'rainbow'
5
6
 
6
- require_relative 'formatter'
7
-
8
7
  module Reek
9
8
  # @public
10
9
  module Report
@@ -15,22 +14,20 @@ module Reek
15
14
  #
16
15
  # @public
17
16
  #
18
- # :reek:TooManyInstanceVariables: { max_instance_variables: 7 }
17
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 7 }
19
18
  class BaseReport
20
19
  NO_WARNINGS_COLOR = :green
21
20
  WARNINGS_COLOR = :red
22
21
 
23
22
  # @public
24
23
  #
25
- # :reek:BooleanParameter
26
- def initialize(heading_formatter: Formatter::QuietHeadingFormatter,
27
- report_formatter: Formatter,
24
+ # @quality :reek:BooleanParameter
25
+ def initialize(heading_formatter: QuietHeadingFormatter,
28
26
  sort_by_issue_count: false,
29
- warning_formatter: Formatter::SimpleWarningFormatter.new,
30
- progress_formatter: Formatter::ProgressFormatter::Quiet.new(0))
27
+ warning_formatter: SimpleWarningFormatter.new,
28
+ progress_formatter: ProgressFormatter::Quiet.new(0))
31
29
  @examiners = []
32
- @heading_formatter = heading_formatter.new(report_formatter)
33
- @report_formatter = report_formatter
30
+ @heading_formatter = heading_formatter.new
34
31
  @progress_formatter = progress_formatter
35
32
  @sort_by_issue_count = sort_by_issue_count
36
33
  @total_smell_count = 0
@@ -57,7 +54,7 @@ module Reek
57
54
  end
58
55
 
59
56
  def smells?
60
- total_smell_count > 0
57
+ total_smell_count.positive?
61
58
  end
62
59
 
63
60
  def smells
@@ -70,7 +67,7 @@ module Reek
70
67
 
71
68
  private
72
69
 
73
- attr_reader :examiners, :heading_formatter, :report_formatter,
70
+ attr_reader :examiners, :heading_formatter,
74
71
  :sort_by_issue_count, :warning_formatter, :progress_formatter
75
72
  end
76
73
  end
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Reek
3
4
  module Report
4
5
  # loads the smell type metadata to present in Code Climate
5
6
  module CodeClimateConfiguration
6
7
  def self.load
7
- config_file = File.expand_path('../code_climate_configuration.yml', __FILE__)
8
+ config_file = File.expand_path('code_climate_configuration.yml', __dir__)
8
9
  YAML.load_file config_file
9
10
  end
10
11
  end