reek 1.2.6 → 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (195) hide show
  1. data/.yardopts +10 -0
  2. data/History.txt +20 -0
  3. data/README.md +90 -0
  4. data/bin/reek +2 -2
  5. data/config/defaults.reek +34 -4
  6. data/features/masking_smells.feature +35 -15
  7. data/features/options.feature +2 -0
  8. data/features/rake_task.feature +11 -18
  9. data/features/reports.feature +13 -15
  10. data/features/samples.feature +90 -105
  11. data/features/stdin.feature +3 -6
  12. data/features/step_definitions/reek_steps.rb +8 -4
  13. data/features/support/env.rb +2 -3
  14. data/features/yaml.feature +124 -0
  15. data/lib/reek.rb +8 -4
  16. data/lib/reek/cli/application.rb +46 -0
  17. data/lib/reek/cli/command_line.rb +106 -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 +91 -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/block_context.rb +18 -0
  24. data/lib/reek/core/class_context.rb +23 -0
  25. data/lib/reek/core/code_context.rb +72 -0
  26. data/lib/reek/core/code_parser.rb +192 -0
  27. data/lib/reek/core/detector_stack.rb +29 -0
  28. data/lib/reek/core/masking_collection.rb +46 -0
  29. data/lib/reek/core/method_context.rb +132 -0
  30. data/lib/reek/core/module_context.rb +64 -0
  31. data/lib/reek/{object_refs.rb → core/object_refs.rb} +8 -6
  32. data/lib/reek/{singleton_method_context.rb → core/singleton_method_context.rb} +10 -5
  33. data/lib/reek/core/smell_configuration.rb +66 -0
  34. data/lib/reek/core/sniffer.rb +110 -0
  35. data/lib/reek/core/stop_context.rb +26 -0
  36. data/lib/reek/examiner.rb +88 -0
  37. data/lib/reek/rake/task.rb +124 -0
  38. data/lib/reek/smell_warning.rb +69 -13
  39. data/lib/reek/smells.rb +29 -0
  40. data/lib/reek/smells/attribute.rb +13 -14
  41. data/lib/reek/smells/boolean_parameter.rb +33 -0
  42. data/lib/reek/smells/class_variable.rb +8 -6
  43. data/lib/reek/smells/control_couple.rb +33 -17
  44. data/lib/reek/smells/data_clump.rb +10 -6
  45. data/lib/reek/smells/duplication.rb +24 -14
  46. data/lib/reek/smells/feature_envy.rb +11 -6
  47. data/lib/reek/smells/irresponsible_module.rb +28 -0
  48. data/lib/reek/smells/large_class.rb +9 -7
  49. data/lib/reek/smells/long_method.rb +6 -5
  50. data/lib/reek/smells/long_parameter_list.rb +11 -9
  51. data/lib/reek/smells/long_yield_list.rb +37 -7
  52. data/lib/reek/smells/nested_iterators.rb +34 -9
  53. data/lib/reek/smells/simulated_polymorphism.rb +15 -11
  54. data/lib/reek/smells/smell_detector.rb +24 -12
  55. data/lib/reek/smells/uncommunicative_method_name.rb +76 -0
  56. data/lib/reek/smells/uncommunicative_module_name.rb +76 -0
  57. data/lib/reek/smells/{uncommunicative_name.rb → uncommunicative_parameter_name.rb} +14 -26
  58. data/lib/reek/smells/uncommunicative_variable_name.rb +90 -0
  59. data/lib/reek/smells/utility_function.rb +33 -9
  60. data/lib/reek/source.rb +18 -0
  61. data/lib/reek/source/code_comment.rb +19 -0
  62. data/lib/reek/source/config_file.rb +72 -0
  63. data/lib/reek/source/core_extras.rb +46 -0
  64. data/lib/reek/source/sexp_formatter.rb +16 -0
  65. data/lib/reek/source/source_code.rb +44 -0
  66. data/lib/reek/source/source_file.rb +32 -0
  67. data/lib/reek/source/source_locator.rb +36 -0
  68. data/lib/reek/source/tree_dresser.rb +128 -0
  69. data/lib/reek/spec.rb +51 -0
  70. data/lib/reek/spec/should_reek.rb +34 -0
  71. data/lib/reek/spec/should_reek_of.rb +37 -0
  72. data/lib/reek/spec/should_reek_only_of.rb +36 -0
  73. data/reek.gemspec +5 -5
  74. data/spec/reek/{help_command_spec.rb → cli/help_command_spec.rb} +3 -4
  75. data/spec/reek/{reek_command_spec.rb → cli/reek_command_spec.rb} +8 -7
  76. data/spec/reek/cli/report_spec.rb +26 -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/block_context_spec.rb +26 -0
  80. data/spec/reek/core/class_context_spec.rb +53 -0
  81. data/spec/reek/{code_context_spec.rb → core/code_context_spec.rb} +15 -37
  82. data/spec/reek/{code_parser_spec.rb → core/code_parser_spec.rb} +5 -5
  83. data/spec/reek/{config_spec.rb → core/config_spec.rb} +2 -6
  84. data/spec/reek/{masking_collection_spec.rb → core/masking_collection_spec.rb} +3 -4
  85. data/spec/reek/{method_context_spec.rb → core/method_context_spec.rb} +6 -7
  86. data/spec/reek/core/module_context_spec.rb +42 -0
  87. data/spec/reek/{object_refs_spec.rb → core/object_refs_spec.rb} +5 -6
  88. data/spec/reek/core/singleton_method_context_spec.rb +15 -0
  89. data/spec/reek/core/smell_configuration_spec.rb +11 -0
  90. data/spec/reek/core/stop_context_spec.rb +17 -0
  91. data/spec/reek/examiner_spec.rb +42 -0
  92. data/spec/reek/smell_warning_spec.rb +82 -33
  93. data/spec/reek/smells/attribute_spec.rb +33 -7
  94. data/spec/reek/smells/boolean_parameter_spec.rb +76 -0
  95. data/spec/reek/smells/class_variable_spec.rb +15 -6
  96. data/spec/reek/smells/control_couple_spec.rb +40 -29
  97. data/spec/reek/smells/data_clump_spec.rb +28 -7
  98. data/spec/reek/smells/duplication_spec.rb +47 -41
  99. data/spec/reek/smells/feature_envy_spec.rb +76 -18
  100. data/spec/reek/smells/irresponsible_module_spec.rb +37 -0
  101. data/spec/reek/smells/large_class_spec.rb +91 -56
  102. data/spec/reek/smells/long_method_spec.rb +32 -7
  103. data/spec/reek/smells/long_parameter_list_spec.rb +42 -13
  104. data/spec/reek/smells/long_yield_list_spec.rb +65 -0
  105. data/spec/reek/smells/nested_iterators_spec.rb +94 -3
  106. data/spec/reek/smells/simulated_polymorphism_spec.rb +48 -20
  107. data/spec/reek/smells/smell_detector_shared.rb +28 -0
  108. data/spec/reek/smells/uncommunicative_method_name_spec.rb +57 -0
  109. data/spec/reek/smells/uncommunicative_module_name_spec.rb +67 -0
  110. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +61 -0
  111. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +124 -0
  112. data/spec/reek/smells/utility_function_spec.rb +45 -3
  113. data/spec/reek/source/code_comment_spec.rb +24 -0
  114. data/spec/reek/source/object_source_spec.rb +20 -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 +165 -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/corrupt_config_file/dirty.rb +1 -1
  130. data/spec/samples/empty_config_file/dirty.rb +2 -1
  131. data/spec/samples/exceptions.reek +1 -1
  132. data/spec/samples/masked/dirty.rb +2 -1
  133. data/spec/samples/masked/masked.reek +3 -1
  134. data/spec/samples/mixed_results/clean_one.rb +1 -0
  135. data/spec/samples/mixed_results/clean_three.rb +1 -0
  136. data/spec/samples/mixed_results/clean_two.rb +1 -0
  137. data/spec/samples/mixed_results/dirty_one.rb +1 -0
  138. data/spec/samples/mixed_results/dirty_two.rb +1 -0
  139. data/spec/samples/not_quite_masked/dirty.rb +2 -1
  140. data/spec/samples/not_quite_masked/masked.reek +1 -1
  141. data/spec/samples/overrides/masked/dirty.rb +2 -1
  142. data/spec/samples/overrides/masked/lower.reek +3 -1
  143. data/spec/samples/three_clean_files/clean_one.rb +1 -0
  144. data/spec/samples/three_clean_files/clean_three.rb +1 -0
  145. data/spec/samples/three_clean_files/clean_two.rb +1 -0
  146. data/spec/samples/two_smelly_files/dirty_one.rb +2 -1
  147. data/spec/samples/two_smelly_files/dirty_two.rb +2 -1
  148. data/spec/spec_helper.rb +1 -2
  149. data/tasks/reek.rake +2 -2
  150. data/tasks/test.rake +12 -3
  151. metadata +81 -62
  152. data/README.rdoc +0 -84
  153. data/lib/reek/adapters/application.rb +0 -46
  154. data/lib/reek/adapters/command_line.rb +0 -77
  155. data/lib/reek/adapters/config_file.rb +0 -31
  156. data/lib/reek/adapters/core_extras.rb +0 -64
  157. data/lib/reek/adapters/rake_task.rb +0 -121
  158. data/lib/reek/adapters/report.rb +0 -86
  159. data/lib/reek/adapters/source.rb +0 -72
  160. data/lib/reek/adapters/spec.rb +0 -133
  161. data/lib/reek/block_context.rb +0 -62
  162. data/lib/reek/class_context.rb +0 -41
  163. data/lib/reek/code_context.rb +0 -68
  164. data/lib/reek/code_parser.rb +0 -203
  165. data/lib/reek/configuration.rb +0 -57
  166. data/lib/reek/detector_stack.rb +0 -37
  167. data/lib/reek/help_command.rb +0 -14
  168. data/lib/reek/if_context.rb +0 -18
  169. data/lib/reek/masking_collection.rb +0 -33
  170. data/lib/reek/method_context.rb +0 -138
  171. data/lib/reek/module_context.rb +0 -49
  172. data/lib/reek/name.rb +0 -57
  173. data/lib/reek/reek_command.rb +0 -28
  174. data/lib/reek/sexp_formatter.rb +0 -10
  175. data/lib/reek/sniffer.rb +0 -177
  176. data/lib/reek/stop_context.rb +0 -35
  177. data/lib/reek/tree_dresser.rb +0 -82
  178. data/lib/reek/version_command.rb +0 -14
  179. data/lib/reek/yield_call_context.rb +0 -12
  180. data/spec/reek/adapters/report_spec.rb +0 -31
  181. data/spec/reek/adapters/should_reek_of_spec.rb +0 -138
  182. data/spec/reek/adapters/should_reek_only_of_spec.rb +0 -87
  183. data/spec/reek/block_context_spec.rb +0 -65
  184. data/spec/reek/class_context_spec.rb +0 -161
  185. data/spec/reek/configuration_spec.rb +0 -12
  186. data/spec/reek/if_context_spec.rb +0 -17
  187. data/spec/reek/module_context_spec.rb +0 -46
  188. data/spec/reek/name_spec.rb +0 -37
  189. data/spec/reek/object_source_spec.rb +0 -23
  190. data/spec/reek/singleton_method_context_spec.rb +0 -16
  191. data/spec/reek/smells/smell_detector_spec.rb +0 -36
  192. data/spec/reek/smells/uncommunicative_name_spec.rb +0 -146
  193. data/spec/reek/sniffer_spec.rb +0 -11
  194. data/spec/reek/stop_context_spec.rb +0 -33
  195. data/spec/reek/tree_dresser_spec.rb +0 -20
