reek 4.8.1 → 5.0.2

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 (227) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +17 -3
  3. data/.simplecov +1 -0
  4. data/.travis.yml +0 -5
  5. data/.yardopts +1 -1
  6. data/CHANGELOG.md +44 -0
  7. data/Gemfile +1 -1
  8. data/README.md +115 -100
  9. data/Rakefile +16 -3
  10. data/bin/reek +1 -3
  11. data/docs/API.md +2 -9
  12. data/docs/Basic-Smell-Options.md +51 -11
  13. data/docs/Code-Smells.md +1 -1
  14. data/docs/Command-Line-Options.md +14 -4
  15. data/docs/Duplicate-Method-Call.md +49 -1
  16. data/docs/Feature-Envy.md +44 -0
  17. data/docs/How-To-Write-New-Detectors.md +2 -3
  18. data/docs/Instance-Variable-Assumption.md +1 -1
  19. data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
  20. data/docs/Rake-Task.md +1 -1
  21. data/docs/Reek-4-to-Reek-5-migration.md +193 -0
  22. data/docs/Reek-Driven-Development.md +1 -1
  23. data/docs/Uncommunicative-Method-Name.md +45 -6
  24. data/docs/Uncommunicative-Module-Name.md +49 -7
  25. data/docs/Uncommunicative-Parameter-Name.md +43 -5
  26. data/docs/Uncommunicative-Variable-Name.md +73 -2
  27. data/docs/Unused-Private-Method.md +1 -1
  28. data/docs/defaults.reek.yml +129 -0
  29. data/docs/yard_plugin.rb +1 -0
  30. data/features/command_line_interface/basic_usage.feature +2 -2
  31. data/features/command_line_interface/options.feature +46 -4
  32. data/features/command_line_interface/show_progress.feature +4 -4
  33. data/features/command_line_interface/smell_selection.feature +1 -1
  34. data/features/command_line_interface/smells_count.feature +6 -6
  35. data/features/command_line_interface/stdin.feature +30 -8
  36. data/features/configuration_files/accept_setting.feature +45 -28
  37. data/features/configuration_files/directory_specific_directives.feature +78 -73
  38. data/features/configuration_files/exclude_directives.feature +11 -10
  39. data/features/configuration_files/exclude_paths_directives.feature +4 -4
  40. data/features/configuration_files/masking_smells.feature +38 -9
  41. data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
  42. data/features/configuration_files/reject_setting.feature +52 -41
  43. data/features/configuration_files/schema_validation.feature +59 -0
  44. data/features/configuration_files/unused_private_method.feature +18 -16
  45. data/features/configuration_loading.feature +53 -10
  46. data/features/configuration_via_source_comments/erroneous_source_comments.feature +2 -2
  47. data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
  48. data/features/locales.feature +2 -2
  49. data/features/rake_task/rake_task.feature +15 -15
  50. data/features/reports/json.feature +3 -3
  51. data/features/reports/reports.feature +34 -34
  52. data/features/reports/yaml.feature +3 -3
  53. data/features/rspec_matcher.feature +9 -1
  54. data/features/samples.feature +287 -287
  55. data/features/step_definitions/reek_steps.rb +4 -0
  56. data/features/step_definitions/sample_file_steps.rb +9 -4
  57. data/features/support/env.rb +2 -2
  58. data/features/todo_list.feature +26 -23
  59. data/lib/reek/ast/node.rb +3 -6
  60. data/lib/reek/ast/object_refs.rb +1 -1
  61. data/lib/reek/ast/sexp_extensions/case.rb +3 -1
  62. data/lib/reek/ast/sexp_extensions/if.rb +2 -2
  63. data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
  64. data/lib/reek/ast/sexp_extensions/methods.rb +1 -1
  65. data/lib/reek/cli/application.rb +4 -3
  66. data/lib/reek/cli/command/report_command.rb +1 -2
  67. data/lib/reek/cli/command/todo_list_command.rb +4 -2
  68. data/lib/reek/cli/options.rb +29 -14
  69. data/lib/reek/cli/silencer.rb +14 -3
  70. data/lib/reek/code_comment.rb +14 -16
  71. data/lib/reek/configuration/app_configuration.rb +32 -28
  72. data/lib/reek/configuration/configuration_converter.rb +110 -0
  73. data/lib/reek/configuration/configuration_file_finder.rb +15 -40
  74. data/lib/reek/configuration/configuration_validator.rb +12 -23
  75. data/lib/reek/configuration/default_directive.rb +17 -3
  76. data/lib/reek/configuration/directory_directives.rb +17 -11
  77. data/lib/reek/configuration/excluded_paths.rb +1 -1
  78. data/lib/reek/configuration/rake_task_converter.rb +29 -0
  79. data/lib/reek/configuration/schema.yml +210 -0
  80. data/lib/reek/configuration/schema_validator.rb +38 -0
  81. data/lib/reek/context/attribute_context.rb +1 -1
  82. data/lib/reek/context/code_context.rb +4 -4
  83. data/lib/reek/context/method_context.rb +2 -2
  84. data/lib/reek/context/module_context.rb +1 -1
  85. data/lib/reek/context_builder.rb +9 -9
  86. data/lib/reek/detector_repository.rb +6 -0
  87. data/lib/reek/documentation_link.rb +28 -0
  88. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +4 -3
  89. data/lib/reek/errors/bad_detector_in_comment_error.rb +4 -3
  90. data/lib/reek/errors/config_file_error.rb +11 -0
  91. data/lib/reek/errors/encoding_error.rb +2 -2
  92. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +4 -3
  93. data/lib/reek/errors/incomprehensible_source_error.rb +2 -2
  94. data/lib/reek/errors/syntax_error.rb +41 -0
  95. data/lib/reek/examiner.rb +9 -19
  96. data/lib/reek/rake/task.rb +3 -3
  97. data/lib/reek/report/base_report.rb +8 -12
  98. data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -10
  99. data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
  100. data/lib/reek/report/heading_formatter.rb +54 -0
  101. data/lib/reek/report/json_report.rb +1 -1
  102. data/lib/reek/report/location_formatter.rb +40 -0
  103. data/lib/reek/report/progress_formatter.rb +79 -0
  104. data/lib/reek/report/simple_warning_formatter.rb +34 -0
  105. data/lib/reek/report/text_report.rb +1 -2
  106. data/lib/reek/report/xml_report.rb +3 -3
  107. data/lib/reek/report/yaml_report.rb +1 -1
  108. data/lib/reek/report.rb +15 -10
  109. data/lib/reek/smell_configuration.rb +2 -2
  110. data/lib/reek/smell_detectors/attribute.rb +0 -1
  111. data/lib/reek/smell_detectors/base_detector.rb +8 -11
  112. data/lib/reek/smell_detectors/boolean_parameter.rb +0 -1
  113. data/lib/reek/smell_detectors/class_variable.rb +0 -1
  114. data/lib/reek/smell_detectors/control_parameter.rb +17 -32
  115. data/lib/reek/smell_detectors/data_clump.rb +3 -4
  116. data/lib/reek/smell_detectors/duplicate_method_call.rb +5 -6
  117. data/lib/reek/smell_detectors/feature_envy.rb +1 -1
  118. data/lib/reek/smell_detectors/instance_variable_assumption.rb +0 -1
  119. data/lib/reek/smell_detectors/irresponsible_module.rb +0 -1
  120. data/lib/reek/smell_detectors/long_parameter_list.rb +1 -2
  121. data/lib/reek/smell_detectors/long_yield_list.rb +2 -3
  122. data/lib/reek/smell_detectors/manual_dispatch.rb +2 -2
  123. data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +6 -7
  124. data/lib/reek/smell_detectors/module_initialize.rb +0 -1
  125. data/lib/reek/smell_detectors/nested_iterators.rb +4 -5
  126. data/lib/reek/smell_detectors/nil_check.rb +0 -1
  127. data/lib/reek/smell_detectors/repeated_conditional.rb +3 -4
  128. data/lib/reek/smell_detectors/subclassed_from_core_class.rb +0 -1
  129. data/lib/reek/smell_detectors/too_many_constants.rb +1 -2
  130. data/lib/reek/smell_detectors/too_many_instance_variables.rb +1 -2
  131. data/lib/reek/smell_detectors/too_many_methods.rb +1 -2
  132. data/lib/reek/smell_detectors/too_many_statements.rb +1 -2
  133. data/lib/reek/smell_detectors/uncommunicative_method_name.rb +2 -3
  134. data/lib/reek/smell_detectors/uncommunicative_module_name.rb +2 -3
  135. data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +2 -3
  136. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +4 -5
  137. data/lib/reek/smell_detectors/unused_parameters.rb +0 -1
  138. data/lib/reek/smell_detectors/unused_private_method.rb +0 -1
  139. data/lib/reek/smell_detectors/utility_function.rb +1 -2
  140. data/lib/reek/smell_detectors.rb +1 -2
  141. data/lib/reek/smell_warning.rb +15 -8
  142. data/lib/reek/source/source_code.rb +40 -55
  143. data/lib/reek/source/source_locator.rb +7 -7
  144. data/lib/reek/spec/should_reek.rb +2 -2
  145. data/lib/reek/spec/should_reek_of.rb +9 -16
  146. data/lib/reek/spec/should_reek_only_of.rb +4 -4
  147. data/lib/reek/spec.rb +6 -6
  148. data/lib/reek/tree_dresser.rb +5 -5
  149. data/lib/reek/version.rb +1 -1
  150. data/reek.gemspec +4 -4
  151. data/samples/checkstyle.xml +1 -1
  152. data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
  153. data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
  154. data/samples/configuration/full_configuration.reek +8 -4
  155. data/samples/configuration/full_mask.reek +5 -4
  156. data/samples/configuration/partial_mask.reek +3 -2
  157. data/samples/configuration/regular_configuration/.reek.yml +4 -0
  158. data/samples/paths.rb +5 -4
  159. data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
  160. data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
  161. data/spec/reek/ast/node_spec.rb +5 -5
  162. data/spec/reek/cli/application_spec.rb +18 -4
  163. data/spec/reek/cli/command/todo_list_command_spec.rb +4 -2
  164. data/spec/reek/cli/silencer_spec.rb +28 -0
  165. data/spec/reek/code_comment_spec.rb +0 -7
  166. data/spec/reek/configuration/app_configuration_spec.rb +44 -31
  167. data/spec/reek/configuration/configuration_file_finder_spec.rb +141 -49
  168. data/spec/reek/configuration/default_directive_spec.rb +1 -1
  169. data/spec/reek/configuration/directory_directives_spec.rb +3 -4
  170. data/spec/reek/configuration/excluded_paths_spec.rb +5 -5
  171. data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
  172. data/spec/reek/configuration/schema_validator_spec.rb +165 -0
  173. data/spec/reek/context/code_context_spec.rb +1 -1
  174. data/spec/reek/documentation_link_spec.rb +20 -0
  175. data/spec/reek/examiner_spec.rb +28 -1
  176. data/spec/reek/report/json_report_spec.rb +13 -46
  177. data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
  178. data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
  179. data/spec/reek/report/text_report_spec.rb +4 -4
  180. data/spec/reek/report/xml_report_spec.rb +1 -1
  181. data/spec/reek/report/yaml_report_spec.rb +9 -38
  182. data/spec/reek/report_spec.rb +3 -3
  183. data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
  184. data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +9 -9
  185. data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
  186. data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
  187. data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
  188. data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
  189. data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +6 -4
  190. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +6 -6
  191. data/spec/reek/smell_detectors/unused_private_method_spec.rb +1 -1
  192. data/spec/reek/smell_warning_spec.rb +4 -0
  193. data/spec/reek/source/source_code_spec.rb +16 -22
  194. data/spec/reek/source/source_locator_spec.rb +11 -11
  195. data/spec/reek/spec/should_reek_of_spec.rb +0 -4
  196. data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
  197. data/spec/reek/spec/should_reek_spec.rb +1 -1
  198. data/spec/reek/tree_dresser_spec.rb +2 -6
  199. data/spec/spec_helper.rb +3 -5
  200. data/tasks/configuration.rake +8 -5
  201. metadata +64 -36
  202. data/.codeclimate.yml +0 -21
  203. data/defaults.reek +0 -131
  204. data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
  205. data/lib/reek/report/formatter/heading_formatter.rb +0 -52
  206. data/lib/reek/report/formatter/location_formatter.rb +0 -42
  207. data/lib/reek/report/formatter/progress_formatter.rb +0 -81
  208. data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
  209. data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -36
  210. data/lib/reek/report/formatter.rb +0 -33
  211. data/lib/reek/smell_detectors/syntax.rb +0 -37
  212. data/samples/configuration/non_public_modifiers_mask.reek +0 -3
  213. data/samples/smelly_with_inline_mask.rb +0 -8
  214. data/samples/smelly_with_modifiers.rb +0 -12
  215. data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
  216. data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
  217. data/spec/reek/smell_detectors/syntax_spec.rb +0 -17
  218. /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
  219. /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
  220. /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
  221. /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
  222. /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
  223. /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
  224. /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
  225. /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
  226. /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
  227. /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
