reek 1.2.3 → 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -1
- data/features/options.feature +1 -9
- data/features/samples.feature +7 -2
- data/features/step_definitions/reek_steps.rb +3 -3
- data/lib/reek.rb +1 -1
- data/lib/reek/adapters/application.rb +22 -27
- data/lib/reek/adapters/command_line.rb +41 -42
- data/lib/reek/adapters/report.rb +30 -23
- data/lib/reek/adapters/spec.rb +1 -1
- data/lib/reek/code_context.rb +6 -2
- data/lib/reek/code_parser.rb +3 -7
- data/lib/reek/detector_stack.rb +2 -4
- data/lib/reek/help_command.rb +14 -0
- data/lib/reek/masking_collection.rb +33 -0
- data/lib/reek/method_context.rb +18 -6
- data/lib/reek/module_context.rb +0 -13
- data/lib/reek/reek_command.rb +28 -0
- data/lib/reek/singleton_method_context.rb +1 -1
- data/lib/reek/smell_warning.rb +5 -3
- data/lib/reek/smells/attribute.rb +17 -1
- data/lib/reek/smells/class_variable.rb +1 -1
- data/lib/reek/smells/control_couple.rb +13 -10
- data/lib/reek/smells/large_class.rb +1 -1
- data/lib/reek/smells/long_method.rb +0 -2
- data/lib/reek/smells/simulated_polymorphism.rb +2 -2
- data/lib/reek/sniffer.rb +1 -3
- data/lib/reek/tree_dresser.rb +35 -23
- data/lib/reek/version_command.rb +14 -0
- data/reek.gemspec +3 -3
- data/spec/reek/adapters/report_spec.rb +8 -8
- data/spec/reek/adapters/should_reek_of_spec.rb +1 -1
- data/spec/reek/adapters/should_reek_only_of_spec.rb +2 -2
- data/spec/reek/adapters/should_reek_spec.rb +3 -3
- data/spec/reek/code_context_spec.rb +11 -11
- data/spec/reek/code_parser_spec.rb +0 -88
- data/spec/reek/help_command_spec.rb +24 -0
- data/spec/reek/masking_collection_spec.rb +236 -0
- data/spec/reek/method_context_spec.rb +43 -1
- data/spec/reek/reek_command_spec.rb +45 -0
- data/spec/reek/smell_warning_spec.rb +12 -4
- data/spec/reek/smells/attribute_spec.rb +79 -7
- data/spec/reek/smells/control_couple_spec.rb +40 -11
- data/spec/reek/smells/long_parameter_list_spec.rb +1 -1
- data/spec/reek/smells/smell_detector_spec.rb +0 -17
- data/spec/reek/tree_dresser_spec.rb +20 -0
- data/spec/reek/version_command_spec.rb +29 -0
- metadata +11 -2
data/lib/reek/code_parser.rb
CHANGED
@@ -91,7 +91,6 @@ module Reek
|
|
91
91
|
|
92
92
|
def process_call(exp)
|
93
93
|
@element.record_call_to(exp)
|
94
|
-
@element.check_for_attribute_declaration(exp)
|
95
94
|
process_default(exp)
|
96
95
|
end
|
97
96
|
|
@@ -120,20 +119,17 @@ module Reek
|
|
120
119
|
|
121
120
|
def process_until(exp)
|
122
121
|
count_clause(exp[2])
|
123
|
-
|
124
|
-
@element.count_statements(-1)
|
122
|
+
process_case(exp)
|
125
123
|
end
|
126
124
|
|
127
125
|
def process_for(exp)
|
128
126
|
count_clause(exp[3])
|
129
|
-
|
130
|
-
@element.count_statements(-1)
|
127
|
+
process_case(exp)
|
131
128
|
end
|
132
129
|
|
133
130
|
def process_rescue(exp)
|
134
131
|
count_clause(exp[1])
|
135
|
-
|
136
|
-
@element.count_statements(-1)
|
132
|
+
process_case(exp)
|
137
133
|
end
|
138
134
|
|
139
135
|
def process_resbody(exp)
|
data/lib/reek/detector_stack.rb
CHANGED
@@ -20,9 +20,7 @@ module Reek
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def num_smells
|
23
|
-
total
|
24
|
-
@detectors.each { |det| total += det.num_smells }
|
25
|
-
total
|
23
|
+
@detectors.inject(0) { |total, detector| total += detector.num_smells }
|
26
24
|
end
|
27
25
|
|
28
26
|
def has_smell?(patterns)
|
@@ -31,7 +29,7 @@ module Reek
|
|
31
29
|
end
|
32
30
|
|
33
31
|
def smelly?
|
34
|
-
# SMELL: Duplication: look at
|
32
|
+
# SMELL: Duplication: look at all those loops!
|
35
33
|
@detectors.each { |det| return true if det.smelly? }
|
36
34
|
false
|
37
35
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
#
|
4
|
+
# A set of items that can be "masked" or "visible". If an item is added
|
5
|
+
# more than once, only the "visible" copy is retained.
|
6
|
+
#
|
7
|
+
class MaskingCollection
|
8
|
+
def initialize
|
9
|
+
@visible_items = SortedSet.new
|
10
|
+
@masked_items = SortedSet.new
|
11
|
+
end
|
12
|
+
def add(item)
|
13
|
+
@visible_items.add(item)
|
14
|
+
@masked_items.delete(item) if @masked_items.include?(item)
|
15
|
+
end
|
16
|
+
def add_masked(item)
|
17
|
+
@masked_items.add(item) unless @visible_items.include?(item)
|
18
|
+
end
|
19
|
+
def num_visible_items
|
20
|
+
@visible_items.length
|
21
|
+
end
|
22
|
+
def num_masked_items
|
23
|
+
@masked_items.length
|
24
|
+
end
|
25
|
+
def each_item(&blk)
|
26
|
+
all = SortedSet.new(@visible_items)
|
27
|
+
all.merge(@masked_items)
|
28
|
+
all.each(&blk)
|
29
|
+
end
|
30
|
+
def each_visible_item(&blk)
|
31
|
+
@visible_items.each(&blk)
|
32
|
+
end
|
33
|
+
end
|
data/lib/reek/method_context.rb
CHANGED
@@ -28,14 +28,26 @@ end
|
|
28
28
|
module Reek
|
29
29
|
|
30
30
|
module MethodParameters
|
31
|
-
def
|
32
|
-
|
31
|
+
def default_assignments
|
32
|
+
assignments = self[-1]
|
33
|
+
result = {}
|
34
|
+
return result unless is_assignment_block?(assignments)
|
35
|
+
assignments[1..-1].each do |exp|
|
36
|
+
result[exp[1]] = exp[2] if exp[0] == :lasgn
|
37
|
+
end
|
38
|
+
result
|
39
|
+
end
|
40
|
+
def is_arg?(param)
|
41
|
+
return false if is_assignment_block?(param)
|
33
42
|
return !(param.to_s =~ /^\&/)
|
34
43
|
end
|
44
|
+
def is_assignment_block?(param)
|
45
|
+
Array === param and param[0] == :block
|
46
|
+
end
|
35
47
|
|
36
48
|
def names
|
37
49
|
return @names if @names
|
38
|
-
@names = self[1..-1].select {|arg|
|
50
|
+
@names = self[1..-1].select {|arg| is_arg?(arg)}.map {|arg| Name.new(arg)}
|
39
51
|
end
|
40
52
|
|
41
53
|
def length
|
@@ -57,7 +69,8 @@ module Reek
|
|
57
69
|
attr_reader :refs
|
58
70
|
attr_reader :num_statements
|
59
71
|
|
60
|
-
def initialize(outer, exp
|
72
|
+
def initialize(outer, exp)
|
73
|
+
# SMELL: Unused Parameter
|
61
74
|
super(outer, exp)
|
62
75
|
@parameters = exp[exp[0] == :defn ? 2 : 3] # SMELL: SimulatedPolymorphism
|
63
76
|
@parameters ||= []
|
@@ -67,7 +80,7 @@ module Reek
|
|
67
80
|
@calls = Hash.new(0)
|
68
81
|
@depends_on_self = false
|
69
82
|
@refs = ObjectRefs.new
|
70
|
-
@outer.record_method(self)
|
83
|
+
@outer.record_method(self) # SMELL: these could be found by tree walking
|
71
84
|
end
|
72
85
|
|
73
86
|
def count_statements(num)
|
@@ -85,7 +98,6 @@ module Reek
|
|
85
98
|
def record_call_to(exp)
|
86
99
|
@calls[exp] += 1
|
87
100
|
record_receiver(exp)
|
88
|
-
check_for_attribute_declaration(exp)
|
89
101
|
end
|
90
102
|
|
91
103
|
def record_receiver(exp)
|
data/lib/reek/module_context.rb
CHANGED
@@ -18,12 +18,9 @@ module Reek
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
attr_reader :attributes
|
22
|
-
|
23
21
|
def initialize(outer, name, exp)
|
24
22
|
super(outer, exp)
|
25
23
|
@name = name
|
26
|
-
@attributes = Set.new
|
27
24
|
@parsed_methods = []
|
28
25
|
end
|
29
26
|
|
@@ -40,20 +37,10 @@ module Reek
|
|
40
37
|
@parsed_methods.select {|meth| meth.parameters.length >= min_clump_size }
|
41
38
|
end
|
42
39
|
|
43
|
-
def record_attribute(attr)
|
44
|
-
@attributes << Name.new(attr)
|
45
|
-
end
|
46
|
-
|
47
40
|
def record_method(meth)
|
48
41
|
@parsed_methods << meth
|
49
42
|
end
|
50
43
|
|
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
44
|
def outer_name
|
58
45
|
"#{@outer.outer_name}#{@name}::"
|
59
46
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'reek/sniffer'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
|
5
|
+
class ReekCommand
|
6
|
+
|
7
|
+
def initialize(sources, report_class, show_all)
|
8
|
+
@sniffer = sources.length > 0 ? sources.sniff : sniff_stdin
|
9
|
+
@report_class = report_class
|
10
|
+
@show_all = show_all
|
11
|
+
end
|
12
|
+
|
13
|
+
def sniff_stdin
|
14
|
+
Reek::Sniffer.new($stdin.to_reek_source('$stdin'))
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute(view)
|
18
|
+
rpt = @report_class.new(@sniffer.sniffers, @show_all)
|
19
|
+
view.output(rpt.report)
|
20
|
+
if @sniffer.smelly?
|
21
|
+
view.report_smells
|
22
|
+
else
|
23
|
+
view.report_success
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/reek/smell_warning.rb
CHANGED
@@ -21,7 +21,9 @@ module Reek
|
|
21
21
|
sort_key <=> other.sort_key
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
def eql?(other)
|
25
|
+
(self <=> other) == 0
|
26
|
+
end
|
25
27
|
|
26
28
|
#
|
27
29
|
# Returns +true+ only if this is a warning about an instance of
|
@@ -49,9 +51,9 @@ module Reek
|
|
49
51
|
|
50
52
|
def report_on(report)
|
51
53
|
if @is_masked
|
52
|
-
report.
|
54
|
+
report.found_masked_smell(self)
|
53
55
|
else
|
54
|
-
report
|
56
|
+
report.found_smell(self)
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
@@ -18,6 +18,8 @@ module Reek
|
|
18
18
|
#
|
19
19
|
class Attribute < SmellDetector
|
20
20
|
|
21
|
+
ATTRIBUTE_METHODS = [:attr, :attr_reader, :attr_writer, :attr_accessor]
|
22
|
+
|
21
23
|
def self.contexts # :nodoc:
|
22
24
|
[:class, :module]
|
23
25
|
end
|
@@ -39,10 +41,24 @@ module Reek
|
|
39
41
|
# MethodContext, ClassContext and ModuleContext all know which
|
40
42
|
# calls constitute attribute declarations. Need a method on
|
41
43
|
# ModuleContext: each_public_call.select [names] {...}
|
42
|
-
mod.
|
44
|
+
attributes_in(mod).each do |attr|
|
43
45
|
found(mod, "declares the attribute #{attr}")
|
44
46
|
end
|
45
47
|
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Collects the names of the class variables declared and/or used
|
51
|
+
# in the given module.
|
52
|
+
#
|
53
|
+
def attributes_in(mod)
|
54
|
+
result = Set.new
|
55
|
+
mod.local_nodes(:call) do |call_node|
|
56
|
+
if ATTRIBUTE_METHODS.include?(call_node.method_name)
|
57
|
+
call_node.arg_names.each {|arg| result << arg }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
result
|
61
|
+
end
|
46
62
|
end
|
47
63
|
end
|
48
64
|
end
|
@@ -35,7 +35,7 @@ module Reek
|
|
35
35
|
result = Set.new
|
36
36
|
collector = proc { |cvar_node| result << cvar_node.name }
|
37
37
|
[:cvar, :cvasgn, :cvdecl].each do |stmt_type|
|
38
|
-
mod.
|
38
|
+
mod.local_nodes(stmt_type, &collector)
|
39
39
|
end
|
40
40
|
result
|
41
41
|
end
|
@@ -34,24 +34,27 @@ module Reek
|
|
34
34
|
# because it includes at least two different code paths.
|
35
35
|
#
|
36
36
|
class ControlCouple < SmellDetector
|
37
|
-
include ExcludeInitialize
|
38
37
|
|
39
38
|
def self.contexts # :nodoc:
|
40
|
-
[:if]
|
39
|
+
[:if, :defn, :defs]
|
41
40
|
end
|
42
41
|
|
43
42
|
#
|
44
43
|
# Checks whether the given conditional statement relies on a control couple.
|
45
44
|
# Remembers any smells found.
|
46
45
|
#
|
47
|
-
def examine_context(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
46
|
+
def examine_context(ctx)
|
47
|
+
case ctx
|
48
|
+
when IfContext
|
49
|
+
return unless ctx.tests_a_parameter?
|
50
|
+
found(ctx, "is controlled by argument #{SexpFormatter.format(ctx.if_expr)}")
|
51
|
+
when MethodContext
|
52
|
+
ctx.parameters.default_assignments.each do |param, value|
|
53
|
+
next unless [:true, :false].include?(value[0])
|
54
|
+
found(ctx, "is controlled by argument #{param.to_s}")
|
55
|
+
end
|
56
|
+
else
|
57
|
+
end
|
55
58
|
end
|
56
59
|
end
|
57
60
|
end
|
@@ -45,7 +45,7 @@ module Reek
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def check_num_methods(klass) # :nodoc:
|
48
|
-
actual = klass.
|
48
|
+
actual = klass.local_nodes(:defn).length
|
49
49
|
return if actual <= value(MAX_ALLOWED_METHODS_KEY, klass, DEFAULT_MAX_METHODS)
|
50
50
|
found(klass, "has at least #{actual} methods")
|
51
51
|
end
|
@@ -63,8 +63,8 @@ module Reek
|
|
63
63
|
condition = node.condition
|
64
64
|
result[condition] += 1 unless condition == s(:call, nil, :block_given?, s(:arglist))
|
65
65
|
}
|
66
|
-
klass.
|
67
|
-
klass.
|
66
|
+
klass.local_nodes(:if, &collector)
|
67
|
+
klass.local_nodes(:case, &collector)
|
68
68
|
result
|
69
69
|
end
|
70
70
|
end
|
data/lib/reek/sniffer.rb
CHANGED
data/lib/reek/tree_dresser.rb
CHANGED
@@ -32,43 +32,55 @@ module Reek
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
module
|
36
|
-
|
37
|
-
|
35
|
+
module SexpExtensions
|
36
|
+
module CaseNode
|
37
|
+
def condition
|
38
|
+
self[1]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module CallNode
|
43
|
+
def receiver() self[1] end
|
44
|
+
def method_name() self[2] end
|
45
|
+
def args() self[3] end
|
46
|
+
def arg_names
|
47
|
+
args[1..-1].map {|arg| arg[1]}
|
48
|
+
end
|
38
49
|
end
|
39
|
-
end
|
40
50
|
|
41
|
-
|
42
|
-
|
43
|
-
self[1]
|
51
|
+
module CvarNode
|
52
|
+
def name() self[1] end
|
44
53
|
end
|
45
|
-
end
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
CvasgnNode = CvarNode
|
56
|
+
CvdeclNode = CvarNode
|
57
|
+
|
58
|
+
module DefnNode
|
59
|
+
def method_name() self[1] end
|
60
|
+
def parameters() self[2] end
|
61
|
+
end
|
62
|
+
|
63
|
+
module IfNode
|
64
|
+
def condition
|
65
|
+
self[1]
|
66
|
+
end
|
50
67
|
end
|
51
68
|
end
|
52
69
|
|
53
70
|
class TreeDresser
|
54
|
-
# SMELL: Duplication
|
55
|
-
# Put these into a new module and build the mapping automagically
|
56
|
-
# based on the node type
|
57
|
-
EXTENSIONS = {
|
58
|
-
:cvar => CvarNode,
|
59
|
-
:cvasgn => CvarNode,
|
60
|
-
:cvdecl => CvarNode,
|
61
|
-
:if => IfNode,
|
62
|
-
:case => CaseNode,
|
63
|
-
}
|
64
71
|
|
65
72
|
def dress(sexp)
|
66
73
|
sexp.extend(SexpNode)
|
67
|
-
|
68
|
-
|
74
|
+
module_name = extensions_for(sexp.sexp_type)
|
75
|
+
if Reek::SexpExtensions.const_defined?(module_name)
|
76
|
+
sexp.extend(Reek::SexpExtensions.const_get(module_name))
|
69
77
|
end
|
70
78
|
sexp[0..-1].each { |sub| dress(sub) if Array === sub }
|
71
79
|
sexp
|
72
80
|
end
|
81
|
+
|
82
|
+
def extensions_for(node_type)
|
83
|
+
"#{node_type.to_s.capitalize}Node"
|
84
|
+
end
|
73
85
|
end
|
74
86
|
end
|