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,74 @@
1
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # An Uncommunicative Name is a name that doesn't communicate its intent
9
+ # well enough.
10
+ #
11
+ # Poor names make it hard for the reader to build a mental picture
12
+ # of what's going on in the code. They can also be mis-interpreted;
13
+ # and they hurt the flow of reading, because the reader must slow
14
+ # down to interpret the names.
15
+ #
16
+ # Currently +UncommunicativeMethodName+ checks for
17
+ # * 1-character names
18
+ # * names ending with a number
19
+ #
20
+ class UncommunicativeMethodName < SmellDetector
21
+
22
+ SMELL_CLASS = 'UncommunicativeName'
23
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
24
+ METHOD_NAME_KEY = 'method_name'
25
+
26
+ # The name of the config field that lists the regexps of
27
+ # smelly names to be reported.
28
+ REJECT_KEY = 'reject'
29
+
30
+ DEFAULT_REJECT_SET = [/^[a-z]$/, /[0-9]$/, /[A-Z]/]
31
+
32
+ # The name of the config field that lists the specific names that are
33
+ # to be treated as exceptions; these names will not be reported as
34
+ # uncommunicative.
35
+ ACCEPT_KEY = 'accept'
36
+
37
+ DEFAULT_ACCEPT_SET = []
38
+
39
+ def self.default_config
40
+ super.adopt(
41
+ REJECT_KEY => DEFAULT_REJECT_SET,
42
+ ACCEPT_KEY => DEFAULT_ACCEPT_SET
43
+ )
44
+ end
45
+
46
+ def self.contexts # :nodoc:
47
+ [:defn, :defs]
48
+ end
49
+
50
+ def initialize(source, config = UncommunicativeMethodName.default_config)
51
+ super(source, config)
52
+ end
53
+
54
+ #
55
+ # Checks the given +context+ for uncommunicative names.
56
+ #
57
+ # @return [Array<SmellWarning>]
58
+ #
59
+ def examine_context(ctx)
60
+ @reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
61
+ @accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
62
+ name = ctx.name
63
+ return [] if @accept_names.include?(ctx.full_name)
64
+ var = name.to_s.gsub(/^[@\*\&]*/, '')
65
+ return [] if @accept_names.include?(var)
66
+ return [] unless @reject_names.detect {|patt| patt === var}
67
+ smell = SmellWarning.new('UncommunicativeName', ctx.full_name, [ctx.exp.line],
68
+ "has the name '#{name}'",
69
+ @source, 'UncommunicativeMethodName', {METHOD_NAME_KEY => name.to_s})
70
+ [smell]
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,74 @@
1
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # An Uncommunicative Name is a name that doesn't communicate its intent
9
+ # well enough.
10
+ #
11
+ # Poor names make it hard for the reader to build a mental picture
12
+ # of what's going on in the code. They can also be mis-interpreted;
13
+ # and they hurt the flow of reading, because the reader must slow
14
+ # down to interpret the names.
15
+ #
16
+ # Currently +UncommunicativeModuleName+ checks for
17
+ # * 1-character names
18
+ # * names ending with a number
19
+ #
20
+ class UncommunicativeModuleName < SmellDetector
21
+
22
+ SMELL_CLASS = 'UncommunicativeName'
23
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
24
+ MODULE_NAME_KEY = 'module_name'
25
+
26
+ # The name of the config field that lists the regexps of
27
+ # smelly names to be reported.
28
+ REJECT_KEY = 'reject'
29
+
30
+ DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/]
31
+
32
+ # The name of the config field that lists the specific names that are
33
+ # to be treated as exceptions; these names will not be reported as
34
+ # uncommunicative.
35
+ ACCEPT_KEY = 'accept'
36
+
37
+ DEFAULT_ACCEPT_SET = ['Inline::C']
38
+
39
+ def self.default_config
40
+ super.adopt(
41
+ REJECT_KEY => DEFAULT_REJECT_SET,
42
+ ACCEPT_KEY => DEFAULT_ACCEPT_SET
43
+ )
44
+ end
45
+
46
+ def self.contexts # :nodoc:
47
+ [:module, :class]
48
+ end
49
+
50
+ def initialize(source, config = UncommunicativeModuleName.default_config)
51
+ super(source, config)
52
+ end
53
+
54
+ #
55
+ # Checks the given +context+ for uncommunicative names.
56
+ #
57
+ # @return [Array<SmellWarning>]
58
+ #
59
+ def examine_context(ctx)
60
+ @reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
61
+ @accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
62
+ name = ctx.exp.simple_name
63
+ return [] if @accept_names.include?(ctx.full_name)
64
+ var = name.to_s.gsub(/^[@\*\&]*/, '')
65
+ return [] if @accept_names.include?(var)
66
+ return [] unless @reject_names.detect {|patt| patt === var}
67
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
68
+ "has the name '#{name}'",
69
+ @source, SMELL_SUBCLASS, {MODULE_NAME_KEY => name.to_s})
70
+ [smell]
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,79 @@
1
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # An Uncommunicative Name is a name that doesn't communicate its intent
9
+ # well enough.
10
+ #
11
+ # Poor names make it hard for the reader to build a mental picture
12
+ # of what's going on in the code. They can also be mis-interpreted;
13
+ # and they hurt the flow of reading, because the reader must slow
14
+ # down to interpret the names.
15
+ #
16
+ # Currently +UncommunicativeParameterName+ checks for
17
+ # * 1-character names
18
+ # * names ending with a number
19
+ #
20
+ class UncommunicativeParameterName < SmellDetector
21
+
22
+ SMELL_CLASS = 'UncommunicativeName'
23
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
24
+ PARAMETER_NAME_KEY = 'parameter_name'
25
+
26
+ # The name of the config field that lists the regexps of
27
+ # smelly names to be reported.
28
+ REJECT_KEY = 'reject'
29
+
30
+ DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
31
+
32
+ # The name of the config field that lists the specific names that are
33
+ # to be treated as exceptions; these names will not be reported as
34
+ # uncommunicative.
35
+ ACCEPT_KEY = 'accept'
36
+
37
+ DEFAULT_ACCEPT_SET = []
38
+
39
+ def self.default_config
40
+ super.adopt(
41
+ REJECT_KEY => DEFAULT_REJECT_SET,
42
+ ACCEPT_KEY => DEFAULT_ACCEPT_SET
43
+ )
44
+ end
45
+
46
+ def self.contexts # :nodoc:
47
+ [:defn, :defs]
48
+ end
49
+
50
+ def initialize(source, config = UncommunicativeParameterName.default_config)
51
+ super(source, config)
52
+ end
53
+
54
+ #
55
+ # Checks the given +context+ for uncommunicative names.
56
+ #
57
+ # @return [Array<SmellWarning>]
58
+ #
59
+ def examine_context(ctx)
60
+ @reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
61
+ @accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
62
+ ctx.exp.parameter_names.select do |name|
63
+ is_bad_name?(name, ctx)
64
+ end.map do |name|
65
+ smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
66
+ "has the parameter name '#{name}'",
67
+ @source, SMELL_SUBCLASS, {PARAMETER_NAME_KEY => name.to_s})
68
+ smell
69
+ end
70
+ end
71
+
72
+ def is_bad_name?(name, ctx)
73
+ var = name.to_s.gsub(/^[@\*\&]*/, '')
74
+ return false if var == '*' or @accept_names.include?(var)
75
+ @reject_names.detect {|patt| patt === var}
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,89 @@
1
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
+
4
+ module Reek
5
+ module Smells
6
+
7
+ #
8
+ # An Uncommunicative Name is a name that doesn't communicate its intent
9
+ # well enough.
10
+ #
11
+ # Poor names make it hard for the reader to build a mental picture
12
+ # of what's going on in the code. They can also be mis-interpreted;
13
+ # and they hurt the flow of reading, because the reader must slow
14
+ # down to interpret the names.
15
+ #
16
+ # Currently +UncommunicativeName+ checks for
17
+ # * 1-character names
18
+ # * names ending with a number
19
+ #
20
+ class UncommunicativeVariableName < SmellDetector
21
+
22
+ SMELL_CLASS = 'UncommunicativeName'
23
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
24
+ VARIABLE_NAME_KEY = 'variable_name'
25
+
26
+ # The name of the config field that lists the regexps of
27
+ # smelly names to be reported.
28
+ REJECT_KEY = 'reject'
29
+
30
+ DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
31
+
32
+ # The name of the config field that lists the specific names that are
33
+ # to be treated as exceptions; these names will not be reported as
34
+ # uncommunicative.
35
+ ACCEPT_KEY = 'accept'
36
+
37
+ DEFAULT_ACCEPT_SET = []
38
+
39
+ def self.default_config
40
+ super.adopt(
41
+ REJECT_KEY => DEFAULT_REJECT_SET,
42
+ ACCEPT_KEY => DEFAULT_ACCEPT_SET
43
+ )
44
+ end
45
+
46
+ def self.contexts # :nodoc:
47
+ [:module, :class, :defn, :defs]
48
+ end
49
+
50
+ def initialize(source, config = UncommunicativeVariableName.default_config)
51
+ super(source, config)
52
+ end
53
+
54
+ #
55
+ # Checks the given +context+ for uncommunicative names.
56
+ #
57
+ # @return [Array<SmellWarning>]
58
+ #
59
+ def examine_context(ctx)
60
+ @reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
61
+ @accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
62
+ variable_names(ctx.exp).select do |name, lines|
63
+ is_bad_name?(name, ctx)
64
+ end.map do |name, lines|
65
+ SmellWarning.new(SMELL_CLASS, ctx.full_name, lines,
66
+ "has the variable name '#{name}'",
67
+ @source, SMELL_SUBCLASS, {VARIABLE_NAME_KEY => name.to_s})
68
+ end
69
+ end
70
+
71
+ def is_bad_name?(name, ctx)
72
+ var = name.to_s.gsub(/^[@\*\&]*/, '')
73
+ return false if @accept_names.include?(var)
74
+ @reject_names.detect {|patt| patt === var}
75
+ end
76
+
77
+ def variable_names(exp)
78
+ assignment_nodes = exp.each_node(:lasgn, [:class, :module, :defs, :defn])
79
+ case exp.first
80
+ when :class, :module
81
+ assignment_nodes += exp.each_node(:iasgn, [:class, :module])
82
+ end
83
+ result = Hash.new {|hash, key| hash[key] = []}
84
+ assignment_nodes.each {|asgn| result[asgn[1]].push(asgn.line) }
85
+ result
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,6 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
1
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
3
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'reference_collector')
3
4
 
