reek 2.0.4 → 3.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 (275) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +47 -0
  4. data/.travis.yml +31 -0
  5. data/.yardopts +3 -6
  6. data/CHANGELOG +24 -0
  7. data/CONTRIBUTING.md +111 -0
  8. data/Gemfile +16 -0
  9. data/README.md +112 -59
  10. data/bin/reek +2 -2
  11. data/config/cucumber.yml +3 -0
  12. data/config/defaults.reek +0 -1
  13. data/docs/API.md +79 -0
  14. data/docs/Attribute.md +43 -0
  15. data/docs/Basic-Smell-Options.md +44 -0
  16. data/docs/Boolean-Parameter.md +52 -0
  17. data/docs/Class-Variable.md +40 -0
  18. data/docs/Code-Smells.md +34 -0
  19. data/docs/Command-Line-Options.md +84 -0
  20. data/docs/Configuration-Files.md +49 -0
  21. data/docs/Control-Couple.md +22 -0
  22. data/docs/Control-Parameter.md +29 -0
  23. data/docs/Data-Clump.md +44 -0
  24. data/docs/Duplicate-Method-Call.md +49 -0
  25. data/docs/Feature-Envy.md +49 -0
  26. data/docs/How-reek-works-internally.md +114 -0
  27. data/docs/Irresponsible-Module.md +39 -0
  28. data/docs/Large-Class.md +20 -0
  29. data/docs/Long-Parameter-List.md +38 -0
  30. data/docs/Long-Yield-List.md +36 -0
  31. data/docs/Module-Initialize.md +62 -0
  32. data/docs/Nested-Iterators.md +38 -0
  33. data/docs/Nil-Check.md +39 -0
  34. data/docs/Prima-Donna-Method.md +53 -0
  35. data/docs/RSpec-matchers.md +137 -0
  36. data/docs/Rake-Task.md +58 -0
  37. data/docs/Reek-Driven-Development.md +37 -0
  38. data/docs/Repeated-Conditional.md +44 -0
  39. data/docs/Simulated-Polymorphism.md +16 -0
  40. data/docs/Smell-Suppression.md +32 -0
  41. data/docs/Too-Many-Instance-Variables.md +43 -0
  42. data/docs/Too-Many-Methods.md +55 -0
  43. data/docs/Too-Many-Statements.md +50 -0
  44. data/docs/Uncommunicative-Method-Name.md +24 -0
  45. data/docs/Uncommunicative-Module-Name.md +23 -0
  46. data/docs/Uncommunicative-Name.md +16 -0
  47. data/docs/Uncommunicative-Parameter-Name.md +24 -0
  48. data/docs/Uncommunicative-Variable-Name.md +24 -0
  49. data/docs/Unused-Parameters.md +27 -0
  50. data/docs/Utility-Function.md +44 -0
  51. data/docs/Versioning-Policy.md +7 -0
  52. data/docs/YAML-Reports.md +111 -0
  53. data/docs/yard_plugin.rb +14 -0
  54. data/features/{ruby_api/api.feature → command_line_interface/basic_usage.feature} +6 -5
  55. data/features/command_line_interface/options.feature +1 -0
  56. data/features/command_line_interface/smell_selection.feature +4 -8
  57. data/features/command_line_interface/smells_count.feature +25 -32
  58. data/features/command_line_interface/stdin.feature +2 -2
  59. data/features/configuration_files/masking_smells.feature +30 -41
  60. data/features/configuration_files/overrides_defaults.feature +5 -3
  61. data/features/configuration_loading.feature +26 -23
  62. data/features/programmatic_access.feature +43 -0
  63. data/features/rake_task/rake_task.feature +25 -22
  64. data/features/reports/json.feature +24 -26
  65. data/features/reports/reports.feature +77 -103
  66. data/features/reports/yaml.feature +26 -20
  67. data/features/samples.feature +26 -31
  68. data/features/step_definitions/.rubocop.yml +5 -0
  69. data/features/step_definitions/reek_steps.rb +16 -28
  70. data/features/step_definitions/sample_file_steps.rb +158 -0
  71. data/features/support/env.rb +16 -27
  72. data/lib/reek/{source → ast}/ast_node_class_map.rb +11 -12
  73. data/lib/reek/ast/node.rb +132 -0
  74. data/lib/reek/{core → ast}/object_refs.rb +2 -1
  75. data/lib/reek/ast/reference_collector.rb +32 -0
  76. data/lib/reek/{source → ast}/sexp_extensions.rb +43 -7
  77. data/lib/reek/ast/sexp_formatter.rb +31 -0
  78. data/lib/reek/cli/application.rb +5 -4
  79. data/lib/reek/cli/command.rb +2 -1
  80. data/lib/reek/cli/input.rb +6 -5
  81. data/lib/reek/cli/option_interpreter.rb +13 -9
  82. data/lib/reek/cli/options.rb +16 -8
  83. data/lib/reek/cli/reek_command.rb +4 -3
  84. data/lib/reek/cli/silencer.rb +14 -0
  85. data/lib/reek/{core → cli}/warning_collector.rb +2 -1
  86. data/lib/reek/code_comment.rb +36 -0
  87. data/lib/reek/configuration/app_configuration.rb +18 -3
  88. data/lib/reek/configuration/configuration_file_finder.rb +18 -32
  89. data/lib/reek/{core → context}/code_context.rb +8 -6
  90. data/lib/reek/{core → context}/method_context.rb +11 -5
  91. data/lib/reek/{core → context}/module_context.rb +9 -4
  92. data/lib/reek/{core/stop_context.rb → context/root_context.rb} +4 -2
  93. data/lib/reek/{core → context}/singleton_method_context.rb +3 -6
  94. data/lib/reek/examiner.rb +32 -29
  95. data/lib/reek/report/formatter.rb +70 -0
  96. data/lib/reek/report/heading_formatter.rb +45 -0
  97. data/lib/reek/report/location_formatter.rb +35 -0
  98. data/lib/reek/report/report.rb +198 -0
  99. data/lib/reek/smells/attribute.rb +41 -11
  100. data/lib/reek/smells/boolean_parameter.rb +4 -2
  101. data/lib/reek/smells/class_variable.rb +4 -2
  102. data/lib/reek/smells/control_parameter.rb +4 -2
  103. data/lib/reek/smells/data_clump.rb +4 -3
  104. data/lib/reek/smells/duplicate_method_call.rb +15 -5
  105. data/lib/reek/smells/feature_envy.rb +8 -2
  106. data/lib/reek/smells/irresponsible_module.rb +13 -8
  107. data/lib/reek/smells/long_parameter_list.rb +6 -4
  108. data/lib/reek/smells/long_yield_list.rb +4 -2
  109. data/lib/reek/smells/module_initialize.rb +4 -2
  110. data/lib/reek/smells/nested_iterators.rb +4 -2
  111. data/lib/reek/smells/nil_check.rb +6 -3
  112. data/lib/reek/smells/prima_donna_method.rb +21 -12
  113. data/lib/reek/smells/repeated_conditional.rb +7 -4
  114. data/lib/reek/{core → smells}/smell_configuration.rb +3 -1
  115. data/lib/reek/smells/smell_detector.rb +11 -5
  116. data/lib/reek/{core → smells}/smell_repository.rb +9 -5
  117. data/lib/reek/smells/smell_warning.rb +101 -0
  118. data/lib/reek/smells/too_many_instance_variables.rb +4 -2
  119. data/lib/reek/smells/too_many_methods.rb +4 -2
  120. data/lib/reek/smells/too_many_statements.rb +4 -2
  121. data/lib/reek/smells/uncommunicative_method_name.rb +4 -2
  122. data/lib/reek/smells/uncommunicative_module_name.rb +4 -2
  123. data/lib/reek/smells/uncommunicative_parameter_name.rb +4 -2
  124. data/lib/reek/smells/uncommunicative_variable_name.rb +4 -2
  125. data/lib/reek/smells/unused_parameters.rb +4 -2
  126. data/lib/reek/smells/utility_function.rb +11 -22
  127. data/lib/reek/smells.rb +24 -11
  128. data/lib/reek/source/source_code.rb +69 -12
  129. data/lib/reek/source/source_locator.rb +34 -19
  130. data/lib/reek/spec/should_reek.rb +4 -3
  131. data/lib/reek/spec/should_reek_of.rb +2 -1
  132. data/lib/reek/spec/should_reek_only_of.rb +4 -3
  133. data/lib/reek/spec.rb +3 -3
  134. data/lib/reek/tree_dresser.rb +32 -0
  135. data/lib/reek/tree_walker.rb +182 -0
  136. data/lib/reek/version.rb +1 -1
  137. data/lib/reek.rb +3 -3
  138. data/reek.gemspec +18 -25
  139. data/spec/factories/factories.rb +4 -2
  140. data/spec/gem/updates_spec.rb +1 -1
  141. data/spec/gem/yard_spec.rb +2 -2
  142. data/spec/quality/reek_source_spec.rb +3 -3
  143. data/spec/reek/{source/sexp_node_spec.rb → ast/node_spec.rb} +4 -6
  144. data/spec/reek/{core → ast}/object_refs_spec.rb +4 -6
  145. data/spec/reek/{source → ast}/reference_collector_spec.rb +27 -19
  146. data/spec/reek/{source → ast}/sexp_extensions_spec.rb +7 -19
  147. data/spec/reek/{source → ast}/sexp_formatter_spec.rb +13 -8
  148. data/spec/reek/cli/option_interpreter_spec.rb +15 -0
  149. data/spec/reek/cli/options_spec.rb +19 -0
  150. data/spec/reek/cli/warning_collector_spec.rb +28 -0
  151. data/spec/reek/{source/code_comment_spec.rb → code_comment_spec.rb} +19 -17
  152. data/spec/reek/configuration/app_configuration_spec.rb +37 -26
  153. data/spec/reek/configuration/configuration_file_finder_spec.rb +67 -21
  154. data/spec/reek/{core → context}/code_context_spec.rb +18 -21
  155. data/spec/reek/{core → context}/method_context_spec.rb +33 -27
  156. data/spec/reek/{core → context}/module_context_spec.rb +5 -7
  157. data/spec/reek/context/root_context_spec.rb +14 -0
  158. data/spec/reek/context/singleton_method_context_spec.rb +17 -0
  159. data/spec/reek/examiner_spec.rb +9 -50
  160. data/spec/reek/{cli → report}/html_report_spec.rb +6 -9
  161. data/spec/reek/report/json_report_spec.rb +20 -0
  162. data/spec/reek/{cli → report}/text_report_spec.rb +16 -19
  163. data/spec/reek/report/xml_report_spec.rb +34 -0
  164. data/spec/reek/report/yaml_report_spec.rb +20 -0
  165. data/spec/reek/smells/attribute_spec.rb +93 -88
  166. data/spec/reek/smells/behaves_like_variable_detector.rb +1 -1
  167. data/spec/reek/smells/boolean_parameter_spec.rb +31 -5
  168. data/spec/reek/smells/class_variable_spec.rb +10 -10
  169. data/spec/reek/smells/control_parameter_spec.rb +5 -5
  170. data/spec/reek/smells/data_clump_spec.rb +6 -6
  171. data/spec/reek/smells/duplicate_method_call_spec.rb +7 -7
  172. data/spec/reek/smells/feature_envy_spec.rb +12 -13
  173. data/spec/reek/smells/irresponsible_module_spec.rb +28 -32
  174. data/spec/reek/smells/long_parameter_list_spec.rb +7 -7
  175. data/spec/reek/smells/long_yield_list_spec.rb +6 -6
  176. data/spec/reek/smells/module_initialize_spec.rb +4 -4
  177. data/spec/reek/smells/nested_iterators_spec.rb +6 -6
  178. data/spec/reek/smells/nil_check_spec.rb +6 -6
  179. data/spec/reek/smells/prima_donna_method_spec.rb +8 -5
  180. data/spec/reek/smells/repeated_conditional_spec.rb +18 -17
  181. data/spec/reek/{core → smells}/smell_configuration_spec.rb +5 -7
  182. data/spec/reek/smells/smell_detector_shared.rb +6 -7
  183. data/spec/reek/smells/smell_repository_spec.rb +29 -0
  184. data/spec/reek/{smell_warning_spec.rb → smells/smell_warning_spec.rb} +7 -9
  185. data/spec/reek/smells/too_many_instance_variables_spec.rb +5 -5
  186. data/spec/reek/smells/too_many_methods_spec.rb +12 -8
  187. data/spec/reek/smells/too_many_statements_spec.rb +10 -9
  188. data/spec/reek/smells/uncommunicative_method_name_spec.rb +5 -5
  189. data/spec/reek/smells/uncommunicative_module_name_spec.rb +8 -8
  190. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +18 -6
  191. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +10 -7
  192. data/spec/reek/smells/unused_parameters_spec.rb +4 -4
  193. data/spec/reek/smells/utility_function_spec.rb +65 -39
  194. data/spec/reek/source/source_code_spec.rb +10 -10
  195. data/spec/reek/source/source_locator_spec.rb +30 -0
  196. data/spec/reek/spec/should_reek_of_spec.rb +9 -25
  197. data/spec/reek/spec/should_reek_only_of_spec.rb +4 -7
  198. data/spec/reek/spec/should_reek_spec.rb +4 -32
  199. data/spec/reek/tree_dresser_spec.rb +16 -0
  200. data/spec/reek/{core/tree_walker_spec.rb → tree_walker_spec.rb} +5 -7
  201. data/spec/samples/checkstyle.xml +2 -0
  202. data/spec/samples/configuration/with_excluded_paths.reek +4 -0
  203. data/spec/samples/{overrides/masked/lower.reek → masked_by_dotfile/.reek} +3 -1
  204. data/spec/samples/source_with_exclude_paths/ignore_me/uncommunicative_method_name.rb +5 -0
  205. data/spec/samples/source_with_exclude_paths/nested/ignore_me_as_well/irresponsible_module.rb +2 -0
  206. data/spec/samples/source_with_exclude_paths/nested/uncommunicative_parameter_name.rb +6 -0
  207. data/spec/samples/{ruby20_syntax.rb → unusual_syntax.rb} +8 -0
  208. data/spec/spec_helper.rb +21 -10
  209. data/tasks/develop.rake +2 -2
  210. data/tasks/reek.rake +1 -1
  211. metadata +158 -141
  212. data/features/support/hooks.rb +0 -15
  213. data/lib/reek/cli/report/formatter.rb +0 -69
  214. data/lib/reek/cli/report/heading_formatter.rb +0 -45
  215. data/lib/reek/cli/report/location_formatter.rb +0 -34
  216. data/lib/reek/cli/report/report.rb +0 -131
  217. data/lib/reek/core/tree_walker.rb +0 -180
  218. data/lib/reek/smell_warning.rb +0 -87
  219. data/lib/reek/source/ast_node.rb +0 -38
  220. data/lib/reek/source/code_comment.rb +0 -37
  221. data/lib/reek/source/core_extras.rb +0 -46
  222. data/lib/reek/source/reference_collector.rb +0 -27
  223. data/lib/reek/source/sexp_formatter.rb +0 -22
  224. data/lib/reek/source/sexp_node.rb +0 -79
  225. data/lib/reek/source/source_file.rb +0 -16
  226. data/lib/reek/source/source_repository.rb +0 -45
  227. data/lib/reek/source/tree_dresser.rb +0 -24
  228. data/lib/reek/source.rb +0 -16
  229. data/spec/reek/cli/json_report_spec.rb +0 -20
  230. data/spec/reek/cli/option_interperter_spec.rb +0 -14
  231. data/spec/reek/cli/yaml_report_spec.rb +0 -23
  232. data/spec/reek/core/singleton_method_context_spec.rb +0 -9
  233. data/spec/reek/core/smell_repository_spec.rb +0 -17
  234. data/spec/reek/core/stop_context_spec.rb +0 -17
  235. data/spec/reek/core/warning_collector_spec.rb +0 -30
  236. data/spec/reek/source/object_source_spec.rb +0 -20
  237. data/spec/reek/source/tree_dresser_spec.rb +0 -18
  238. data/spec/samples/config/allow_duplication.reek +0 -3
  239. data/spec/samples/config/deeper_nested_iterators.reek +0 -3
  240. data/spec/samples/configuration_loading/minimal_dirty.rb +0 -4
  241. data/spec/samples/configuration_loading/reek-test-run-disable_smells.reek +0 -7
  242. data/spec/samples/configuration_loading/reek-test-run-enable_smells.reek +0 -7
  243. data/spec/samples/corrupt_config_file/corrupt.reek +0 -1
  244. data/spec/samples/corrupt_config_file/dirty.rb +0 -8
  245. data/spec/samples/demo/demo.rb +0 -8
  246. data/spec/samples/empty_config_file/dirty.rb +0 -8
  247. data/spec/samples/empty_config_file/empty.reek +0 -0
  248. data/spec/samples/inline_config/dirty.rb +0 -16
  249. data/spec/samples/inline_config/masked.reek +0 -7
  250. data/spec/samples/mask_some/dirty.rb +0 -8
  251. data/spec/samples/mask_some/some.reek +0 -8
  252. data/spec/samples/masked/dirty.rb +0 -8
  253. data/spec/samples/masked/masked.reek +0 -5
  254. data/spec/samples/minimal_smelly_and_masked/config.reek +0 -7
  255. data/spec/samples/minimal_smelly_and_masked/minimal_dirty.rb +0 -4
  256. data/spec/samples/mixed_results/clean_one.rb +0 -7
  257. data/spec/samples/mixed_results/clean_three.rb +0 -7
  258. data/spec/samples/mixed_results/clean_two.rb +0 -7
  259. data/spec/samples/mixed_results/dirty_one.rb +0 -8
  260. data/spec/samples/mixed_results/dirty_two.rb +0 -8
  261. data/spec/samples/not_quite_masked/dirty.rb +0 -8
  262. data/spec/samples/not_quite_masked/masked.reek +0 -5
  263. data/spec/samples/not_quite_masked/smelly.rb +0 -3
  264. data/spec/samples/one_smelly_file/dirty.rb +0 -3
  265. data/spec/samples/overrides/masked/dirty.rb +0 -8
  266. data/spec/samples/overrides/upper.reek +0 -5
  267. data/spec/samples/overrides_defaults/camel_case.rb +0 -14
  268. data/spec/samples/overrides_defaults/config.reek +0 -6
  269. data/spec/samples/ruby21_syntax.rb +0 -5
  270. data/spec/samples/standard_smelly/dirty.rb +0 -8
  271. data/spec/samples/standard_smelly/minimal_dirty.rb +0 -4
  272. data/spec/samples/three_smelly_files/dirty_one.rb +0 -3
  273. data/spec/samples/three_smelly_files/dirty_three.rb +0 -5
  274. data/spec/samples/three_smelly_files/dirty_two.rb +0 -4
  275. /data/spec/samples/{simple_configuration.reek → configuration/simple_configuration.reek} +0 -0