@@ -7,7 +7,7 @@ module Reek
7
7
  #
8
8
  # A context wrapper for any method definition found in a syntax tree.
9
9
  #
10
- # :reek:Attribute
10
+ # @quality :reek:Attribute
11
11
  class MethodContext < CodeContext
12
12
  attr_accessor :visibility
13
13
  attr_reader :refs
@@ -35,7 +35,7 @@ module Reek
35
35
  local_nodes([:lvar, :lvasgn]).find { |node| node.var_name == param.name }
36
36
  end
37
37
 
38
- # :reek:FeatureEnvy
38
+ # @quality :reek:FeatureEnvy
39
39
  def unused_params
40
40
  exp.arguments.reject do |param|
41
41
  param.anonymous_splat? ||
@@ -73,7 +73,7 @@ module Reek
73
73
  #
74
74
  # @return true if the module is a namespace module
75
75
  #
76
- # :reek:FeatureEnvy
76
+ # @quality :reek:FeatureEnvy
77
77
  def namespace_module?
78
78
  return false if exp.type == :casgn
79
79
  children = exp.direct_children
@@ -20,9 +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_/ ] }
25
- # :reek:DataClump
23
+ # @quality :reek:TooManyMethods { max_methods: 31 }
24
+ # @quality :reek:UnusedPrivateMethod { exclude: [ !ruby/regexp /process_/ ] }
25
+ # @quality :reek:DataClump
26
26
  class ContextBuilder
