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