@@ -1,5 +1,6 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative '../ast/node'
2
+ require_relative 'smell_detector'
3
+ require_relative 'smell_warning'
3
4
 
4
5
  module Reek
5
6
  module Smells
@@ -22,6 +23,8 @@ module Reek
22
23
  # +RepeatedConditional+ checks for multiple conditionals
23
24
  # testing the same value throughout a single class.
24
25
  #
26
+ # See {file:docs/Repeated-Conditional.md} for details.
27
+ # @api private
25
28
  class RepeatedConditional < SmellDetector
26
29
  # The name of the config field that sets the maximum number of
27
30
  # identical conditionals permitted within any single class.
@@ -32,7 +35,7 @@ module Reek
32
35
  'SimulatedPolymorphism'
33
36
  end
34
37
 
35
- BLOCK_GIVEN_CONDITION = AST::Node.new(:send, [nil, :block_given?])
38
+ BLOCK_GIVEN_CONDITION = ::Parser::AST::Node.new(:send, [nil, :block_given?])
36
39
 
37
40
  def self.contexts # :nodoc:
38
41
  [:class]
@@ -53,7 +56,7 @@ module Reek
53
56
  lines.length > @max_identical_ifs
54
57
  end.map do |key, lines|
55
58
  occurs = lines.length
