reek 1.2.7.1 → 1.2.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +13 -1
- data/config/defaults.reek +4 -1
- data/features/masking_smells.feature +7 -7
- data/features/rake_task.feature +2 -2
- data/features/reports.feature +3 -3
- data/features/samples.feature +5 -2
- data/features/yaml.feature +0 -39
- data/lib/reek.rb +1 -1
- data/lib/reek/cli/command_line.rb +3 -3
- data/lib/reek/cli/reek_command.rb +5 -6
- data/lib/reek/cli/report.rb +9 -20
- data/lib/reek/cli/yaml_command.rb +1 -1
- data/lib/reek/core/class_context.rb +1 -2
- data/lib/reek/core/code_context.rb +10 -27
- data/lib/reek/core/code_parser.rb +1 -18
- data/lib/reek/core/detector_stack.rb +4 -0
- data/lib/reek/core/masking_collection.rb +6 -0
- data/lib/reek/core/method_context.rb +8 -56
- data/lib/reek/core/module_context.rb +6 -32
- data/lib/reek/core/object_refs.rb +36 -36
- data/lib/reek/core/singleton_method_context.rb +10 -21
- data/lib/reek/core/sniffer.rb +3 -2
- data/lib/reek/examiner.rb +39 -31
- data/lib/reek/smell_warning.rb +8 -0
- data/lib/reek/smells/attribute.rb +4 -2
- data/lib/reek/smells/class_variable.rb +3 -3
- data/lib/reek/smells/control_couple.rb +1 -2
- data/lib/reek/smells/data_clump.rb +86 -9
- data/lib/reek/smells/duplication.rb +2 -3
- data/lib/reek/smells/feature_envy.rb +9 -4
- data/lib/reek/smells/simulated_polymorphism.rb +1 -2
- data/lib/reek/smells/smell_detector.rb +0 -6
- data/lib/reek/smells/uncommunicative_method_name.rb +8 -2
- data/lib/reek/smells/uncommunicative_parameter_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
- data/lib/reek/smells/utility_function.rb +17 -5
- data/lib/reek/source/reference_collector.rb +21 -0
- data/lib/reek/source/sexp_formatter.rb +1 -0
- data/lib/reek/source/tree_dresser.rb +67 -9
- data/lib/reek/spec/should_reek.rb +1 -1
- data/lib/reek/spec/should_reek_of.rb +1 -1
- data/lib/reek/spec/should_reek_only_of.rb +1 -1
- data/reek.gemspec +3 -3
- data/spec/reek/cli/reek_command_spec.rb +3 -2
- data/spec/reek/cli/report_spec.rb +2 -2
- data/spec/reek/cli/yaml_command_spec.rb +2 -2
- data/spec/reek/core/code_context_spec.rb +39 -54
- data/spec/reek/core/method_context_spec.rb +7 -26
- data/spec/reek/core/module_context_spec.rb +0 -15
- data/spec/reek/core/singleton_method_context_spec.rb +0 -6
- data/spec/reek/examiner_spec.rb +6 -6
- data/spec/reek/smells/attribute_spec.rb +30 -32
- data/spec/reek/smells/class_variable_spec.rb +15 -18
- data/spec/reek/smells/data_clump_spec.rb +22 -6
- data/spec/reek/smells/duplication_spec.rb +33 -19
- data/spec/reek/smells/feature_envy_spec.rb +82 -88
- data/spec/reek/smells/large_class_spec.rb +1 -1
- data/spec/reek/smells/smell_detector_shared.rb +1 -1
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +37 -35
- data/spec/reek/smells/utility_function_spec.rb +7 -0
- data/spec/reek/source/reference_collector_spec.rb +53 -0
- data/spec/reek/source/tree_dresser_spec.rb +10 -0
- data/spec/reek/spec/should_reek_only_of_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -0
- metadata +4 -5
- data/features/profile.feature +0 -34
- data/lib/reek/core/block_context.rb +0 -18
- data/spec/reek/core/block_context_spec.rb +0 -26
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'sexp'
|
2
|
-
require File.join(File.dirname(File.expand_path(__FILE__)), 'block_context')
|
3
2
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'class_context')
|
4
3
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
|
5
4
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'module_context')
|
@@ -61,21 +60,6 @@ module Reek
|
|
61
60
|
@element.record_use_of_self
|
62
61
|
end
|
63
62
|
|
64
|
-
def process_lit(exp)
|
65
|
-
val = exp[1]
|
66
|
-
@element.record_depends_on_self if val == :self
|
67
|
-
end
|
68
|
-
|
69
|
-
def process_iter(exp)
|
70
|
-
process(exp[1])
|
71
|
-
scope = BlockContext.new(@element, exp)
|
72
|
-
push(scope) do
|
73
|
-
process_default(exp[2..-1])
|
74
|
-
check_smells(exp[0])
|
75
|
-
end
|
76
|
-
scope
|
77
|
-
end
|
78
|
-
|
79
63
|
def process_block(exp)
|
80
64
|
@element.count_statements(CodeParser.count_statements(exp))
|
81
65
|
process_default(exp)
|
@@ -144,8 +128,7 @@ module Reek
|
|
144
128
|
end
|
145
129
|
|
146
130
|
def process_iasgn(exp)
|
147
|
-
@element.
|
148
|
-
@element.record_depends_on_self
|
131
|
+
@element.record_use_of_self
|
149
132
|
process_default(exp)
|
150
133
|
end
|
151
134
|
|
@@ -12,6 +12,12 @@ module Reek
|
|
12
12
|
@visible_items = SortedSet.new
|
13
13
|
@masked_items = SortedSet.new
|
14
14
|
end
|
15
|
+
|
16
|
+
def collect_from(sources, config)
|
17
|
+
sources.each { |src| Core::Sniffer.new(src, config).report_on(self) }
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
15
21
|
def all_items
|
16
22
|
all = SortedSet.new(@visible_items)
|
17
23
|
all.merge(@masked_items)
|
@@ -1,31 +1,5 @@
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'object_refs')
|
2
2
|
|
3
|
-
#
|
4
|
-
# Extensions to +Array+ needed by Reek.
|
5
|
-
#
|
6
|
-
class Array
|
7
|
-
def power_set
|
8
|
-
self.inject([[]]) { |cum, element| cum.cross(element) }
|
9
|
-
end
|
10
|
-
|
11
|
-
def bounded_power_set(lower_bound)
|
12
|
-
power_set.select {|ps| ps.length > lower_bound}
|
13
|
-
end
|
14
|
-
|
15
|
-
def cross(element)
|
16
|
-
result = []
|
17
|
-
self.each do |set|
|
18
|
-
result << set
|
19
|
-
result << (set + [element])
|
20
|
-
end
|
21
|
-
result
|
22
|
-
end
|
23
|
-
|
24
|
-
def intersection
|
25
|
-
self.inject { |res, elem| elem & res }
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
3
|
module Reek
|
30
4
|
module Core
|
31
5
|
|
@@ -77,10 +51,7 @@ module Reek
|
|
77
51
|
@parameters = exp[exp[0] == :defn ? 2 : 3] # SMELL: SimulatedPolymorphism
|
78
52
|
@parameters ||= []
|
79
53
|
@parameters.extend(MethodParameters)
|
80
|
-
@name = exp[1].to_s
|
81
|
-
@scope_connector = '#'
|
82
54
|
@num_statements = 0
|
83
|
-
@depends_on_self = false
|
84
55
|
@refs = ObjectRefs.new
|
85
56
|
@outer.record_method(self) # SMELL: these could be found by tree walking
|
86
57
|
end
|
@@ -89,44 +60,25 @@ module Reek
|
|
89
60
|
@num_statements += num
|
90
61
|
end
|
91
62
|
|
92
|
-
def depends_on_instance?
|
93
|
-
@depends_on_self
|
94
|
-
end
|
95
|
-
|
96
63
|
def record_call_to(exp)
|
97
|
-
|
64
|
+
receiver, meth = exp[1..2]
|
65
|
+
receiver ||= [:self]
|
66
|
+
case receiver[0]
|
67
|
+
when :lvar
|
68
|
+
@refs.record_ref(receiver) unless meth == :new
|
69
|
+
when :self
|
70
|
+
record_use_of_self
|
71
|
+
end
|
98
72
|
end
|
99
73
|
|
100
74
|
def record_use_of_self
|
101
|
-
record_depends_on_self
|
102
75
|
@refs.record_reference_to_self
|
103
76
|
end
|
104
77
|
|
105
|
-
def record_instance_variable(sym)
|
106
|
-
record_use_of_self
|
107
|
-
end
|
108
|
-
|
109
|
-
def record_depends_on_self
|
110
|
-
@depends_on_self = true
|
111
|
-
end
|
112
|
-
|
113
78
|
def envious_receivers
|
114
79
|
return [] if @refs.self_is_max?
|
115
80
|
@refs.max_keys
|
116
81
|
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
def record_receiver(exp)
|
121
|
-
receiver, meth = exp[1..2]
|
122
|
-
receiver ||= [:self]
|
123
|
-
case receiver[0]
|
124
|
-
when :lvar
|
125
|
-
@refs.record_ref(receiver) unless meth == :new
|
126
|
-
when :self
|
127
|
-
record_use_of_self
|
128
|
-
end
|
129
|
-
end
|
130
82
|
end
|
131
83
|
end
|
132
84
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
-
require File.join(
|
2
|
-
require File.join(
|
3
|
-
require File.join(
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_context')
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_parser')
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'sniffer')
|
4
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'sexp_formatter')
|
4
5
|
|
5
6
|
module Reek
|
6
7
|
module Core
|
@@ -12,8 +13,8 @@ module Reek
|
|
12
13
|
|
13
14
|
class << self
|
14
15
|
def create(outer, exp)
|
15
|
-
res =
|
16
|
-
new(
|
16
|
+
res = Source::SexpFormatter.format(exp[1])
|
17
|
+
new(outer, res, exp)
|
17
18
|
end
|
18
19
|
|
19
20
|
def from_s(src)
|
@@ -21,41 +22,14 @@ module Reek
|
|
21
22
|
sniffer = Sniffer.new(source)
|
22
23
|
CodeParser.new(sniffer).do_module_or_class(source.syntax_tree, self)
|
23
24
|
end
|
24
|
-
|
25
|
-
def resolve(exp, context)
|
26
|
-
unless Array === exp
|
27
|
-
return resolve_string(exp.to_s, context)
|
28
|
-
end
|
29
|
-
name = exp[1]
|
30
|
-
case exp[0]
|
31
|
-
when :colon2
|
32
|
-
return [resolve(name, context)[0], exp[2].to_s]
|
33
|
-
when :const
|
34
|
-
return [ModuleContext.create(context, exp), name.to_s]
|
35
|
-
when :colon3
|
36
|
-
return [StopContext.new, name.to_s]
|
37
|
-
else
|
38
|
-
return [context, name.to_s]
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def resolve_string(str, context)
|
43
|
-
return [context, str.to_s] unless str =~ /::/
|
44
|
-
resolve(RubyParser.new.parse(str), context)
|
45
|
-
end
|
46
25
|
end
|
47
26
|
|
48
27
|
def initialize(outer, name, exp)
|
49
28
|
super(outer, exp)
|
50
29
|
@name = name
|
51
|
-
@scope_connector = '::'
|
52
30
|
@parsed_methods = []
|
53
31
|
end
|
54
32
|
|
55
|
-
def parameterized_methods(min_clump_size)
|
56
|
-
@parsed_methods.select {|meth| meth.parameters.length >= min_clump_size }
|
57
|
-
end
|
58
|
-
|
59
33
|
def record_method(meth)
|
60
34
|
@parsed_methods << meth
|
61
35
|
end
|
@@ -1,51 +1,51 @@
|
|
1
1
|
module Reek
|
2
2
|
module Core
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
#
|
5
|
+
# Manages and counts the references out of a method to other objects.
|
6
|
+
#
|
7
|
+
class ObjectRefs # :nodoc:
|
8
|
+
def initialize
|
9
|
+
@refs = Hash.new(0)
|
10
|
+
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def record_reference_to_self
|
13
|
+
record_ref(SELF_REF)
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
def record_ref(exp)
|
17
|
+
type = exp[0]
|
18
|
+
case type
|
19
|
+
when :gvar
|
20
|
+
return
|
21
|
+
when :self
|
22
|
+
record_reference_to_self
|
23
|
+
else
|
24
|
+
@refs[exp] += 1
|
25
|
+
end
|
25
26
|
end
|
26
|
-
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def refs_to_self
|
29
|
+
@refs[SELF_REF]
|
30
|
+
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
def max_refs
|
33
|
+
@refs.values.max or 0
|
34
|
+
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
def max_keys
|
37
|
+
max = max_refs
|
38
|
+
@refs.reject {|key,val| val != max}
|
39
|
+
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
def self_is_max?
|
42
|
+
max_keys.length == 0 || @refs[SELF_REF] == max_refs
|
43
|
+
end
|
44
44
|
|
45
|
-
|
45
|
+
private
|
46
46
|
|
47
|
-
|
47
|
+
SELF_REF = Sexp.from_array([:lit, :self])
|
48
48
|
|
49
|
-
|
49
|
+
end
|
50
50
|
end
|
51
51
|
end
|
@@ -1,31 +1,20 @@
|
|
1
1
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
|
2
|
-
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
3
2
|
|
4
3
|
module Reek
|
5
4
|
module Core
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
#
|
7
|
+
# A context wrapper for any singleton method definition found in a syntax tree.
|
8
|
+
#
|
9
|
+
class SingletonMethodContext < MethodContext
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@receiver = Source::SexpFormatter.format(exp[1])
|
16
|
-
@scope_connector = ""
|
17
|
-
record_depends_on_self
|
18
|
-
end
|
19
|
-
|
20
|
-
def envious_receivers
|
21
|
-
[]
|
22
|
-
end
|
11
|
+
def initialize(outer, exp)
|
12
|
+
super(outer, exp)
|
13
|
+
end
|
23
14
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
"#{prefix}#{@receiver}.#{@name}"
|
15
|
+
def envious_receivers
|
16
|
+
[]
|
17
|
+
end
|
28
18
|
end
|
29
19
|
end
|
30
|
-
end
|
31
20
|
end
|
data/lib/reek/core/sniffer.rb
CHANGED
@@ -65,7 +65,8 @@ module Reek
|
|
65
65
|
]
|
66
66
|
end
|
67
67
|
|
68
|
-
def initialize(src)
|
68
|
+
def initialize(src, config_strategy = ActiveSmellsOnly.new) # SMELL: open secret -- need a Strategy
|
69
|
+
@config_strategy = config_strategy
|
69
70
|
@already_checked_for_smells = false
|
70
71
|
@typed_detectors = nil
|
71
72
|
@detectors = Hash.new
|
@@ -83,7 +84,7 @@ module Reek
|
|
83
84
|
end
|
84
85
|
|
85
86
|
def configure(klass, config)
|
86
|
-
@detectors[klass]
|
87
|
+
@config_strategy.configure(@detectors[klass], config)
|
87
88
|
end
|
88
89
|
|
89
90
|
def report_on(report)
|
data/lib/reek/examiner.rb
CHANGED
@@ -4,8 +4,28 @@ require File.join(File.dirname(File.expand_path(__FILE__)), 'source')
|
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
|
7
|
+
class ActiveSmellsOnly
|
8
|
+
def configure(detectors, config)
|
9
|
+
detectors.adopt(config)
|
10
|
+
end
|
11
|
+
|
12
|
+
def smells_in(sources)
|
13
|
+
Core::MaskingCollection.new.collect_from(sources, self).all_active_items.to_a
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ActiveAndMaskedSmells
|
18
|
+
def configure(detectors, config)
|
19
|
+
detectors.push(config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def smells_in(sources)
|
23
|
+
Core::MaskingCollection.new.collect_from(sources, self).all_items
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
7
27
|
#
|
8
|
-
# Finds the code smells in Ruby source code.
|
28
|
+
# Finds the active code smells in Ruby source code.
|
9
29
|
#
|
10
30
|
class Examiner
|
11
31
|
|
@@ -27,62 +47,50 @@ module Reek
|
|
27
47
|
# and if it is an Array, it is assumed to be a list of file paths,
|
28
48
|
# each of which is opened and parsed for source code.
|
29
49
|
#
|
30
|
-
|
31
|
-
|
50
|
+
# @param [#smells_in]
|
51
|
+
# The +collector+ will be asked to examine the sources and report
|
52
|
+
# an array of SmellWarning objects. The default collector is an
|
53
|
+
# instance of ActiveSmellsOnly, which completely ignores all smells
|
54
|
+
# that have been masked by configuration options.
|
55
|
+
#
|
56
|
+
def initialize(source, collector = ActiveSmellsOnly.new)
|
57
|
+
sources = case source
|
32
58
|
when Array
|
33
59
|
@description = 'dir'
|
34
|
-
Source::SourceLocator.new(source).all_sources
|
60
|
+
Source::SourceLocator.new(source).all_sources
|
35
61
|
when Source::SourceCode
|
36
62
|
@description = source.desc
|
37
|
-
[
|
63
|
+
[source]
|
38
64
|
else
|
39
65
|
src = source.to_reek_source
|
40
66
|
@description = src.desc
|
41
|
-
[
|
67
|
+
[src]
|
42
68
|
end
|
43
|
-
@
|
44
|
-
sniffers.each {|sniffer| sniffer.report_on(@warnings)}
|
69
|
+
@smells = collector.smells_in(sources)
|
45
70
|
end
|
46
71
|
|
47
72
|
#
|
48
|
-
# Returns an Array of SmellWarning objects, one for each
|
73
|
+
# Returns an Array of SmellWarning objects, one for each active smell
|
49
74
|
# in the source.
|
50
75
|
#
|
51
76
|
# @return [Array<SmellWarning>]
|
52
77
|
#
|
53
|
-
def
|
54
|
-
@
|
55
|
-
end
|
56
|
-
|
57
|
-
#
|
58
|
-
# Returns an Array of SmellWarning objects, one for each smell
|
59
|
-
# in the source; includes active smells and masked smells.
|
60
|
-
#
|
61
|
-
# @return [Array<SmellWarning>]
|
62
|
-
#
|
63
|
-
def all_smells
|
64
|
-
@warnings.all_items
|
78
|
+
def smells
|
79
|
+
@smells
|
65
80
|
end
|
66
81
|
|
67
82
|
#
|
68
83
|
# Returns the number of non-masked smells in the source.
|
69
84
|
#
|
70
|
-
def
|
71
|
-
@
|
72
|
-
end
|
73
|
-
|
74
|
-
#
|
75
|
-
# Returns the number of masked smells in the source.
|
76
|
-
#
|
77
|
-
def num_masked_smells
|
78
|
-
@warnings.num_masked_items
|
85
|
+
def num_smells
|
86
|
+
@smells.length
|
79
87
|
end
|
80
88
|
|
81
89
|
#
|
82
90
|
# True if and only if there are non-masked code smells in the given source.
|
83
91
|
#
|
84
92
|
def smelly?
|
85
|
-
not
|
93
|
+
not @smells.empty?
|
86
94
|
end
|
87
95
|
end
|
88
96
|
end
|