reek 1.2.6 → 1.2.8

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 (209) hide show
  1. data/.yardopts +10 -0
  2. data/History.txt +62 -0
  3. data/README.md +74 -0
  4. data/bin/reek +2 -2
  5. data/config/defaults.reek +44 -5
  6. data/features/api.feature +20 -0
  7. data/features/masking_smells.feature +60 -42
  8. data/features/options.feature +7 -2
  9. data/features/rake_task.feature +29 -22
  10. data/features/reports.feature +9 -41
  11. data/features/samples.feature +184 -196
  12. data/features/stdin.feature +5 -8
  13. data/features/step_definitions/reek_steps.rb +8 -4
  14. data/features/support/env.rb +2 -3
  15. data/features/yaml.feature +85 -0
  16. data/lib/reek/cli/application.rb +46 -0
  17. data/lib/reek/cli/command_line.rb +108 -0
  18. data/lib/reek/cli/help_command.rb +18 -0
  19. data/lib/reek/cli/reek_command.rb +37 -0
  20. data/lib/reek/cli/report.rb +58 -0
  21. data/lib/reek/cli/version_command.rb +19 -0
  22. data/lib/reek/cli/yaml_command.rb +32 -0
  23. data/lib/reek/core/code_context.rb +64 -0
  24. data/lib/reek/core/code_parser.rb +166 -0
  25. data/lib/reek/core/method_context.rb +84 -0
  26. data/lib/reek/core/module_context.rb +20 -0
  27. data/lib/reek/core/object_refs.rb +51 -0
  28. data/lib/reek/core/singleton_method_context.rb +20 -0
  29. data/lib/reek/core/smell_configuration.rb +62 -0
  30. data/lib/reek/core/sniffer.rb +105 -0
  31. data/lib/reek/core/stop_context.rb +30 -0
  32. data/lib/reek/core/warning_collector.rb +27 -0
  33. data/lib/reek/examiner.rb +104 -0
  34. data/lib/reek/rake/task.rb +142 -0
  35. data/lib/reek/smell_warning.rb +73 -23
  36. data/lib/reek/smells/attribute.rb +26 -21
  37. data/lib/reek/smells/boolean_parameter.rb +38 -0
  38. data/lib/reek/smells/class_variable.rb +22 -9
  39. data/lib/reek/smells/control_couple.rb +40 -16
  40. data/lib/reek/smells/data_clump.rb +114 -23
  41. data/lib/reek/smells/duplication.rb +55 -15
  42. data/lib/reek/smells/feature_envy.rb +16 -7
  43. data/lib/reek/smells/irresponsible_module.rb +37 -0
  44. data/lib/reek/smells/large_class.rb +37 -19
  45. data/lib/reek/smells/long_method.rb +20 -9
  46. data/lib/reek/smells/long_parameter_list.rb +22 -10
  47. data/lib/reek/smells/long_yield_list.rb +43 -7
  48. data/lib/reek/smells/nested_iterators.rb +68 -8
  49. data/lib/reek/smells/simulated_polymorphism.rb +26 -15
  50. data/lib/reek/smells/smell_detector.rb +29 -51
  51. data/lib/reek/smells/uncommunicative_method_name.rb +74 -0
  52. data/lib/reek/smells/uncommunicative_module_name.rb +74 -0
  53. data/lib/reek/smells/uncommunicative_parameter_name.rb +79 -0
  54. data/lib/reek/smells/uncommunicative_variable_name.rb +89 -0
  55. data/lib/reek/smells/utility_function.rb +51 -12
  56. data/lib/reek/smells.rb +29 -0
  57. data/lib/reek/source/code_comment.rb +37 -0
  58. data/lib/reek/source/config_file.rb +72 -0
  59. data/lib/reek/source/core_extras.rb +46 -0
  60. data/lib/reek/source/reference_collector.rb +28 -0
  61. data/lib/reek/source/sexp_formatter.rb +17 -0
  62. data/lib/reek/source/source_code.rb +44 -0
  63. data/lib/reek/source/source_file.rb +32 -0
  64. data/lib/reek/source/source_locator.rb +42 -0
  65. data/lib/reek/source/tree_dresser.rb +204 -0
  66. data/lib/reek/source.rb +18 -0
  67. data/lib/reek/spec/should_reek.rb +31 -0
  68. data/lib/reek/spec/should_reek_of.rb +37 -0
  69. data/lib/reek/spec/should_reek_only_of.rb +37 -0
  70. data/lib/reek/spec.rb +51 -0
  71. data/lib/reek.rb +8 -4
  72. data/reek.gemspec +9 -7
  73. data/spec/matchers/smell_of_matcher.rb +58 -0
  74. data/spec/reek/{help_command_spec.rb → cli/help_command_spec.rb} +3 -4
  75. data/spec/reek/cli/reek_command_spec.rb +46 -0
  76. data/spec/reek/cli/report_spec.rb +30 -0
  77. data/spec/reek/{version_command_spec.rb → cli/version_command_spec.rb} +3 -3
  78. data/spec/reek/cli/yaml_command_spec.rb +47 -0
  79. data/spec/reek/core/code_context_spec.rb +145 -0
  80. data/spec/reek/{code_parser_spec.rb → core/code_parser_spec.rb} +7 -6
  81. data/spec/reek/{config_spec.rb → core/config_spec.rb} +2 -6
  82. data/spec/reek/{method_context_spec.rb → core/method_context_spec.rb} +16 -36
  83. data/spec/reek/core/module_context_spec.rb +27 -0
  84. data/spec/reek/{object_refs_spec.rb → core/object_refs_spec.rb} +5 -6
  85. data/spec/reek/core/singleton_method_context_spec.rb +9 -0
  86. data/spec/reek/core/smell_configuration_spec.rb +11 -0
  87. data/spec/reek/core/stop_context_spec.rb +17 -0
  88. data/spec/reek/core/warning_collector_spec.rb +27 -0
  89. data/spec/reek/examiner_spec.rb +103 -0
  90. data/spec/reek/smell_warning_spec.rb +74 -82
  91. data/spec/reek/smells/attribute_spec.rb +42 -23
  92. data/spec/reek/smells/behaves_like_variable_detector.rb +2 -2
  93. data/spec/reek/smells/boolean_parameter_spec.rb +66 -0
  94. data/spec/reek/smells/class_variable_spec.rb +60 -75
  95. data/spec/reek/smells/control_couple_spec.rb +37 -32
  96. data/spec/reek/smells/data_clump_spec.rb +63 -24
  97. data/spec/reek/smells/duplication_spec.rb +106 -57
  98. data/spec/reek/smells/feature_envy_spec.rb +113 -92
  99. data/spec/reek/smells/irresponsible_module_spec.rb +58 -0
  100. data/spec/reek/smells/large_class_spec.rb +67 -57
  101. data/spec/reek/smells/long_method_spec.rb +40 -10
  102. data/spec/reek/smells/long_parameter_list_spec.rb +50 -28
  103. data/spec/reek/smells/long_yield_list_spec.rb +57 -0
  104. data/spec/reek/smells/nested_iterators_spec.rb +115 -7
  105. data/spec/reek/smells/simulated_polymorphism_spec.rb +56 -20
  106. data/spec/reek/smells/smell_detector_shared.rb +42 -0
  107. data/spec/reek/smells/uncommunicative_method_name_spec.rb +42 -0
  108. data/spec/reek/smells/uncommunicative_module_name_spec.rb +66 -0
  109. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +71 -0
  110. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +120 -0
  111. data/spec/reek/smells/utility_function_spec.rb +50 -4
  112. data/spec/reek/source/code_comment_spec.rb +82 -0
  113. data/spec/reek/source/object_source_spec.rb +20 -0
  114. data/spec/reek/source/reference_collector_spec.rb +53 -0
  115. data/spec/reek/{adapters/source_spec.rb → source/source_code_spec.rb} +7 -8
  116. data/spec/reek/source/tree_dresser_spec.rb +270 -0
  117. data/spec/reek/spec/should_reek_of_spec.rb +76 -0
  118. data/spec/reek/spec/should_reek_only_of_spec.rb +89 -0
  119. data/spec/reek/{adapters → spec}/should_reek_spec.rb +8 -32
  120. data/spec/samples/all_but_one_masked/clean_one.rb +1 -0
  121. data/spec/samples/all_but_one_masked/dirty.rb +1 -0
  122. data/spec/samples/all_but_one_masked/masked.reek +5 -1
  123. data/spec/samples/clean_due_to_masking/clean_one.rb +1 -0
  124. data/spec/samples/clean_due_to_masking/clean_three.rb +1 -0
  125. data/spec/samples/clean_due_to_masking/clean_two.rb +1 -0
  126. data/spec/samples/clean_due_to_masking/dirty_one.rb +1 -1
  127. data/spec/samples/clean_due_to_masking/dirty_two.rb +1 -1
  128. data/spec/samples/clean_due_to_masking/masked.reek +5 -1
  129. data/spec/samples/config/allow_duplication.reek +3 -0
  130. data/spec/samples/config/deeper_nested_iterators.reek +3 -0
  131. data/spec/samples/corrupt_config_file/dirty.rb +1 -1
  132. data/spec/samples/demo/demo.rb +8 -0
  133. data/spec/samples/empty_config_file/dirty.rb +2 -1
  134. data/spec/samples/exceptions.reek +1 -1
  135. data/spec/samples/inline_config/dirty.rb +16 -0
  136. data/spec/samples/inline_config/masked.reek +7 -0
  137. data/spec/samples/mask_some/dirty.rb +8 -0
  138. data/spec/samples/mask_some/some.reek +8 -0
  139. data/spec/samples/masked/dirty.rb +2 -1
  140. data/spec/samples/masked/masked.reek +3 -1
  141. data/spec/samples/mixed_results/clean_one.rb +1 -0
  142. data/spec/samples/mixed_results/clean_three.rb +1 -0
  143. data/spec/samples/mixed_results/clean_two.rb +1 -0
  144. data/spec/samples/mixed_results/dirty_one.rb +1 -0
  145. data/spec/samples/mixed_results/dirty_two.rb +1 -0
  146. data/spec/samples/not_quite_masked/dirty.rb +2 -1
  147. data/spec/samples/not_quite_masked/masked.reek +1 -1
  148. data/spec/samples/overrides/masked/dirty.rb +2 -1
  149. data/spec/samples/overrides/masked/lower.reek +3 -1
  150. data/spec/samples/three_clean_files/clean_one.rb +1 -0
  151. data/spec/samples/three_clean_files/clean_three.rb +1 -0
  152. data/spec/samples/three_clean_files/clean_two.rb +1 -0
  153. data/spec/samples/two_smelly_files/dirty_one.rb +2 -1
  154. data/spec/samples/two_smelly_files/dirty_two.rb +2 -1
  155. data/spec/spec_helper.rb +10 -2
  156. data/tasks/reek.rake +2 -2
  157. data/tasks/test.rake +12 -3
  158. metadata +121 -79
  159. data/README.rdoc +0 -84
  160. data/features/profile.feature +0 -34
  161. data/lib/reek/adapters/application.rb +0 -46
  162. data/lib/reek/adapters/command_line.rb +0 -77
  163. data/lib/reek/adapters/config_file.rb +0 -31
  164. data/lib/reek/adapters/core_extras.rb +0 -64
  165. data/lib/reek/adapters/rake_task.rb +0 -121
  166. data/lib/reek/adapters/report.rb +0 -86
  167. data/lib/reek/adapters/source.rb +0 -72
  168. data/lib/reek/adapters/spec.rb +0 -133
  169. data/lib/reek/block_context.rb +0 -62
  170. data/lib/reek/class_context.rb +0 -41
  171. data/lib/reek/code_context.rb +0 -68
  172. data/lib/reek/code_parser.rb +0 -203
  173. data/lib/reek/configuration.rb +0 -57
  174. data/lib/reek/detector_stack.rb +0 -37
  175. data/lib/reek/help_command.rb +0 -14
  176. data/lib/reek/if_context.rb +0 -18
  177. data/lib/reek/masking_collection.rb +0 -33
  178. data/lib/reek/method_context.rb +0 -138
  179. data/lib/reek/module_context.rb +0 -49
  180. data/lib/reek/name.rb +0 -57
  181. data/lib/reek/object_refs.rb +0 -49
  182. data/lib/reek/reek_command.rb +0 -28
  183. data/lib/reek/sexp_formatter.rb +0 -10
  184. data/lib/reek/singleton_method_context.rb +0 -26
  185. data/lib/reek/smells/uncommunicative_name.rb +0 -84
  186. data/lib/reek/sniffer.rb +0 -177
  187. data/lib/reek/stop_context.rb +0 -35
  188. data/lib/reek/tree_dresser.rb +0 -82
  189. data/lib/reek/version_command.rb +0 -14
  190. data/lib/reek/yield_call_context.rb +0 -12
  191. data/spec/reek/adapters/report_spec.rb +0 -31
  192. data/spec/reek/adapters/should_reek_of_spec.rb +0 -138
  193. data/spec/reek/adapters/should_reek_only_of_spec.rb +0 -87
  194. data/spec/reek/block_context_spec.rb +0 -65
  195. data/spec/reek/class_context_spec.rb +0 -161
  196. data/spec/reek/code_context_spec.rb +0 -182
  197. data/spec/reek/configuration_spec.rb +0 -12
  198. data/spec/reek/if_context_spec.rb +0 -17
  199. data/spec/reek/masking_collection_spec.rb +0 -236
  200. data/spec/reek/module_context_spec.rb +0 -46
  201. data/spec/reek/name_spec.rb +0 -37
  202. data/spec/reek/object_source_spec.rb +0 -23
  203. data/spec/reek/reek_command_spec.rb +0 -45
  204. data/spec/reek/singleton_method_context_spec.rb +0 -16
  205. data/spec/reek/smells/smell_detector_spec.rb +0 -36
  206. data/spec/reek/smells/uncommunicative_name_spec.rb +0 -146
  207. data/spec/reek/sniffer_spec.rb +0 -11
  208. data/spec/reek/stop_context_spec.rb +0 -33
  209. data/spec/reek/tree_dresser_spec.rb +0 -20
