kevinrutherford-reek 1.1.3.13 → 1.1.3.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/History.txt +8 -2
  2. data/config/defaults.reek +13 -2
  3. data/features/masking_smells.feature +29 -1
  4. data/features/samples.feature +17 -4
  5. data/lib/reek/adapters/object_source.rb +2 -1
  6. data/lib/reek/class_context.rb +7 -0
  7. data/lib/reek/code_parser.rb +3 -1
  8. data/lib/reek/configuration.rb +51 -0
  9. data/lib/reek/detector_stack.rb +1 -3
  10. data/lib/reek/exceptions.reek +3 -0
  11. data/lib/reek/smell_warning.rb +10 -2
  12. data/lib/reek/smells/duplication.rb +4 -2
  13. data/lib/reek/smells/large_class.rb +8 -4
  14. data/lib/reek/smells/long_method.rb +4 -6
  15. data/lib/reek/smells/long_parameter_list.rb +11 -3
  16. data/lib/reek/smells/simulated_polymorphism.rb +58 -0
  17. data/lib/reek/smells/smell_detector.rb +53 -51
  18. data/lib/reek/smells/uncommunicative_name.rb +13 -9
  19. data/lib/reek/sniffer.rb +4 -0
  20. data/lib/reek.rb +1 -1
  21. data/reek.gemspec +3 -3
  22. data/spec/reek/adapters/report_spec.rb +5 -4
  23. data/spec/reek/class_context_spec.rb +21 -0
  24. data/spec/reek/configuration_spec.rb +12 -0
  25. data/spec/reek/smell_warning_spec.rb +30 -2
  26. data/spec/reek/smells/duplication_spec.rb +2 -1
  27. data/spec/reek/smells/feature_envy_spec.rb +15 -0
  28. data/spec/reek/smells/large_class_spec.rb +21 -20
  29. data/spec/reek/smells/simulated_polymorphism_spec.rb +50 -0
  30. data/spec/reek/smells/smell_detector_spec.rb +5 -5
  31. data/spec/reek/smells/uncommunicative_name_spec.rb +14 -0
  32. data/spec/samples/exceptions.reek +4 -0
  33. data/spec/samples/overrides/masked/dirty.rb +7 -0
  34. data/spec/samples/overrides/masked/lower.reek +5 -0
  35. data/spec/samples/overrides/upper.reek +5 -0
  36. data/tasks/test.rake +6 -1
  37. metadata +12 -3
