reek 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +5 -0
- data/README.md +70 -92
- data/config/defaults.reek +3 -0
- data/features/samples.feature +24 -20
- data/features/step_definitions/reek_steps.rb +1 -1
- data/features/support/env.rb +7 -7
- data/lib/reek/core/code_context.rb +1 -1
- data/lib/reek/core/code_parser.rb +19 -18
- data/lib/reek/core/method_context.rb +8 -7
- data/lib/reek/core/module_context.rb +1 -1
- data/lib/reek/core/smell_repository.rb +1 -0
- data/lib/reek/core/sniffer.rb +3 -1
- data/lib/reek/rake/task.rb +1 -5
- data/lib/reek/smell_description.rb +26 -0
- data/lib/reek/smell_warning.rb +35 -49
- data/lib/reek/smells.rb +1 -0
- data/lib/reek/smells/attribute.rb +1 -1
- data/lib/reek/smells/control_parameter.rb +14 -7
- data/lib/reek/smells/data_clump.rb +1 -1
- data/lib/reek/smells/duplicate_method_call.rb +2 -9
- data/lib/reek/smells/module_initialize.rb +38 -0
- data/lib/reek/smells/nested_iterators.rb +1 -1
- data/lib/reek/smells/nil_check.rb +3 -3
- data/lib/reek/smells/repeated_conditional.rb +3 -2
- data/lib/reek/smells/smell_detector.rb +1 -1
- 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/uncommunicative_method_name.rb +0 -4
- data/lib/reek/smells/uncommunicative_parameter_name.rb +0 -4
- data/lib/reek/smells/uncommunicative_variable_name.rb +11 -9
- data/lib/reek/smells/utility_function.rb +2 -2
- data/lib/reek/source/ast_node.rb +40 -0
- data/lib/reek/source/ast_node_class_map.rb +37 -0
- data/lib/reek/source/reference_collector.rb +3 -3
- data/lib/reek/source/sexp_extensions.rb +133 -59
- data/lib/reek/source/sexp_formatter.rb +10 -4
- data/lib/reek/source/sexp_node.rb +25 -17
- data/lib/reek/source/source_code.rb +21 -9
- data/lib/reek/source/tree_dresser.rb +10 -33
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +2 -4
- data/spec/matchers/smell_of_matcher.rb +9 -1
- data/spec/quality/reek_source_spec.rb +0 -35
- data/spec/reek/core/code_context_spec.rb +22 -8
- data/spec/reek/core/method_context_spec.rb +10 -10
- data/spec/reek/smell_description_spec.rb +43 -0
- data/spec/reek/smell_warning_spec.rb +0 -3
- data/spec/reek/smells/control_parameter_spec.rb +24 -0
- data/spec/reek/smells/feature_envy_spec.rb +50 -17
- data/spec/reek/smells/irresponsible_module_spec.rb +25 -17
- data/spec/reek/smells/module_initialize_spec.rb +20 -0
- data/spec/reek/smells/prima_donna_method_spec.rb +2 -2
- data/spec/reek/smells/repeated_conditional_spec.rb +10 -4
- data/spec/reek/smells/too_many_instance_variables_spec.rb +47 -21
- data/spec/reek/smells/too_many_statements_spec.rb +11 -1
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +1 -1
- data/spec/reek/smells/utility_function_spec.rb +26 -25
- data/spec/reek/source/sexp_extensions_spec.rb +164 -91
- data/spec/reek/source/sexp_formatter_spec.rb +13 -1
- data/spec/reek/source/sexp_node_spec.rb +5 -5
- data/spec/reek/source/source_code_spec.rb +18 -6
- data/spec/reek/source/tree_dresser_spec.rb +5 -5
- data/spec/spec_helper.rb +8 -4
- metadata +16 -50
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'sexp'
|
2
1
|
require 'reek/core/method_context'
|
3
2
|
require 'reek/core/module_context'
|
4
3
|
require 'reek/core/stop_context'
|
@@ -12,6 +11,7 @@ module Reek
|
|
12
11
|
#
|
13
12
|
# SMELL: This class is responsible for counting statements and for feeding
|
14
13
|
# each context to the smell repository.
|
14
|
+
# SMELL: This class has a name that doesn't match its responsibility.
|
15
15
|
class CodeParser
|
16
16
|
def initialize(smell_repository, ctx = StopContext.new)
|
17
17
|
@smell_repository = smell_repository
|
@@ -19,14 +19,14 @@ module Reek
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def process(exp)
|
22
|
-
meth = "process_#{exp
|
22
|
+
meth = "process_#{exp.type}"
|
23
23
|
meth = :process_default unless self.respond_to?(meth)
|
24
24
|
send(meth, exp)
|
25
25
|
@element
|
26
26
|
end
|
27
27
|
|
28
28
|
def process_default(exp)
|
29
|
-
exp.each { |sub| process(sub) if sub.is_a?
|
29
|
+
exp.children.each { |sub| process(sub) if sub.is_a? AST::Node }
|
30
30
|
end
|
31
31
|
|
32
32
|
def process_module(exp)
|
@@ -37,16 +37,16 @@ module Reek
|
|
37
37
|
|
38
38
|
alias_method :process_class, :process_module
|
39
39
|
|
40
|
-
def
|
40
|
+
def process_def(exp)
|
41
41
|
inside_new_context(MethodContext, exp) do
|
42
|
-
|
42
|
+
count_clause(exp.body)
|
43
43
|
process_default(exp)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
def process_defs(exp)
|
48
48
|
inside_new_context(SingletonMethodContext, exp) do
|
49
|
-
|
49
|
+
count_clause(exp.body)
|
50
50
|
process_default(exp)
|
51
51
|
end
|
52
52
|
end
|
@@ -57,20 +57,20 @@ module Reek
|
|
57
57
|
# Recording of calls to methods and self
|
58
58
|
#
|
59
59
|
|
60
|
-
def
|
60
|
+
def process_send(exp)
|
61
61
|
@element.record_call_to(exp)
|
62
62
|
process_default(exp)
|
63
63
|
end
|
64
64
|
|
65
|
-
alias_method :process_attrasgn, :
|
66
|
-
alias_method :
|
65
|
+
alias_method :process_attrasgn, :process_send
|
66
|
+
alias_method :process_op_asgn, :process_send
|
67
67
|
|
68
68
|
def process_ivar(exp)
|
69
69
|
@element.record_use_of_self
|
70
70
|
process_default(exp)
|
71
71
|
end
|
72
72
|
|
73
|
-
alias_method :
|
73
|
+
alias_method :process_ivasgn, :process_ivar
|
74
74
|
|
75
75
|
def process_self(_)
|
76
76
|
@element.record_use_of_self
|
@@ -82,17 +82,19 @@ module Reek
|
|
82
82
|
# Statement counting
|
83
83
|
#
|
84
84
|
|
85
|
-
def
|
86
|
-
count_clause(exp
|
85
|
+
def process_block(exp)
|
86
|
+
count_clause(exp.block)
|
87
87
|
process_default(exp)
|
88
88
|
end
|
89
89
|
|
90
|
-
def
|
91
|
-
count_statement_list(exp
|
90
|
+
def process_begin(exp)
|
91
|
+
count_statement_list(exp.children)
|
92
92
|
@element.count_statements(-1)
|
93
93
|
process_default(exp)
|
94
94
|
end
|
95
95
|
|
96
|
+
alias_method :process_kwbegin, :process_begin
|
97
|
+
|
96
98
|
def process_if(exp)
|
97
99
|
count_clause(exp[2])
|
98
100
|
count_clause(exp[3])
|
@@ -126,14 +128,13 @@ module Reek
|
|
126
128
|
end
|
127
129
|
|
128
130
|
def process_case(exp)
|
129
|
-
|
131
|
+
count_clause(exp.else_body)
|
130
132
|
@element.count_statements(-1)
|
131
133
|
process_default(exp)
|
132
134
|
end
|
133
135
|
|
134
136
|
def process_when(exp)
|
135
|
-
|
136
|
-
@element.count_statements(-1)
|
137
|
+
count_clause(exp.body)
|
137
138
|
process_default(exp)
|
138
139
|
end
|
139
140
|
|
@@ -151,7 +152,7 @@ module Reek
|
|
151
152
|
scope = klass.new(@element, exp)
|
152
153
|
push(scope) do
|
153
154
|
yield
|
154
|
-
check_smells(exp
|
155
|
+
check_smells(exp.type)
|
155
156
|
end
|
156
157
|
scope
|
157
158
|
end
|
@@ -9,8 +9,8 @@ module Reek
|
|
9
9
|
module MethodParameters
|
10
10
|
def default_assignments
|
11
11
|
result = []
|
12
|
-
|
13
|
-
result << exp[1..2] if exp.
|
12
|
+
each do |exp|
|
13
|
+
result << exp[1..2] if exp.optional_argument?
|
14
14
|
end
|
15
15
|
result
|
16
16
|
end
|
@@ -26,9 +26,8 @@ module Reek
|
|
26
26
|
|
27
27
|
def initialize(outer, exp)
|
28
28
|
super(outer, exp)
|
29
|
-
@parameters = exp
|
30
|
-
@parameters
|
31
|
-
@parameters.extend(MethodParameters)
|
29
|
+
@parameters = exp.parameters.dup
|
30
|
+
@parameters.extend MethodParameters
|
32
31
|
@num_statements = 0
|
33
32
|
@refs = ObjectRefs.new
|
34
33
|
end
|
@@ -41,6 +40,8 @@ module Reek
|
|
41
40
|
receiver, meth = exp[1..2]
|
42
41
|
receiver ||= [:self]
|
43
42
|
case receiver[0]
|
43
|
+
when :lvasgn
|
44
|
+
@refs.record_reference_to(receiver.updated(:lvar))
|
44
45
|
when :lvar
|
45
46
|
@refs.record_reference_to(receiver) unless meth == :new
|
46
47
|
when :self
|
@@ -58,7 +59,7 @@ module Reek
|
|
58
59
|
end
|
59
60
|
|
60
61
|
def uses_param?(param)
|
61
|
-
local_nodes(:lvar).
|
62
|
+
local_nodes(:lvar).find { |node| node.var_name == param.to_sym }
|
62
63
|
end
|
63
64
|
|
64
65
|
def unused_params
|
@@ -70,7 +71,7 @@ module Reek
|
|
70
71
|
end
|
71
72
|
|
72
73
|
def uses_super_with_implicit_arguments?
|
73
|
-
exp.body.contains_nested_node?
|
74
|
+
(body = exp.body) && body.contains_nested_node?(:zsuper)
|
74
75
|
end
|
75
76
|
end
|
76
77
|
end
|
data/lib/reek/core/sniffer.rb
CHANGED
@@ -8,7 +8,9 @@ module Reek
|
|
8
8
|
# Configures all available smell detectors and applies them to a source.
|
9
9
|
#
|
10
10
|
class Sniffer
|
11
|
-
def initialize(src,
|
11
|
+
def initialize(src,
|
12
|
+
extra_config_files = [],
|
13
|
+
smell_repository = Core::SmellRepository.new(src.desc))
|
12
14
|
@smell_repository = smell_repository
|
13
15
|
@source = src
|
14
16
|
|
data/lib/reek/rake/task.rb
CHANGED
@@ -97,10 +97,6 @@ module Reek
|
|
97
97
|
raise('Smells found!') if !system(cmd) && fail_on_error
|
98
98
|
end
|
99
99
|
|
100
|
-
def self.reek_script
|
101
|
-
File.expand_path(File.dirname(__FILE__) + '/../../../bin/reek')
|
102
|
-
end
|
103
|
-
|
104
100
|
def self.ruby_exe
|
105
101
|
File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
|
106
102
|
end
|
@@ -108,7 +104,7 @@ module Reek
|
|
108
104
|
def cmd_words
|
109
105
|
[Task.ruby_exe] +
|
110
106
|
ruby_options +
|
111
|
-
[%(
|
107
|
+
[%(reek)] +
|
112
108
|
[sort_option] +
|
113
109
|
config_file_list.map { |fn| ['-c', %("#{fn}")] }.flatten +
|
114
110
|
source_file_list.map { |fn| %("#{fn}") }
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Reek
|
2
|
+
class SmellDescription
|
3
|
+
attr_reader :smell_class, :smell_subclass, :message, :details
|
4
|
+
|
5
|
+
def initialize(smell_class, smell_subclass, message, details)
|
6
|
+
@smell_class = smell_class
|
7
|
+
@smell_subclass = smell_subclass
|
8
|
+
@message = message
|
9
|
+
@details = details
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
@details[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def encode_with coder
|
17
|
+
coder.tag = nil
|
18
|
+
coder['class'] = @smell_class
|
19
|
+
coder['subclass'] = @smell_subclass
|
20
|
+
coder['message'] = @message
|
21
|
+
@details.each do |k, v|
|
22
|
+
coder[k] = v
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/reek/smell_warning.rb
CHANGED
@@ -1,71 +1,45 @@
|
|
1
|
+
require 'reek/smell_description'
|
2
|
+
|
1
3
|
module Reek
|
2
4
|
#
|
3
5
|
# Reports a warning that a smell has been found.
|
4
|
-
# This object is essentially a DTO, and therefore contains a :reek:attribute or two.
|
5
6
|
#
|
6
7
|
class SmellWarning
|
7
8
|
include Comparable
|
8
9
|
|
9
|
-
MESSAGE_KEY = 'message'
|
10
|
-
SUBCLASS_KEY = 'subclass'
|
11
|
-
CLASS_KEY = 'class'
|
12
|
-
|
13
|
-
CONTEXT_KEY = 'context'
|
14
|
-
LINES_KEY = 'lines'
|
15
|
-
SOURCE_KEY = 'source'
|
16
|
-
|
17
|
-
ACTIVE_KEY = 'is_active'
|
18
|
-
|
19
10
|
def initialize(class_name, context, lines, message,
|
20
11
|
source = '', subclass_name = '', parameters = {})
|
21
|
-
@smell =
|
22
|
-
CLASS_KEY => class_name,
|
23
|
-
SUBCLASS_KEY => subclass_name,
|
24
|
-
MESSAGE_KEY => message
|
25
|
-
}
|
26
|
-
@smell.merge!(parameters)
|
27
|
-
@status = {
|
28
|
-
ACTIVE_KEY => true
|
29
|
-
}
|
12
|
+
@smell = SmellDescription.new(class_name, subclass_name, message, parameters)
|
30
13
|
@location = {
|
31
|
-
|
32
|
-
|
33
|
-
|
14
|
+
'context' => context.to_s,
|
15
|
+
'lines' => lines,
|
16
|
+
'source' => source
|
34
17
|
}
|
35
18
|
end
|
36
19
|
|
37
20
|
#
|
38
|
-
# Details of the smell found, including its class
|
39
|
-
# subclass ({SUBCLASS_KEY}) and summary message ({MESSAGE_KEY})
|
21
|
+
# Details of the smell found, including its class, subclass and summary message.
|
40
22
|
#
|
41
23
|
# @return [Hash{String => String}]
|
42
24
|
#
|
43
25
|
attr_reader :smell
|
44
26
|
|
45
|
-
def
|
46
|
-
def
|
47
|
-
def
|
27
|
+
def smell_classes() [smell_class, subclass] end
|
28
|
+
def smell_class() @smell.smell_class end
|
29
|
+
def subclass() @smell.smell_subclass end
|
30
|
+
def message() @smell.message end
|
48
31
|
|
49
32
|
#
|
50
|
-
# Details of the smell's location, including its context
|
51
|
-
# the line numbers on which it occurs
|
52
|
-
# file ({SOURCE_KEY})
|
33
|
+
# Details of the smell's location, including its context,
|
34
|
+
# the line numbers on which it occurs and the source file
|
53
35
|
#
|
54
36
|
# @return [Hash{String => String, Array<Number>}]
|
55
37
|
#
|
56
38
|
attr_reader :location
|
57
39
|
|
58
|
-
def context() @location
|
59
|
-
def lines() @location
|
60
|
-
def source() @location
|
61
|
-
|
62
|
-
#
|
63
|
-
# Details of the smell's status, including whether it is active ({ACTIVE_KEY})
|
64
|
-
# (as opposed to being masked by a config file)
|
65
|
-
#
|
66
|
-
# @return [Hash{String => Boolean}]
|
67
|
-
#
|
68
|
-
attr_reader :status
|
40
|
+
def context() @location.fetch('context') end
|
41
|
+
def lines() @location.fetch('lines') end
|
42
|
+
def source() @location.fetch('source') end
|
69
43
|
|
70
44
|
def hash
|
71
45
|
sort_key.hash
|
@@ -79,23 +53,35 @@ module Reek
|
|
79
53
|
(self <=> other) == 0
|
80
54
|
end
|
81
55
|
|
82
|
-
def contains_all?(patterns)
|
83
|
-
rpt = sort_key.to_s
|
84
|
-
patterns.all? { |pattern| pattern =~ rpt }
|
85
|
-
end
|
86
|
-
|
87
56
|
def matches?(klass, patterns)
|
88
|
-
|
57
|
+
smell_classes.include?(klass.to_s) && contains_all?(patterns)
|
89
58
|
end
|
90
59
|
|
91
60
|
def report_on(listener)
|
92
61
|
listener.found_smell(self)
|
93
62
|
end
|
94
63
|
|
64
|
+
def init_with(coder)
|
65
|
+
@location = coder['location']
|
66
|
+
smell_attributes = coder['smell']
|
67
|
+
smell_class = smell_attributes.delete('class')
|
68
|
+
smell_subclass = smell_attributes.delete('subclass')
|
69
|
+
smell_message = smell_attributes.delete('message')
|
70
|
+
@smell = SmellDescription.new(smell_class,
|
71
|
+
smell_subclass,
|
72
|
+
smell_message,
|
73
|
+
smell_attributes)
|
74
|
+
end
|
75
|
+
|
95
76
|
protected
|
96
77
|
|
78
|
+
def contains_all?(patterns)
|
79
|
+
rpt = sort_key.to_s
|
80
|
+
patterns.all? { |pattern| pattern =~ rpt }
|
81
|
+
end
|
82
|
+
|
97
83
|
def sort_key
|
98
|
-
[
|
84
|
+
[context, message, smell_class]
|
99
85
|
end
|
100
86
|
end
|
101
87
|
end
|
data/lib/reek/smells.rb
CHANGED
@@ -8,6 +8,7 @@ require 'reek/smells/feature_envy'
|
|
8
8
|
require 'reek/smells/irresponsible_module'
|
9
9
|
require 'reek/smells/long_parameter_list'
|
10
10
|
require 'reek/smells/long_yield_list'
|
11
|
+
require 'reek/smells/module_initialize'
|
11
12
|
require 'reek/smells/nested_iterators'
|
12
13
|
require 'reek/smells/nil_check'
|
13
14
|
require 'reek/smells/prima_donna_method'
|
@@ -50,7 +50,7 @@ module Reek
|
|
50
50
|
def attributes_in(module_ctx)
|
51
51
|
result = Set.new
|
52
52
|
attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
|
53
|
-
module_ctx.local_nodes(:
|
53
|
+
module_ctx.local_nodes(:send) do |call_node|
|
54
54
|
if attr_defn_methods.include?(call_node.method_name)
|
55
55
|
call_node.arg_names.each { |arg| result << [arg, call_node.line] }
|
56
56
|
end
|
@@ -88,6 +88,8 @@ module Reek
|
|
88
88
|
|
89
89
|
# Finds cases of ControlParameter in a particular node for a particular parameter
|
90
90
|
class ControlParameterFinder
|
91
|
+
CONDITIONAL_NODE_TYPES = [:if, :case, :and, :or]
|
92
|
+
|
91
93
|
def initialize(node, param)
|
92
94
|
@node = node
|
93
95
|
@param = param
|
@@ -108,7 +110,7 @@ module Reek
|
|
108
110
|
private
|
109
111
|
|
110
112
|
def conditional_nodes
|
111
|
-
@node.
|
113
|
+
@node.body_nodes(CONDITIONAL_NODE_TYPES)
|
112
114
|
end
|
113
115
|
|
114
116
|
def nested_finders
|
@@ -118,8 +120,8 @@ module Reek
|
|
118
120
|
end
|
119
121
|
|
120
122
|
def uses_param_in_call_in_condition?
|
121
|
-
return
|
122
|
-
condition.each_node(:
|
123
|
+
return unless condition
|
124
|
+
condition.each_node(:send) do |inner|
|
123
125
|
next unless regular_call_involving_param? inner
|
124
126
|
return true
|
125
127
|
end
|
@@ -127,10 +129,15 @@ module Reek
|
|
127
129
|
end
|
128
130
|
|
129
131
|
def uses_of_param_in_condition
|
130
|
-
return [] unless
|
132
|
+
return [] unless condition
|
131
133
|
condition.each_node(:lvar).select { |inner| inner.var_name == @param }
|
132
134
|
end
|
133
135
|
|
136
|
+
def condition
|
137
|
+
return nil unless CONDITIONAL_NODE_TYPES.include? @node.type
|
138
|
+
@node.condition
|
139
|
+
end
|
140
|
+
|
134
141
|
def regular_call_involving_param?(call_node)
|
135
142
|
call_involving_param?(call_node) && !comparison_call?(call_node)
|
136
143
|
end
|
@@ -140,15 +147,15 @@ module Reek
|
|
140
147
|
end
|
141
148
|
|
142
149
|
def comparison_method_names
|
143
|
-
[:==,
|
150
|
+
[:==, :!=, :=~]
|
144
151
|
end
|
145
152
|
|
146
153
|
def call_involving_param?(call_node)
|
147
|
-
call_node.
|
154
|
+
call_node.each_node(:lvar).any? { |it| it.var_name == @param }
|
148
155
|
end
|
149
156
|
|
150
157
|
def uses_param_in_body?
|
151
|
-
nodes = @node.
|
158
|
+
nodes = @node.body_nodes([:lvar], [:if, :case, :and, :or])
|
152
159
|
nodes.any? { |lvar_node| lvar_node.var_name == @param }
|
153
160
|
end
|
154
161
|
end
|