@@ -0,0 +1,85 @@
1
+ @yaml
2
+ Feature: Report smells using simple YAML layout
3
+ In order to parse reek's output simply and consistently, simply
4
+ output a list of smells in Yaml.
5
+
6
+ Scenario: output is empty when there are no smells
7
+ When I run reek --yaml spec/samples/three_clean_files
8
+ Then it succeeds
9
+ And stdout equals ""
10
+
11
+ @masking
12
+ Scenario: masked smells always appear
13
+ When I run reek --yaml spec/samples/masked/dirty.rb
14
+ Then the exit status indicates smells
15
+ And it reports:
16
+ """
17
+ ---
18
+ - !ruby/object:Reek::SmellWarning
19
+ location:
20
+ lines:
21
+ - 4
22
+ - 6
23
+ context: Dirty#a
24
+ source: spec/samples/masked/dirty.rb
25
+ smell:
26
+ class: Duplication
27
+ occurrences: 2
28
+ subclass: DuplicateMethodCall
29
+ call: "@s.title"
30
+ message: calls @s.title twice
31
+ status:
32
+ is_active: true
33
+ - !ruby/object:Reek::SmellWarning
34
+ location:
35
+ lines:
36
+ - 5
37
+ - 7
38
+ context: Dirty#a
39
+ source: spec/samples/masked/dirty.rb
40
+ smell:
41
+ class: Duplication
42
+ occurrences: 2
43
+ subclass: DuplicateMethodCall
44
+ call: puts(@s.title)
45
+ message: calls puts(@s.title) twice
46
+ status:
47
+ is_active: true
48
+ - !ruby/object:Reek::SmellWarning
49
+ location:
50
+ lines:
51
+ - 5
52
+ context: Dirty#a
53
+ source: spec/samples/masked/dirty.rb
54
+ smell:
55
+ class: NestedIterators
56
+ depth: 2
57
+ subclass: NestedIterators
58
+ message: contains iterators nested 2 deep
59
+ status:
60
+ is_active: true
61
+
62
+ """
63
+
64
+ @stdin
65
+ Scenario: return non-zero status when there are smells
66
+ When I pass "class Turn; end" to reek --yaml
67
+ Then the exit status indicates smells
68
+ And it reports:
69
+ """
70
+ ---
71
+ - !ruby/object:Reek::SmellWarning
72
+ location:
73
+ lines:
74
+ - 1
75
+ context: Turn
76
+ source: $stdin
77
+ smell:
78
+ class: IrresponsibleModule
79
+ subclass: IrresponsibleModule
80
+ module_name: Turn
81
+ message: has no descriptive comment
82
+ status:
83
+ is_active: true
84
+
85
+ """
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'command_line')
2
+
3
+ module Reek
4
+ module Cli
5
+
6
+ #
7
+ # Represents an instance of a Reek application.
8
+ # This is the entry point for all invocations of Reek from the
9
+ # command line.
10
+ #
11
+ class Application
12
+
13
+ STATUS_SUCCESS = 0
14
+ STATUS_ERROR = 1
15
+ STATUS_SMELLS = 2
16
+
17
+ def initialize(argv)
18
+ @options = Options.new(argv)
19
+ @status = STATUS_SUCCESS
20
+ end
21
+
22
+ def execute
23
+ begin
24
+ cmd = @options.parse
25
+ cmd.execute(self)
26
+ rescue Exception => error
27
+ $stderr.puts "Error: #{error}"
28
+ @status = STATUS_ERROR
29
+ end
30
+ return @status
31
+ end
32
+
33
+ def output(text)
34
+ print text
35
+ end
36
+
37
+ def report_success
38
+ @status = STATUS_SUCCESS
39
+ end
40
+
41
+ def report_smells
42
+ @status = STATUS_SMELLS
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,108 @@
1
+ require 'optparse'
2
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'report')
3
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'reek_command')
4
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'help_command')
5
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'version_command')
6
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'yaml_command')
7
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
8
+
9
+ module Reek
10
+ module Cli
11
+
12
+ #
13
+ # Parses the command line
14
+ #
15
+ class Options
16
+
17
+ def initialize(argv)
18
+ @argv = argv
19
+ @parser = OptionParser.new
20
+ @report_class = VerboseReport
21
+ @command_class = ReekCommand
22
+ @config_files = []
23
+ set_options
24
+ end
25
+
26
+ def banner
27
+ progname = @parser.program_name
28
+ # SMELL:
29
+ # The following banner isn't really correct. Help, Version and Reek
30
+ # are really sub-commands (in the git/svn sense) and so the usage
31
+ # banner should show three different command-lines. The other
32
+ # options are all flags for the Reek sub-command.
33
+ #
34
+ # reek -h|--help Display a help message
35
+ #
36
+ # reek -v|--version Output the tool's version number
37
+ #
38
+ # reek [options] files List the smells in the given files
39
+ # -c|--config file Specify file(s) with config options
40
+ # -q|-[no-]quiet Only list files that have smells
41
+ # files Names of files or dirs to be checked
42
+ #
43
+ return <<EOB
44
+ Usage: #{progname} [options] [files]
45
+
46
+ Examples:
47
+
48
+ #{progname} lib/*.rb
49
+ #{progname} -q lib
50
+ cat my_class.rb | #{progname}
51
+
52
+ See http://wiki.github.com/kevinrutherford/reek for detailed help.
53
+
54
+ EOB
55
+ end
56
+
57
+ def set_options
58
+ @parser.banner = banner
59
+ @parser.separator "Common options:"
60
+ @parser.on("-h", "--help", "Show this message") do
61
+ @command_class = HelpCommand
62
+ end
63
+ @parser.on("-v", "--version", "Show version") do
64
+ @command_class = VersionCommand
65
+ end
66
+
67
+ @parser.separator "\nConfiguration:"
68
+ @parser.on("-c", "--config FILE", "Read configuration options from FILE") do |file|
69
+ @config_files << file
70
+ end
71
+
72
+ @parser.separator "\nReport formatting:"
73
+ @parser.on("-q", "--[no-]quiet", "Suppress headings for smell-free source files") do |opt|
74
+ @report_class = opt ? QuietReport : VerboseReport
75
+ end
76
+ @parser.on("-y", "--yaml", "Report smells in YAML format") do
77
+ @command_class = YamlCommand
78
+ # SMELL: the args passed to the command should be tested, because it may
79
+ # turn out that they are passed too soon, ie. before the files have been
80
+ # separated out from the options
81
+ end
82
+ end
83
+
84
+ def parse
85
+ @parser.parse!(@argv)
86
+ if @command_class == HelpCommand
87
+ HelpCommand.new(@parser)
88
+ elsif @command_class == VersionCommand
89
+ VersionCommand.new(@parser.program_name)
90
+ elsif @command_class == YamlCommand
91
+ sources = get_sources
92
+ YamlCommand.create(sources, @config_files)
93
+ else
94
+ sources = get_sources
95
+ ReekCommand.create(sources, @report_class, @config_files)
96
+ end
97
+ end
98
+
99
+ def get_sources
100
+ if @argv.empty?
101
+ return [$stdin.to_reek_source('$stdin')]
102
+ else
103
+ return Source::SourceLocator.new(@argv).all_sources
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module Reek
3
+ module Cli
4
+
5
+ #
6
+ # A command to display usage information for this application.
7
+ #
8
+ class HelpCommand
9
+ def initialize(parser)
10
+ @parser = parser
11
+ end
12
+ def execute(view)
13
+ view.output(@parser.to_s)
14
+ view.report_success
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'examiner')
2
+
3
+ module Reek
4
+ module Cli
5
+
6
+ #
7
+ # A command to collect smells from a set of sources and write them out in
8
+ # text report format.
9
+ #
10
+ class ReekCommand
11
+ def self.create(sources, report_class, config_files = [])
12
+ new(report_class, sources, config_files)
13
+ end
14
+
15
+ def initialize(report_class, sources, config_files = [])
16
+ @sources = sources
17
+ @report_class = report_class
18
+ @config_files = config_files
19
+ end
20
+
21
+ def execute(view)
22
+ had_smells = false
23
+ @sources.each do |source|
24
+ examiner = Examiner.new(source, @config_files)
25
+ rpt = @report_class.new(examiner)
26
+ had_smells ||= examiner.smelly?
27
+ view.output(rpt.report)
28
+ end
29
+ if had_smells
30
+ view.report_smells
31
+ else
32
+ view.report_success
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ module Reek
2
+ module Cli
3
+
4
+ module ReportFormatter
5
+ def header(desc, count)
6
+ result = "#{desc} -- #{count} warning"
7
+ result += 's' unless count == 1
8
+ result
9
+ end
10
+
11
+ def format_list(warnings)
12
+ warnings.map do |warning|
13
+ " #{warning.context} #{warning.message} (#{warning.smell_class})"
14
+ end.join("\n")
15
+ end
16
+
17
+ module_function :format_list
18
+ end
19
+
20
+ #
21
+ # A report that lists every source, including those that have no smells.
22
+ #
23
+ class VerboseReport
24
+
25
+ include ReportFormatter
26
+
27
+ def initialize(examiner)
28
+ @examiner = examiner
29
+ end
30
+
31
+ def report
32
+ warnings = @examiner.smells
33
+ warning_count = warnings.length
34
+ result = header(@examiner.description, warning_count)
35
+ result += ":\n#{format_list(warnings)}" if warning_count > 0
36
+ result + "\n"
37
+ end
38
+ end
39
+
40
+ #
41
+ # A report that lists a section for each source that has smells.
42
+ #
43
+ class QuietReport
44
+
45
+ include ReportFormatter
46
+
47
+ def initialize(examiner)
48
+ @warnings = examiner.smells
49
+ @smell_count = @warnings.length
50
+ @desc = examiner.description
51
+ end
52
+
53
+ def report
54
+ @smell_count > 0 ? "#{header(@desc, @smell_count)}:\n#{format_list(@warnings)}\n" : ''
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))), 'reek')
2
+
3
+ module Reek
4
+ module Cli
5
+
6
+ #
7
+ # A command to report the application's current version number.
8
+ #
9
+ class VersionCommand
10
+ def initialize(progname)
11
+ @progname = progname
12
+ end
13
+ def execute(view)
14
+ view.output("#{@progname} #{Reek::VERSION}\n")
15
+ view.report_success
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'examiner')
2
+
3
+ module Reek
4
+ module Cli
5
+
6
+ #
7
+ # A command to collect smells from a set of sources and write them out in
8
+ # YAML format.
9
+ #
10
+ class YamlCommand
11
+ def self.create(sources, config_files)
12
+ examiners = sources.map {|src| Examiner.new(src, config_files) }
13
+ new(examiners)
14
+ end
15
+
16
+ def initialize(examiners)
17
+ @examiners = examiners
18
+ end
19
+
20
+ def execute(view)
21
+ smells = []
22
+ @examiners.each {|examiner| smells += examiner.smells}
23
+ if smells.empty?
24
+ view.report_success
25
+ else
26
+ view.output(smells.to_yaml)
27
+ view.report_smells
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,64 @@
1
+
2
+ module Reek
3
+ module Core
4
+
5
+ #
6
+ # Superclass for all types of source code context. Each instance represents
7
+ # a code element of some kind, and each provides behaviour relevant to that
8
+ # code element. CodeContexts form a tree in the same way the code does,
9
+ # with each context holding a reference to a unique outer context.
10
+ #
11
+ class CodeContext
12
+
13
+ attr_reader :exp, :config
14
+
15
+ def initialize(outer, exp)
16
+ @outer = outer
17
+ @exp = exp
18
+ @config = local_config
19
+ end
20
+
21
+ def name
22
+ @exp.name
23
+ end
24
+
25
+ def local_nodes(type, &blk)
26
+ each_node(type, [:class, :module], &blk)
27
+ end
28
+
29
+ def each_node(type, ignoring, &blk)
30
+ @exp.each_node(type, ignoring, &blk)
31
+ end
32
+
33
+ def matches?(candidates)
34
+ my_fq_name = full_name
35
+ candidates.any? {|str| /#{str}/ === my_fq_name }
36
+ end
37
+
38
+ #
39
+ # Bounces messages up the context tree to the first enclosing context
40
+ # that knows how to deal with the request.
41
+ #
42
+ def method_missing(method, *args)
43
+ @outer.send(method, *args)
44
+ end
45
+
46
+ def num_methods
47
+ 0
48
+ end
49
+
50
+ def full_name
51
+ outer = @outer ? @outer.full_name : ''
52
+ exp.full_name(outer)
53
+ end
54
+
55
+ def local_config
56
+ return Hash.new if @exp.nil?
57
+ config = Source::CodeComment.new(@exp.comments || '').config
58
+ return config unless @outer
59
+ @outer.config.deep_copy.adopt!(config)
60
+ # no tests for this -----^
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,166 @@
1
+ require 'sexp'
2
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
3
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'module_context')
4
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'stop_context')
5
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'singleton_method_context')
6
+
7
+ module Reek
8
+ module Core
9
+
10
+ #
11
+ # Traverses a Sexp abstract syntax tree and fires events whenever
12
+ # it encounters specific node types.
13
+ #
14
+ class CodeParser
15
+ def initialize(sniffer, ctx = StopContext.new)
16
+ @sniffer = sniffer
17
+ @element = ctx
18
+ end
19
+
20
+ def process(exp)
21
+ meth = "process_#{exp[0]}"
22
+ meth = :process_default unless self.respond_to?(meth)
23
+ self.send(meth, exp)
24
+ @element
25
+ end
26
+
27
+ def process_default(exp)
28
+ exp.each { |sub| process(sub) if Array === sub }
29
+ end
30
+
31
+ def process_module(exp)
32
+ name = Source::SexpFormatter.format(exp[1])
33
+ scope = ModuleContext.new(@element, name, exp)
34
+ push(scope) do
35
+ process_default(exp) unless exp.superclass == [:const, :Struct]
36
+ check_smells(exp[0])
37
+ end
38
+ scope
39
+ end
40
+
41
+ def process_class(exp)
42
+ process_module(exp)
43
+ end
44
+
45
+ def process_defn(exp)
46
+ handle_context(MethodContext, exp[0], exp)
47
+ end
48
+
49
+ def process_defs(exp)
50
+ handle_context(SingletonMethodContext, exp[0], exp)
51
+ end
52
+
53
+ def process_args(exp) end
54
+
55
+ def process_zsuper(exp)
56
+ @element.record_use_of_self
57
+ end
58
+
59
+ def process_block(exp)
60
+ @element.count_statements(CodeParser.count_statements(exp))
61
+ process_default(exp)
62
+ end
63
+
64
+ def process_call(exp)
65
+ @element.record_call_to(exp)
66
+ process_default(exp)
67
+ end
68
+
69
+ def process_attrasgn(exp)
70
+ process_call(exp)
71
+ end
72
+
73
+ def process_op_asgn1(exp)
74
+ process_call(exp)
75
+ end
76
+
77
+ def process_if(exp)
78
+ count_clause(exp[2])
79
+ count_clause(exp[3])
80
+ process_default(exp)
81
+ @element.count_statements(-1)
82
+ end
83
+
84
+ def process_while(exp)
85
+ process_until(exp)
86
+ end
87
+
88
+ def process_until(exp)
89
+ count_clause(exp[2])
90
+ process_case(exp)
91
+ end
92
+
93
+ def process_for(exp)
94
+ count_clause(exp[3])
95
+ process_case(exp)
96
+ end
97
+
98
+ def process_rescue(exp)
99
+ count_clause(exp[1])
100
+ process_case(exp)
101
+ end
102
+
103
+ def process_resbody(exp)
104
+ process_when(exp)
105
+ end
106
+
107
+ def process_case(exp)
108
+ process_default(exp)
109
+ @element.count_statements(-1)
110
+ end
111
+
112
+ def process_when(exp)
113
+ count_clause(exp[2])
114
+ process_default(exp)
115
+ end
116
+
117
+ def process_ivar(exp)
118
+ process_iasgn(exp)
119
+ end
120
+
121
+ def process_iasgn(exp)
122
+ @element.record_use_of_self
123
+ process_default(exp)
124
+ end
125
+
126
+ def process_self(exp)
127
+ @element.record_use_of_self
128
+ end
129
+
130
+ def count_clause(sexp)
131
+ if sexp and !sexp.has_type?(:block)
132
+ @element.count_statements(1)
133
+ end
134
+ end
135
+
136
+ def self.count_statements(exp)
137
+ stmts = exp[1..-1]
138
+ ignore = 0
139
+ ignore += 1 if stmts[1] == s(:nil)
140
+ stmts.length - ignore
141
+ end
142
+
143
+ private
144
+
145
+ def handle_context(klass, type, exp)
146
+ scope = klass.new(@element, exp)
147
+ push(scope) do
148
+ process_default(exp)
149
+ check_smells(type)
150
+ end
151
+ scope
152
+ end
153
+
154
+ def check_smells(type)
155
+ @sniffer.examine(@element, type)
156
+ end
157
+
158
+ def push(context)
159
+ orig = @element
160
+ @element = context
161
+ yield
162
+ @element = orig
163
+ end
164
+ end
165
+ end
166
+ end