56
- expression = key.format_ruby
59
+ expression = key.format_to_ruby
57
60
  SmellWarning.new self,
58
61
  context: ctx.full_name,
59
62
  lines: lines,
@@ -1,8 +1,9 @@
1
1
  module Reek
2
- module Core
2
+ module Smells
3
3
  #
4
4
  # Represents a single set of configuration options for a smell detector
5
5
  #
6
+ # @api private
6
7
  class SmellConfiguration
7
8
  # The name of the config field that specifies whether a smell is
8
9
  # enabled. Set to +true+ or +false+.
@@ -45,6 +46,7 @@ module Reek
45
46
  #
46
47
  # A set of context-specific overrides for smell detectors.
47
48
  #
49
+ # @api private
48
50
  class Overrides
49
51
  def initialize(hash)
50
52
  @hash = hash
@@ -1,12 +1,18 @@
1
1
  require 'set'
2
- require 'reek/smell_warning'
3
- require 'reek/core/smell_configuration'
2
+ require_relative 'smell_configuration'
4
3
 
5
4
  module Reek
6
5
  module Smells
7
6
  #
8
7
  # Shared responsibilities of all smell detectors.
9
8
  #
9
+ # See
10
+ # - {file:docs/Basic-Smell-Options.md}
11
+ # - {file:docs/Code-Smells.md}
12
+ # - {file:docs/Configuration-Files.md}
13
+ # for details.
14
+ #
15
+ # @api private
10
16
  class SmellDetector
