reek 1.2.7.1 → 1.2.7.2
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 +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
data/lib/reek/smell_warning.rb
CHANGED
@@ -44,6 +44,10 @@ module Reek
|
|
44
44
|
#
|
45
45
|
attr_reader :smell
|
46
46
|
|
47
|
+
def smell_class() @smell[CLASS_KEY] end
|
48
|
+
def subclass() @smell[SUBCLASS_KEY] end
|
49
|
+
def message() @smell[MESSAGE_KEY] end
|
50
|
+
|
47
51
|
#
|
48
52
|
# Details of the smell's location, including its context ({CONTEXT_KEY}),
|
49
53
|
# the line numbers on which it occurs ({LINES_KEY}) and the source
|
@@ -53,6 +57,10 @@ module Reek
|
|
53
57
|
#
|
54
58
|
attr_reader :location
|
55
59
|
|
60
|
+
def context() @location[CONTEXT_KEY] end
|
61
|
+
def lines() @location[LINES_KEY] end
|
62
|
+
def source() @location[SOURCE_KEY] end
|
63
|
+
|
56
64
|
#
|
57
65
|
# Details of the smell's status, including whether it is active ({ACTIVE_KEY})
|
58
66
|
# (as opposed to being masked by a config file)
|
@@ -20,7 +20,8 @@ module Reek
|
|
20
20
|
#
|
21
21
|
class Attribute < SmellDetector
|
22
22
|
|
23
|
-
|
23
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
24
|
+
ATTRIBUTE_KEY = 'attribute'
|
24
25
|
|
25
26
|
def self.contexts # :nodoc:
|
26
27
|
[:class, :module]
|
@@ -51,8 +52,9 @@ module Reek
|
|
51
52
|
#
|
52
53
|
def attributes_in(module_ctx)
|
53
54
|
result = Set.new
|
55
|
+
attr_defn_methods = [:attr, :attr_reader, :attr_writer, :attr_accessor]
|
54
56
|
module_ctx.local_nodes(:call) do |call_node|
|
55
|
-
if
|
57
|
+
if attr_defn_methods.include?(call_node.method_name)
|
56
58
|
call_node.arg_names.each {|arg| result << [arg, call_node.line] }
|
57
59
|
end
|
58
60
|
end
|
@@ -24,7 +24,7 @@ module Reek
|
|
24
24
|
# Remembers any smells found.
|
25
25
|
#
|
26
26
|
def examine_context(ctx)
|
27
|
-
class_variables_in(ctx).each do |cvar_name|
|
27
|
+
class_variables_in(ctx.exp).each do |cvar_name|
|
28
28
|
found(ctx, "declares the class variable #{cvar_name}", '', {'variable' => cvar_name.to_s})
|
29
29
|
end
|
30
30
|
end
|
@@ -33,11 +33,11 @@ module Reek
|
|
33
33
|
# Collects the names of the class variables declared and/or used
|
34
34
|
# in the given module.
|
35
35
|
#
|
36
|
-
def class_variables_in(
|
36
|
+
def class_variables_in(ast)
|
37
37
|
result = Set.new
|
38
38
|
collector = proc { |cvar_node| result << cvar_node.name }
|
39
39
|
[:cvar, :cvasgn, :cvdecl].each do |stmt_type|
|
40
|
-
|
40
|
+
ast.each_node(stmt_type, [:class, :module], &collector)
|
41
41
|
end
|
42
42
|
result
|
43
43
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
2
2
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
3
|
-
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
4
3
|
|
5
4
|
module Reek
|
6
5
|
module Smells
|
@@ -51,7 +50,7 @@ module Reek
|
|
51
50
|
#
|
52
51
|
def examine_context(method_ctx)
|
53
52
|
control_parameters(method_ctx).each do |cond, occurs|
|
54
|
-
param =
|
53
|
+
param = cond.format
|
55
54
|
lines = occurs.map {|exp| exp.line}
|
56
55
|
found(method_ctx, "is controlled by argument #{param}",
|
57
56
|
'ControlParameter', {'parameter' => param}, lines)
|
@@ -2,6 +2,32 @@ require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
2
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
3
3
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
4
4
|
|
5
|
+
#
|
6
|
+
# Extensions to +Array+ needed by Reek.
|
7
|
+
#
|
8
|
+
class Array
|
9
|
+
def power_set
|
10
|
+
self.inject([[]]) { |cum, element| cum.cross(element) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def bounded_power_set(lower_bound)
|
14
|
+
power_set.select {|ps| ps.length > lower_bound}
|
15
|
+
end
|
16
|
+
|
17
|
+
def cross(element)
|
18
|
+
result = []
|
19
|
+
self.each do |set|
|
20
|
+
result << set
|
21
|
+
result << (set + [element])
|
22
|
+
end
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def intersection
|
27
|
+
self.inject { |res, elem| elem & res }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
5
31
|
module Reek
|
6
32
|
module Smells
|
7
33
|
|
@@ -19,6 +45,12 @@ module Reek
|
|
19
45
|
#
|
20
46
|
class DataClump < SmellDetector
|
21
47
|
|
48
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
49
|
+
|
50
|
+
METHODS_KEY = 'methods'
|
51
|
+
OCCURRENCES_KEY = 'occurrences'
|
52
|
+
PARAMETERS_KEY = 'parameters'
|
53
|
+
|
22
54
|
def self.contexts # :nodoc:
|
23
55
|
[:class, :module]
|
24
56
|
end
|
@@ -50,11 +82,18 @@ module Reek
|
|
50
82
|
def examine_context(ctx)
|
51
83
|
max_copies = value(MAX_COPIES_KEY, ctx, DEFAULT_MAX_COPIES)
|
52
84
|
min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx, DEFAULT_MIN_CLUMP_SIZE)
|
53
|
-
MethodGroup.new(ctx, min_clump_size, max_copies).clumps.each do |clump,
|
54
|
-
|
55
|
-
|
85
|
+
MethodGroup.new(ctx, min_clump_size, max_copies).clumps.each do |clump, methods|
|
86
|
+
smell = SmellWarning.new('DataClump', ctx.full_name,
|
87
|
+
methods.map {|meth| meth.line},
|
88
|
+
"takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods", @masked,
|
89
|
+
@source, 'DataClump', {
|
90
|
+
PARAMETERS_KEY => clump.map {|name| name.to_s},
|
91
|
+
OCCURRENCES_KEY => methods.length,
|
92
|
+
METHODS_KEY => methods.map {|meth| meth.name}
|
93
|
+
})
|
94
|
+
@smells_found << smell
|
95
|
+
#SMELL: serious duplication
|
56
96
|
# SMELL: name.to_s is becoming a nuisance
|
57
|
-
# TODO: record the methods in [lines] and in the hash
|
58
97
|
end
|
59
98
|
end
|
60
99
|
|
@@ -69,24 +108,62 @@ module Reek
|
|
69
108
|
class MethodGroup # :nodoc:
|
70
109
|
|
71
110
|
def self.intersection_of_parameters_of(methods)
|
72
|
-
methods.map {|meth| meth.
|
111
|
+
methods.map {|meth| meth.arg_names.sort {|a,b| a.to_s <=> b.to_s}}.intersection
|
73
112
|
end
|
74
113
|
|
75
114
|
def initialize(ctx, min_clump_size, max_copies)
|
76
|
-
@ctx = ctx
|
77
115
|
@min_clump_size = min_clump_size
|
78
116
|
@max_copies = max_copies
|
117
|
+
@candidate_methods = ctx.local_nodes(:defn).select do |meth|
|
118
|
+
meth.arg_names.length >= @min_clump_size
|
119
|
+
end.map {|defn_node| CandidateMethod.new(defn_node)}
|
120
|
+
prune_candidates
|
79
121
|
end
|
80
122
|
|
81
123
|
def clumps
|
82
|
-
results = Hash.new(
|
83
|
-
@
|
124
|
+
results = Hash.new([])
|
125
|
+
@candidate_methods.bounded_power_set(@max_copies).each do |methods|
|
84
126
|
clump = MethodGroup.intersection_of_parameters_of(methods)
|
85
127
|
if clump.length >= @min_clump_size
|
86
|
-
results[clump] =
|
128
|
+
results[clump] = methods if methods.length > results[clump].length
|
87
129
|
end
|
88
130
|
end
|
89
131
|
results
|
90
132
|
end
|
133
|
+
|
134
|
+
def prune_candidates
|
135
|
+
@candidate_methods.each do |meth|
|
136
|
+
meth.arg_names.each do |param|
|
137
|
+
count = @candidate_methods.select {|cm| cm.arg_names.include?(param)}.length
|
138
|
+
meth.delete(param) if count <= @max_copies
|
139
|
+
end
|
140
|
+
end
|
141
|
+
@candidate_methods = @candidate_methods.select do |meth|
|
142
|
+
meth.arg_names.length >= @min_clump_size
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class CandidateMethod
|
148
|
+
def initialize(defn_node)
|
149
|
+
@defn = defn_node
|
150
|
+
@params = defn_node.arg_names.clone
|
151
|
+
end
|
152
|
+
|
153
|
+
def arg_names
|
154
|
+
@params
|
155
|
+
end
|
156
|
+
|
157
|
+
def delete(param)
|
158
|
+
@params.delete(param)
|
159
|
+
end
|
160
|
+
|
161
|
+
def line
|
162
|
+
@defn.line
|
163
|
+
end
|
164
|
+
|
165
|
+
def name
|
166
|
+
@defn.name.to_s # BUG: should report the symbols!
|
167
|
+
end
|
91
168
|
end
|
92
169
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
2
2
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
3
|
-
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
4
3
|
|
5
4
|
module Reek
|
6
5
|
module Smells
|
@@ -38,7 +37,7 @@ module Reek
|
|
38
37
|
calls(method_ctx).each do |call_exp, copies|
|
39
38
|
occurs = copies.length
|
40
39
|
next if occurs <= value(MAX_ALLOWED_CALLS_KEY, method_ctx, DEFAULT_MAX_CALLS)
|
41
|
-
call =
|
40
|
+
call = call_exp.format
|
42
41
|
multiple = occurs == 2 ? 'twice' : "#{occurs} times"
|
43
42
|
found(method_ctx, "calls #{call} #{multiple}",
|
44
43
|
'DuplicateMethodCall', {'call' => call, 'occurrences' => occurs},
|
@@ -53,7 +52,7 @@ module Reek
|
|
53
52
|
result[call_node].push(call_node)
|
54
53
|
end
|
55
54
|
method_ctx.local_nodes(:attrasgn) do |asgn_node|
|
56
|
-
result[asgn_node].push(asgn_node)
|
55
|
+
result[asgn_node].push(asgn_node) unless asgn_node.args.length < 2
|
57
56
|
end
|
58
57
|
result
|
59
58
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
2
2
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
3
|
-
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
4
3
|
|
5
4
|
module Reek
|
6
5
|
module Smells
|
@@ -35,6 +34,12 @@ module Reek
|
|
35
34
|
class FeatureEnvy < SmellDetector
|
36
35
|
include ExcludeInitialize
|
37
36
|
|
37
|
+
SMELL_CLASS = 'LowCohesion'
|
38
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
39
|
+
|
40
|
+
RECEIVER_KEY = 'receiver'
|
41
|
+
REFERENCES_KEY = 'references'
|
42
|
+
|
38
43
|
#
|
39
44
|
# Checks whether the given +context+ includes any code fragment that
|
40
45
|
# might "belong" on another class.
|
@@ -42,10 +47,10 @@ module Reek
|
|
42
47
|
#
|
43
48
|
def examine_context(method_ctx)
|
44
49
|
method_ctx.envious_receivers.each do |ref, occurs|
|
45
|
-
target =
|
46
|
-
smell = SmellWarning.new(
|
50
|
+
target = ref.format
|
51
|
+
smell = SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
|
47
52
|
"refers to #{target} more than self", @masked,
|
48
|
-
@source,
|
53
|
+
@source, SMELL_SUBCLASS, {RECEIVER_KEY => target, REFERENCES_KEY => occurs})
|
49
54
|
@smells_found << smell
|
50
55
|
#SMELL: serious duplication
|
51
56
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
2
2
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
3
|
-
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
4
3
|
|
5
4
|
module Reek
|
6
5
|
module Smells
|
@@ -50,7 +49,7 @@ module Reek
|
|
50
49
|
conditional_counts(klass).each do |key, lines|
|
51
50
|
occurs = lines.length
|
52
51
|
next unless occurs > value(MAX_IDENTICAL_IFS_KEY, klass, DEFAULT_MAX_IFS)
|
53
|
-
expr =
|
52
|
+
expr = key.format
|
54
53
|
found(klass, "tests #{expr} at least #{occurs} times",
|
55
54
|
'RepeatedConditional', {'expression' => expr, 'occurrences' => occurs}, lines)
|
56
55
|
end
|
@@ -19,11 +19,15 @@ module Reek
|
|
19
19
|
#
|
20
20
|
class UncommunicativeMethodName < SmellDetector
|
21
21
|
|
22
|
+
SMELL_CLASS = 'UncommunicativeName'
|
23
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
24
|
+
METHOD_NAME_KEY = 'method_name'
|
25
|
+
|
22
26
|
# The name of the config field that lists the regexps of
|
23
27
|
# smelly names to be reported.
|
24
28
|
REJECT_KEY = 'reject'
|
25
29
|
|
26
|
-
DEFAULT_REJECT_SET = [
|
30
|
+
DEFAULT_REJECT_SET = [/^[a-z]$/, /[0-9]$/, /[A-Z]/]
|
27
31
|
|
28
32
|
# The name of the config field that lists the specific names that are
|
29
33
|
# to be treated as exceptions; these names will not be reported as
|
@@ -57,11 +61,13 @@ module Reek
|
|
57
61
|
return false unless is_bad_name?(name, method_ctx)
|
58
62
|
smell = SmellWarning.new('UncommunicativeName', method_ctx.full_name, [method_ctx.exp.line],
|
59
63
|
"has the name '#{name}'", @masked,
|
60
|
-
@source, 'UncommunicativeMethodName', {
|
64
|
+
@source, 'UncommunicativeMethodName', {METHOD_NAME_KEY => name.to_s})
|
61
65
|
@smells_found << smell
|
62
66
|
#SMELL: serious duplication
|
63
67
|
end
|
64
68
|
|
69
|
+
private
|
70
|
+
|
65
71
|
def accept?(context)
|
66
72
|
value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(context.full_name)
|
67
73
|
end
|
@@ -23,7 +23,7 @@ module Reek
|
|
23
23
|
# smelly names to be reported.
|
24
24
|
REJECT_KEY = 'reject'
|
25
25
|
|
26
|
-
DEFAULT_REJECT_SET = [/^.$/, /[0-9]
|
26
|
+
DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
|
27
27
|
|
28
28
|
# The name of the config field that lists the specific names that are
|
29
29
|
# to be treated as exceptions; these names will not be reported as
|
@@ -23,7 +23,7 @@ module Reek
|
|
23
23
|
# smelly names to be reported.
|
24
24
|
REJECT_KEY = 'reject'
|
25
25
|
|
26
|
-
DEFAULT_REJECT_SET = [/^.$/, /[0-9]
|
26
|
+
DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
|
27
27
|
|
28
28
|
# The name of the config field that lists the specific names that are
|
29
29
|
# to be treated as exceptions; these names will not be reported as
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
2
2
|
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
3
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'reference_collector')
|
3
4
|
|
4
5
|
module Reek
|
5
6
|
module Smells
|
@@ -42,8 +43,13 @@ module Reek
|
|
42
43
|
|
43
44
|
DEFAULT_HELPER_CALLS_LIMIT = 1
|
44
45
|
|
45
|
-
|
46
|
-
|
46
|
+
class << self
|
47
|
+
def contexts # :nodoc:
|
48
|
+
[:defn]
|
49
|
+
end
|
50
|
+
def default_config
|
51
|
+
super.adopt(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
def initialize(source, config = UtilityFunction.default_config)
|
@@ -55,9 +61,9 @@ module Reek
|
|
55
61
|
# Remembers any smells found.
|
56
62
|
#
|
57
63
|
def examine_context(method_ctx)
|
58
|
-
return false if method_ctx.num_statements == 0
|
59
|
-
|
60
|
-
|
64
|
+
return false if method_ctx.num_statements == 0
|
65
|
+
return false if depends_on_instance?(method_ctx.exp.body)
|
66
|
+
return false if num_helper_methods(method_ctx) <= value(HELPER_CALLS_LIMIT_KEY, method_ctx, DEFAULT_HELPER_CALLS_LIMIT)
|
61
67
|
# SMELL: loads of calls to value{} with the above pattern
|
62
68
|
smell = SmellWarning.new('LowCohesion', method_ctx.full_name, [method_ctx.exp.line],
|
63
69
|
"doesn't depend on instance state", @masked,
|
@@ -66,6 +72,12 @@ module Reek
|
|
66
72
|
#SMELL: serious duplication
|
67
73
|
end
|
68
74
|
|
75
|
+
private
|
76
|
+
|
77
|
+
def depends_on_instance?(exp)
|
78
|
+
Reek::Source::ReferenceCollector.new(exp).num_refs_to_self > 0
|
79
|
+
end
|
80
|
+
|
69
81
|
def num_helper_methods(method_ctx)
|
70
82
|
method_ctx.local_nodes(:call).length
|
71
83
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Reek
|
3
|
+
module Source
|
4
|
+
class ReferenceCollector
|
5
|
+
def initialize(ast)
|
6
|
+
@ast = ast
|
7
|
+
end
|
8
|
+
|
9
|
+
def num_refs_to_self
|
10
|
+
result = 0
|
11
|
+
[:self, :zsuper, :ivar, :iasgn].each do |node_type|
|
12
|
+
@ast.look_for(node_type, [:class, :module, :defn, :defs]) { result += 1}
|
13
|
+
end
|
14
|
+
@ast.look_for(:call, [:class, :module, :defn, :defs]) do |call|
|
15
|
+
result += 1 unless call.receiver
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -14,6 +14,16 @@ module Reek
|
|
14
14
|
is_language_node? and first == type
|
15
15
|
end
|
16
16
|
|
17
|
+
def each_node(type, ignoring, &blk)
|
18
|
+
if block_given?
|
19
|
+
look_for(type, ignoring, &blk)
|
20
|
+
else
|
21
|
+
result = []
|
22
|
+
look_for(type, ignoring) {|exp| result << exp}
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
17
27
|
#
|
18
28
|
# Carries out a depth-first traversal of this syntax tree, yielding
|
19
29
|
# every Sexp of type +target_type+. The traversal ignores any node
|
@@ -27,13 +37,22 @@ module Reek
|
|
27
37
|
end
|
28
38
|
blk.call(self) if first == target_type
|
29
39
|
end
|
40
|
+
def format
|
41
|
+
return self[0].to_s unless Array === self
|
42
|
+
Ruby2Ruby.new.process(deep_copy)
|
43
|
+
end
|
44
|
+
def deep_copy
|
45
|
+
YAML::load(YAML::dump(self))
|
46
|
+
end
|
30
47
|
end
|
31
48
|
|
32
49
|
module SexpExtensions
|
50
|
+
module AttrasgnNode
|
51
|
+
def args() self[3] end
|
52
|
+
end
|
53
|
+
|
33
54
|
module CaseNode
|
34
|
-
def condition
|
35
|
-
self[1]
|
36
|
-
end
|
55
|
+
def condition() self[1] end
|
37
56
|
end
|
38
57
|
|
39
58
|
module CallNode
|
@@ -45,6 +64,15 @@ module Reek
|
|
45
64
|
end
|
46
65
|
end
|
47
66
|
|
67
|
+
module ClassNode
|
68
|
+
def name() self[1] end
|
69
|
+
def superclass() self[2] end
|
70
|
+
def full_name(outer)
|
71
|
+
prefix = outer == '' ? '' : "#{outer}::"
|
72
|
+
"#{prefix}#{name}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
48
76
|
module CvarNode
|
49
77
|
def name() self[1] end
|
50
78
|
end
|
@@ -53,29 +81,47 @@ module Reek
|
|
53
81
|
CvdeclNode = CvarNode
|
54
82
|
|
55
83
|
module DefnNode
|
56
|
-
def
|
84
|
+
def name() self[1] end
|
85
|
+
def arg_names
|
86
|
+
unless @args
|
87
|
+
@args = self[2][1..-1].reject {|param| Sexp === param or param.to_s =~ /^&/}
|
88
|
+
end
|
89
|
+
@args
|
90
|
+
end
|
57
91
|
def parameters()
|
58
|
-
|
92
|
+
unless @params
|
93
|
+
@params = self[2].reject {|param| Sexp === param}
|
94
|
+
end
|
95
|
+
@params
|
59
96
|
end
|
60
97
|
def parameter_names
|
61
98
|
parameters[1..-1]
|
62
99
|
end
|
100
|
+
def body() self[3] end
|
101
|
+
def full_name(outer)
|
102
|
+
prefix = outer == '' ? '' : "#{outer}#"
|
103
|
+
"#{prefix}#{name}"
|
104
|
+
end
|
63
105
|
end
|
64
106
|
|
65
107
|
module DefsNode
|
66
|
-
def
|
108
|
+
def receiver() self[1] end
|
109
|
+
def name() self[2] end
|
67
110
|
def parameters
|
68
111
|
self[3].reject {|param| Sexp === param}
|
69
112
|
end
|
70
113
|
def parameter_names
|
71
114
|
parameters[1..-1]
|
72
115
|
end
|
116
|
+
def body() self[4] end
|
117
|
+
def full_name(outer)
|
118
|
+
prefix = outer == '' ? '' : "#{outer}#"
|
119
|
+
"#{prefix}#{receiver.format}.#{name}"
|
120
|
+
end
|
73
121
|
end
|
74
122
|
|
75
123
|
module IfNode
|
76
|
-
def condition
|
77
|
-
self[1]
|
78
|
-
end
|
124
|
+
def condition() self[1] end
|
79
125
|
end
|
80
126
|
|
81
127
|
module IterNode
|
@@ -96,6 +142,18 @@ module Reek
|
|
96
142
|
end
|
97
143
|
end
|
98
144
|
|
145
|
+
module LitNode
|
146
|
+
def value() self[1] end
|
147
|
+
end
|
148
|
+
|
149
|
+
module ModuleNode
|
150
|
+
def name() self[1] end
|
151
|
+
def full_name(outer)
|
152
|
+
prefix = outer == '' ? '' : "#{outer}::"
|
153
|
+
"#{prefix}#{name}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
99
157
|
module YieldNode
|
100
158
|
def args() self[1..-1] end
|
101
159
|
def arg_names
|