@@ -0,0 +1,28 @@
1
+ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
2
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
3
+
4
+ # Part of Reek's core
5
+ module Reek
6
+ module Smells
7
+
8
+ #
9
+ # It is considered good practice to annotate every class and module
10
+ # with a brief comment outlining its responsibilities.
11
+ #
12
+ class IrresponsibleModule < SmellDetector
13
+
14
+ def self.contexts # :nodoc:
15
+ [:class]
16
+ end
17
+
18
+ #
19
+ # Checks the given class or module for a descriptive comment.
20
+ # Remembers any smells found.
21
+ #
22
+ def examine_context(ctx)
23
+ comment = Source::CodeComment.new(ctx.exp.comments)
24
+ found(ctx, "has no descriptive comment") unless comment.is_descriptive?
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,5 +1,5 @@
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
3
 
4
4
  module Reek
5
5
  module Smells
@@ -15,6 +15,8 @@ module Reek
15
15
  # included modules.
16
16
  #
17
17
  class LargeClass < SmellDetector
18
+ SUBCLASS_TOO_MANY_METHODS = 'TooManyMethods'
19
+ SUBCLASS_TOO_MANY_IVARS = 'TooManyInstanceVariables'
18
20
 
19
21
  # The name of the config field that sets the maximum number of methods