11
17
  attr_reader :source
12
18
 
@@ -26,7 +32,7 @@ module Reek
26
32
 
27
33
  def default_config
28
34
  {
29
- Core::SmellConfiguration::ENABLED_KEY => true,
35
+ SmellConfiguration::ENABLED_KEY => true,
30
36
  EXCLUDE_KEY => DEFAULT_EXCLUDE_SET.dup
31
37
  }
32
38
  end
@@ -67,7 +73,7 @@ module Reek
67
73
 
68
74
  def initialize(source, config = self.class.default_config)
69
75
  @source = source
70
- @config = Core::SmellConfiguration.new(config)
76
+ @config = SmellConfiguration.new(config)
71
77
  @smells_found = []
72
78
  end
73
79
 
@@ -94,7 +100,7 @@ module Reek
94
100
  end
95
101
 
96
102
  def enabled_for?(context)
97
- enabled? && config_for(context)[Core::SmellConfiguration::ENABLED_KEY] != false
103
+ enabled? && config_for(context)[SmellConfiguration::ENABLED_KEY] != false
98
104
  end
99
105
 
100
106
  def exception?(context)
@@ -1,16 +1,18 @@
1
- require 'reek/smells'
2
- require 'reek/smells/smell_detector'
1
+ require_relative '../smells'
2
+ require_relative 'smell_detector'
3
+ require_relative '../configuration/app_configuration'
3
4
 