data/History.txt CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  === Major Changes
4
4
  * Reek passes all its tests under ruby 1.8.6, 1.8.7 and 1.9.1 (fixed #16)
5
+ * New smell -- Simulated Polymorphism:
6
+ ** Currently only performs basic check for multiple tests of same value
5
7
  * Reek's output reports are now formatted differently:
6
8
  ** Reek is no longer silent about smell-free source code
7
9
  ** Output now reports on all files examined, even if they have no smells
@@ -16,15 +18,19 @@
16
18
  === Minor Changes
17
19
  * Reek's RDoc is now hosted at http://rdoc.info/projects/kevinrutherford/reek
18
20
  * If a dir is passed on the command-line all **/*.rb files below it are examined (fixed #41)
21
+ * Duplication warnings now report the number of identical calls
22
+ * FeatureEnvy no longer ignores :self when passed as a method parameter
23
+ * LargeClass is disabled when checking in-memory classes (fixed #28)
24
+ * LongParameterList accepts upto 5 parameters for #initialize methods
19
25
  * Several changes to the LongMethod counting algorithm:
20
26
  ** LongMethod now counts statements deeper into each method (fixed #25)
21
27
  ** LongMethod no longer counts control structures, only their contained stmts
22
28
  ** See http://wiki.github.com/kevinrutherford/reek/long-method for details
23
- * LargeClass is disabled when checking in-memory classes (fixed #28)
29
+ * UncommunicativeName warns about any name ending in a number (fixed #18)
24
30
  * UtilityFunction no longer reports methods that call 'super' (fixed #39)
25
- * Duplication warnings now report the number of identical calls
26
31
  * Now reports an error for corrupt config files
27
32
  * Empty config files are ignored
33
+ * Smells can be configured with scope-specific overrides for any config item
28
34
 
29
35
 
30
36
  == 1.1.3 2009-05-19
data/config/defaults.reek CHANGED
@@ -10,6 +10,9 @@ LongParameterList:
10
10
  exclude: []
11
11
 
12
12
  enabled: true
13
+ overrides:
14
+ initialize:
15
+ max_params: 5
13
16
  FeatureEnvy:
14
17
  exclude:
15
18
  - initialize
@@ -21,9 +24,10 @@ UncommunicativeName:
21
24
 
22
25
  enabled: true
23
26
  reject:
24
- - !ruby/regexp /^.[0-9]*$/
27
+ - !ruby/regexp /^.$/
28
+ - !ruby/regexp /[0-9]$/
25
29
  NestedIterators:
26
- exclude: []
30
+ exclude: &id001 []
27
31
 
28
32
  enabled: true
29
33
  LongMethod:
@@ -37,9 +41,13 @@ Duplication:
37
41
  enabled: true
38
42
  max_calls: 1
39
43
  UtilityFunction:
44
+ exclude: *id001
45
+ enabled: true
46
+ SimulatedPolymorphism:
40
47
  exclude: []
41
48
 
42
49
  enabled: true
50
+ max_ifs: 2
43
51
  ControlCouple:
44
52
  exclude:
45
53
  - initialize
@@ -49,3 +57,6 @@ LongYieldList:
49
57
  exclude: []
50
58
 
51
59
  enabled: true
60
+ overrides:
61
+ initialize:
62
+ max_params: 5
@@ -56,7 +56,7 @@ Feature: Masking smells using config files
56
56
 
57
57
  """
58
58
 
59
- Scenario: switch off one smell and show hide them in the report
59
+ Scenario: switch off one smell and hide them in the report
60
60
  When I run reek --no-show-all spec/samples/masked/dirty.rb
61
61
  Then it fails with exit status 2
62
62
  And it reports:
@@ -81,3 +81,31 @@ Feature: Masking smells using config files
81
81
  Dirty#a/block/block is nested (Nested Iterators)
82
82
 
83
83
  """
84
+
85
+ @overrides
86
+ Scenario: lower overrides upper
87
+ When I run reek spec/samples/overrides
88
+ Then it fails with exit status 2
89
+ And it reports:
90
+ """
91
+ spec/samples/overrides/masked/dirty.rb -- 2 warnings (+4 masked):
92
+ Dirty#a calls @s.title twice (Duplication)
93
+ Dirty#a calls puts(@s.title) twice (Duplication)
94
+
95
+ """
96
+
97
+ @overrides
98
+ Scenario: all show up masked even when overridden
99
+ When I run reek --show-all spec/samples/overrides
100
+ Then it fails with exit status 2
101
+ And it reports:
102
+ """
103
+ spec/samples/overrides/masked/dirty.rb -- 2 warnings (+4 masked):
104
+ (masked) Dirty has the variable name '@s' (Uncommunicative Name)
105
+ Dirty#a calls @s.title twice (Duplication)
106
+ Dirty#a calls puts(@s.title) twice (Duplication)
107
+ (masked) Dirty#a has the name 'a' (Uncommunicative Name)
108
+ (masked) Dirty#a/block has the variable name 'x' (Uncommunicative Name)
109
+ (masked) Dirty#a/block/block is nested (Nested Iterators)
110
+
111
+ """
@@ -9,8 +9,11 @@ Feature: Basic smell detection
9
9
  Then it fails with exit status 2
10
10
  And it reports:
11
11
  """
12
- spec/samples/inline.rb -- 32 warnings:
12
+ spec/samples/inline.rb -- 35 warnings (+1 masked):
13
13
  Inline::C has at least 13 instance variables (Large Class)
14
+ Inline::C tests $DEBUG at least 7 times (Simulated Polymorphism)
15
+ Inline::C tests $TESTING at least 4 times (Simulated Polymorphism)
16
+ Inline::C tests @@type_map.has_key?(type) at least 3 times (Simulated Polymorphism)
14
17
  Inline::C#build calls ($? == 0) twice (Duplication)
15
18
  Inline::C#build calls Inline.directory 5 times (Duplication)
16
19
  Inline::C#build calls io.puts 6 times (Duplication)
@@ -50,8 +53,13 @@ Feature: Basic smell detection
50
53
  Then it fails with exit status 2
51
54
  And it reports:
52
55
  """
53
- spec/samples/optparse.rb -- 117 warnings:
56
+ spec/samples/optparse.rb -- 121 warnings:
54
57
  OptionParser has at least 59 methods (Large Class)
58
+ OptionParser tests ((argv.size == 1) and Array.===(argv[0])) at least 3 times (Simulated Polymorphism)
59
+ OptionParser tests a at least 7 times (Simulated Polymorphism)
60
+ OptionParser tests default_pattern at least 7 times (Simulated Polymorphism)
61
+ OptionParser tests not_style at least 3 times (Simulated Polymorphism)
62
+ OptionParser tests s at least 7 times (Simulated Polymorphism)
55
63
  OptionParser#CompletingHash#match/block/block is nested (Nested Iterators)
56
64
  OptionParser#Completion::complete calls candidates.size twice (Duplication)
57
65
  OptionParser#Completion::complete calls k.id2name twice (Duplication)
@@ -102,7 +110,6 @@ Feature: Basic smell detection
102
110
  OptionParser#Switch#summarize has the variable name 'l' (Uncommunicative Name)
103
111
  OptionParser#Switch#summarize has the variable name 'r' (Uncommunicative Name)
104
112
  OptionParser#Switch#summarize has the variable name 's' (Uncommunicative Name)
105
- OptionParser#Switch#summarize refers to left more than self (Feature Envy)
106
113
  OptionParser#Switch#summarize/block has the variable name 's' (Uncommunicative Name)
107
114
  OptionParser#Switch#summarize/block/block is nested (Nested Iterators)
108
115
  OptionParser#block has the variable name 'f' (Uncommunicative Name)
@@ -176,8 +183,12 @@ Feature: Basic smell detection
176
183
  Then it fails with exit status 2
177
184
  And it reports:
178
185
  """
179
- spec/samples/redcloth.rb -- 93 warnings:
186
+ spec/samples/redcloth.rb -- 99 warnings:
180
187
  RedCloth has at least 44 methods (Large Class)
188
+ RedCloth tests atts at least 6 times (Simulated Polymorphism)
189
+ RedCloth tests codepre.zero? at least 3 times (Simulated Polymorphism)
190
+ RedCloth tests href at least 3 times (Simulated Polymorphism)
191
+ RedCloth tests title at least 4 times (Simulated Polymorphism)
181
192
  RedCloth#block has the variable name 'a' (Uncommunicative Name)
182
193
  RedCloth#block has the variable name 'b' (Uncommunicative Name)
183
194
  RedCloth#block_markdown_atx refers to text more than self (Feature Envy)
@@ -235,6 +246,8 @@ Feature: Basic smell detection
235
246
  RedCloth#inline_markdown_reflink/block has the variable name 'm' (Uncommunicative Name)
236
247
  RedCloth#inline_textile_code/block has the variable name 'm' (Uncommunicative Name)
237
248
  RedCloth#inline_textile_image has approx 17 statements (Long Method)
249
+ RedCloth#inline_textile_image/block has the variable name 'href_a1' (Uncommunicative Name)
250
+ RedCloth#inline_textile_image/block has the variable name 'href_a2' (Uncommunicative Name)
238
251
  RedCloth#inline_textile_image/block has the variable name 'm' (Uncommunicative Name)
239
252
  RedCloth#inline_textile_link has approx 9 statements (Long Method)
240
253
  RedCloth#inline_textile_link/block has the variable name 'm' (Uncommunicative Name)
@@ -1,4 +1,5 @@
1
1
  require 'reek/adapters/source'
2
+ require 'reek/configuration'
2
3
 
3
4
  module Reek
4
5
  class ObjectSource < Source # :nodoc:
@@ -13,7 +14,7 @@ module Reek
13
14
 
14
15
  def configure(sniffer)
15
16
  super
16
- disabled_config = {Reek::Smells::SmellDetector::ENABLED_KEY => false}
17
+ disabled_config = {Reek::SmellConfiguration::ENABLED_KEY => false}
17
18
  sniffer.configure(LargeClass, disabled_config)
18
19
  end
19
20
 
@@ -24,6 +24,8 @@ module Reek
24
24
  CodeParser.new(sniffer).process_class(source.syntax_tree)
25
25
  end
26
26
 
27
+ attr_reader :conditionals
28
+
27
29
  # SMELL: inconsistent with other contexts (not linked to the sexp)
28
30
  def initialize(outer, name, superclass = nil)
29
31
  super(outer, nil)
@@ -31,6 +33,7 @@ module Reek
31
33
  @superclass = superclass
32
34
  @parsed_methods = []
33
35
  @instance_variables = Set.new
36
+ @conditionals = []
34
37
  end
35
38
 
36
39
  def myself
@@ -74,5 +77,9 @@ module Reek
74
77
  def variable_names
75
78
  @instance_variables
76
79
  end
80
+
81
+ def record_conditional(exp)
82
+ @conditionals << exp
83
+ end
77
84
  end
78
85
  end
@@ -128,6 +128,7 @@ module Reek
128
128
  end
129
129
 
130
130
  def process_if(exp)
131
+ @element.record_conditional(exp[1])
131
132
  count_clause(exp[2])
132
133
  count_clause(exp[3])
133
134
  handle_context(IfContext, :if, exp)
@@ -159,6 +160,7 @@ module Reek
159
160
  end
160
161
 
161
162
  def process_case(exp)
163
+ @element.record_conditional(exp[1])
162
164
  process_default(exp)
163
165
  @element.count_statements(-1)
164
166
  end
@@ -184,7 +186,7 @@ module Reek
184
186
  end
185
187
 
186
188
  def process_self(exp)
187
- @element.record_depends_on_self
189
+ @element.record_use_of_self
188
190
  end
189
191
 
190
192
  def count_clause(sexp)
@@ -0,0 +1,51 @@
1
+ module Reek
2
+
3
+ #
4
+ # Represents a single set of configuration options for a smell detector
5
+ #
6
+ class SmellConfiguration
7
+
8
+ # The name of the config field that specifies whether a smell is
9
+ # enabled. Set to +true+ or +false+.
10
+ ENABLED_KEY = 'enabled'
11
+
12
+ # The name of the config field that sets scope-specific overrides
13
+ # for other values in the current smell detector's configuration.
14
+ OVERRIDES_KEY = 'overrides'
15
+
16
+ attr_reader :hash
17
+
18
+ def initialize(hash)
19
+ @hash = hash
20
+ end
21
+
22
+ # SMELL: Getter
23
+ def enabled?
24
+ @hash[ENABLED_KEY]
25
+ end
26
+
27
+ def overrides_for(context)
28
+ Overrides.new(@hash.fetch(OVERRIDES_KEY, {})).for_context(context)
29
+ end
30
+
31
+ # Retrieves the value, if any, for the given +key+.
32
+ #
33
+ # Returns +fall_back+ if this config has no value for the key.
34
+ #
35
+ def value(key, context, fall_back)
36
+ overrides_for(context).each { |conf| return conf[key] if conf.has_key?(key) }
37
+ return @hash.fetch(key, fall_back)
38
+ end
39
+ end
40
+
41
+ class Overrides
42
+ def initialize(hash)
43
+ @hash = hash
44
+ end
45
+
46
+ def for_context(context)
47
+ contexts = @hash.keys.select {|ckey| context.matches?([ckey])}
48
+ contexts.map { |exc| @hash[exc] }
49
+ end
50
+ end
51
+ end
@@ -7,9 +7,7 @@ module Reek
7
7
  end
8
8
 
9
9
  def push(config)
10
- clone = @detectors[0].copy
11
- clone.configure_with(config)
12
- @detectors.each {|det| det.be_masked}
10
+ clone = @detectors[-1].supersede_with(config)
13
11
  @detectors << clone
14
12
  end
15
13
 
@@ -12,6 +12,9 @@ LongMethod:
12
12
  exclude:
13
13
  - Reek::SexpFormatter#self.format
14
14
  - Reek::Options#set_options
15
+ UncommunicativeName:
16
+ accept:
17
+ - process_op_asgn1
15
18
  UtilityFunction:
16
19
  exclude:
17
20
  - Reek::Spec
@@ -8,11 +8,11 @@ module Reek
8
8
  class SmellWarning
9
9
  include Comparable
10
10
 
11
- def initialize(smell, context, warning)
11
+ def initialize(smell, context, warning, masked)
12
12
  @detector = smell
13
13
  @context = context
14
14
  @warning = warning
15
- @is_masked = smell.masked?
15
+ @is_masked = masked
16
16
  end
17
17
 
18
18
  def hash # :nodoc:
@@ -55,5 +55,13 @@ module Reek
55
55
  def report
56
56
  basic_report.gsub(/\%m/, @is_masked ? '(masked) ' : '')
57
57
  end
58
+
59
+ def report_on(report)
60
+ if @is_masked
61
+ report.record_masked_smell(self)
62
+ else
63
+ report << self
64
+ end
65
+ end
58
66
  end
59
67
  end
@@ -24,8 +24,10 @@ module Reek
24
24
  # identical calls to be permitted within any single method.
25
25
  MAX_ALLOWED_CALLS_KEY = 'max_calls'
26
26
 
27
+ DEFAULT_MAX_CALLS = 1
28
+
27
29
  def self.default_config
28
- super.adopt(MAX_ALLOWED_CALLS_KEY => 1)
30
+ super.adopt(MAX_ALLOWED_CALLS_KEY => DEFAULT_MAX_CALLS)
29
31
  end
30
32
 
31
33
  def initialize(config = Duplication.default_config)
@@ -42,7 +44,7 @@ module Reek
42
44
 
43
45
  def smelly_calls(method) # :nodoc:
44
46
  method.calls.select do |key,val|
45
- val > @config[MAX_ALLOWED_CALLS_KEY] and key[2] != :new
47
+ val > value(MAX_ALLOWED_CALLS_KEY, method, DEFAULT_MAX_CALLS) and key[2] != :new
46
48
  end
47
49
  end
48
50
  end
@@ -20,18 +20,22 @@ module Reek
20
20
  # permitted in a class.
21
21
  MAX_ALLOWED_METHODS_KEY = 'max_methods'
22
22
 
23
+ DEFAULT_MAX_METHODS = 25
24
+
23
25
  # The name of the config field that sets the maximum number of instance
24
26
  # variables permitted in a class.
25
27
  MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
26
28
 
29
+ DEFAULT_MAX_IVARS = 9
30
+
27
31
  def self.contexts # :nodoc:
28
32
  [:class]
29
33
  end
30
34
 
31
35
  def self.default_config
32
36
  super.adopt(
33
- MAX_ALLOWED_METHODS_KEY => 25,
34
- MAX_ALLOWED_IVARS_KEY => 9,
37
+ MAX_ALLOWED_METHODS_KEY => DEFAULT_MAX_METHODS,
38
+ MAX_ALLOWED_IVARS_KEY => DEFAULT_MAX_IVARS,
35
39
  EXCLUDE_KEY => []
36
40
  )
37
41
  end
@@ -42,13 +46,13 @@ module Reek
42
46
 
43
47
  def check_num_methods(klass) # :nodoc:
44
48
  count = klass.num_methods
45
- return if count <= @config[MAX_ALLOWED_METHODS_KEY]
49
+ return if count <= value(MAX_ALLOWED_METHODS_KEY, klass, DEFAULT_MAX_METHODS)
46
50
  found(klass, "has at least #{count} methods")
47
51
  end
48
52
 
49
53
  def check_num_ivars(klass) # :nodoc:
50
54
  count = klass.variable_names.length
51
- return if count <= @config[MAX_ALLOWED_IVARS_KEY]
55
+ return if count <= value(MAX_ALLOWED_IVARS_KEY, klass, DEFAULT_MAX_IVARS)
52
56
  found(klass, "has at least #{count} instance variables")
53
57
  end
54
58
 
@@ -16,9 +16,11 @@ module Reek
16
16
  # statements permitted in any method.
17
17
  MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
18
18
 
19
+ DEFAULT_MAX_STATEMENTS = 5
20
+
19
21
  def self.default_config
20
22
  super.adopt(
21
- MAX_ALLOWED_STATEMENTS_KEY => 5,
23
+ MAX_ALLOWED_STATEMENTS_KEY => DEFAULT_MAX_STATEMENTS,
22
24
  EXCLUDE_KEY => ['initialize']
23
25
  )
24
26
  end
@@ -29,17 +31,13 @@ module Reek
29
31
  super(config)
30
32
  end
31
33
 
32
- def max_statements
33
- @config['max_statements']
34
- end
35
-
36
34
  #
37
35
  # Checks the length of the given +method+.
38
36
  # Remembers any smells found.
39
37
  #
40
38
  def examine_context(method)
41
39
  num = method.num_statements
42
- return false if num <= max_statements
40
+ return false if num <= value(MAX_ALLOWED_STATEMENTS_KEY, method, DEFAULT_MAX_STATEMENTS)
43
41
  found(method, "has approx #{num} statements")
44
42
  end
45
43
  end
@@ -1,5 +1,4 @@
1
1
  require 'reek/smells/smell_detector'
2
- require 'reek/smell_warning'
3
2
 
4
3
  module Reek
5
4
  module Smells
@@ -18,8 +17,17 @@ module Reek
18
17
  # parameters permitted in any method or block.
19
18
  MAX_ALLOWED_PARAMS_KEY = 'max_params'
20
19
 
20
+ # The default value of the +MAX_ALLOWED_PARAMS_KEY+ configuration
21
+ # value.
22
+ DEFAULT_MAX_ALLOWED_PARAMS = 3
23
+
21
24
  def self.default_config
22
- super.adopt(MAX_ALLOWED_PARAMS_KEY => 3)
25
+ super.adopt(
26
+ MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
27
+ SmellConfiguration::OVERRIDES_KEY => {
28
+ "initialize" => {MAX_ALLOWED_PARAMS_KEY => 5}
29
+ }
30
+ )
23
31
  end
24
32
 
25
33
  def initialize(config = LongParameterList.default_config)
@@ -33,7 +41,7 @@ module Reek
33
41
  #
34
42
  def examine_context(ctx)
35
43
  num_params = ctx.parameters.length
36
- return false if num_params <= @config['max_params']
44
+ return false if num_params <= value(MAX_ALLOWED_PARAMS_KEY, ctx, DEFAULT_MAX_ALLOWED_PARAMS)
37
45
  found(ctx, "#{@action} #{num_params} parameters")
38
46
  end
39
47
  end
@@ -0,0 +1,58 @@
1
+ require 'reek/smells/smell_detector'
2
+ require 'reek/smell_warning'
3
+ require 'reek/sexp_formatter'
4
+
5
+ module Reek
6
+ module Smells
7
+
8
+ #
9
+ # Simulated Polymorphism occurs when
10
+ # * code uses a case statement (especially on a type field);
11
+ # * or code has several if statements in a row (especially if they're comparing against the same value);
12
+ # * or code uses instance_of?, kind_of?, is_a?, or === to decide what type it's working with;
13
+ # * or multiple conditionals in different places test the same value.
14
+ #
15
+ # Conditional code is hard to read and understand, because the reader must
16
+ # hold more state in his head.
17
+ # When the same value is tested in multiple places throughout an application,
18
+ # any change to the set of possible values will require many methods and
19
+ # classes to change. Tests for the type of an object may indicate that the
20
+ # abstraction represented by that type is not completely defined (or understood).
21
+ #
22
+ # In the current implementation, Reek only checks for multiple conditionals
23
+ # testing the same value throughout a single class.
24
+ #
25
+ class SimulatedPolymorphism < SmellDetector
26
+
27
+ def self.contexts # :nodoc:
28
+ [:class]
29
+ end
30
+
31
+ # The name of the config field that sets the maximum number of
32
+ # identical conditionals permitted within any single class.
33
+ MAX_IDENTICAL_IFS_KEY = 'max_ifs'
34
+
35
+ DEFAULT_MAX_IFS = 2
36
+
37
+ def self.default_config
38
+ super.adopt(MAX_IDENTICAL_IFS_KEY => DEFAULT_MAX_IFS)
39
+ end
40
+
41
+ def initialize(config = SimulatedPolymorphism.default_config)
42
+ super(config)
43
+ end
44
+
45
+ #
46
+ # Checks the given ClassContext for multiple identical conditional tests.
47
+ # Remembers any smells found.
48
+ #
49
+ def examine_context(klass)
50
+ counts = Hash.new(0)
51
+ klass.conditionals.each {|cond| counts[cond] += 1}
52
+ counts.each do |key, val|
53
+ found(klass, "tests #{SexpFormatter.format(key)} at least #{val} times") if val > value(MAX_IDENTICAL_IFS_KEY, klass, DEFAULT_MAX_IFS)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end