4
5
  module Reek
5
6
  module Smells
@@ -16,8 +17,27 @@ module Reek
16
17
  # * doesn't use any of self's instance variables, and
17
18
  # * doesn't use any of self's methods
18
19
  #
20
+ # A Utility Function often arises because it must manipulate
21
+ # other objects (usually its arguments) to get them into a
22
+ # useful form; one force preventing them (the
23
+ # arguments) doing this themselves is that the common
24
+ # knowledge lives outside the arguments, or the arguments
25
+ # are of too basic a type to justify extending that type.
26
+ # Therefore there must be something which 'knows' about the contents
27
+ # or purposes of the arguments. That thing would have to
28
+ # be more than just a basic type, because the basic types
29
+ # are either containers which don't know about their
30
+ # contents, or they are single objects which can't capture
31
+ # their relationship with their fellows of the same type.
32
+ # So, this thing with the extra knowledge should be
33
+ # reified into a class, and the utility method will most
34
+ # likely belong there.
35
+ #
19
36
  class UtilityFunction < SmellDetector
20
37
 
38
+ SMELL_SUBCLASS = self.name.split(/::/)[-1]
39
+ SMELL_CLASS = 'LowCohesion'
40
+
21
41
  # The name of the config field that sets the maximum number of
22
42
  # calls permitted within a helper method. Any method with more than