4
5
  module Reek
5
- module Core
6
+ module Smells
6
7
  #
7
8
  # Contains all the existing smells and exposes operations on them.
8
9
  #
10
+ # @api private
9
11
  class SmellRepository
10
12
  attr_reader :detectors
11
13
 
12
14
  def self.smell_types
13
- Reek::Smells::SmellDetector.descendants
15
+ Reek::Smells::SmellDetector.descendants.sort_by(&:name)
14
16
  end
15
17
 
16
18
  def initialize(source_description = nil, smell_types = self.class.smell_types)
@@ -23,7 +25,9 @@ module Reek
23
25
  end
24
26
 
25
27
  def configure(klass, config)
26
- @detectors[klass].configure_with(config) if @detectors[klass]
28
+ detector = @detectors[klass]
29
+ raise ArgumentError, "Unknown smell type #{klass} found in configuration" unless detector
30
+ detector.configure_with(config)
27
31
  end
28
32
 
29
33
  def report_on(listener)
@@ -0,0 +1,101 @@
1
+ require 'forwardable'
2
+
3
+ module Reek
4
+ module Smells
5
+ #
6
+ # Reports a warning that a smell has been found.
7
+ #
8
+ class SmellWarning
9
+ include Comparable
10
+ extend Forwardable
11
+ attr_accessor :smell_detector, :context, :lines, :message, :parameters
12
+ def_delegators :smell_detector, :smell_category, :smell_type, :source
13
+
14
+ def initialize(smell_detector, options = {})
15
+ self.smell_detector = smell_detector
16
+ self.context = options.fetch(:context, '').to_s
17
+ self.lines = options.fetch(:lines)
18
+ self.message = options.fetch(:message)
19
+ self.parameters = options.fetch(:parameters, {})
20
+ end
21
+
22
+ def hash
23
+ sort_key.hash
24
+ end
25
+
26
+ def <=>(other)
27
+ sort_key <=> other.sort_key
28
+ end
29
+
30
+ def eql?(other)
31
+ (self <=> other) == 0
32
+ end
33
+
34
+ # @api private
35
+ def matches?(klass, other_parameters = {})
36
+ smell_classes.include?(klass.to_s) && common_parameters_equal?(other_parameters)
37
+ end
38
+
39
+ # @api private
40
+ def report_on(listener)
41
+ listener.found_smell(self)
42
+ end
43
+
44
+ def yaml_hash(warning_formatter = nil)
45
+ stringified_params = Hash[parameters.map { |key, val| [key.to_s, val] }]
46
+ core_yaml_hash.
47
+ merge(stringified_params).
48
+ merge(wiki_link_hash(warning_formatter))
49
+ end
50
+
51
+ protected
52
+
53
+ def sort_key
54
+ [context, message, smell_category]
55
+ end
56
+
57
+ private
58
+
59
+ def smell_classes
60
+ [smell_detector.smell_category, smell_detector.smell_type]
61
+ end
62
+
63
+ def common_parameters_equal?(other_parameters)
64
+ other_parameters.keys.each do |key|
65
+ unless parameters.key?(key)
66
+ raise ArgumentError, "The parameter #{key} you want to check for doesn't exist"
67
+ end
68
+ end
69
+
70
+ # Why not check for strict parameter equality instead of just the common ones?
71
+ #
72
+ # In `self`, `parameters` might look like this: {:name=>"@other.thing", :count=>2}
73
+ # Coming from specs, 'other_parameters' might look like this, e.g.:
74
+ # {:name=>"@other.thing"}
75
+ # So in this spec we are just specifying the "name" parameter but not the "count".
76
+ # In order to allow for this kind of leniency we just test for common parameter equality,
77
+ # not for a strict one.
78
+ parameters.values_at(*other_parameters.keys) == other_parameters.values
79
+ end
80
+
81
+ def core_yaml_hash
82
+ {
83
+ 'context' => context,
84
+ 'lines' => lines,
85
+ 'message' => message,
86
+ 'smell_category' => smell_detector.smell_category,
87
+ 'smell_type' => smell_detector.smell_type,
88
+ 'source' => smell_detector.source
89
+ }
90
+ end
91
+
92
+ def wiki_link_hash(warning_formatter)
93
+ if warning_formatter.respond_to?(:explanatory_link)
94
+ { 'wiki_link' => warning_formatter.explanatory_link(smell_detector) }
95
+ else
96
+ {}
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,5 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -10,6 +10,8 @@ module Reek
10
10
  # +TooManyInstanceVariables' reports classes having more than a
