reek 1.2.3 → 1.2.4
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 +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
|