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
@@ -0,0 +1,35 @@
1
+ module Reek
2
+ module Report
3
+ #
4
+ # Formats the location of a warning as an empty string.
5
+ #
6
+ # @api private
7
+ module BlankLocationFormatter
8
+ def self.format(_warning)
9
+ ''
10
+ end
11
+ end
12
+
13
+ #
14
+ # Formats the location of a warning as an array of line numbers.
15
+ #
16
+ # @api private
17
+ module DefaultLocationFormatter
18
+ def self.format(warning)
19
+ "#{warning.lines.inspect}:"
20
+ end
21
+ end
22
+
23
+ #
24
+ # Formats the location of a warning as a combination of source file name
25
+ # and line number. In this format, it is not possible to show more than
26
+ # one line number, so the first number is displayed.
27
+ #
28
+ # @api private
29
+ module SingleLineLocationFormatter
30
+ def self.format(warning)
31
+ "#{warning.source}:#{warning.lines.first}: "
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,198 @@
1
+ require 'rainbow'
2
+ require 'json'
3
+ require_relative 'formatter'
4
+ require_relative 'heading_formatter'
5
+
6
+ module Reek
7
+ module Report
8
+ #
9
+ # A report that contains the smells and smell counts following source code analysis.
10
+ #
11
+ # @abstract Subclass and override {#show} to create a concrete report class.
12
+ class Base
13
+ # @api private
14
+ NO_WARNINGS_COLOR = :green
15
+ # @api private
16
+ WARNINGS_COLOR = :red
17
+
18
+ def initialize(options = {})
19
+ @examiners = []
20
+ @total_smell_count = 0
21
+ @options = options
22
+ @warning_formatter = options.fetch :warning_formatter, SimpleWarningFormatter.new
23
+ @report_formatter = options.fetch :report_formatter, Formatter
24
+ @sort_by_issue_count = options.fetch :sort_by_issue_count, false
25
+ end
26
+
27
+ # Add Examiner to report on. The report will output results for all
28
+ # added examiners.
29
+ #
30
+ # @param [Reek::Examiner] examiner object to report on
31
+ def add_examiner(examiner)
32
+ @total_smell_count += examiner.smells_count
33
+ @examiners << examiner
34
+ self
35
+ end
36
+
37
+ # Render the report results on STDOUT
38
+ def show
39
+ raise NotImplementedError
40
+ end
41
+
42
+ # @api private
43
+ def smells?
44
+ @total_smell_count > 0
45
+ end
46
+
47
+ # @api private
48
+ def smells
49
+ @examiners.map(&:smells).flatten
50
+ end
51
+ end
52
+
53
+ #
54
+ # Generates a sorted, text summary of smells in examiners
55
+ #
56
+ class TextReport < Base
57
+ def show
58
+ sort_examiners if smells?
59
+ display_summary
60
+ display_total_smell_count
61
+ end
62
+
63
+ private
64
+
65
+ def smell_summaries
66
+ @examiners.map { |ex| summarize_single_examiner(ex) }.reject(&:empty?)
67
+ end
68
+
69
+ def display_summary
70
+ smell_summaries.each { |smell| puts smell }
71
+ end
72
+
73
+ def display_total_smell_count
74
+ return unless @examiners.size > 1
75
+ print total_smell_count_message
76
+ end
77
+
78
+ def summarize_single_examiner(examiner)
79
+ result = heading_formatter.header(examiner)
80
+ if examiner.smelly?
81
+ formatted_list = @report_formatter.format_list(examiner.smells,
82
+ @warning_formatter)
83
+ result += ":\n#{formatted_list}"
84
+ end
85
+ result
86
+ end
87
+
88
+ def sort_examiners
89
+ @examiners.sort_by!(&:smells_count).reverse! if @sort_by_issue_count
90
+ end
91
+
92
+ def total_smell_count_message
93
+ colour = smells? ? WARNINGS_COLOR : NO_WARNINGS_COLOR
94
+ s = @total_smell_count == 1 ? '' : 's'
95
+ Rainbow("#{@total_smell_count} total warning#{s}\n").color(colour)
96
+ end
97
+
98
+ def heading_formatter
99
+ @heading_formatter ||=
100
+ @options.fetch(:heading_formatter, HeadingFormatter::Quiet).new(@report_formatter)
101
+ end
102
+ end
103
+
104
+ #
105
+ # Displays a list of smells in YAML format
106
+ # YAML with empty array for 0 smells
107
+ class YAMLReport < Base
108
+ def show
109
+ print smells.map(&:yaml_hash).to_yaml
110
+ end
111
+ end
112
+
113
+ #
114
+ # Displays a list of smells in JSON format
115
+ # JSON with empty array for 0 smells
116
+ class JSONReport < Base
117
+ def show
118
+ print ::JSON.generate(
119
+ smells.map do |smell|
120
+ smell.yaml_hash(@warning_formatter)
121
+ end
122
+ )
123
+ end
124
+ end
125
+
126
+ #
127
+ # Saves the report as a HTML file
128
+ #
129
+ class HTMLReport < Base
130
+ require 'erb'
131
+
132
+ def show
133
+ path = File.expand_path('../../../../assets/html_output.html.erb',
134
+ __FILE__)
135
+ File.open('reek.html', 'w+') do |file|
136
+ file.puts ERB.new(File.read(path)).result(binding)
137
+ end
138
+ print("Html file saved\n")
139
+ end
140
+ end
141
+
142
+ #
143
+ # Generates a list of smells in XML format
144
+ #
145
+ class XMLReport < Base
146
+ require 'rexml/document'
147
+
148
+ def show
149
+ checkstyle = REXML::Element.new('checkstyle', document)
150
+
151
+ smells.group_by(&:source).each do |file, file_smells|
152
+ file_to_xml(file, file_smells, checkstyle)
153
+ end
154
+
155
+ print_xml(checkstyle.parent)
156
+ end
157
+
158
+ private
159
+
160
+ def document
161
+ REXML::Document.new.tap do |doc|
162
+ doc << REXML::XMLDecl.new
163
+ end
164
+ end
165
+
166
+ def file_to_xml(file, file_smells, parent)
167
+ REXML::Element.new('file', parent).tap do |element|
168
+ element.attributes['name'] = File.realpath(file)
169
+ smells_to_xml(file_smells, element)
170
+ end
171
+ end
172
+
173
+ def smells_to_xml(smells, parent)
174
+ smells.each do |smell|
175
+ smell_to_xml(smell, parent)
176
+ end
177
+ end
178
+
179
+ def smell_to_xml(smell, parent)
180
+ REXML::Element.new('error', parent).tap do |element|
181
+ attributes = [
182
+ ['line', smell.lines.first],
183
+ ['column', 0],
184
+ ['severity', 'warning'],
185
+ ['message', smell.message],
186
+ ['source', smell.smell_type]
187
+ ]
188
+ element.add_attributes(attributes)
189
+ end
190
+ end
191
+
192
+ def print_xml(document)
193
+ formatter = REXML::Formatters::Default.new
194
+ puts formatter.write(document, '')
195
+ end
196
+ end
197
+ end
198
+ end
@@ -1,6 +1,6 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
3
- require 'reek/core/smell_configuration'
1
+ require_relative 'smell_configuration'
2
+ require_relative 'smell_detector'
3
+ require_relative 'smell_warning'
4
4
 
