kevinrutherford-reek 1.1.3.13 → 1.1.3.14

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 (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