27
27
  attr_reader :context_tree
28
28
 
@@ -470,7 +470,7 @@ module Reek
470
470
  self.class.private_method_defined?(name)
471
471
  end
472
472
 
473
- # :reek:ControlParameter
473
+ # @quality :reek:ControlParameter
474
474
  def increase_statement_count_by(sexp)
475
475
  current_context.statement_counter.increase_by sexp
476
476
  end
@@ -482,8 +482,8 @@ module Reek
482
482
  # Stores a reference to the current context, creates a nested new one,
483
483
  # yields to the given block and then restores the previous context.
484
484
  #
485
- # @param klass [Context::*Context] - context class
486
- # @param args - arguments for the class initializer
485
+ # @param klass [Context::*Context] context class
486
+ # @param args arguments for the class initializer
487
487
  # @yield block
488
488
  #
489
489
  def inside_new_context(klass, *args)
@@ -497,10 +497,10 @@ module Reek
497
497
  # Appends a new child context to the current context but does not change
498
498
  # the current context.
499
499
  #
500
- # @param klass [Context::*Context] - context class
501
- # @param args - arguments for the class initializer
500
+ # @param klass [Context::*Context] context class
501
+ # @param args arguments for the class initializer
502
502
  #
503
- # @return [Context::*Context] - the context that was appended
503
+ # @return [Context::*Context] the context that was appended
504
504
  #