11
11
  # configurable number of instance variables.
12
12
  #
13
+ # See {file:docs/Too-Many-Instance-Variables.md} for details.
14
+ # @api private
13
15
  class TooManyInstanceVariables < SmellDetector
14
16
  # The name of the config field that sets the maximum number of instance
15
17
  # variables permitted in a class.
@@ -1,5 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -12,6 +12,8 @@ module Reek
12
12
  # methods, and excludes methods inherited from superclasses or included
13
13
  # modules.
14
14
  #
15
+ # See {file:docs/Too-Many-Methods.md} for details.
16
+ # @api private
15
17
  class TooManyMethods < SmellDetector
16
18
  # The name of the config field that sets the maximum number of methods
17
19
  # permitted in a class.
@@ -1,5 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -8,6 +8,8 @@ module Reek
8
8
  #
9
9
  # +TooManyStatements+ reports any method with more than 5 statements.
10
10
  #
11
+ # See {file:docs/Too-Many-Statements.md} for details.
12
+ # @api private
11
13
  class TooManyStatements < SmellDetector
12
14
  # The name of the config field that sets the maximum number of
13
15
  # statements permitted in any method.
@@ -1,5 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -16,6 +16,8 @@ module Reek
16
16
  # * 1-character names
17
17
  # * names ending with a number
18
18
  #
19
+ # See {file:docs/Uncommunicative-Method-Name.md} for details.
20
+ # @api private
19
21
  class UncommunicativeMethodName < SmellDetector
20
22
  # The name of the config field that lists the regexps of
21
23
  # smelly names to be reported.
@@ -1,5 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -16,6 +16,8 @@ module Reek
16
16
  # * 1-character names
17
17
  # * names ending with a number
18
18
  #
19
+ # See {file:docs/Uncommunicative-Module-Name.md} for details.
20
+ # @api private
19
21
  class UncommunicativeModuleName < SmellDetector
20
22
  # The name of the config field that lists the regexps of
21
23
  # smelly names to be reported.
@@ -1,5 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -16,6 +16,8 @@ module Reek
16
16
  # * 1-character names
17
17
  # * names ending with a number
18
18
  #
19
+ # See {file:docs/Uncommunicative-Parameter-Name.md} for details.
20
+ # @api private
19
21
  class UncommunicativeParameterName < SmellDetector
20
22
  # The name of the config field that lists the regexps of
21
23
  # smelly names to be reported.
@@ -1,5 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
@@ -16,6 +16,8 @@ module Reek
16
16
  # * 1-character names
17
17
  # * names ending with a number
18
18
  #
19
+ # See {file:docs/Uncommunicative-Variable-Name.md} for details.
20
+ # @api private
19
21
  class UncommunicativeVariableName < SmellDetector
20
22
  # The name of the config field that lists the regexps of
21
23
  # smelly names to be reported.
@@ -1,11 +1,13 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
3
3
 
4
4
  module Reek
5
5
  module Smells
6
6
  #
7
7
  # Methods should use their parameters.
8
8
  #
9
+ # See {file:docs/Unused-Parameters.md} for details.
10
+ # @api private
9
11
  class UnusedParameters < SmellDetector
10
12
  def self.smell_category
11
13
  'UnusedCode'