20
22
  # permitted in a class.
@@ -40,20 +42,20 @@ module Reek
40
42
  )
41
43
  end
42
44
 
43
- def initialize(config = LargeClass.default_config)
44
- super(config)
45
+ def initialize(source, config = LargeClass.default_config)
46
+ super(source, config)
45
47
  end
46
48
 
47
49
  def check_num_methods(klass) # :nodoc:
48
50
  actual = klass.local_nodes(:defn).length
49
51
  return if actual <= value(MAX_ALLOWED_METHODS_KEY, klass, DEFAULT_MAX_METHODS)
50
- found(klass, "has at least #{actual} methods")
52
+ found(klass, "has at least #{actual} methods", SUBCLASS_TOO_MANY_METHODS, {'method_count' => actual})
51
53
  end
52
54
 
53
55
  def check_num_ivars(klass) # :nodoc:
54
- count = klass.variable_names.length
56
+ count = klass.local_nodes(:iasgn).map {|iasgn| iasgn[1]}.uniq.length
55
57
  return if count <= value(MAX_ALLOWED_IVARS_KEY, klass, DEFAULT_MAX_IVARS)
56
- found(klass, "has at least #{count} instance variables")
58
+ found(klass, "has at least #{count} instance variables", SUBCLASS_TOO_MANY_IVARS, {'ivar_count' => count})
57
59
  end