505
505
  def append_new_context(klass, *args)
506
506
  klass.new(*args).tap do |new_context|
@@ -44,6 +44,12 @@ module Reek
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
55
  attr_reader :configuration, :smell_types
@@ -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,13 +1,14 @@
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 = <<-MESSAGE.freeze
11
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE
11
12
 
12
13
  Error: You are trying to configure the smell detector '%<detector>s'
13
14
  in one of your source code comments with the unknown option %<option>s.
@@ -17,8 +18,8 @@ module Reek
17
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
 
@@ -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,7 +9,7 @@ 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 = <<-MESSAGE.freeze
12
+ UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-MESSAGE
12
13
 
13
14
  Error: You are trying to configure an unknown smell detector '%<detector>s' in one
14
15
  of your source code comments.
@@ -18,8 +19,8 @@ module Reek
18
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
26
  MESSAGE
@@ -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
@@ -6,9 +6,9 @@ module Reek
6
6
  module Errors
7
7
  # Gets raised when Reek is unable to process the source due to an EncodingError
8
8
  class EncodingError < BaseError
9
- TEMPLATE = "Source '%<source>s' cannot be processed by Reek due to an encoding error in the source file.".freeze
9
+ TEMPLATE = "Source '%<source>s' cannot be processed by Reek due to an encoding error in the source file."
10
10
 
