reek 1.2.1 → 1.2.3
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 +10 -0
- data/Rakefile +0 -1
- data/config/defaults.reek +8 -6
- data/features/masking_smells.feature +9 -9
- data/features/options.feature +2 -2
- data/features/profile.feature +34 -0
- data/features/rake_task.feature +74 -0
- data/features/reports.feature +1 -1
- data/features/samples.feature +4 -4
- data/features/stdin.feature +1 -1
- data/features/step_definitions/reek_steps.rb +11 -7
- data/features/support/env.rb +26 -18
- data/lib/reek/adapters/application.rb +9 -2
- data/lib/reek/adapters/command_line.rb +2 -2
- data/lib/reek/adapters/core_extras.rb +0 -8
- data/lib/reek/adapters/source.rb +4 -1
- data/lib/reek/adapters/spec.rb +1 -4
- data/lib/reek/block_context.rb +14 -8
- data/lib/reek/class_context.rb +6 -55
- data/lib/reek/code_context.rb +10 -0
- data/lib/reek/code_parser.rb +26 -53
- data/lib/reek/configuration.rb +12 -6
- data/lib/reek/if_context.rb +2 -3
- data/lib/reek/method_context.rb +8 -12
- data/lib/reek/module_context.rb +35 -16
- data/lib/reek/name.rb +2 -0
- data/lib/reek/object_refs.rb +0 -3
- data/lib/reek/sexp_formatter.rb +0 -2
- data/lib/reek/smells/attribute.rb +48 -0
- data/lib/reek/smells/class_variable.rb +17 -4
- data/lib/reek/smells/control_couple.rb +3 -10
- data/lib/reek/smells/data_clump.rb +10 -10
- data/lib/reek/smells/feature_envy.rb +1 -8
- data/lib/reek/smells/large_class.rb +3 -3
- data/lib/reek/smells/simulated_polymorphism.rb +17 -3
- data/lib/reek/smells/smell_detector.rb +11 -2
- data/lib/reek/smells/utility_function.rb +1 -1
- data/lib/reek/sniffer.rb +2 -8
- data/lib/reek/stop_context.rb +1 -1
- data/lib/reek/tree_dresser.rb +74 -0
- data/lib/reek.rb +1 -1
- data/reek.gemspec +3 -3
- data/spec/reek/adapters/should_reek_of_spec.rb +7 -1
- data/spec/reek/block_context_spec.rb +6 -6
- data/spec/reek/class_context_spec.rb +2 -23
- data/spec/reek/code_context_spec.rb +149 -67
- data/spec/reek/code_parser_spec.rb +35 -51
- data/spec/reek/method_context_spec.rb +4 -4
- data/spec/reek/singleton_method_context_spec.rb +1 -1
- data/spec/reek/smells/attribute_spec.rb +26 -0
- data/spec/reek/smells/behaves_like_variable_detector.rb +39 -0
- data/spec/reek/smells/class_variable_spec.rb +77 -43
- data/spec/reek/smells/control_couple_spec.rb +1 -1
- data/spec/reek/smells/data_clump_spec.rb +31 -13
- data/spec/reek/smells/feature_envy_spec.rb +1 -1
- data/spec/reek/smells/large_class_spec.rb +32 -69
- data/spec/reek/smells/long_parameter_list_spec.rb +0 -12
- data/spec/reek/smells/simulated_polymorphism_spec.rb +66 -18
- data/spec/reek/smells/utility_function_spec.rb +0 -21
- data/spec/reek/sniffer_spec.rb +1 -0
- data/spec/samples/not_quite_masked/dirty.rb +2 -0
- data/spec/spec_helper.rb +1 -1
- data/tasks/reek.rake +1 -1
- data/tasks/test.rake +3 -4
- metadata +8 -5
- data/lib/reek/adapters/object_source.rb +0 -52
- data/lib/reek/exceptions.reek +0 -20
- data/spec/quality/reek_source_spec.rb +0 -15
data/lib/reek/code_parser.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'sexp'
|
3
2
|
require 'reek/block_context'
|
4
3
|
require 'reek/class_context'
|
@@ -9,31 +8,12 @@ require 'reek/method_context'
|
|
9
8
|
require 'reek/singleton_method_context'
|
10
9
|
require 'reek/yield_call_context'
|
11
10
|
|
12
|
-
#
|
13
|
-
# Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
|
14
|
-
# syntax tree more easily.
|
15
|
-
#
|
16
|
-
class Sexp
|
17
|
-
def children
|
18
|
-
find_all { |item| Sexp === item }
|
19
|
-
end
|
20
|
-
|
21
|
-
def is_language_node?
|
22
|
-
first.class == Symbol
|
23
|
-
end
|
24
|
-
|
25
|
-
def has_type?(type)
|
26
|
-
is_language_node? and first == type
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
11
|
module Reek
|
31
|
-
|
12
|
+
#
|
13
|
+
# Traverses a Sexp abstract syntax tree and fires events whenever
|
14
|
+
# it encounters specific node types.
|
15
|
+
#
|
32
16
|
class CodeParser
|
33
|
-
|
34
|
-
#
|
35
|
-
# Creates a new Ruby code checker.
|
36
|
-
#
|
37
17
|
def initialize(sniffer, ctx = StopContext.new)
|
38
18
|
@sniffer = sniffer
|
39
19
|
@element = ctx
|
@@ -50,42 +30,29 @@ module Reek
|
|
50
30
|
exp[0..-1].each { |sub| process(sub) if Array === sub }
|
51
31
|
end
|
52
32
|
|
53
|
-
def
|
54
|
-
scope =
|
55
|
-
push(scope) do
|
56
|
-
process_default(exp)
|
57
|
-
check_smells(:module)
|
58
|
-
end
|
59
|
-
scope
|
60
|
-
end
|
61
|
-
|
62
|
-
def process_class(exp)
|
63
|
-
scope = ClassContext.create(@element, exp)
|
33
|
+
def do_module_or_class(exp, context_class)
|
34
|
+
scope = context_class.create(@element, exp)
|
64
35
|
push(scope) do
|
65
36
|
process_default(exp) unless @element.is_struct?
|
66
|
-
check_smells(
|
37
|
+
check_smells(exp[0])
|
67
38
|
end
|
68
39
|
scope
|
69
40
|
end
|
70
41
|
|
71
|
-
def
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
def process_cvasgn(exp)
|
76
|
-
process_cvar(exp)
|
42
|
+
def process_module(exp)
|
43
|
+
do_module_or_class(exp, ModuleContext)
|
77
44
|
end
|
78
45
|
|
79
|
-
def
|
80
|
-
|
46
|
+
def process_class(exp)
|
47
|
+
do_module_or_class(exp, ClassContext)
|
81
48
|
end
|
82
49
|
|
83
50
|
def process_defn(exp)
|
84
|
-
handle_context(MethodContext,
|
51
|
+
handle_context(MethodContext, exp[0], exp)
|
85
52
|
end
|
86
53
|
|
87
54
|
def process_defs(exp)
|
88
|
-
handle_context(SingletonMethodContext,
|
55
|
+
handle_context(SingletonMethodContext, exp[0], exp)
|
89
56
|
end
|
90
57
|
|
91
58
|
def process_args(exp) end
|
@@ -105,7 +72,12 @@ module Reek
|
|
105
72
|
|
106
73
|
def process_iter(exp)
|
107
74
|
process(exp[1])
|
108
|
-
|
75
|
+
scope = BlockContext.new(@element, exp)
|
76
|
+
push(scope) do
|
77
|
+
process_default(exp[2..-1])
|
78
|
+
check_smells(exp[0])
|
79
|
+
end
|
80
|
+
scope
|
109
81
|
end
|
110
82
|
|
111
83
|
def process_block(exp)
|
@@ -114,11 +86,12 @@ module Reek
|
|
114
86
|
end
|
115
87
|
|
116
88
|
def process_yield(exp)
|
117
|
-
handle_context(YieldCallContext,
|
89
|
+
handle_context(YieldCallContext, exp[0], exp)
|
118
90
|
end
|
119
91
|
|
120
92
|
def process_call(exp)
|
121
93
|
@element.record_call_to(exp)
|
94
|
+
@element.check_for_attribute_declaration(exp)
|
122
95
|
process_default(exp)
|
123
96
|
end
|
124
97
|
|
@@ -135,10 +108,9 @@ module Reek
|
|
135
108
|
end
|
136
109
|
|
137
110
|
def process_if(exp)
|
138
|
-
@element.record_conditional(exp[1])
|
139
111
|
count_clause(exp[2])
|
140
112
|
count_clause(exp[3])
|
141
|
-
handle_context(IfContext,
|
113
|
+
handle_context(IfContext, exp[0], exp)
|
142
114
|
@element.count_statements(-1)
|
143
115
|
end
|
144
116
|
|
@@ -154,12 +126,14 @@ module Reek
|
|
154
126
|
|
155
127
|
def process_for(exp)
|
156
128
|
count_clause(exp[3])
|
157
|
-
|
129
|
+
process_default(exp)
|
130
|
+
@element.count_statements(-1)
|
158
131
|
end
|
159
132
|
|
160
133
|
def process_rescue(exp)
|
161
134
|
count_clause(exp[1])
|
162
|
-
|
135
|
+
process_default(exp)
|
136
|
+
@element.count_statements(-1)
|
163
137
|
end
|
164
138
|
|
165
139
|
def process_resbody(exp)
|
@@ -167,7 +141,6 @@ module Reek
|
|
167
141
|
end
|
168
142
|
|
169
143
|
def process_case(exp)
|
170
|
-
@element.record_conditional(exp[1])
|
171
144
|
process_default(exp)
|
172
145
|
@element.count_statements(-1)
|
173
146
|
end
|
data/lib/reek/configuration.rb
CHANGED
@@ -13,19 +13,25 @@ module Reek
|
|
13
13
|
# for other values in the current smell detector's configuration.
|
14
14
|
OVERRIDES_KEY = 'overrides'
|
15
15
|
|
16
|
-
attr_reader :hash
|
17
|
-
|
18
16
|
def initialize(hash)
|
19
|
-
@
|
17
|
+
@options = hash
|
18
|
+
end
|
19
|
+
|
20
|
+
def adopt!(options)
|
21
|
+
@options.adopt!(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def deep_copy
|
25
|
+
@options.deep_copy # SMELL: Open Secret -- returns a Hash
|
20
26
|
end
|
21
27
|
|
22
28
|
# SMELL: Getter
|
23
29
|
def enabled?
|
24
|
-
@
|
30
|
+
@options[ENABLED_KEY]
|
25
31
|
end
|
26
32
|
|
27
33
|
def overrides_for(context)
|
28
|
-
Overrides.new(@
|
34
|
+
Overrides.new(@options.fetch(OVERRIDES_KEY, {})).for_context(context)
|
29
35
|
end
|
30
36
|
|
31
37
|
# Retrieves the value, if any, for the given +key+.
|
@@ -34,7 +40,7 @@ module Reek
|
|
34
40
|
#
|
35
41
|
def value(key, context, fall_back)
|
36
42
|
overrides_for(context).each { |conf| return conf[key] if conf.has_key?(key) }
|
37
|
-
return @
|
43
|
+
return @options.fetch(key, fall_back)
|
38
44
|
end
|
39
45
|
end
|
40
46
|
|
data/lib/reek/if_context.rb
CHANGED
@@ -5,8 +5,7 @@ module Reek
|
|
5
5
|
attr_reader :if_expr
|
6
6
|
|
7
7
|
def initialize(outer, exp)
|
8
|
-
|
9
|
-
@exp = exp
|
8
|
+
super
|
10
9
|
@if_expr = exp[1]
|
11
10
|
end
|
12
11
|
|
@@ -18,7 +17,7 @@ module Reek
|
|
18
17
|
@outer.outer_name
|
19
18
|
end
|
20
19
|
|
21
|
-
def to_s
|
20
|
+
def to_s # SMELL: should be unnecessary :(
|
22
21
|
@outer.to_s
|
23
22
|
end
|
24
23
|
end
|
data/lib/reek/method_context.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'reek/name'
|
2
|
-
require 'reek/
|
2
|
+
require 'reek/block_context'
|
3
3
|
require 'reek/object_refs'
|
4
4
|
|
5
5
|
class Array
|
@@ -51,7 +51,7 @@ module Reek
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
class MethodContext <
|
54
|
+
class MethodContext < VariableContainer
|
55
55
|
attr_reader :parameters
|
56
56
|
attr_reader :calls
|
57
57
|
attr_reader :refs
|
@@ -62,7 +62,6 @@ module Reek
|
|
62
62
|
@parameters = exp[exp[0] == :defn ? 2 : 3] # SMELL: SimulatedPolymorphism
|
63
63
|
@parameters ||= []
|
64
64
|
@parameters.extend(MethodParameters)
|
65
|
-
@local_variables = []
|
66
65
|
@name = Name.new(exp[1])
|
67
66
|
@num_statements = 0
|
68
67
|
@calls = Hash.new(0)
|
@@ -85,6 +84,11 @@ module Reek
|
|
85
84
|
|
86
85
|
def record_call_to(exp)
|
87
86
|
@calls[exp] += 1
|
87
|
+
record_receiver(exp)
|
88
|
+
check_for_attribute_declaration(exp)
|
89
|
+
end
|
90
|
+
|
91
|
+
def record_receiver(exp)
|
88
92
|
receiver, meth = exp[1..2]
|
89
93
|
receiver ||= [:self]
|
90
94
|
case receiver[0]
|
@@ -109,25 +113,17 @@ module Reek
|
|
109
113
|
@depends_on_self = true
|
110
114
|
end
|
111
115
|
|
112
|
-
def record_local_variable(sym)
|
113
|
-
@local_variables << Name.new(sym)
|
114
|
-
end
|
115
|
-
|
116
116
|
def outer_name
|
117
117
|
"#{@outer.outer_name}#{@name}/"
|
118
118
|
end
|
119
119
|
|
120
|
-
def to_s
|
121
|
-
"#{@outer.outer_name}#{@name}"
|
122
|
-
end
|
123
|
-
|
124
120
|
def envious_receivers
|
125
121
|
return [] if @refs.self_is_max?
|
126
122
|
@refs.max_keys
|
127
123
|
end
|
128
124
|
|
129
125
|
def variable_names
|
130
|
-
@parameters.names + @local_variables
|
126
|
+
@parameters.names + @local_variables.to_a
|
131
127
|
end
|
132
128
|
end
|
133
129
|
end
|
data/lib/reek/module_context.rb
CHANGED
@@ -1,25 +1,30 @@
|
|
1
1
|
require 'reek/code_context'
|
2
|
+
require 'reek/code_parser'
|
3
|
+
require 'reek/sniffer'
|
2
4
|
|
3
5
|
module Reek
|
4
6
|
class ModuleContext < CodeContext
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
class << self
|
9
|
+
def create(outer, exp)
|
10
|
+
res = Name.resolve(exp[1], outer)
|
11
|
+
new(res[0], res[1], exp)
|
12
|
+
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
def from_s(src)
|
15
|
+
source = src.to_reek_source
|
16
|
+
sniffer = Sniffer.new(source)
|
17
|
+
CodeParser.new(sniffer).do_module_or_class(source.syntax_tree, self)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
|
-
attr_reader :
|
21
|
+
attr_reader :attributes
|
18
22
|
|
19
|
-
def initialize(outer, name)
|
20
|
-
super(outer,
|
23
|
+
def initialize(outer, name, exp)
|
24
|
+
super(outer, exp)
|
21
25
|
@name = name
|
22
|
-
@
|
26
|
+
@attributes = Set.new
|
27
|
+
@parsed_methods = []
|
23
28
|
end
|
24
29
|
|
25
30
|
def myself
|
@@ -31,12 +36,26 @@ module Reek
|
|
31
36
|
@myself.const_or_nil(modname.to_s)
|
32
37
|
end
|
33
38
|
|
34
|
-
def
|
35
|
-
|
39
|
+
def parameterized_methods(min_clump_size)
|
40
|
+
@parsed_methods.select {|meth| meth.parameters.length >= min_clump_size }
|
36
41
|
end
|
37
42
|
|
38
|
-
def
|
39
|
-
@
|
43
|
+
def record_attribute(attr)
|
44
|
+
@attributes << Name.new(attr)
|
45
|
+
end
|
46
|
+
|
47
|
+
def record_method(meth)
|
48
|
+
@parsed_methods << meth
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_for_attribute_declaration(exp)
|
52
|
+
if [:attr, :attr_reader, :attr_writer, :attr_accessor].include? exp[2]
|
53
|
+
exp[3][1..-1].each {|arg| record_attribute(arg[1])}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def outer_name
|
58
|
+
"#{@outer.outer_name}#{@name}::"
|
40
59
|
end
|
41
60
|
|
42
61
|
def variable_names
|
data/lib/reek/name.rb
CHANGED
data/lib/reek/object_refs.rb
CHANGED
data/lib/reek/sexp_formatter.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module Smells
|
5
|
+
|
6
|
+
#
|
7
|
+
# A class that publishes a getter or setter for an instance variable
|
8
|
+
# invites client classes to become too intimate with its inner workings,
|
9
|
+
# and in particular with its representation of state.
|
10
|
+
#
|
11
|
+
# Currently this detector raises a warning for every +attr+,
|
12
|
+
# +attr_reader+, +attr_writer+ and +attr_accessor+ -- including those
|
13
|
+
# that are private.
|
14
|
+
#
|
15
|
+
# TODO:
|
16
|
+
# * eliminate private attributes
|
17
|
+
# * catch attributes declared "by hand"
|
18
|
+
#
|
19
|
+
class Attribute < SmellDetector
|
20
|
+
|
21
|
+
def self.contexts # :nodoc:
|
22
|
+
[:class, :module]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.default_config
|
26
|
+
super.adopt(SmellConfiguration::ENABLED_KEY => false)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(config = Attribute.default_config)
|
30
|
+
super(config)
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Checks whether the given class declares any attributes.
|
35
|
+
# Remembers any smells found.
|
36
|
+
#
|
37
|
+
def examine_context(mod)
|
38
|
+
# SMELL: Duplication
|
39
|
+
# MethodContext, ClassContext and ModuleContext all know which
|
40
|
+
# calls constitute attribute declarations. Need a method on
|
41
|
+
# ModuleContext: each_public_call.select [names] {...}
|
42
|
+
mod.attributes.each do |attr|
|
43
|
+
found(mod, "declares the attribute #{attr}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -18,14 +18,27 @@ module Reek
|
|
18
18
|
end
|
19
19
|
|
20
20
|
#
|
21
|
-
# Checks whether the given class declares any class variables.
|
21
|
+
# Checks whether the given class or module declares any class variables.
|
22
22
|
# Remembers any smells found.
|
23
23
|
#
|
24
|
-
def examine_context(
|
25
|
-
|
26
|
-
found(
|
24
|
+
def examine_context(mod)
|
25
|
+
class_variables_in(mod).each do |cvar_name|
|
26
|
+
found(mod, "declares the class variable #{cvar_name}")
|
27
27
|
end
|
28
28
|
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Collects the names of the class variables declared and/or used
|
32
|
+
# in the given module.
|
33
|
+
#
|
34
|
+
def class_variables_in(mod)
|
35
|
+
result = Set.new
|
36
|
+
collector = proc { |cvar_node| result << cvar_node.name }
|
37
|
+
[:cvar, :cvasgn, :cvdecl].each do |stmt_type|
|
38
|
+
mod.each(stmt_type, [:class, :module], &collector)
|
39
|
+
end
|
40
|
+
result
|
41
|
+
end
|
29
42
|
end
|
30
43
|
end
|
31
44
|
end
|
@@ -34,19 +34,12 @@ module Reek
|
|
34
34
|
# because it includes at least two different code paths.
|
35
35
|
#
|
36
36
|
class ControlCouple < SmellDetector
|
37
|
+
include ExcludeInitialize
|
37
38
|
|
38
39
|
def self.contexts # :nodoc:
|
39
40
|
[:if]
|
40
41
|
end
|
41
42
|
|
42
|
-
def self.default_config
|
43
|
-
super.adopt(EXCLUDE_KEY => ['initialize'])
|
44
|
-
end
|
45
|
-
|
46
|
-
def initialize(config = ControlCouple.default_config)
|
47
|
-
super
|
48
|
-
end
|
49
|
-
|
50
43
|
#
|
51
44
|
# Checks whether the given conditional statement relies on a control couple.
|
52
45
|
# Remembers any smells found.
|
@@ -56,8 +49,8 @@ module Reek
|
|
56
49
|
# SMELL: Duplication
|
57
50
|
# This smell is reported once for each conditional that tests the
|
58
51
|
# same parameter. Which means that the same smell can recur within
|
59
|
-
# a single
|
60
|
-
#
|
52
|
+
# a single detector. Which in turn means that SmellDetector must
|
53
|
+
# use a Set to hold smells found.
|
61
54
|
found(cond, "is controlled by argument #{SexpFormatter.format(cond.if_expr)}")
|
62
55
|
end
|
63
56
|
end
|
@@ -20,7 +20,7 @@ module Reek
|
|
20
20
|
class DataClump < SmellDetector
|
21
21
|
|
22
22
|
def self.contexts # :nodoc:
|
23
|
-
[:class]
|
23
|
+
[:class, :module]
|
24
24
|
end
|
25
25
|
|
26
26
|
# The name of the config field that sets the maximum allowed
|
@@ -44,14 +44,14 @@ module Reek
|
|
44
44
|
end
|
45
45
|
|
46
46
|
#
|
47
|
-
# Checks the given
|
47
|
+
# Checks the given class or module for multiple identical parameter sets.
|
48
48
|
# Remembers any smells found.
|
49
49
|
#
|
50
|
-
def examine_context(
|
51
|
-
max_copies = value(MAX_COPIES_KEY,
|
52
|
-
min_clump_size = value(MIN_CLUMP_SIZE_KEY,
|
53
|
-
MethodGroup.new(
|
54
|
-
found(
|
50
|
+
def examine_context(ctx)
|
51
|
+
max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
|
52
|
+
min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
|
53
|
+
MethodGroup.new(ctx, min_clump_size, max_copies).clumps.each do |clump, occurs|
|
54
|
+
found(ctx, "takes parameters #{DataClump.print_clump(clump)} to #{occurs} methods")
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
@@ -68,15 +68,15 @@ module Reek
|
|
68
68
|
methods.map {|meth| meth.parameters.names.sort}.intersection
|
69
69
|
end
|
70
70
|
|
71
|
-
def initialize(
|
72
|
-
@
|
71
|
+
def initialize(ctx, min_clump_size, max_copies)
|
72
|
+
@ctx = ctx
|
73
73
|
@min_clump_size = min_clump_size
|
74
74
|
@max_copies = max_copies
|
75
75
|
end
|
76
76
|
|
77
77
|
def clumps
|
78
78
|
results = Hash.new(0)
|
79
|
-
@
|
79
|
+
@ctx.parameterized_methods(@min_clump_size).bounded_power_set(@max_copies).each do |methods|
|
80
80
|
clump = MethodGroup.intersection_of_parameters_of(methods)
|
81
81
|
if clump.length >= @min_clump_size
|
82
82
|
results[clump] = [methods.length, results[clump]].max
|
@@ -33,14 +33,7 @@ module Reek
|
|
33
33
|
# often than it refers to (ie. send messages to) some other object.
|
34
34
|
#
|
35
35
|
class FeatureEnvy < SmellDetector
|
36
|
-
|
37
|
-
def self.default_config
|
38
|
-
super.adopt(EXCLUDE_KEY => ['initialize'])
|
39
|
-
end
|
40
|
-
|
41
|
-
def initialize(config = FeatureEnvy.default_config)
|
42
|
-
super
|
43
|
-
end
|
36
|
+
include ExcludeInitialize
|
44
37
|
|
45
38
|
#
|
46
39
|
# Checks whether the given +context+ includes any code fragment that
|
@@ -45,9 +45,9 @@ module Reek
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def check_num_methods(klass) # :nodoc:
|
48
|
-
|
49
|
-
return if
|
50
|
-
found(klass, "has at least #{
|
48
|
+
actual = klass.each(:defn, [:class, :module]).length
|
49
|
+
return if actual <= value(MAX_ALLOWED_METHODS_KEY, klass, DEFAULT_MAX_METHODS)
|
50
|
+
found(klass, "has at least #{actual} methods")
|
51
51
|
end
|
52
52
|
|
53
53
|
def check_num_ivars(klass) # :nodoc:
|
@@ -47,12 +47,26 @@ module Reek
|
|
47
47
|
# Remembers any smells found.
|
48
48
|
#
|
49
49
|
def examine_context(klass)
|
50
|
-
|
51
|
-
klass.conditionals.each {|cond| counts[cond] += 1}
|
52
|
-
counts.each do |key, val|
|
50
|
+
conditional_counts(klass).each do |key, val|
|
53
51
|
found(klass, "tests #{SexpFormatter.format(key)} at least #{val} times") if val > value(MAX_IDENTICAL_IFS_KEY, klass, DEFAULT_MAX_IFS)
|
54
52
|
end
|
55
53
|
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Returns a Hash listing all of the conditional expressions in
|
57
|
+
# the given syntax tree together with the number of times each
|
58
|
+
# occurs. Ignores nested classes and modules.
|
59
|
+
#
|
60
|
+
def conditional_counts(klass)
|
61
|
+
result = Hash.new(0)
|
62
|
+
collector = proc { |node|
|
63
|
+
condition = node.condition
|
64
|
+
result[condition] += 1 unless condition == s(:call, nil, :block_given?, s(:arglist))
|
65
|
+
}
|
66
|
+
klass.each(:if, [:class, :module], &collector)
|
67
|
+
klass.each(:case, [:class, :module], &collector)
|
68
|
+
result
|
69
|
+
end
|
56
70
|
end
|
57
71
|
end
|
58
72
|
end
|
@@ -10,6 +10,15 @@ end
|
|
10
10
|
module Reek
|
11
11
|
module Smells
|
12
12
|
|
13
|
+
module ExcludeInitialize
|
14
|
+
def self.default_config
|
15
|
+
super.adopt(EXCLUDE_KEY => ['initialize'])
|
16
|
+
end
|
17
|
+
def initialize(config = self.class.default_config)
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
13
22
|
class SmellDetector
|
14
23
|
|
15
24
|
# The name of the config field that lists the names of code contexts
|
@@ -70,11 +79,11 @@ module Reek
|
|
70
79
|
end
|
71
80
|
|
72
81
|
def configure_with(config)
|
73
|
-
@config.
|
82
|
+
@config.adopt!(config)
|
74
83
|
end
|
75
84
|
|
76
85
|
def copy
|
77
|
-
self.class.new(@config.
|
86
|
+
self.class.new(@config.deep_copy)
|
78
87
|
end
|
79
88
|
|
80
89
|
def supersede_with(config)
|
data/lib/reek/sniffer.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'reek/detector_stack'
|
2
2
|
|
3
3
|
# SMELL: Duplication -- all these should be found automagically
|
4
|
+
require 'reek/smells/attribute'
|
4
5
|
require 'reek/smells/class_variable'
|
5
6
|
require 'reek/smells/control_couple'
|
6
7
|
require 'reek/smells/data_clump'
|
@@ -49,6 +50,7 @@ module Reek
|
|
49
50
|
def self.smell_classes
|
50
51
|
# SMELL: Duplication -- these should be loaded by listing the files
|
51
52
|
[
|
53
|
+
Smells::Attribute,
|
52
54
|
Smells::ClassVariable,
|
53
55
|
Smells::ControlCouple,
|
54
56
|
Smells::DataClump,
|
@@ -123,10 +125,6 @@ module Reek
|
|
123
125
|
stack.has_smell?(patterns)
|
124
126
|
end
|
125
127
|
|
126
|
-
def smells_only_of?(klass, patterns)
|
127
|
-
num_smells == 1 and has_smell?(klass, patterns)
|
128
|
-
end
|
129
|
-
|
130
128
|
def sniff
|
131
129
|
self
|
132
130
|
end
|
@@ -173,10 +171,6 @@ private
|
|
173
171
|
total
|
174
172
|
end
|
175
173
|
|
176
|
-
def smells_only_of?(klass, patterns)
|
177
|
-
num_smells == 1 and has_smell?(klass, patterns)
|
178
|
-
end
|
179
|
-
|
180
174
|
def sniff
|
181
175
|
self
|
182
176
|
end
|