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,19 +1,22 @@
1
1
  require 'optparse'
2
2
  require 'ostruct'
3
3
  require 'rainbow'
4
- require 'reek/cli/option_interpreter'
5
- require 'reek/version'
4
+ require_relative 'option_interpreter'
5
+ require_relative '../version'
6
6
 
7
7
  module Reek
8
- module Cli
8
+ module CLI
9
9
  #
10
10
  # Parses the command line
11
11
  #
12
+ # See {file:docs/Command-Line-Options.md} for details.
13
+ # @api private
12
14
  class Options
13
- def initialize(argv)
15
+ def initialize(argv = [])
14
16
  @argv = argv
15
17
  @parser = OptionParser.new
16
- @options = OpenStruct.new(colored: true, smells_to_detect: [])
18
+ @options = OpenStruct.new(colored: color_support?,
19
+ smells_to_detect: [])
17
20
  set_up_parser
18
21
  end
19
22
 
@@ -26,6 +29,10 @@ module Reek
26
29
 
27
30
  private
28
31
 
32
+ def color_support?
33
+ $stdout.tty?
34
+ end
35
+
29
36
  def set_up_parser
30
37
  set_banner
31
38
  set_configuration_options
@@ -53,9 +60,9 @@ module Reek
53
60
  def set_alternative_formatter_options
54
61
  @parser.separator "\nReport format:"
55
62
  @parser.on(
56
- '-f', '--format FORMAT', [:html, :text, :yaml, :json],
63
+ '-f', '--format FORMAT', [:html, :text, :yaml, :json, :xml],
57
64
  'Report smells in the given format:',
58
- ' html', ' text (default)', ' yaml', ' json'
65
+ ' html', ' text (default)', ' yaml', ' json', ' xml'
59
66
  ) do |opt|
60
67
  @options.report_format = opt
61
68
  end
@@ -64,7 +71,8 @@ module Reek
64
71
  def set_configuration_options
65
72
  @parser.separator 'Configuration:'
66
73
  @parser.on('-c', '--config FILE', 'Read configuration options from FILE') do |file|
67
- @options.config_file = file
74
+ raise ArgumentError, "Config file #{file} doesn't exist" unless File.exist?(file)
75
+ @options.config_file = Pathname.new(file)
68
76
  end
69
77
  @parser.on('--smell SMELL', 'Detect smell SMELL (default: all enabled smells)') do |smell|
70
78
  @options.smells_to_detect << smell
@@ -1,12 +1,13 @@
1
- require 'reek/cli/command'
2
- require 'reek/examiner'
1
+ require_relative 'command'
2
+ require_relative '../examiner'
3
3
 
4
4
  module Reek
5
- module Cli
5
+ module CLI
6
6
  #
7
7
  # A command to collect smells from a set of sources and write them out in
8
8
  # text report format.
9
9
  #
10
+ # @api private
10
11
  class ReekCommand < Command
11
12
  def execute(app)
12
13
  @options.sources.each do |source|
@@ -0,0 +1,14 @@
1
+ module Reek
2
+ module CLI
3
+ # CLI silencer
4
+ # @api private
5
+ module Silencer
6
+ def self.silently
7
+ old_verbose, $VERBOSE = $VERBOSE, nil
8
+ yield if block_given?
9
+ ensure
10
+ $VERBOSE = old_verbose
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,10 +1,11 @@
1
1
  require 'set'
2
2
 
3
3
  module Reek
4
- module Core
4
+ module CLI
5
5
  #
6
6
  # Collects and sorts smells warnings.
7
7
  #
8
+ # @api private
8
9
  class WarningCollector
9
10
  def initialize
10
11
  @warnings = Set.new