11
- LONG_TEMPLATE = <<-MESSAGE.freeze
11
+ LONG_TEMPLATE = <<-MESSAGE
12
12
  !!!
13
13
  %<message>s
14
14
 
@@ -1,13 +1,14 @@
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 = <<-MESSAGE.freeze
11
+ BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-MESSAGE
11
12
 
12
13
  Error: You are trying to configure the smell detector '%<detector>s'.
13
14
  Unfortunately we cannot parse the configuration you have given.
@@ -17,8 +18,8 @@ module Reek
17
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
25
  MESSAGE
@@ -6,9 +6,9 @@ module Reek
6
6
  module Errors
7
7
  # Gets raised when Reek is unable to process the source
8
8
  class IncomprehensibleSourceError < BaseError
9
- TEMPLATE = 'Source %<source>s cannot be processed by Reek.'.freeze
9
+ TEMPLATE = 'Source %<source>s cannot be processed by Reek.'
10
10
 
11
- LONG_TEMPLATE = <<-MESSAGE.freeze
11
+ LONG_TEMPLATE = <<-MESSAGE
12
12
  !!!
13
13
  %<message>s
14
14
 
@@ -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
@@ -4,6 +4,7 @@ require_relative 'context_builder'
4
4
  require_relative 'detector_repository'
5
5
  require_relative 'errors/incomprehensible_source_error'
6
6
  require_relative 'errors/encoding_error'
7
+ require_relative 'errors/syntax_error'
7
8
  require_relative 'source/source_code'
8
9
 
9
10
  module Reek
@@ -11,6 +12,7 @@ module Reek
11
12
  # Applies all available smell detectors to a source.
12
13
  #
13
14
  # @public
15
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 5 }
14
16
  class Examiner
15
17
  # Handles no errors
16
18
  class NullHandler
@@ -39,26 +41,17 @@ module Reek
39
41
  detector_repository_class: DetectorRepository,