23
43
  # this number of method calls on other objects will be considered a
@@ -26,24 +46,43 @@ module Reek
26
46
 
27
47
  DEFAULT_HELPER_CALLS_LIMIT = 1
28
48
 
29
- def self.default_config
30
- super.adopt(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
49
+ class << self
50
+ def contexts # :nodoc:
51
+ [:defn]
52
+ end
53
+ def default_config
54
+ super.adopt(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
55
+ end
31
56
  end
32
57
 
33
- def initialize(config = UtilityFunction.default_config)
34
- super(config)
58
+ def initialize(source, config = UtilityFunction.default_config)
59
+ super(source, config)
35
60
  end
36
61
 
37
62
  #
38
63
  # Checks whether the given +method+ is a utility function.
39
- # Remembers any smells found.
40
64
  #
41
- def examine_context(method)
42
- return false if method.num_statements == 0 or
43
- method.depends_on_instance? or
44
- method.calls.keys.length <= value(HELPER_CALLS_LIMIT_KEY, method, DEFAULT_HELPER_CALLS_LIMIT)
65
+ # @return [Array<SmellWarning>]
66
+ #
67
+ def examine_context(method_ctx)
68
+ return [] if method_ctx.num_statements == 0
69
+ return [] if depends_on_instance?(method_ctx.exp)
70
+ return [] if num_helper_methods(method_ctx) <= value(HELPER_CALLS_LIMIT_KEY, method_ctx, DEFAULT_HELPER_CALLS_LIMIT)
45
71
  # SMELL: loads of calls to value{} with the above pattern
46
- found(method, "doesn't depend on instance state")
72
+ smell = SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
73
+ "doesn't depend on instance state",
74
+ @source, SMELL_SUBCLASS)
75
+ [smell]
76
+ end
77
+
78
+ private
79
+
80
+ def depends_on_instance?(exp)
81
+ Reek::Source::ReferenceCollector.new(exp).num_refs_to_self > 0
82
+ end
83
+
84
+ def num_helper_methods(method_ctx)
85
+ method_ctx.local_nodes(:call).length
47
86
  end
48
87
  end
49
88
  end
@@ -0,0 +1,29 @@
1
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'attribute')
2
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'boolean_parameter')
3
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'class_variable')
4
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'control_couple')
5
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'data_clump')
6
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'duplication')
7
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'feature_envy')
8
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'irresponsible_module')
9
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'large_class')
10
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'long_method')
11
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'long_parameter_list')
12
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'long_yield_list')
13
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'nested_iterators')
14
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'simulated_polymorphism')
15
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_method_name')
16
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_module_name')
17
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_parameter_name')
18
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_variable_name')
19
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'utility_function')
20
+ # SMELL: Duplication -- all these should be found automagically
21
+
22
+ module Reek
23
+
24
+ #
25
+ # This module contains the various smell detectors.
26
+ #
27
+ module Smells
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+
2
+ module Reek
3
+ module Source
4
+
5
+ #
6
+ # A comment header from an abstract syntax tree; found directly above
7
+ # module, class and method definitions.
8
+ #
9
+ class CodeComment
10
+ CONFIG_REGEX = /:reek:(\w+)(:\s*\{.*?\})?/
11
+
12
+ def initialize(text)
13
+ @config = Hash.new { |hash,key| hash[key] = {} }
14
+ @text = text.gsub(CONFIG_REGEX) do |m|
15
+ add_to_config($1, $2)
16
+ ''
17
+ end.gsub(/#/, '').gsub(/\n/, '').strip
18
+ end
19
+
20
+ def config
21
+ @config
22
+ end
23
+
24
+ def is_descriptive?
25
+ @text.split(/\s+/).length >= 2
26
+ end
27
+
28
+ protected
29
+ def add_to_config(smell, options)
30
+ options ||= ': { enabled: false }'
31
+ @config.merge! YAML.load(smell.gsub(/(?:^|_)(.)/) { $1.upcase } + options)
32
+ # extend this to all configs --------------------------^
33
+ # extend to allow configuration of whole smell class, not just subclass
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,72 @@
1
+ require 'yaml'
2
+
3
+ module Reek
4
+ module Source
5
+
6
+ #
7
+ # A file called <something>.reek containing configuration settings for
8
+ # any or all of the smell detectors.
9
+ #
10
+ class ConfigFile
11
+ @@bad_config_files = []
12
+
13
+ #
14
+ # Load the YAML config file from the supplied +file_path+.
15
+ #
16
+ def initialize(file_path)
17
+ @file_path = file_path
18
+ @hash = load
19
+ end
20
+
21
+ #
22
+ # Configure the given sniffer using the contents of the config file.
23
+ #
24
+ def configure(sniffer)
25
+ @hash.each do |klass_name, config|
26
+ klass = find_class(klass_name)
27
+ sniffer.configure(klass, config) if klass
28
+ end
29
+ end
30
+
31
+ #
32
+ # Find the class with this name if it exsits.
33
+ # If not, report the problem and return +nil+.
34
+ #
35
+ def find_class(name)
36
+ begin
37
+ klass = Reek::Smells.const_get(name)
38
+ rescue
39
+ klass = nil
40
+ end
41
+ problem("\"#{name}\" is not a code smell") unless klass
42
+ klass
43
+ end
44
+
45
+ #
46
+ # Load the file path with which this was initialized,
47
+ # unless it is already known to be a bad configuration file.
48
+ # If it won't load, then it is considered a bad file.
49
+ #
50
+ def load
51
+ unless @@bad_config_files.include?(@file_path)
52
+ begin
53
+ return YAML.load_file(@file_path) || {}
54
+ rescue Exception => err
55
+ @@bad_config_files << @file_path
56
+ problem(err.to_s)
57
+ end
58
+ end
59
+ return {}
60
+ end
61
+
62
+ #
63
+ # Report invalid configuration file to standard
64
+ # Error.
65
+ #
66
+ def problem(reason)
67
+ $stderr.puts "Error: Invalid configuration file \"#{File.basename(@file_path)}\" -- #{reason}"
68
+ # SMELL: Duplication of 'Error:'
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'source_code')
2
+
3
+ #
4
+ # Extensions to +File+ needed by Reek.
5
+ #
6
+ class File
7
+ #
8
+ # Creates a new +Source+ that assumes this File contains Ruby source
9
+ # code and prepares it to be examined for code smells.
10
+ #
11
+ # @return [Reek::Source::SourceFile]
12
+ #
13
+ def to_reek_source
14
+ Reek::Source::SourceFile.new(path)
15
+ end
16
+ end
17
+
18
+ #
19
+ # Extensions to +IO+ needed by Reek.
20
+ #
21
+ class IO
22
+ #
23
+ # Creates a new +Source+ that assumes this IO stream contains Ruby source
24
+ # code and prepares it to be examined for code smells.
25
+ #
26
+ # @return [Reek::Source::SourceCode]
27
+ #
28
+ def to_reek_source(description = 'io')
29
+ Reek::Source::SourceCode.new(self.readlines.join, description)
30
+ end
31
+ end
32
+
33
+ #
34
+ # Extensions to +String+ needed by Reek.
35
+ #
36
+ class String
37
+ #
38
+ # Creates a new +Source+ that assumes this string contains Ruby source
39
+ # code and prepares it to be examined for code smells.
40
+ #
41
+ # @return [Reek::Source::SourceCode]
42
+ #
43
+ def to_reek_source
44
+ Reek::Source::SourceCode.new(self, 'string')
45
+ end
46
+ end
@@ -0,0 +1,28 @@
1
+
2
+ module Reek
3
+ module Source
4
+
5
+ #
6
+ # Locates references to the current object within a portion
7
+ # of an abstract syntax tree.
8
+ #
9
+ class ReferenceCollector
10
+ STOP_NODES = [:class, :module, :defn, :defs]
11
+
12
+ def initialize(ast)
13
+ @ast = ast
14
+ end
15
+
16
+ def num_refs_to_self
17
+ result = 0
18
+ [:self, :zsuper, :ivar, :iasgn].each do |node_type|
19
+ @ast.look_for(node_type, STOP_NODES) { result += 1}
20
+ end
21
+ @ast.look_for(:call, STOP_NODES) do |call|
22
+ result += 1 unless call.receiver
23
+ end
24
+ result
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'ruby2ruby'
2
+
3
+ module Reek
4
+ module Source
5
+
6
+ #
7
+ # Formats snippets of syntax tree back into Ruby source code.
8
+ #
9
+ class SexpFormatter
10
+ def self.format(sexp)
11
+ return sexp.to_s unless Array === sexp
12
+ sexp = YAML::load(YAML::dump(sexp))
13
+ Ruby2Ruby.new.process(sexp)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ require 'ruby_parser'
2
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'config_file')
3
+ require File.join(File.dirname(File.expand_path(__FILE__)), 'tree_dresser')
4
+
5
+ module Reek
6
+ module Source
7
+
8
+ #
9
+ # A +Source+ object represents a chunk of Ruby source code.
10
+ #
11
+ class SourceCode
12
+
13
+ @@err_io = $stderr
14
+
15
+ class << self
16
+ def err_io=(io)
17
+ original = @@err_io
18
+ @@err_io = io
19
+ original
20
+ end
21
+ end
22
+
23
+ attr_reader :desc
24
+
25
+ def initialize(code, desc, parser = RubyParser.new)
26
+ @source = code
27
+ @desc = desc
28
+ @parser = parser
29
+ end
30
+
31
+ def configure(sniffer) end
32
+
33
+ def syntax_tree
34
+ begin
35
+ ast = @parser.parse(@source, @desc)
36
+ rescue Exception => error
37
+ @@err_io.puts "#{desc}: #{error.class.name}: #{error}"
38
+ end
39
+ ast ||= s()
40
+ TreeDresser.new.dress(ast)
41
+ end
42
+ end
43
+ end
44
+ end