58
60
 
59
61
  #
@@ -1,5 +1,5 @@
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
3
 
4
4
  module Reek
5
5
  module Smells
@@ -11,6 +11,7 @@ module Reek
11
11
  # 5 statements.
12
12
  #
13
13
  class LongMethod < SmellDetector
14
+ SUBCLASS_TOO_MANY_STATEMENTS = 'TooManyStatements'
14
15
 
15
16
  # The name of the config field that sets the maximum number of
16
17
  # statements permitted in any method.
@@ -25,8 +26,8 @@ module Reek
25
26
  )
26
27
  end
27
28
 
28
- def initialize(config = LongMethod.default_config)
29
- super(config)
29
+ def initialize(source, config = LongMethod.default_config)
30
+ super(source, config)
30
31
  end
31
32
 
32
33
  #
@@ -36,7 +37,7 @@ module Reek
36
37
  def examine_context(method)
37
38
  num = method.num_statements
38
39
  return false if num <= value(MAX_ALLOWED_STATEMENTS_KEY, method, DEFAULT_MAX_STATEMENTS)
39
- found(method, "has approx #{num} statements")
40
+ found(method, "has approx #{num} statements", SUBCLASS_TOO_MANY_STATEMENTS, {'statement_count' => num})
40
41
  end
41
42
  end
42
43
  end
@@ -1,4 +1,6 @@
1
- require 'reek/smells/smell_detector'
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__))), 'core', 'smell_configuration')
2
4
 
3
5
  module Reek
4
6
  module Smells
@@ -24,25 +26,25 @@ module Reek
24
26
  def self.default_config