5
5
  module Reek
6
6
  module Smells
@@ -13,16 +13,27 @@ module Reek
13
13
  # +attr_reader+, +attr_writer+ and +attr_accessor+ -- including those
14
14
  # that are private.
15
15
  #
16
- # TODO: Eliminate private attributes
17
- # TODO: Catch attributes declared "by hand"
16
+ # See {file:docs/Attribute.md} for details.
17
+ # @api private
18
18
  #
19
+ # TODO: Catch attributes declared "by hand"
19
20
  class Attribute < SmellDetector
21
+ ATTR_DEFN_METHODS = [:attr, :attr_reader, :attr_writer, :attr_accessor]
22
+ VISIBILITY_MODIFIERS = [:private, :public, :protected]
23
+
24
+ def initialize(*args)
25
+ @visiblity_tracker = {}
26
+ @visiblity_mode = :public
27
+ @result = Set.new
28
+ super
29
+ end
30
+
20
31
  def self.contexts # :nodoc:
21
32
  [:class, :module]
22
33
  end
23
34
 
24
35
  def self.default_config
25
- super.merge(Core::SmellConfiguration::ENABLED_KEY => false)
36
+ super.merge(SmellConfiguration::ENABLED_KEY => false)
26
37
  end
27
38
 
28
39
  #
