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.
- data/History.txt +8 -2
- data/config/defaults.reek +13 -2
- data/features/masking_smells.feature +29 -1
- data/features/samples.feature +17 -4
- data/lib/reek/adapters/object_source.rb +2 -1
- data/lib/reek/class_context.rb +7 -0
- data/lib/reek/code_parser.rb +3 -1
- data/lib/reek/configuration.rb +51 -0
- data/lib/reek/detector_stack.rb +1 -3
- data/lib/reek/exceptions.reek +3 -0
- data/lib/reek/smell_warning.rb +10 -2
- data/lib/reek/smells/duplication.rb +4 -2
- data/lib/reek/smells/large_class.rb +8 -4
- data/lib/reek/smells/long_method.rb +4 -6
- data/lib/reek/smells/long_parameter_list.rb +11 -3
- data/lib/reek/smells/simulated_polymorphism.rb +58 -0
- data/lib/reek/smells/smell_detector.rb +53 -51
- data/lib/reek/smells/uncommunicative_name.rb +13 -9
- data/lib/reek/sniffer.rb +4 -0
- data/lib/reek.rb +1 -1
- data/reek.gemspec +3 -3
- data/spec/reek/adapters/report_spec.rb +5 -4
- data/spec/reek/class_context_spec.rb +21 -0
- data/spec/reek/configuration_spec.rb +12 -0
- data/spec/reek/smell_warning_spec.rb +30 -2
- data/spec/reek/smells/duplication_spec.rb +2 -1
- data/spec/reek/smells/feature_envy_spec.rb +15 -0
- data/spec/reek/smells/large_class_spec.rb +21 -20
- data/spec/reek/smells/simulated_polymorphism_spec.rb +50 -0
- data/spec/reek/smells/smell_detector_spec.rb +5 -5
- data/spec/reek/smells/uncommunicative_name_spec.rb +14 -0
- data/spec/samples/exceptions.reek +4 -0
- data/spec/samples/overrides/masked/dirty.rb +7 -0
- data/spec/samples/overrides/masked/lower.reek +5 -0
- data/spec/samples/overrides/upper.reek +5 -0
- data/tasks/test.rake +6 -1
- 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
|
-
*
|
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
|
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
|
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
|
+
"""
|
data/features/samples.feature
CHANGED
@@ -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 --
|
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 --
|
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 --
|
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::
|
17
|
+
disabled_config = {Reek::SmellConfiguration::ENABLED_KEY => false}
|
17
18
|
sniffer.configure(LargeClass, disabled_config)
|
18
19
|
end
|
19
20
|
|
data/lib/reek/class_context.rb
CHANGED
@@ -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
|
data/lib/reek/code_parser.rb
CHANGED
@@ -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.
|
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
|
data/lib/reek/detector_stack.rb
CHANGED
data/lib/reek/exceptions.reek
CHANGED
data/lib/reek/smell_warning.rb
CHANGED
@@ -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 =
|
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 =>
|
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 >
|
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 =>
|
34
|
-
MAX_ALLOWED_IVARS_KEY =>
|
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 <=
|
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 <=
|
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 =>
|
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 <=
|
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(
|
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 <=
|
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
|