25
27
  super.adopt(
26
28
  MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
27
- SmellConfiguration::OVERRIDES_KEY => {
29
+ Core::SmellConfiguration::OVERRIDES_KEY => {
28
30
  "initialize" => {MAX_ALLOWED_PARAMS_KEY => 5}
29
31
  }
30
32
  )
31
33
  end
32
34
 
33
- def initialize(config = LongParameterList.default_config)
34
- super(config)
35
- @action = 'has'
35
+ def initialize(source, config = LongParameterList.default_config)
36
+ super(source, config)
36
37
  end
37
38
 
38
39
  #
39
40
  # Checks the number of parameters in the given scope.
40
41
  # Remembers any smells found.
41
42
  #
42
- def examine_context(ctx)
43
- num_params = ctx.parameters.length
44
- return false if num_params <= value(MAX_ALLOWED_PARAMS_KEY, ctx, DEFAULT_MAX_ALLOWED_PARAMS)
45
- found(ctx, "#{@action} #{num_params} parameters")
43
+ def examine_context(method_ctx)
44
+ num_params = method_ctx.parameters.length
45
+ return false if num_params <= value(MAX_ALLOWED_PARAMS_KEY, method_ctx, DEFAULT_MAX_ALLOWED_PARAMS)
46
+ found(method_ctx, "has #{num_params} parameters",
47
+ 'LongParameterList', {'parameter_count' => num_params})
46
48
  end
47
49
  end
48
50
  end
@@ -1,17 +1,47 @@
1
- require 'reek/smells/smell_detector'
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')
2
3
 
3
4
  module Reek
4
5
  module Smells
5
6
 
6
- class LongYieldList < LongParameterList
7
+ #
8
+ # A variant on LongParameterList that checks the number of items
9
+ # passed to a block by a +yield+ call.
10
+ #
11
+ class LongYieldList < SmellDetector
7
12
 
8
- def self.contexts # :nodoc:
9
- [:yield]
13
+ # The name of the config field that sets the maximum number of
14
+ # parameters permitted in any method or block.
15
+ MAX_ALLOWED_PARAMS_KEY = 'max_params'
16
+
17
+ # The default value of the +MAX_ALLOWED_PARAMS_KEY+ configuration
18
+ # value.
19
+ DEFAULT_MAX_ALLOWED_PARAMS = 3
20
+
21
+ def self.default_config
22
+ super.adopt(
23
+ MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS
24
+ )
25
+ end
26
+
27
+ def initialize(source, config = LongYieldList.default_config)
28
+ super(source, config)
10
29
  end
11
30
 
12
- def initialize(config = LongYieldList.default_config)
13
- super
14
- @action = 'yields'
31
+ #
32
+ # Checks the number of parameters in the given scope.
33
+ # Remembers any smells found.
34
+ #
35
+ def examine_context(method_ctx)
36
+ method_ctx.local_nodes(:yield).each do |yield_node|
37
+ num_params = yield_node.args.length
38
+ next if num_params <= value(MAX_ALLOWED_PARAMS_KEY, method_ctx, DEFAULT_MAX_ALLOWED_PARAMS)
39
+ smell = SmellWarning.new('LongParameterList', method_ctx.full_name, [yield_node.line],
40
+ "yields #{num_params} parameters", @masked,
41
+ @source, 'LongYieldList', {'parameter_count' => num_params})
42
+ @smells_found << smell
43
+ #SMELL: serious duplication
44
+ end
15
45
  end
16
46
  end
17
47
  end
@@ -1,5 +1,5 @@
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
3
 
4
4
  module Reek
5
5
  module Smells
@@ -10,18 +10,43 @@ module Reek
10
10
  # +NestedIterators+ reports failing methods only once.
11
11
  #
12
12
  class NestedIterators < SmellDetector
13
-
14
- def self.contexts # :nodoc:
15
- [:iter]
16
- end
13
+ # SMELL: should be a subclass of UnnecessaryComplexity
17
14
 
18
15
  #
19
16
  # Checks whether the given +block+ is inside another.
20
17
  # Remembers any smells found.
21
18
  #
22
- def examine_context(block)
23
- return false unless block.nested_block?
24
- found(block, 'is nested')
19
+ def examine_context(method_ctx)
20
+ find_deepest_iterators(method_ctx).each do |iter|
21
+ depth = iter[1]
22
+ found(method_ctx, "contains iterators nested #{depth} deep", '',
23
+ {'depth' => depth}, [iter[0].line])
24
+ end
25
+ # TODO: report the nesting depth and the innermost line
26
+ # BUG: no longer reports nesting outside methods (eg. in Optparse)
27
+ end
28
+
29
+ def find_deepest_iterators(method_ctx)
30
+ result = []
31
+ find_iters(method_ctx.exp, 1, result)
32
+ result.select {|item| item[1] >= 2}
33
+ end
34
+
35
+ def find_iters(exp, depth, result)
36
+ exp.each do |elem|
37
+ next unless Sexp === elem
38
+ case elem.first
39
+ when :iter
40
+ find_iters([elem.call], depth, result)
41
+ current = result.length
42
+ find_iters([elem.block], depth+1, result)
43
+ result << [elem, depth] if result.length == current
44
+ when :class, :defn, :defs, :module
45
+ next
46
+ else
47
+ find_iters(elem, depth, result)
48
+ end
49
+ end
25
50
  end
26
51
  end
27
52
  end
@@ -1,6 +1,6 @@
1
- require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
3
- require 'reek/sexp_formatter'
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')
4
4
 
5
5
  module Reek
6
6
  module Smells
@@ -38,8 +38,8 @@ module Reek
38
38
  super.adopt(MAX_IDENTICAL_IFS_KEY => DEFAULT_MAX_IFS)
39
39
  end
40
40
 
41
- def initialize(config = SimulatedPolymorphism.default_config)
42
- super(config)
41
+ def initialize(source, config = SimulatedPolymorphism.default_config)
42
+ super(source, config)
43
43
  end
44
44
 
45
45
  #
@@ -47,8 +47,12 @@ module Reek
47
47
  # Remembers any smells found.
48
48
  #
49
49
  def examine_context(klass)
50
- conditional_counts(klass).each do |key, val|
51
- found(klass, "tests #{SexpFormatter.format(key)} at least #{val} times") if val > value(MAX_IDENTICAL_IFS_KEY, klass, DEFAULT_MAX_IFS)
50
+ conditional_counts(klass).each do |key, lines|
51
+ occurs = lines.length
52
+ next unless occurs > value(MAX_IDENTICAL_IFS_KEY, klass, DEFAULT_MAX_IFS)
53
+ expr = Source::SexpFormatter.format(key)
54
+ found(klass, "tests #{expr} at least #{occurs} times",
55
+ 'RepeatedConditional', {'expression' => expr, 'occurrences' => occurs}, lines)
52
56
  end
53
57
  end
54
58
 
@@ -58,13 +62,13 @@ module Reek
58
62
  # occurs. Ignores nested classes and modules.
59
63
  #
60
64
  def conditional_counts(klass)
61
- result = Hash.new(0)
65
+ result = Hash.new {|hash,key| hash[key] = []}
62
66
  collector = proc { |node|
63
67
  condition = node.condition
64
- result[condition] += 1 unless condition == s(:call, nil, :block_given?, s(:arglist))
68
+ next if condition == s(:call, nil, :block_given?, s(:arglist))
69
+ result[condition].push(condition.line)
65
70
  }
66
- klass.local_nodes(:if, &collector)
67
- klass.local_nodes(:case, &collector)
71
+ [:if, :case].each {|stmt| klass.local_nodes(stmt, &collector) }
68
72
  result
69
73
  end
70
74
  end
@@ -1,4 +1,6 @@
1
- require 'reek/configuration'
1
+ require 'set'
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__))), 'core', 'smell_configuration')
2
4
 