@@ -43,14 +54,33 @@ module Reek
43
54
  private
44
55
 
45
56
  def attributes_in(module_ctx)
46
- result = Set.new
47
- attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
48
57
  module_ctx.local_nodes(:send) do |call_node|
49
- if attr_defn_methods.include?(call_node.method_name)
50
- call_node.arg_names.each { |arg| result << [arg, call_node.line] }
58
+ if visibility_modifier?(call_node)
59
+ track_visibility(call_node)
60
+ elsif ATTR_DEFN_METHODS.include?(call_node.method_name)
61
+ call_node.arg_names.each do |arg|
62
+ @visiblity_tracker[arg] = @visiblity_mode
63
+ @result << [arg, call_node.line]
64
+ end
51
65
  end
52
66
  end
53
- result
67
+ @result.select { |args| recorded_public_methods.include?(args[0]) }
68
+ end
69
+
70
+ def visibility_modifier?(call_node)
71
+ VISIBILITY_MODIFIERS.include?(call_node.method_name)
72
+ end
73
+
74
+ def track_visibility(call_node)
75
+ if call_node.arg_names.any?
76
+ call_node.arg_names.each { |arg| @visiblity_tracker[arg] = call_node.method_name }
77
+ else
78
+ @visiblity_mode = call_node.method_name
79
+ end
80
+ end
81
+
82
+ def recorded_public_methods
83
+ @visiblity_tracker.select { |_, visbility| visbility == :public }
54
84
  end
55
85
  end
56
86
  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
@@ -11,6 +11,8 @@ module Reek
11
11
  # Currently Reek can only detect a Boolean parameter when it has a
12
12
  # default initializer.
13
13
  #
14
+ # See {file:docs/Boolean-Parameter.md} for details.
15
+ # @api private
14
16
  class BooleanParameter < SmellDetector
15
17
  def self.smell_category
16
18
  'ControlCouple'
@@ -1,6 +1,6 @@
1
1
  require 'set'
2
- require 'reek/smells/smell_detector'
3
- require 'reek/smell_warning'
2
+ require_relative 'smell_detector'
3
+ require_relative 'smell_warning'
4
4
 
5
5
  module Reek
6
6
  module Smells
@@ -12,6 +12,8 @@ module Reek
12
12
  # In particular, class variables can make it hard to set up tests (because
13
13
  # the context of the test includes all global state).
14
14
  #
15
+ # See {file:docs/Class-Variable.md} for details.
16
+ # @api private
15
17
  class ClassVariable < SmellDetector
16
18
  def self.contexts # :nodoc:
17
19
  [:class, :module]
@@ -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
@@ -40,6 +40,8 @@ module Reek
40
40
  # that remains next to where the caller invokes it in
41
41
  # the source code.
42
42
  #
43
+ # See {file:docs/Control-Parameter.md} for details.
44
+ # @api private
43
45
  class ControlParameter < SmellDetector
44
46
  def self.smell_category
45
47
  'ControlCouple'
@@ -1,6 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
3
- require 'reek/source'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -16,6 +15,8 @@ module Reek
16
15
  # Currently Reek looks for a group of two or more parameters with
17
16
  # the same names that are expected by three or more methods of a class.
18
17
  #
18
+ # See {file:docs/Data-Clump.md} for details.
19
+ # @api private
19
20
  class DataClump < SmellDetector
20
21
  #
21
22
  # The name of the config field that sets the maximum allowed
@@ -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
  # @other.thing + @other.thing
17
17
  # end
18
18
  #
19
+ # See {file:docs/Duplicate-Method-Call.md} for details.
20
+ # @api private
19
21
  class DuplicateMethodCall < SmellDetector
20
22
  # The name of the config field that sets the maximum number of
21
23
  # identical calls to be permitted within any single method.
@@ -70,7 +72,7 @@ module Reek
70
72
  end
71
73
 
72
74
  def call
73
- @call ||= @call_node.format_ruby
75
+ @call ||= @call_node.format_to_ruby
74
76
  end
