reek 1.3.6 → 1.3.7
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.
- checksums.yaml +4 -4
- data/CHANGELOG +6 -0
- data/README.md +11 -1
- data/config/defaults.reek +1 -0
- data/features/command_line_interface/options.feature +1 -0
- data/features/rake_task/rake_task.feature +3 -0
- data/features/ruby_api/api.feature +1 -3
- data/features/samples.feature +27 -20
- data/features/support/env.rb +2 -2
- data/lib/reek/cli/application.rb +0 -4
- data/lib/reek/cli/command_line.rb +10 -12
- data/lib/reek/cli/reek_command.rb +1 -1
- data/lib/reek/cli/report.rb +36 -8
- data/lib/reek/config_file_exception.rb +3 -0
- data/lib/reek/core/code_context.rb +18 -8
- data/lib/reek/core/code_parser.rb +65 -61
- data/lib/reek/core/method_context.rb +4 -0
- data/lib/reek/core/module_context.rb +2 -2
- data/lib/reek/core/smell_repository.rb +3 -0
- data/lib/reek/core/sniffer.rb +0 -1
- data/lib/reek/core/stop_context.rb +1 -1
- data/lib/reek/smells/attribute.rb +1 -1
- data/lib/reek/smells/control_parameter.rb +79 -45
- data/lib/reek/smells/data_clump.rb +1 -1
- data/lib/reek/smells/duplicate_method_call.rb +1 -1
- data/lib/reek/smells/long_parameter_list.rb +1 -1
- data/lib/reek/smells/long_yield_list.rb +1 -1
- data/lib/reek/smells/nested_iterators.rb +1 -1
- data/lib/reek/smells/nil_check.rb +10 -5
- data/lib/reek/smells/repeated_conditional.rb +1 -1
- data/lib/reek/smells/smell_detector.rb +2 -3
- data/lib/reek/smells/too_many_instance_variables.rb +1 -1
- data/lib/reek/smells/too_many_methods.rb +1 -1
- data/lib/reek/smells/too_many_statements.rb +1 -1
- data/lib/reek/smells/uncommunicative_method_name.rb +4 -4
- data/lib/reek/smells/uncommunicative_module_name.rb +4 -4
- data/lib/reek/smells/uncommunicative_parameter_name.rb +9 -9
- data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
- data/lib/reek/smells/unused_parameters.rb +2 -6
- data/lib/reek/smells/utility_function.rb +1 -1
- data/lib/reek/source/code_comment.rb +1 -1
- data/lib/reek/source/config_file.rb +9 -8
- data/lib/reek/source/sexp_extensions.rb +2 -2
- data/lib/reek/source/sexp_node.rb +8 -5
- data/lib/reek/source/source_repository.rb +5 -0
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +3 -2
- data/spec/reek/cli/report_spec.rb +38 -8
- data/spec/reek/core/code_context_spec.rb +35 -3
- data/spec/reek/core/module_context_spec.rb +1 -1
- data/spec/reek/smells/repeated_conditional_spec.rb +1 -1
- data/spec/reek/smells/smell_detector_shared.rb +1 -2
- data/spec/reek/smells/too_many_statements_spec.rb +39 -25
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +44 -30
- data/spec/reek/smells/unused_parameters_spec.rb +15 -11
- data/spec/reek/source/sexp_extensions_spec.rb +2 -2
- data/spec/reek/source/sexp_node_spec.rb +0 -1
- data/spec/samples/ruby20_syntax.rb +1 -5
- metadata +172 -162
- data/lib/reek/cli/yaml_command.rb +0 -32
- data/lib/reek/core/hash_extensions.rb +0 -29
- data/spec/reek/cli/yaml_command_spec.rb +0 -47
- data/spec/reek/core/config_spec.rb +0 -38
@@ -10,12 +10,11 @@ module Reek
|
|
10
10
|
#
|
11
11
|
class CodeContext
|
12
12
|
|
13
|
-
attr_reader :exp
|
13
|
+
attr_reader :exp
|
14
14
|
|
15
15
|
def initialize(outer, exp)
|
16
16
|
@outer = outer
|
17
17
|
@exp = exp
|
18
|
-
@config = local_config
|
19
18
|
end
|
20
19
|
|
21
20
|
def name
|
@@ -56,12 +55,23 @@ module Reek
|
|
56
55
|
exp.full_name(outer)
|
57
56
|
end
|
58
57
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
58
|
+
def config_for(detector_class)
|
59
|
+
outer_config_for(detector_class).merge(
|
60
|
+
config[detector_class.smell_class_name] || {})
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def config
|
66
|
+
@config ||= if @exp
|
67
|
+
Source::CodeComment.new(@exp.comments || '').config
|
68
|
+
else
|
69
|
+
{}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def outer_config_for(detector_class)
|
74
|
+
@outer ? @outer.config_for(detector_class) : {}
|
65
75
|
end
|
66
76
|
end
|
67
77
|
end
|
@@ -29,124 +29,128 @@ module Reek
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def process_module(exp)
|
32
|
-
|
33
|
-
|
34
|
-
push(scope) do
|
35
|
-
process_default(exp) unless exp.superclass == [:const, :Struct]
|
36
|
-
check_smells(exp[0])
|
32
|
+
inside_new_context(ModuleContext, exp) do
|
33
|
+
process_default(exp)
|
37
34
|
end
|
38
|
-
scope
|
39
35
|
end
|
40
36
|
|
41
|
-
|
42
|
-
process_module(exp)
|
43
|
-
end
|
37
|
+
alias process_class process_module
|
44
38
|
|
45
39
|
def process_defn(exp)
|
46
|
-
|
40
|
+
inside_new_context(MethodContext, exp) do
|
41
|
+
count_statement_list(exp.body)
|
42
|
+
process_default(exp)
|
43
|
+
end
|
47
44
|
end
|
48
45
|
|
49
46
|
def process_defs(exp)
|
50
|
-
|
47
|
+
inside_new_context(SingletonMethodContext, exp) do
|
48
|
+
count_statement_list(exp.body)
|
49
|
+
process_default(exp)
|
50
|
+
end
|
51
51
|
end
|
52
52
|
|
53
|
-
def process_args(
|
53
|
+
def process_args(_) end
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
#
|
56
|
+
# Recording of calls to methods and self
|
57
|
+
#
|
58
58
|
|
59
|
-
def
|
60
|
-
@element.
|
59
|
+
def process_call(exp)
|
60
|
+
@element.record_call_to(exp)
|
61
61
|
process_default(exp)
|
62
62
|
end
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
alias process_attrasgn process_call
|
65
|
+
alias process_op_asgn1 process_call
|
66
|
+
|
67
|
+
def process_ivar(exp)
|
68
|
+
@element.record_use_of_self
|
66
69
|
process_default(exp)
|
67
70
|
end
|
68
71
|
|
69
|
-
|
70
|
-
|
72
|
+
alias process_iasgn process_ivar
|
73
|
+
|
74
|
+
def process_self(_)
|
75
|
+
@element.record_use_of_self
|
76
|
+
end
|
77
|
+
|
78
|
+
alias process_zsuper process_self
|
79
|
+
|
80
|
+
#
|
81
|
+
# Statement counting
|
82
|
+
#
|
83
|
+
|
84
|
+
def process_iter(exp)
|
85
|
+
count_clause(exp[3])
|
86
|
+
process_default(exp)
|
71
87
|
end
|
72
88
|
|
73
|
-
def
|
74
|
-
|
89
|
+
def process_block(exp)
|
90
|
+
count_statement_list(exp[1..-1])
|
91
|
+
@element.count_statements(-1)
|
92
|
+
process_default(exp)
|
75
93
|
end
|
76
94
|
|
77
95
|
def process_if(exp)
|
78
96
|
count_clause(exp[2])
|
79
97
|
count_clause(exp[3])
|
80
|
-
process_default(exp)
|
81
98
|
@element.count_statements(-1)
|
99
|
+
process_default(exp)
|
82
100
|
end
|
83
101
|
|
84
102
|
def process_while(exp)
|
85
|
-
process_until(exp)
|
86
|
-
end
|
87
|
-
|
88
|
-
def process_until(exp)
|
89
103
|
count_clause(exp[2])
|
90
|
-
|
104
|
+
@element.count_statements(-1)
|
105
|
+
process_default(exp)
|
91
106
|
end
|
92
107
|
|
108
|
+
alias process_until process_while
|
109
|
+
|
93
110
|
def process_for(exp)
|
94
111
|
count_clause(exp[3])
|
95
|
-
|
112
|
+
@element.count_statements(-1)
|
113
|
+
process_default(exp)
|
96
114
|
end
|
97
115
|
|
98
116
|
def process_rescue(exp)
|
99
117
|
count_clause(exp[1])
|
100
|
-
|
118
|
+
@element.count_statements(-1)
|
119
|
+
process_default(exp)
|
101
120
|
end
|
102
121
|
|
103
122
|
def process_resbody(exp)
|
104
|
-
|
123
|
+
count_statement_list(exp[2..-1].compact)
|
124
|
+
process_default(exp)
|
105
125
|
end
|
106
126
|
|
107
127
|
def process_case(exp)
|
108
|
-
|
128
|
+
count_statement_list(exp[2..-1].compact)
|
109
129
|
@element.count_statements(-1)
|
110
|
-
end
|
111
|
-
|
112
|
-
def process_when(exp)
|
113
|
-
@element.count_statements(CodeParser.count_statements(exp[2..-1].compact))
|
114
130
|
process_default(exp)
|
115
131
|
end
|
116
132
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
def process_iasgn(exp)
|
122
|
-
@element.record_use_of_self
|
133
|
+
def process_when(exp)
|
134
|
+
count_statement_list(exp[2..-1].compact)
|
135
|
+
@element.count_statements(-1)
|
123
136
|
process_default(exp)
|
124
137
|
end
|
125
138
|
|
126
|
-
|
127
|
-
@element.record_use_of_self
|
128
|
-
end
|
139
|
+
private
|
129
140
|
|
130
141
|
def count_clause(sexp)
|
131
|
-
if sexp
|
132
|
-
@element.count_statements(1)
|
133
|
-
end
|
142
|
+
@element.count_statements(1) if sexp
|
134
143
|
end
|
135
144
|
|
136
|
-
def
|
137
|
-
|
138
|
-
ignore += 1 if stmts[1] == s(:nil)
|
139
|
-
stmts.length - ignore
|
145
|
+
def count_statement_list(statement_list)
|
146
|
+
@element.count_statements statement_list.length
|
140
147
|
end
|
141
148
|
|
142
|
-
|
143
|
-
|
144
|
-
def handle_context(klass, type, exp)
|
149
|
+
def inside_new_context(klass, exp)
|
145
150
|
scope = klass.new(@element, exp)
|
146
151
|
push(scope) do
|
147
|
-
|
148
|
-
|
149
|
-
check_smells(type)
|
152
|
+
yield
|
153
|
+
check_smells(exp[0])
|
150
154
|
end
|
151
155
|
scope
|
152
156
|
end
|
@@ -155,9 +159,9 @@ module Reek
|
|
155
159
|
@sniffer.examine(@element, type)
|
156
160
|
end
|
157
161
|
|
158
|
-
def push(
|
162
|
+
def push(scope)
|
159
163
|
orig = @element
|
160
|
-
@element =
|
164
|
+
@element = scope
|
161
165
|
yield
|
162
166
|
@element = orig
|
163
167
|
end
|
data/lib/reek/core/sniffer.rb
CHANGED
@@ -8,10 +8,10 @@ module Reek
|
|
8
8
|
# Control Coupling occurs when a method or block checks the value of
|
9
9
|
# a parameter in order to decide which execution path to take. The
|
10
10
|
# offending parameter is often called a Control Couple.
|
11
|
-
#
|
11
|
+
#
|
12
12
|
# A simple example would be the <tt>quoted</tt> parameter
|
13
13
|
# in the following method:
|
14
|
-
#
|
14
|
+
#
|
15
15
|
# def write(quoted)
|
16
16
|
# if quoted
|
17
17
|
# write_quoted(@value)
|
@@ -19,15 +19,15 @@ module Reek
|
|
19
19
|
# puts @value
|
20
20
|
# end
|
21
21
|
# end
|
22
|
-
#
|
22
|
+
#
|
23
23
|
# Control Coupling is a kind of duplication, because the calling method
|
24
24
|
# already knows which path should be taken.
|
25
|
-
#
|
25
|
+
#
|
26
26
|
# Control Coupling reduces the code's flexibility by creating a
|
27
27
|
# dependency between the caller and callee:
|
28
28
|
# any change to the possible values of the controlling parameter must
|
29
29
|
# be reflected on both sides of the call.
|
30
|
-
#
|
30
|
+
#
|
31
31
|
# A Control Couple also reveals a loss of simplicity: the called
|
32
32
|
# method probably has more than one responsibility,
|
33
33
|
# because it includes at least two different code paths.
|
@@ -55,60 +55,94 @@ module Reek
|
|
55
55
|
# @return [Array<SmellWarning>]
|
56
56
|
#
|
57
57
|
def examine_context(ctx)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
@source, SMELL_SUBCLASS,
|
64
|
-
{PARAMETER_KEY => param})
|
65
|
-
smell
|
58
|
+
ControlParameterCollector.new(ctx).control_parameters.map do |control_parameter|
|
59
|
+
SmellWarning.new(SMELL_CLASS, ctx.full_name, control_parameter.lines,
|
60
|
+
control_parameter.smell_message,
|
61
|
+
@source, SMELL_SUBCLASS,
|
62
|
+
{PARAMETER_KEY => control_parameter.name})
|
66
63
|
end
|
67
64
|
end
|
68
65
|
|
69
|
-
|
66
|
+
#
|
67
|
+
# Collects information about a single control parameter.
|
68
|
+
#
|
69
|
+
class FoundControlParameter
|
70
|
+
def initialize(param)
|
71
|
+
@param = param
|
72
|
+
@occurences = []
|
73
|
+
end
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
75
|
+
def record(occurences)
|
76
|
+
@occurences.concat occurences
|
77
|
+
end
|
78
|
+
|
79
|
+
def smell_message
|
80
|
+
"is controlled by argument #{name}"
|
81
|
+
end
|
82
|
+
|
83
|
+
def lines
|
84
|
+
@occurences.map(&:line)
|
76
85
|
end
|
77
|
-
result
|
78
|
-
end
|
79
86
|
|
80
|
-
|
81
|
-
|
82
|
-
method_ctx.exp.each_node(:lvar, [:if, :case, :and, :or, :args]) do |node|
|
83
|
-
return true if node.value == param
|
87
|
+
def name
|
88
|
+
@param.to_s
|
84
89
|
end
|
85
|
-
false
|
86
90
|
end
|
87
91
|
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
#
|
93
|
+
# Collects all control parameters in a given context.
|
94
|
+
#
|
95
|
+
class ControlParameterCollector
|
96
|
+
def initialize(context)
|
97
|
+
@context = context
|
98
|
+
end
|
99
|
+
|
100
|
+
def control_parameters
|
101
|
+
result = Hash.new {|hash, key| hash[key] = FoundControlParameter.new(key)}
|
102
|
+
potential_parameters.each do |param|
|
103
|
+
matches = find_matches(param)
|
104
|
+
result[param].record(matches) if matches.any?
|
95
105
|
end
|
106
|
+
result.values
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Returns parameters that aren't used outside of a conditional statements and that
|
112
|
+
# could be good candidates for being a control parameter.
|
113
|
+
def potential_parameters
|
114
|
+
@context.exp.parameter_names.select {|param| !used_outside_conditional?(param)}
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns wether the parameter is used outside of the conditional statement.
|
118
|
+
def used_outside_conditional?(param)
|
119
|
+
nodes = @context.exp.each_node(:lvar, [:if, :case, :and, :or, :args])
|
120
|
+
nodes.any? {|node| node.value == param}
|
121
|
+
end
|
122
|
+
|
123
|
+
# Find the use of the param that match the definition of a control parameter.
|
124
|
+
def find_matches(param)
|
125
|
+
matches = []
|
126
|
+
[:if, :case, :and, :or].each do |keyword|
|
127
|
+
@context.local_nodes(keyword).each do |node|
|
128
|
+
return [] if used_besides_in_condition?(node, param)
|
129
|
+
node.each_node(:lvar, []) {|inner| matches.push(inner) if inner.value == param}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
matches
|
96
133
|
end
|
97
|
-
matchs
|
98
|
-
end
|
99
134
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
times_in_conditional += 1 if inner.class == Sexp && inner[VALUE_POSITION] == param
|
135
|
+
# Returns wether the parameter is used somewhere besides in the condition of the
|
136
|
+
# conditional statement.
|
137
|
+
def used_besides_in_condition?(node, param)
|
138
|
+
times_in_conditional, times_total = 0, 0
|
139
|
+
node.each_node(:lvar, [:if, :case]) {|lvar| times_total +=1 if lvar.value == param}
|
140
|
+
if node.condition
|
141
|
+
times_in_conditional += 1 if node.condition[VALUE_POSITION] == param
|
142
|
+
times_in_conditional += node.condition.count {|inner| inner.class == Sexp && inner[VALUE_POSITION] == param}
|
109
143
|
end
|
144
|
+
return times_total > times_in_conditional
|
110
145
|
end
|
111
|
-
return times_total > times_in_conditional
|
112
146
|
end
|
113
147
|
end
|
114
148
|
end
|