3
5
  module Reek
4
6
  module Smells
@@ -7,11 +9,14 @@ module Reek
7
9
  def self.default_config
8
10
  super.adopt(EXCLUDE_KEY => ['initialize'])
9
11
  end
10
- def initialize(config = self.class.default_config)
11
- super
12
+ def initialize(source, config = self.class.default_config)
13
+ super(source, config)
12
14
  end
13
15
  end
14
16
 
17
+ #
18
+ # Shared responsibilities of all smell detectors.
19
+ #
15
20
  class SmellDetector
16
21
 
17
22
  # The name of the config field that lists the names of code contexts
@@ -30,14 +35,17 @@ module Reek
30
35
 
31
36
  def default_config
32
37
  {
33
- SmellConfiguration::ENABLED_KEY => true,
38
+ Core::SmellConfiguration::ENABLED_KEY => true,
34
39
  EXCLUDE_KEY => DEFAULT_EXCLUDE_SET
35
40
  }
36
41
  end
37
42
  end
38
43
 
39
- def initialize(config = self.class.default_config)
40
- @config = SmellConfiguration.new(config)
44
+ attr_reader :smells_found # SMELL: only published for tests
45
+
46
+ def initialize(source, config = self.class.default_config)
47
+ @source = source
48
+ @config = Core::SmellConfiguration.new(config)
41
49
  @smells_found = Set.new