@@ -1,6 +1,6 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
3
- require 'reek/source/reference_collector'
1
+ require_relative '../ast/reference_collector'
2
+ require_relative 'smell_detector'
3
+ require_relative 'smell_warning'
4
4
 
5
5
  module Reek
6
6
  module Smells
@@ -32,14 +32,12 @@ module Reek
32
32
  # reified into a class, and the utility method will most
33
33
  # likely belong there.
34
34
  #
35
+ # If the method does refer to self, but refers to some other object more,
36
+ # +FeatureEnvy+ is reported instead.
37
+ #
38
+ # See {file:docs/Utility-Function.md} for details.
39
+ # @api private
35
40
  class UtilityFunction < SmellDetector
36
- # The name of the config field that sets the maximum number of
37
- # calls permitted within a helper method. Any method with more than
38
- # this number of method calls on other objects will be considered a
39
- # candidate Utility Function.
40
- HELPER_CALLS_LIMIT_KEY = 'max_helper_calls'
41
- DEFAULT_HELPER_CALLS_LIMIT = 1
42
-
43
41
  def self.smell_category
44
42
  'LowCohesion'
45
43
  end
@@ -48,10 +46,6 @@ module Reek
48
46
  def contexts # :nodoc:
49
47
  [:def]
50
48
  end