40
42
  error_handler: NullHandler.new)
41
43
  @source = Source::SourceCode.from(source)
44
+ @origin = @source.origin
42
45
  @smell_types = detector_repository_class.eligible_smell_types(filter_by_smells)
43
46
  @detector_repository = detector_repository_class.new(smell_types: @smell_types,
44
- configuration: configuration.directive_for(description))
47
+ configuration: configuration.directive_for(@origin))
45
48
  @error_handler = error_handler
46
49
  end
47
50
 
48
51
  # @return [String] origin of the source being analysed
49
52
  #
50
53
  # @public
51
- def origin
52
- @origin ||= source.origin
53
- end
54
-
55
- # @return [String] description of the source being analysed
56
- #
57
- # @public
58
- # @deprecated Use origin
59
- def description
60
- origin
61
- end
54
+ attr_reader :origin
62
55
 
63
56
  #
64
57
  # @return [Array<SmellWarning>] the smells found in the source
@@ -98,20 +91,22 @@ module Reek
98
91
  #
99
92
  def run
100
93
  wrap_exceptions do
101
- examine_tree || report_syntax_errors
94
+ examine_tree
102
95
  end
103
96
  rescue StandardError => exception
104
97
  raise unless @error_handler.handle exception
105
98
  []
106
99
  end
107
100
 
108
- # :reek:TooManyStatements { max_statements: 6 }
101
+ # @quality :reek:TooManyStatements { max_statements: 6 }
109
102
  def wrap_exceptions
110
103
  yield
111
104
  rescue Errors::BaseError
112
105
  raise
113
106
  rescue EncodingError
114
107
  raise Errors::EncodingError, origin: origin
108
+ rescue Parser::SyntaxError
109
+ raise Errors::SyntaxError, origin: origin
115
110
  rescue StandardError
116
111
  raise Errors::IncomprehensibleSourceError, origin: origin
117
112
  end
@@ -121,14 +116,9 @@ module Reek
121
116
  end
122
117
 
123
118
  def examine_tree
124
- return unless syntax_tree
125
119
  ContextBuilder.new(syntax_tree).context_tree.flat_map do |element|
126
120
  detector_repository.examine(element)
127
121
  end
128
122
  end
129
-
130
- def report_syntax_errors
131
- SmellDetectors::Syntax.smells_from_source(source)
132
- end
133
123
  end
134
124
  end
@@ -33,8 +33,8 @@ module Reek
33
33
  #
34
34
  # @public
35
35
  #
36
- # :reek:TooManyInstanceVariables: { max_instance_variables: 6 }
37
- # :reek:Attribute
36
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 6 }
37
+ # @quality :reek:Attribute
38
38
  class Task < ::Rake::TaskLib
39
39
  # Name of Reek task. Defaults to :reek.
40
40
  # @public
@@ -114,7 +114,7 @@ module Reek
114
114
  reject(&:empty?)
115
115
  end
116
116
 
117
- # :reek:UtilityFunction
117
+ # @quality :reek:UtilityFunction
118
118
  def sys_call_failed?
119
119
  !$CHILD_STATUS.success?
120
120
  end
@@ -4,8 +4,6 @@ require 'json'
4
4
  require 'pathname'
5
5
  require 'rainbow'
6
6
 
7
- require_relative 'formatter'
8
-
9
7
  module Reek
10
8
  # @public
11
9
  module Report
@@ -16,22 +14,20 @@ module Reek
16
14
  #
17
15
  # @public
18
16
  #
19
- # :reek:TooManyInstanceVariables: { max_instance_variables: 7 }
17
+ # @quality :reek:TooManyInstanceVariables { max_instance_variables: 7 }
20
18
  class BaseReport
21
19
  NO_WARNINGS_COLOR = :green
22
20
  WARNINGS_COLOR = :red
23
21
 
24
22
  # @public
25
23
  #