42
50
  @masked = false
43
51
  end
@@ -62,7 +70,7 @@ module Reek
62
70
  end
63
71
 
64
72
  def copy
65
- self.class.new(@config.deep_copy)
73
+ self.class.new(@source, @config.deep_copy)
66
74
  end
67
75
 
68
76
  def supersede_with(config)
@@ -73,20 +81,20 @@ module Reek
73
81
  end
74
82
 
75
83
  def examine(context)
76
- before = @smells_found.size
77
84
  examine_context(context) if @config.enabled? and !exception?(context)
78
- @smells_found.length > before
79
85
  end
80
86
 
81
87
  def examine_context(context)
82
88
  end
83
-
89
+
84
90
  def exception?(context)
85
91
  context.matches?(value(EXCLUDE_KEY, context, DEFAULT_EXCLUDE_SET))
86
92
  end
87
93
 
88
- def found(context, message)
89
- smell = SmellWarning.new(self, context.full_name, context.exp.line, message, @masked)
94
+ def found(context, message, subclass = '', parameters = {}, lines = nil)
95
+ lines ||= [context.exp.line] # SMELL: nil?!?!?! Yuk
96
+ smell = SmellWarning.new(self.class.name.split(/::/)[-1], context.full_name, lines, message, @masked,
97
+ @source, subclass, parameters)
90
98
  @smells_found << smell
91
99
  smell
92
100
  end
@@ -97,6 +105,10 @@ module Reek
97
105
  false
98
106
  end
99
107
 
108
+ def smell_type
109
+ self.class.name.split(/::/)[-1]
110
+ end
111
+
100
112
  def report_on(report)
101
113
  @smells_found.each { |smell| smell.report_on(report) }
102
114
  end
@@ -0,0 +1,76 @@
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
+ # The name of the config field that lists the regexps of
23
+ # smelly names to be reported.
24
+ REJECT_KEY = 'reject'
25
+
26
+ DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/]
27
+
28
+ # The name of the config field that lists the specific names that are
29
+ # to be treated as exceptions; these names will not be reported as
30
+ # uncommunicative.
31
+ ACCEPT_KEY = 'accept'
32
+
33
+ DEFAULT_ACCEPT_SET = []
34
+
35
+ def self.default_config
36
+ super.adopt(
37
+ REJECT_KEY => DEFAULT_REJECT_SET,
38
+ ACCEPT_KEY => DEFAULT_ACCEPT_SET
39
+ )
40
+ end
41
+
42
+ def self.contexts # :nodoc:
43
+ [:defn, :defs]
44
+ end
45
+
46
+ def initialize(source, config = UncommunicativeMethodName.default_config)
47
+ super(source, config)
48
+ end
49
+
50
+ #
51
+ # Checks the given +context+ for uncommunicative names.
52
+ # Remembers any smells found.
53
+ #
54
+ def examine_context(method_ctx)
55
+ name = method_ctx.name
56
+ return false if accept?(method_ctx)
57
+ return false unless is_bad_name?(name, method_ctx)
58
+ smell = SmellWarning.new('UncommunicativeName', method_ctx.full_name, [method_ctx.exp.line],
59
+ "has the name '#{name}'", @masked,
60
+ @source, 'UncommunicativeMethodName', {'method_name' => name.to_s})
61
+ @smells_found << smell
62
+ #SMELL: serious duplication
63
+ end
64
+
65
+ def accept?(context)
66
+ value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(context.full_name)
67
+ end
68
+
69
+ def is_bad_name?(name, context) # :nodoc:
70
+ var = name.to_s.gsub(/^[@\*\&]*/, '')
71
+ return false if value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(var)
72
+ value(REJECT_KEY, context, DEFAULT_REJECT_SET).detect {|patt| patt === var}
73
+ end
74
+ end
75
+ end
76
+ end