75
77
 
76
78
  def occurs
@@ -106,8 +108,8 @@ module Reek
106
108
 
107
109
  def collect_calls(result)
108
110
  context.each_node(:send, [:mlhs]) do |call_node|
109
- next if call_node.method_name == :new
110
- next if !call_node.receiver && call_node.args.empty?
111
+ next if initializer_call? call_node
112
+ next if simple_method_call? call_node
111
113
  result[call_node].record(call_node)
112
114
  end
113
115
  context.local_nodes(:block) do |call_node|
@@ -119,6 +121,14 @@ module Reek
119
121
  found_call.occurs > @max_allowed_calls && !allow_calls?(found_call.call)
120
122
  end
121
123
 
124
+ def simple_method_call?(call_node)
125
+ !call_node.receiver && call_node.args.empty?
126
+ end
127
+
128
+ def initializer_call?(call_node)
129
+ call_node.method_name == :new
130
+ end
131
+
122
132
  def allow_calls?(method)
123
133
  @allow_calls.any? { |allow| /#{allow}/ =~ method }
124
134
  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
@@ -30,6 +30,11 @@ module Reek
30
30
  # Currently +FeatureEnvy+ reports any method that refers to self less
31
31
  # often than it refers to (ie. send messages to) some other object.
32
32
  #
33
+ # If the method doesn't reference self at all, +UtilityFunction+ is
34
+ # reported instead.
35
+ #
36
+ # See {file:docs/Feature-Envy.md} for details.
37
+ # @api private
33
38
  class FeatureEnvy < SmellDetector
34
39
  def self.smell_category
35
40
  'LowCohesion'
@@ -42,6 +47,7 @@ module Reek
42
47
  # @return [Array<SmellWarning>]
43
48
  #
44
49
  def examine_context(method_ctx)
50
+ return [] unless method_ctx.references_self?
45
51
  method_ctx.envious_receivers.map do |ref, occurs|
46
52
  target = ref.to_s
47
53
  SmellWarning.new self,
@@ -1,6 +1,5 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
3
- require 'reek/source/code_comment'
1
+ require_relative 'smell_detector'
2
+ require_relative 'smell_warning'
4
3
 
5
4
  module Reek
6
5
  module Smells
@@ -8,12 +7,14 @@ module Reek
8
7
  # It is considered good practice to annotate every class and module
9
8
  # with a brief comment outlining its responsibilities.
10
9
  #
10
+ # See {file:docs/Irresponsible-Module.md} for details.
11
+ # @api private
11
12
  class IrresponsibleModule < SmellDetector
12
13
  def self.contexts # :nodoc:
13
14
  [:class]
14
15
  end
15
16
 
16
- def self.descriptive # :nodoc:
17
+ def descriptive # :nodoc:
17
18
  @descriptive ||= {}
18
19
  end
19
20
 
@@ -23,13 +24,17 @@ module Reek
23
24
  # @return [Array<SmellWarning>]
24
25
  #
25
26
  def examine_context(ctx)
26
- comment = Source::CodeComment.new(ctx.exp.comments)
27
- return [] if self.class.descriptive[ctx.full_name] ||= comment.descriptive?
27
+ return [] if descriptive?(ctx)
28
+ expression = ctx.exp
28
29
  [SmellWarning.new(self,
29
30
  context: ctx.full_name,
30
- lines: [ctx.exp.line],
31
+ lines: [expression.line],
31
32
  message: 'has no descriptive comment',
32
- parameters: { name: ctx.exp.text_name })]
33
+ parameters: { name: expression.text_name })]
34
+ end
35
+
36
+ def descriptive?(ctx)
37
+ descriptive[ctx.full_name] ||= ctx.descriptively_commented?
33
38
  end
34
39
  end
35
40
  end
@@ -1,6 +1,6 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
3
- require 'reek/core/smell_configuration'
1
+ require_relative 'smell_configuration'
2
+ require_relative 'smell_detector'
3
+ require_relative 'smell_warning'
4
4
 
5
5
  module Reek
6
6
  module Smells
@@ -12,6 +12,8 @@ module Reek
12
12
  # Currently +LongParameterList+ reports any method or block with too
13
13
  # many parameters.
14
14
  #
