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
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
|