@@ -0,0 +1,36 @@
1
+ require 'yaml'
2
+
3
+ module Reek
4
+ #
5
+ # A comment header from an abstract syntax tree; found directly above
6
+ # module, class and method definitions.
7
+ #
8
+ # @api private
9
+ class CodeComment
10
+ CONFIG_REGEX = /:reek:(\w+)(:\s*\{.*?\})?/
11
+
12
+ def initialize(text)
13
+ @text = text.gsub(CONFIG_REGEX) do
14
+ add_to_config($1, $2)
15
+ ''
16
+ end.gsub(/#/, '').gsub(/\n/, '').strip
17
+ end
18
+
19
+ def config
20
+ @config ||= Hash.new { |hash, key| hash[key] = {} }
21
+ end
22
+
23
+ def descriptive?
24
+ @text.split(/\s+/).length >= 2
25
+ end
26
+
27
+ protected
28
+
29
+ def add_to_config(smell, options)
30
+ options ||= ': { enabled: false }'
31
+ config.merge! YAML.load(smell.gsub(/(?:^|_)(.)/) { $1.upcase } + options)
32
+ # TODO: extend this to all configs -------------------^
33
+ # TODO: extend to allow configuration of whole smell class, not just subclass
34
+ end
35
+ end
36
+ end
@@ -1,13 +1,18 @@
1
1
  require_relative './configuration_file_finder'
2
2
 
3
3
  module Reek
4
+ # @api private
4
5
  module Configuration
6
+ # @api private
5
7
  class ConfigFileException < StandardError; end
6
8
  #
7
9
  # Reek's singleton configuration instance.
8
10
  #
11
+ # @api private
9
12
  module AppConfiguration
10
- @configuration = {}
13
+ NON_SMELL_TYPE_KEYS = %w(exclude_paths)
14
+ EXCLUDE_PATHS_KEY = 'exclude_paths'
15
+ @configuration = {}
11
16
  @has_been_initialized = false
12
17
 
13
18
  class << self
@@ -15,7 +20,7 @@ module Reek
15
20
 
16
21
  def initialize_with(options)
17
22
  @has_been_initialized = true
18
- configuration_file_path = ConfigurationFileFinder.find(options)
23
+ configuration_file_path = ConfigurationFileFinder.find(options: options)
19
24
  return unless configuration_file_path
20
25
  load_from_file configuration_file_path
21
26
  end
@@ -24,7 +29,7 @@ module Reek
24
29
  # Let users call this method directly without having initialized AppConfiguration before
25
30
  # and if they do, initialize it without application context
26
31
  initialize_with(nil) unless @has_been_initialized
27
- @configuration.each do |klass_name, config|
32
+ for_smell_types.each do |klass_name, config|
28
33
  klass = load_smell_type(klass_name)
29
34
  smell_repository.configure(klass, config) if klass
30
35
  end
@@ -49,8 +54,18 @@ module Reek
49
54
  @configuration.clear
50
55
  end
51
56
 
57
+ def exclude_paths
58
+ @exclude_paths ||= @configuration.
59
+ fetch(EXCLUDE_PATHS_KEY, []).
60
+ map { |path| path.chomp('/') }
61
+ end
62
+
52
63
  private
53
64
 
65
+ def for_smell_types
66
+ @configuration.reject { |key, _value| NON_SMELL_TYPE_KEYS.include?(key) }
67
+ end
68
+
54
69
  def load_smell_type(name)
55
70
  Reek::Smells.const_get(name)
56
71
  rescue NameError
@@ -13,42 +13,28 @@ module Reek
13
13
  #
14
14
  # The order in which ConfigurationFileFinder tries to find such a
15
15
  # configuration file is exactly like above.
16
+ # @api private
16
17
  module ConfigurationFileFinder
17
- class << self
18
- def find(options)
19
- configuration_by_cli(options) ||
20
- configuration_in_file_system ||
21
- configuration_in_home_directory
22
- end
23
-
24
- private
25
-
26
- def configuration_by_cli(options)
27
- return unless options # Return gracefully allowing calls without app context
28
- config_file_option = options.config_file
29
- return unless config_file_option
30
- path_name = Pathname.new config_file_option
31
- raise ArgumentError, "Config file #{path_name} doesn't exist" unless path_name.exist?
32
- path_name
33
- end
34
-
35
- def configuration_in_file_system
36
- detect_or_traverse_up Pathname.pwd
37
- end
18
+ module_function
38
19
 
39
- def configuration_in_home_directory
40
- detect_configuration_in_directory Pathname.new(Dir.home)
41
- end
20
+ # FIXME: switch to kwargs on upgrade to Ruby 2 and drop `params.fetch` calls:
21
+ # def find(options: nil, current: Pathname.pwd, home: Pathname.new(Dir.home))
22
+ def find(params = {})
23
+ options = params.fetch(:options) { nil }
24
+ current = params.fetch(:current) { Pathname.pwd }
25
+ home = params.fetch(:home) { Pathname.new(Dir.home) }
26
+ find_by_cli(options) || find_by_dir(current) || find_by_dir(home)
27
+ end
42
28
 
43
- def detect_or_traverse_up(directory)
44
- file = detect_configuration_in_directory(directory)
45
- return file unless file.nil?
46
- return if directory.root?
47
- detect_or_traverse_up directory.parent
48
- end
29
+ def find_by_cli(options)
30
+ options && options.config_file
31
+ end
49
32
 
50
- def detect_configuration_in_directory(directory)
51
- directory.children.select(&:file?).find { |path| path.to_s.end_with?('.reek') }
33
+ def find_by_dir(start)
34
+ start.ascend do |dir|
35
+ files = dir.children.select(&:file?).sort
36
+ found = files.find { |file| file.to_s.end_with?('.reek') }
37
+ return found if found
52
38
  end
53
39
  end
54
40
  end
@@ -1,19 +1,21 @@
1
+ require_relative '../code_comment'
1
2
 
2
3
  module Reek
3
- module Core
4
+ module Context
4
5
  #
5
6
  # Superclass for all types of source code context. Each instance represents
6
7
  # a code element of some kind, and each provides behaviour relevant to that
7
8
  # code element. CodeContexts form a tree in the same way the code does,
8
9
  # with each context holding a reference to a unique outer context.
9
10
  #
11
+ # @api private
10
12
  class CodeContext
11
13
  attr_reader :exp
12
14
 
13
15
  # Initializes a new CodeContext.
14
16
  #
15
17
  # context - *_context from the `core` namespace
16
- # exp - Reek::Source::AstNode
18
+ # exp - Reek::Source::ASTNode
17
19
  #
18
20
  # Examples:
19
21
  #
@@ -21,9 +23,9 @@ module Reek
21
23
  #
22
24
  # class Omg; def foo(x); puts x; end; end
23
25
  #
24
- # the first time this is instantianted from TreeWalker `context` is a StopContext:
26
+ # the first time this is instantianted from TreeWalker `context` is a RootContext:
25
27
  #
26
- # #<Reek::Core::StopContext:0x00000002231098 @name="">
28
+ # #<Reek::Context::RootContext:0x00000002231098 @name="">
27
29
  #
28
30
  # and `exp` looks like this:
29
31
  #
@@ -37,7 +39,7 @@ module Reek
37
39
  #
38
40
  # The next time we instantiate a CodeContext via TreeWalker `context` would be:
39
41
  #
40
- # Reek::Core::ModuleContext
42
+ # Reek::Context::ModuleContext
41
43
  #
42
44
  # and `exp` is:
43
45
  #
@@ -97,7 +99,7 @@ module Reek
97
99
 
98
100
  def config
99
101
  @config ||= if @exp
100
- Source::CodeComment.new(@exp.comments || '').config
102
+ CodeComment.new(@exp.full_comment || '').config
101
103
  else
102
104
  {}
103
105
  end
@@ -1,11 +1,12 @@
1
- require 'reek/core/code_context'
2
- require 'reek/core/object_refs'
1
+ require_relative 'code_context'
2
+ require_relative '../ast/object_refs'
3
3
 
4
4
  module Reek
5
- module Core
5
+ module Context
6
6
  #
7
7
  # The parameters in a method's definition.
8
8
  #
9
+ # @api private
9
10
  module MethodParameters
10
11
  def default_assignments
11
12
  result = []
@@ -19,17 +20,18 @@ module Reek
19
20
  #
20
21
  # A context wrapper for any method definition found in a syntax tree.
21
22
  #
23
+ # @api private
22
24
  class MethodContext < CodeContext
23
25
  attr_reader :parameters
24
26
  attr_reader :refs
25
27
  attr_reader :num_statements
26
28
 
27
29
  def initialize(outer, exp)
28
- super(outer, exp)
30
+ super
29
31
  @parameters = exp.parameters.dup
30
32
  @parameters.extend MethodParameters
31
33
  @num_statements = 0
32
- @refs = ObjectRefs.new
34
+ @refs = AST::ObjectRefs.new
33
35
  end
34
36
 
35
37
  def count_statements(num)
@@ -58,6 +60,10 @@ module Reek
58
60
  @refs.max_keys
59
61
  end
60
62
 
63
+ def references_self?
64
+ exp.depends_on_instance?
65
+ end
66
+
61
67
  def uses_param?(param)
62
68
  local_nodes(:lvar).find { |node| node.var_name == param.to_sym }
63
69
  end
@@ -1,20 +1,25 @@
1
- require 'reek/core/code_context'
2
- require 'reek/source/sexp_formatter'
1
+ require_relative 'code_context'
2
+ require_relative '../ast/sexp_formatter'
3
3
 
4
4
  module Reek
5
- module Core
5
+ module Context
6
6
  #
7
7
  # A context wrapper for any module found in a syntax tree.
8
8
  #
9
+ # @api private
9
10
  class ModuleContext < CodeContext
10
11
  def initialize(outer, exp)
11
12
  super(outer, exp)
12
- @name = Source::SexpFormatter.format(exp.children.first)
13
+ @name = AST::SexpFormatter.format(exp.children.first)
13
14
  end
14
15
 
15
16
  def node_instance_methods
16
17
  local_nodes(:def)
17
18
  end
19
+
20
+ def descriptively_commented?
21
+ CodeComment.new(exp.leading_comment).descriptive?
22
+ end
18
23
  end
19
24
  end
20
25
  end
@@ -1,9 +1,11 @@
1
1
  module Reek
2
- module Core
2
+ # @api private
3
+ module Context
3
4
  #
4
5
  # A context wrapper representing the root of an abstract syntax tree.
5
6
  #
6
- class StopContext
7
+ # @api private
8
+ class RootContext
7
9
  def initialize
8
10
  @name = ''
9
11
  end
@@ -1,15 +1,12 @@
1
- require 'reek/core/method_context'
1
+ require_relative 'method_context'
2
2
 
3
3
  module Reek
4
- module Core
4
+ module Context
5
5
  #
6
6
  # A context wrapper for any singleton method definition found in a syntax tree.
7
7
  #
8
+ # @api private
8
9
  class SingletonMethodContext < MethodContext
9
- def initialize(outer, exp)
10
- super(outer, exp)
11
- end
12
-
13
10
  def envious_receivers
14
11
  []
15
12
  end
data/lib/reek/examiner.rb CHANGED
@@ -1,55 +1,60 @@
1
- require 'reek/source/source_repository'
2
- require 'reek/core/warning_collector'
3
- require 'reek/core/smell_repository'
4
- require 'reek/core/tree_walker'
1
+ # NOTE: tree_walker is required first to ensure unparser is required before
2
+ # parser. This prevents a potentially incompatible version of parser from being
3
+ # loaded first. This is only relevant when running bin/reek straight from a
4
+ # checkout directory without using Bundler.
5
+ #
6
+ # See also https://github.com/troessner/reek/pull/468
7
+ require_relative 'tree_walker'
8
+ require_relative 'source/source_code'
9
+ require_relative 'cli/warning_collector'
10
+ require_relative 'smells/smell_repository'
5
11
 
6
12
  module Reek
7
13
  #
8
14
  # Applies all available smell detectors to a source.
9
15
  #
10
16
  class Examiner
11
- #
12
- # A simple description of the source being analysed for smells.
13
- # If the source is a single File, this will be the file's path.
14
- #
15
- attr_accessor :description
16
-
17
17
  #
18
18
  # Creates an Examiner which scans the given +source+ for code smells.
19
19
  #
20
- # @param [Source::SourceCode, Array<String>, #to_reek_source]
20
+ # @param source [File, IO, String]
21
21
  # If +source+ is a String it is assumed to be Ruby source code;
22
- # if it is a File, the file is opened and parsed for Ruby source code;
23
- # and if it is an Array, it is assumed to be a list of file paths,
24
- # each of which is opened and parsed for source code.
22
+ # if it is a File or IO, it is opened and Ruby source code is read from it;
23
+ #
24
+ # @param smell_types_to_filter_by [Array<String>]
25
+ # List of smell types to filter by.
25
26
  #
26
27
  def initialize(source, smell_types_to_filter_by = [])
27
- @sources = Source::SourceRepository.parse(source)
28
- @description = @sources.description
29
- @collector = Core::WarningCollector.new
28
+ @source = Source::SourceCode.from(source)
29
+ @collector = CLI::WarningCollector.new
30
30
  @smell_types = eligible_smell_types(smell_types_to_filter_by)
31
31
 
32
32
  run
33
33
  end
34
34
 
35
35
  #
36
- # List the smells found in the source.
36
+ # @return [String] description of the source being analysed
37
37
  #
38
- # @return [Array<SmellWarning>]
38
+ def description
39
+ @description ||= @source.description
40
+ end
41
+
42
+ #
43
+ # @return [Array<SmellWarning>] the smells found in the source
39
44
  #
40
45
  def smells
41
46
  @smells ||= @collector.warnings
42
47
  end
43
48
 
44
49
  #
45
- # Returns the number of smells found in the source
50
+ # @return [Integer] the number of smells found in the source
46
51
  #
47
52
  def smells_count
48
53
  smells.length
49
54
  end
50
55
 
51
56
  #
52
- # True if and only if there are code smells in the source.
57
+ # @return [Boolean] true if and only if there are code smells in the source.
53
58
  #
54
59
  def smelly?
55
60
  !smells.empty?
@@ -58,21 +63,19 @@ module Reek
58
63
  private
59
64
 
60
65
  def run
61
- @sources.each do |source|
62
- smell_repository = Core::SmellRepository.new(source.desc, @smell_types)
63
- syntax_tree = source.syntax_tree
64
- Core::TreeWalker.new(smell_repository).process(syntax_tree) if syntax_tree
65
- smell_repository.report_on(@collector)
66
- end
66
+ smell_repository = Smells::SmellRepository.new(description, @smell_types)
67
+ syntax_tree = @source.syntax_tree
68
+ TreeWalker.new(smell_repository).process(syntax_tree) if syntax_tree
69
+ smell_repository.report_on(@collector)
67
70
  end
68
71
 
69
72
  def eligible_smell_types(smell_types_to_filter_by = [])
70
73
  if smell_types_to_filter_by.any?
71
- Core::SmellRepository.smell_types.select do |klass|
74
+ Smells::SmellRepository.smell_types.select do |klass|
72
75
  smell_types_to_filter_by.include? klass.smell_type
73
76
  end
74
77
  else
75
- Core::SmellRepository.smell_types
78
+ Smells::SmellRepository.smell_types
76
79
  end
77
80
  end
78
81
  end
@@ -0,0 +1,70 @@
1
+ require_relative 'location_formatter'
2
+
3
+ module Reek
4
+ module Report
5
+ #
6
+ # Formatter handling the formatting of the report at large.
7
+ # Formatting of the individual warnings is handled by the
8
+ # passed-in warning formatter.
9
+ #
10
+ # @api private
11
+ module Formatter
12
+ def self.format_list(warnings, formatter = SimpleWarningFormatter.new)
13
+ warnings.map do |warning|
14
+ " #{formatter.format warning}"
15
+ end.join("\n")
16
+ end
17
+
18
+ def self.header(examiner)
19
+ count = examiner.smells_count
20
+ result = Rainbow("#{examiner.description} -- ").cyan +
21
+ Rainbow("#{count} warning").yellow
22
+ result += Rainbow('s').yellow unless count == 1
23
+ result
24
+ end
25
+ end
26
+
27
+ #
28
+ # Basic formatter that just shows a simple message for each warning,
29
+ # prepended with the result of the passed-in location formatter.
30
+ #
31
+ # @api private
32
+ class SimpleWarningFormatter
33
+ def initialize(location_formatter = BlankLocationFormatter)
34
+ @location_formatter = location_formatter
35
+ end
36
+
37
+ def format(warning)
38
+ "#{@location_formatter.format(warning)}#{base_format(warning)}"
39
+ end
40
+
41
+ private
42
+
43
+ def base_format(warning)
44
+ "#{warning.context} #{warning.message} (#{warning.smell_type})"
45
+ end
46
+ end
47
+
48
+ #
49
+ # Formatter that adds a link to the wiki to the basic message from
50
+ # SimpleWarningFormatter.
51
+ #
52
+ # @api private
53
+ class WikiLinkWarningFormatter < SimpleWarningFormatter
54
+ BASE_URL_FOR_HELP_LINK = 'https://github.com/troessner/reek/wiki/'
55
+
56
+ def format(warning)
57
+ "#{super} " \
58
+ "[#{explanatory_link(warning)}]"
59
+ end
60
+
61
+ def explanatory_link(warning)
62
+ "#{BASE_URL_FOR_HELP_LINK}#{class_name_to_param(warning.smell_type)}"
63
+ end
64
+
65
+ def class_name_to_param(name)
66
+ name.split(/(?=[A-Z])/).join('-')
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,45 @@
1
+ module Reek
2
+ module Report
3
+ # @api private
4
+ module HeadingFormatter
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 Base
11
+ attr_reader :report_formatter
12
+
13
+ def initialize(report_formatter)
14
+ @report_formatter = report_formatter
15
+ end
16
+
17
+ def header(examiner)
18
+ if show_header?(examiner)
19
+ report_formatter.header examiner
20
+ else
21
+ ''
22
+ end
23
+ end
24
+ end
25
+
26
+ #
27
+ # Lists out each examiner, even if it has no smell
28
+ #
29
+ class Verbose < Base
30
+ def show_header?(_examiner)
31
+ true
32
+ end
33
+ end
34
+
35
+ #
36
+ # Lists only smelly examiners
37
+ #
38
+ class Quiet < Base
39
+ def show_header?(examiner)
40
+ examiner.smelly?
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end