15
+ # See {file:docs/Long-Parameter-List.md} for details.
16
+ # @api private
15
17
  class LongParameterList < SmellDetector
16
18
  # The name of the config field that sets the maximum number of
17
19
  # parameters permitted in any method or block.
@@ -21,7 +23,7 @@ module Reek
21
23
  def self.default_config
22
24
  super.merge(
23
25
  MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
24
- Core::SmellConfiguration::OVERRIDES_KEY => {
26
+ SmellConfiguration::OVERRIDES_KEY => {
25
27
  'initialize' => { MAX_ALLOWED_PARAMS_KEY => 5 }
26
28
  }
27
29
  )
@@ -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
@@ -7,6 +7,8 @@ module Reek
7
7
  # A variant on LongParameterList that checks the number of items
8
8
  # passed to a block by a +yield+ call.
9
9
  #
10
+ # See {file:docs/Long-Yield-List.md} for details.
11
+ # @api private
10
12
  class LongYieldList < SmellDetector
11
13
  # The name of the config field that sets the maximum number of
12
14
  # parameters permitted in any method or block.
@@ -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
  # hard to tell initialization order and parameters so having 'initialize'
9
9
  # in a module is usually a bad idea
10
10
  #
11
+ # See {file:docs/Module-Initialize.md} for details.
12
+ # @api private
11
13
  class ModuleInitialize < SmellDetector
12
14
  def self.contexts # :nodoc:
13
15
  [:module]
@@ -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
  # +NestedIterators+ reports failing methods only once.
10
10
  #
11
+ # See {file:docs/Nested-Iterators.md} for details.
12
+ # @api private
11
13
  class NestedIterators < SmellDetector
12
14
  # The name of the config field that sets the maximum depth
13
15
  # of nested iterators to be permitted within any single method.
@@ -1,10 +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
  # Checking for nil is a special kind of type check, and therefore a case of
7
7
  # SimulatedPolymorphism.
8
+ #
9
+ # See {file:docs/Nil-Check.md} for details.
10
+ # @api private
8
11
  class NilCheck < SmellDetector
9
12
  def self.smell_category
10
13
  'SimulatedPolymorphism'
@@ -19,7 +22,7 @@ module Reek
19
22
  SmellWarning.new self,
20
23
  context: ctx.full_name,
21
24
  lines: [node.line],
22
- message: 'performs a nil-check.'
25
+ message: 'performs a nil-check'
23
26
  end
24
27
  end
25
28
 
@@ -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
@@ -22,6 +22,8 @@ module Reek
22
22
  #
23
23
  # Such a method is called PrimaDonnaMethod and is reported as a smell.
24
24
  #
25
+ # See {file:docs/Prima-Donna-Method.md} for details.
26
+ # @api private
25
27
  class PrimaDonnaMethod < SmellDetector
26
28
  def self.contexts # :nodoc:
27
29
  [:class]
@@ -29,18 +31,25 @@ module Reek
29
31
 
30
32
  def examine_context(ctx)
31
33
  ctx.node_instance_methods.map do |method_sexp|
32
- next unless method_sexp.ends_with_bang?
34
+ check_for_smells(method_sexp, ctx)
35
+ end.compact
36
+ end
33
37
 
34
- version_without_bang = ctx.node_instance_methods.find do |sexp_item|
35
- sexp_item.name.to_s == method_sexp.name_without_bang
36
- end
37
- next if version_without_bang
38
+ private
38
39
 
39
- SmellWarning.new self,
40
- context: ctx.full_name,
41
- lines: [ctx.exp.line],
42
- message: "has prima donna method `#{method_sexp.name}`"
43
- end.compact
40
+ def check_for_smells(method_sexp, ctx)
41
+ return unless method_sexp.ends_with_bang?
42
+
43
+ version_without_bang = ctx.node_instance_methods.find do |sexp_item|
44
+ sexp_item.name.to_s == method_sexp.name_without_bang
45
+ end
46
+
47
+ return if version_without_bang
48
+
49
+ SmellWarning.new self,
50
+ context: ctx.full_name,
51
+ lines: [ctx.exp.line],
52
+ message: "has prima donna method `#{method_sexp.name}`"
44
53
  end
45
54
  end
46
55
  end