26
- # :reek:BooleanParameter
27
- def initialize(heading_formatter: Formatter::QuietHeadingFormatter,
28
- report_formatter: Formatter,
24
+ # @quality :reek:BooleanParameter
25
+ def initialize(heading_formatter: QuietHeadingFormatter,
29
26
  sort_by_issue_count: false,
30
- warning_formatter: Formatter::SimpleWarningFormatter.new,
31
- progress_formatter: Formatter::ProgressFormatter::Quiet.new(0))
27
+ warning_formatter: SimpleWarningFormatter.new,
28
+ progress_formatter: ProgressFormatter::Quiet.new(0))
32
29
  @examiners = []
33
- @heading_formatter = heading_formatter.new(report_formatter)
34
- @report_formatter = report_formatter
30
+ @heading_formatter = heading_formatter.new
35
31
  @progress_formatter = progress_formatter
36
32
  @sort_by_issue_count = sort_by_issue_count
37
33
  @total_smell_count = 0
@@ -58,7 +54,7 @@ module Reek
58
54
  end
59
55
 
60
56
  def smells?
61
- total_smell_count > 0
57
+ total_smell_count.positive?
62
58
  end
63
59
 
64
60
  def smells
@@ -71,7 +67,7 @@ module Reek
71
67
 
72
68
  private
73
69
 
74
- attr_reader :examiners, :heading_formatter, :report_formatter,
70
+ attr_reader :examiners, :heading_formatter,
75
71
  :sort_by_issue_count, :warning_formatter, :progress_formatter
76
72
  end
77
73
  end
@@ -284,7 +284,7 @@ InstanceVariableAssumption:
284
284
  would report:
285
285
 
286
286
  ```Bash
287
- [1]:InstanceVariableAssumption: Dummy assumes too much for instance variable @ivar [https://github.com/troessner/reek/blob/master/docs/Instance-Variable-Assumption.md]
287
+ [1]:InstanceVariableAssumption: Dummy assumes too much for instance variable @ivar
288
288
  ```
289
289
 
290
290
  Note that this example would trigger this smell warning as well:
@@ -565,10 +565,10 @@ NilCheck:
565
565
  test.rb -- 1 warning:
566
566
  [3]:Klass#nil_checker performs a nil-check. (NilCheck)
567
567
  ```
568
- PrimaDonnaMethod:
568
+ MissingSafeMethod:
569
569
  remediation_points: 250_000
570
570
  content: |
571
- A candidate method for the `Prima Donna Method` smell are methods whose names end with an exclamation mark.
571
+ A candidate method for the `Missing Safe Method` smell are methods whose names end with an exclamation mark.
572
572
 
573
573
  An exclamation mark in method names means (the explanation below is taken from [here](http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist) ):
574
574
 
@@ -576,7 +576,7 @@ PrimaDonnaMethod:
576
576
  The ! in method names that end with ! means, “This method is dangerous”—or, more precisely, this method is the “dangerous” version of an otherwise equivalent method, with the same name minus the !. “Danger” is relative; the ! doesn’t mean anything at all unless the method name it’s in corresponds to a similar but bang-less method name.
577
577
  So, for example, gsub! is the dangerous version of gsub. exit! is the dangerous version of exit. flatten! is the dangerous version of flatten. And so forth.
578
578
 
579
- Such a method is called `Prima Donna Method` if and only if her non-bang version does not exist and this method is reported as a smell.
579
+ Such a method is called `Missing Safe Method` if and only if her non-bang version does not exist and this method is reported as a smell.
580
580
 
581
581
  ## Example
582
582
 
@@ -590,7 +590,7 @@ PrimaDonnaMethod:
590
590
  end
591
591
  ```
592
592
 
593
- Reek would report `bar!` as `prima donna method` smell but not `foo!`.
593
+ Reek would report `bar!` as `Missing Safe Method` smell but not `foo!`.
594
594
 
595
595
  Reek reports this smell only in a class context, not in a module context in order to allow perfectly legit code like this:
596
596
 
@@ -612,7 +612,7 @@ PrimaDonnaMethod:
612
612
  end
613
613
  ```
614
614
 
615
- In this example, Reek would not report the `prima donna method` smell for the method `foo` of the `Dangerous` module.
615
+ In this example, Reek would not report the `Missing Safe Method` smell for the method `foo` of the `Dangerous` module.
616
616
  RepeatedConditional:
617
617
  remediation_points: 400_000
618
618
  content: |
@@ -860,7 +860,3 @@ SubclassedFromCoreClass:
860
860
  you are gonna have a bad time.
861
861
 
862
862
  Source: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
863
- Syntax:
864
- remediation_points: 250_000
865
- content: |
866
- Check syntax errors.
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'simple_warning_formatter'
4
+
5
+ module Reek
6
+ module Report
7
+ #
8
+ # Formatter that adds a link to the docs to the basic message from
9
+ # SimpleWarningFormatter.
10
+ #
11
+ class DocumentationLinkWarningFormatter < SimpleWarningFormatter
12
+ def format(warning)
13
+ "#{super} [#{warning.explanatory_link}]"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reek
4
+ module Report
5
+ #
6
+ # Base class for heading formatters.
7
+ # Is responsible for formatting the heading emitted for each examiner
8
+ #
9
+ # @abstract Override {#show_header?} to implement a heading formatter.
10
+ class HeadingFormatterBase
11
+ # @quality :reek:UtilityFunction
12
+ def show_header?(_examiner)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def header(examiner)
17
+ if show_header?(examiner)
18
+ formatted_header examiner
19
+ else
20
+ ''
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def formatted_header(examiner)
27
+ count = examiner.smells_count
28
+ result = Rainbow("#{examiner.origin} -- ").cyan +
29
+ Rainbow("#{count} warning").yellow
30
+ result += Rainbow('s').yellow unless count == 1
31
+ result
32
+ end
33
+ end
34
+
35
+ #
36
+ # Lists out each examiner, even if it has no smell
37
+ #
38
+ class VerboseHeadingFormatter < HeadingFormatterBase
39
+ def show_header?(_examiner)
40
+ true
41
+ end
42
+ end
43
+
44
+ #
45
+ # Lists only smelly examiners
46
+ #
47
+ class QuietHeadingFormatter < HeadingFormatterBase
48
+ # @quality :reek:UtilityFunction
49
+ def show_header?(examiner)
50
+ examiner.smelly?
51
+ end
52
+ end
53
+ end
54
+ end
@@ -12,7 +12,7 @@ module Reek
12
12
  #
13
13
  class JSONReport < BaseReport
14
14
  def show(out = $stdout)
15
- out.print ::JSON.generate smells.map { |smell| warning_formatter.format_hash(smell) }
15
+ out.print ::JSON.generate smells.map(&:yaml_hash)
16
16
  end
17
17
  end
18
18
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reek
4
+ module Report
5
+ #
6
+ # Formats the location of a warning as an empty string.
7
+ #
8
+ module BlankLocationFormatter
9
+ module_function
10
+
11
+ def format(_warning)
12
+ ''
13
+ end
14
+ end
15
+
16
+ #
17
+ # Formats the location of a warning as an array of line numbers.
18
+ #
19
+ module DefaultLocationFormatter
20
+ module_function
21
+
22
+ def format(warning)
23
+ "#{warning.lines.sort.inspect}:"
24
+ end
25
+ end
26
+
27
+ #
28
+ # Formats the location of a warning as a combination of source file name
29
+ # and line number. In this format, it is not possible to show more than
30
+ # one line number, so the first number is displayed.
31
+ #
32
+ module SingleLineLocationFormatter
33
+ module_function
34
+
35
+ def format(warning)
36
+ "#{warning.source}:#{warning.lines.min}: "
37
+ end
38
+ end
39
+ end
40
+ end