51
-
52
- def default_config
53
- super.merge(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
54
- end
55
49
  end
56
50
 
57
51
  #
@@ -60,11 +54,10 @@ module Reek
60
54
  # @return [Array<SmellWarning>]
61
55
  #
62
56
  def examine_context(method_ctx)
57
+ return [] if method_ctx.exp.singleton_method?
63
58
  return [] if method_ctx.num_statements == 0
64
- return [] if depends_on_instance?(method_ctx.exp)
65
- return [] if num_helper_methods(method_ctx) <= value(HELPER_CALLS_LIMIT_KEY,
66
- method_ctx,
67
- DEFAULT_HELPER_CALLS_LIMIT)
59
+ return [] if method_ctx.references_self?
60
+ return [] if num_helper_methods(method_ctx).zero?
68
61
 
69
62
  [SmellWarning.new(self,
70
63
  context: method_ctx.full_name,
@@ -75,10 +68,6 @@ module Reek
75
68
 
76
69
  private
77
70
 
78
- def depends_on_instance?(exp)
79
- Reek::Source::ReferenceCollector.new(exp).num_refs_to_self > 0
80
- end
81
-
82
71
  def num_helper_methods(method_ctx)
83
72
  method_ctx.local_nodes(:send).length
84
73
  end
data/lib/reek/smells.rb CHANGED
@@ -1,11 +1,24 @@
1
- require 'require_all'
2
-
3
- require_rel 'smells'
4
-
5
- module Reek
6
- #
7
- # This module contains the various smell detectors.
8
- #
9
- module Smells
10
- end
11
- end
1
+ require_relative 'smells/attribute'
2
+ require_relative 'smells/boolean_parameter'
3
+ require_relative 'smells/class_variable'
4
+ require_relative 'smells/control_parameter'
5
+ require_relative 'smells/data_clump'
6
+ require_relative 'smells/duplicate_method_call'
7
+ require_relative 'smells/feature_envy'
8
+ require_relative 'smells/irresponsible_module'
9
+ require_relative 'smells/long_parameter_list'
10
+ require_relative 'smells/long_yield_list'
11
+ require_relative 'smells/module_initialize'
12
+ require_relative 'smells/nested_iterators'
13
+ require_relative 'smells/nil_check'
14
+ require_relative 'smells/prima_donna_method'
15
+ require_relative 'smells/repeated_conditional'
16
+ require_relative 'smells/too_many_instance_variables'
17
+ require_relative 'smells/too_many_methods'
18
+ require_relative 'smells/too_many_statements'
19
+ require_relative 'smells/uncommunicative_method_name'
20
+ require_relative 'smells/uncommunicative_module_name'
21
+ require_relative 'smells/uncommunicative_parameter_name'
22
+ require_relative 'smells/uncommunicative_variable_name'
23
+ require_relative 'smells/unused_parameters'
24
+ require_relative 'smells/utility_function'
@@ -1,32 +1,89 @@
1
- old_verbose, $VERBOSE = $VERBOSE, nil
2
- require 'parser/current'
3
- $VERBOSE = old_verbose
4
- require 'reek/source/tree_dresser'
5
- require 'reek/source/ast_node'
1
+ require_relative '../cli/silencer'
2
+ Reek::CLI::Silencer.silently do
3
+ require 'parser/ruby22'
4
+ end
5
+ require_relative '../tree_dresser'
6
+ require_relative '../ast/node'
6
7
 
7
8
  module Reek
9
+ # @api private
8
10
  module Source
9
11
  #
10
12
  # A +Source+ object represents a chunk of Ruby source code.
11
13
  #
14
+ # @api private
12
15
  class SourceCode
13
- attr_reader :desc
16
+ attr_reader :description
17
+
18
+ # Initializer.
19
+ #
20
+ # code - ruby code as String
21
+ # description - in case of STDIN this is "STDIN" otherwise it's a filepath as String
22
+ # parser - the parser to use for generating AST's out of the given source
23
+ def initialize(code, description, parser = Parser::Ruby22)
24
+ @source = code
25
+ @description = description
26
+ @parser = parser
27
+ end
14
28
 
15
- def initialize(code, desc, parser = Parser::Ruby21)
16
- @source = code
17
- @desc = desc
18
- @parser = parser
29
+ # Initializes an instance of SourceCode given a source.
30
+ # This source can come via 3 different ways:
31
+ # - from files a la `reek lib/reek/`
32
+ # - from IO (STDIN) a la `echo "class Foo; end" | reek`
33
+ # - from String via our rspec matchers a la `expect("class Foo; end").to reek`
34
+ #
35
+ # @param source [File|IO|String] - the given source
36
+ #
37
+ # @return an instance of SourceCode
38
+ def self.from(source)
39
+ case source
40
+ when File then new(source.read, source.path)
41
+ when IO then new(source.readlines.join, 'STDIN')
42
+ when String then new(source, 'string')
43
+ end
19
44
  end
20
45
 
46
+ # Parses the given source into an AST and associates the source code comments with it.
47
+ # This AST is then traversed by a TreeDresser which adorns the nodes in the AST
48
+ # with our SexpExtensions.
49
+ # Finally this AST is returned where each node is an anonymous subclass of Reek::AST::Node
50
+ #
51
+ # Important to note is that reek will not fail on unparseable files but rather print out
52
+ # a warning and then just continue.
53
+ #
54
+ # Given this @source:
55
+ #
56
+ # # comment about C
57
+ # class C
58
+ # def m
59
+ # puts 'nada'
60
+ # end
61
+ # end
62
+ #
63
+ # this method would return something that looks like
64
+ #
65
+ # (class
66
+ # (const nil :C) nil
67
+ # (def :m
68
+ # (args)
69
+ # (send nil :puts
70
+ # (str "nada"))))
71
+ #
72
+ # where each node is possibly adorned with our SexpExtensions (see ast/ast_node_class_map
73
+ # and ast/sexp_extensions for details).
74
+ #
75
+ # @return [Anonymous subclass of Reek::AST::Node] the AST presentation
76
+ # for the given source
21
77
  def syntax_tree
22
78
  @syntax_tree ||=
23
79
  begin
24
80
  begin
25
- ast, comments = @parser.parse_with_comments(@source, @desc)
81
+ ast, comments = @parser.parse_with_comments(@source, @description)
26
82
  rescue Racc::ParseError, Parser::SyntaxError => error
27
- $stderr.puts "#{desc}: #{error.class.name}: #{error}"
83
+ $stderr.puts "#{description}: #{error.class.name}: #{error}"
28
84
  end
29
85
 
86
+ # See https://whitequark.github.io/parser/Parser/Source/Comment/Associator.html
30
87
  comment_map = Parser::Source::Comment.associate(ast, comments) if ast
31
88
  TreeDresser.new.dress(ast, comment_map)
32
89
  end
@@ -1,40 +1,55 @@
1
- require 'reek/source/core_extras'
1
+ require 'find'
2
2
 
3
3
  module Reek
4
4
  module Source
5
5
  #
6
6
  # Finds Ruby source files in a filesystem.
7
7
  #
8
+ # @api private
8
9
  class SourceLocator
10
+ # Initialize with the paths we want to search.
11
+ #
12
+ # paths - a list of paths as Strings
9
13
  def initialize(paths)
10
14
  @paths = paths.map { |path| path.chomp('/') }
11
15
  end
12
16
 
13
- def all_sources
14
- valid_paths.map { |path| File.new(path).to_reek_source }
17
+ # Traverses all paths we initialized the SourceLocator with, finds
18
+ # all relevant ruby files and returns them as a list.
19
+ #
20
+ # @return [Array<File>] - Ruby files found
21
+ def sources
22
+ source_paths.map { |pathname| File.new(pathname) }
15
23
  end
16
24
 
17
25
  private
18
26
 
19
- def all_ruby_source_files(paths)
20
- paths.map do |path|
21
- if test 'd', path
22
- all_ruby_source_files(Dir["#{path}/**/*.rb"])
23
- else
24
- path
27
+ def source_paths
28
+ relevant_paths = []
29
+ @paths.map do |given_path|
30
+ print_no_such_file_error(given_path) && next unless path_exists?(given_path)
31
+ Find.find(given_path) do |path|
32
+ pathname = Pathname.new(path)
33
+ if pathname.directory?
34
+ exclude_path?(pathname) ? Find.prune : next
35
+ else
36
+ relevant_paths << pathname
37
+ end
25
38
  end
26
- end.flatten.sort
39
+ end
40
+ relevant_paths.flatten.sort
27
41
  end
28
42
 
29
- def valid_paths
30
- all_ruby_source_files(@paths).select do |path|
31
- if test 'f', path
32
- true
33
- else
34
- $stderr.puts "Error: No such file - #{path}"
35
- false
36
- end
37
- end
43
+ def exclude_path?(pathname)
44
+ Configuration::AppConfiguration.exclude_paths.include? pathname.to_s
45
+ end
46
+
47
+ def path_exists?(path)
48
+ Pathname.new(path).exist?
49
+ end
50
+
51
+ def print_no_such_file_error(path)
52
+ $stderr.puts "Error: No such file - #{path}"
38
53
  end
39
54
  end
40
55
  end