reek 1.2.7.2 → 1.2.7.3
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/config/defaults.reek +4 -4
- data/features/masking_smells.feature +14 -57
- data/features/options.feature +1 -2
- data/features/rake_task.feature +6 -6
- data/features/reports.feature +8 -38
- data/features/samples.feature +181 -181
- data/features/stdin.feature +3 -3
- data/lib/reek.rb +1 -1
- data/lib/reek/cli/command_line.rb +2 -7
- data/lib/reek/cli/reek_command.rb +6 -6
- data/lib/reek/cli/report.rb +27 -49
- data/lib/reek/core/code_parser.rb +6 -15
- data/lib/reek/core/method_context.rb +1 -1
- data/lib/reek/core/module_context.rb +0 -18
- data/lib/reek/core/smell_configuration.rb +0 -4
- data/lib/reek/core/sniffer.rb +11 -19
- data/lib/reek/core/warning_collector.rb +27 -0
- data/lib/reek/examiner.rb +43 -35
- data/lib/reek/rake/task.rb +2 -0
- data/lib/reek/smell_warning.rb +10 -25
- data/lib/reek/smells/boolean_parameter.rb +1 -1
- data/lib/reek/smells/control_couple.rb +4 -1
- data/lib/reek/smells/data_clump.rb +40 -33
- data/lib/reek/smells/feature_envy.rb +1 -1
- data/lib/reek/smells/long_parameter_list.rb +4 -1
- data/lib/reek/smells/long_yield_list.rb +6 -3
- data/lib/reek/smells/simulated_polymorphism.rb +1 -1
- data/lib/reek/smells/smell_detector.rb +4 -32
- data/lib/reek/smells/uncommunicative_method_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_module_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_parameter_name.rb +2 -2
- data/lib/reek/smells/uncommunicative_variable_name.rb +11 -18
- data/lib/reek/smells/utility_function.rb +7 -4
- data/lib/reek/source/reference_collector.rb +9 -2
- data/lib/reek/source/source_locator.rb +6 -0
- data/lib/reek/spec/should_reek.rb +3 -6
- data/lib/reek/spec/should_reek_only_of.rb +4 -3
- data/reek.gemspec +4 -4
- data/spec/reek/cli/reek_command_spec.rb +3 -4
- data/spec/reek/cli/report_spec.rb +10 -6
- data/spec/reek/cli/yaml_command_spec.rb +1 -1
- data/spec/reek/core/code_context_spec.rb +1 -3
- data/spec/reek/core/module_context_spec.rb +1 -1
- data/spec/reek/core/warning_collector_spec.rb +27 -0
- data/spec/reek/examiner_spec.rb +80 -19
- data/spec/reek/smell_warning_spec.rb +4 -61
- data/spec/reek/smells/attribute_spec.rb +4 -7
- data/spec/reek/smells/behaves_like_variable_detector.rb +2 -2
- data/spec/reek/smells/class_variable_spec.rb +0 -1
- data/spec/reek/smells/control_couple_spec.rb +8 -15
- data/spec/reek/smells/data_clump_spec.rb +85 -1
- data/spec/reek/smells/duplication_spec.rb +7 -8
- data/spec/reek/smells/feature_envy_spec.rb +2 -32
- data/spec/reek/smells/long_parameter_list_spec.rb +9 -16
- data/spec/reek/smells/long_yield_list_spec.rb +8 -15
- data/spec/reek/smells/smell_detector_shared.rb +12 -0
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +9 -10
- data/spec/reek/smells/utility_function_spec.rb +11 -15
- data/spec/reek/spec/should_reek_only_of_spec.rb +6 -6
- data/spec/reek/spec/should_reek_spec.rb +3 -3
- metadata +36 -22
- data/lib/reek/core/class_context.rb +0 -22
- data/lib/reek/core/detector_stack.rb +0 -33
- data/lib/reek/core/masking_collection.rb +0 -52
- data/spec/reek/core/class_context_spec.rb +0 -53
- data/spec/reek/core/masking_collection_spec.rb +0 -235
data/lib/reek/rake/task.rb
CHANGED
data/lib/reek/smell_warning.rb
CHANGED
@@ -18,7 +18,7 @@ module Reek
|
|
18
18
|
|
19
19
|
ACTIVE_KEY = 'is_active'
|
20
20
|
|
21
|
-
def initialize(class_name, context, lines, message,
|
21
|
+
def initialize(class_name, context, lines, message,
|
22
22
|
source = '', subclass_name = '', parameters = {})
|
23
23
|
@smell = {
|
24
24
|
CLASS_KEY => class_name,
|
@@ -27,10 +27,10 @@ module Reek
|
|
27
27
|
}
|
28
28
|
@smell.merge!(parameters)
|
29
29
|
@status = {
|
30
|
-
ACTIVE_KEY =>
|
30
|
+
ACTIVE_KEY => true
|
31
31
|
}
|
32
32
|
@location = {
|
33
|
-
CONTEXT_KEY => context,
|
33
|
+
CONTEXT_KEY => context.to_s,
|
34
34
|
LINES_KEY => lines,
|
35
35
|
SOURCE_KEY => source
|
36
36
|
}
|
@@ -69,6 +69,8 @@ module Reek
|
|
69
69
|
#
|
70
70
|
attr_reader :status
|
71
71
|
|
72
|
+
def is_active() @status[ACTIVE_KEY] end
|
73
|
+
|
72
74
|
def hash # :nodoc:
|
73
75
|
sort_key.hash
|
74
76
|
end
|
@@ -90,31 +92,14 @@ module Reek
|
|
90
92
|
@smell.values.include?(klass.to_s) and contains_all?(patterns)
|
91
93
|
end
|
92
94
|
|
93
|
-
def
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
protected :sort_key
|
98
|
-
|
99
|
-
def report(format)
|
100
|
-
format.gsub(/\%s/, smell_name).
|
101
|
-
gsub(/\%c/, @location[CONTEXT_KEY]).
|
102
|
-
gsub(/\%w/, @smell[MESSAGE_KEY]).
|
103
|
-
gsub(/\%m/, @status[ACTIVE_KEY] ? '' : '(masked) ')
|
104
|
-
end
|
105
|
-
|
106
|
-
def report_on(report)
|
107
|
-
if @status[ACTIVE_KEY]
|
108
|
-
report.found_smell(self)
|
109
|
-
else
|
110
|
-
report.found_masked_smell(self)
|
111
|
-
end
|
95
|
+
def report_on(listener)
|
96
|
+
listener.found_smell(self)
|
112
97
|
end
|
113
98
|
|
114
|
-
|
99
|
+
protected
|
115
100
|
|
116
|
-
def
|
117
|
-
@
|
101
|
+
def sort_key
|
102
|
+
[@location[CONTEXT_KEY], @smell[MESSAGE_KEY], @smell[CLASS_KEY]]
|
118
103
|
end
|
119
104
|
end
|
120
105
|
end
|
@@ -22,7 +22,7 @@ module Reek
|
|
22
22
|
method_ctx.parameters.default_assignments.each do |param, value|
|
23
23
|
next unless [:true, :false].include?(value[0])
|
24
24
|
smell = SmellWarning.new('ControlCouple', method_ctx.full_name, [method_ctx.exp.line],
|
25
|
-
"has boolean parameter '#{param.to_s}'",
|
25
|
+
"has boolean parameter '#{param.to_s}'",
|
26
26
|
@source, 'BooleanParameter', {'parameter' => param.to_s})
|
27
27
|
@smells_found << smell
|
28
28
|
#SMELL: serious duplication
|
@@ -43,6 +43,9 @@ module Reek
|
|
43
43
|
#
|
44
44
|
class ControlCouple < SmellDetector
|
45
45
|
|
46
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
47
|
+
SMELL_SUBCLASS = 'ControlParameter'
|
48
|
+
|
46
49
|
#
|
47
50
|
# Checks whether the given method chooses its execution path
|
48
51
|
# by testing the value of one of its parameters.
|
@@ -53,7 +56,7 @@ module Reek
|
|
53
56
|
param = cond.format
|
54
57
|
lines = occurs.map {|exp| exp.line}
|
55
58
|
found(method_ctx, "is controlled by argument #{param}",
|
56
|
-
|
59
|
+
SMELL_SUBCLASS, {'parameter' => param}, lines)
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
@@ -6,23 +6,6 @@ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'sourc
|
|
6
6
|
# Extensions to +Array+ needed by Reek.
|
7
7
|
#
|
8
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
9
|
def intersection
|
27
10
|
self.inject { |res, elem| elem & res }
|
28
11
|
end
|
@@ -85,7 +68,7 @@ module Reek
|
|
85
68
|
MethodGroup.new(ctx, min_clump_size, max_copies).clumps.each do |clump, methods|
|
86
69
|
smell = SmellWarning.new('DataClump', ctx.full_name,
|
87
70
|
methods.map {|meth| meth.line},
|
88
|
-
"takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods",
|
71
|
+
"takes parameters #{DataClump.print_clump(clump)} to #{methods.length} methods",
|
89
72
|
@source, 'DataClump', {
|
90
73
|
PARAMETERS_KEY => clump.map {|name| name.to_s},
|
91
74
|
OCCURRENCES_KEY => methods.length,
|
@@ -105,10 +88,10 @@ module Reek
|
|
105
88
|
|
106
89
|
# Represents a group of methods
|
107
90
|
# @private
|
108
|
-
class MethodGroup
|
91
|
+
class MethodGroup
|
109
92
|
|
110
93
|
def self.intersection_of_parameters_of(methods)
|
111
|
-
methods.map {|meth| meth.arg_names
|
94
|
+
methods.map {|meth| meth.arg_names}.intersection
|
112
95
|
end
|
113
96
|
|
114
97
|
def initialize(ctx, min_clump_size, max_copies)
|
@@ -117,37 +100,61 @@ module Reek
|
|
117
100
|
@candidate_methods = ctx.local_nodes(:defn).select do |meth|
|
118
101
|
meth.arg_names.length >= @min_clump_size
|
119
102
|
end.map {|defn_node| CandidateMethod.new(defn_node)}
|
120
|
-
|
103
|
+
delete_infrequent_parameters
|
104
|
+
delete_small_methods
|
121
105
|
end
|
122
106
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
clump = MethodGroup.intersection_of_parameters_of(methods)
|
107
|
+
def clumps_containing(method, methods, results)
|
108
|
+
methods.each do |other_method|
|
109
|
+
clump = [method.arg_names, other_method.arg_names].intersection
|
127
110
|
if clump.length >= @min_clump_size
|
128
|
-
|
111
|
+
others = methods.select do |other| # BUG: early ones have already been eliminated
|
112
|
+
clump - other.arg_names == []
|
113
|
+
end
|
114
|
+
results[clump] += [method] + others
|
129
115
|
end
|
130
116
|
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def collect_clumps_in(methods, results)
|
120
|
+
return if methods.length <= @max_copies
|
121
|
+
tail = methods[1..-1]
|
122
|
+
clumps_containing(methods[0], tail, results)
|
123
|
+
collect_clumps_in(tail, results)
|
124
|
+
end
|
125
|
+
|
126
|
+
def clumps
|
127
|
+
results = Hash.new([])
|
128
|
+
collect_clumps_in(@candidate_methods, results)
|
129
|
+
results.each_key do |key|
|
130
|
+
results[key].uniq!
|
131
|
+
end
|
131
132
|
results
|
132
133
|
end
|
133
134
|
|
134
|
-
def
|
135
|
+
def delete_small_methods
|
136
|
+
@candidate_methods = @candidate_methods.select do |meth|
|
137
|
+
meth.arg_names.length >= @min_clump_size
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def delete_infrequent_parameters
|
135
142
|
@candidate_methods.each do |meth|
|
136
143
|
meth.arg_names.each do |param|
|
137
|
-
|
138
|
-
meth.delete(param) if
|
144
|
+
occurs = @candidate_methods.inject(0) {|sum, cm| cm.arg_names.include?(param) ? sum+1 : sum}
|
145
|
+
meth.delete(param) if occurs <= @max_copies
|
139
146
|
end
|
140
147
|
end
|
141
|
-
@candidate_methods = @candidate_methods.select do |meth|
|
142
|
-
meth.arg_names.length >= @min_clump_size
|
143
|
-
end
|
144
148
|
end
|
145
149
|
end
|
146
150
|
|
151
|
+
#
|
152
|
+
# A method definition and a copy of its parameters
|
153
|
+
#
|
147
154
|
class CandidateMethod
|
148
155
|
def initialize(defn_node)
|
149
156
|
@defn = defn_node
|
150
|
-
@params = defn_node.arg_names.clone
|
157
|
+
@params = defn_node.arg_names.clone.sort {|a,b| a.to_s <=> b.to_s}
|
151
158
|
end
|
152
159
|
|
153
160
|
def arg_names
|
@@ -49,7 +49,7 @@ module Reek
|
|
49
49
|
method_ctx.envious_receivers.each do |ref, occurs|
|
50
50
|
target = ref.format
|
51
51
|
smell = SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
|
52
|
-
"refers to #{target} more than self",
|
52
|
+
"refers to #{target} more than self",
|
53
53
|
@source, SMELL_SUBCLASS, {RECEIVER_KEY => target, REFERENCES_KEY => occurs})
|
54
54
|
@smells_found << smell
|
55
55
|
#SMELL: serious duplication
|
@@ -15,6 +15,9 @@ module Reek
|
|
15
15
|
#
|
16
16
|
class LongParameterList < SmellDetector
|
17
17
|
|
18
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
19
|
+
SMELL_SUBCLASS = 'LongParameterList'
|
20
|
+
|
18
21
|
# The name of the config field that sets the maximum number of
|
19
22
|
# parameters permitted in any method or block.
|
20
23
|
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
@@ -44,7 +47,7 @@ module Reek
|
|
44
47
|
num_params = method_ctx.parameters.length
|
45
48
|
return false if num_params <= value(MAX_ALLOWED_PARAMS_KEY, method_ctx, DEFAULT_MAX_ALLOWED_PARAMS)
|
46
49
|
found(method_ctx, "has #{num_params} parameters",
|
47
|
-
|
50
|
+
SMELL_SUBCLASS, {'parameter_count' => num_params})
|
48
51
|
end
|
49
52
|
end
|
50
53
|
end
|
@@ -10,6 +10,9 @@ module Reek
|
|
10
10
|
#
|
11
11
|
class LongYieldList < SmellDetector
|
12
12
|
|
13
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
14
|
+
SMELL_CLASS = 'LongParameterList'
|
15
|
+
|
13
16
|
# The name of the config field that sets the maximum number of
|
14
17
|
# parameters permitted in any method or block.
|
15
18
|
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
@@ -36,9 +39,9 @@ module Reek
|
|
36
39
|
method_ctx.local_nodes(:yield).each do |yield_node|
|
37
40
|
num_params = yield_node.args.length
|
38
41
|
next if num_params <= value(MAX_ALLOWED_PARAMS_KEY, method_ctx, DEFAULT_MAX_ALLOWED_PARAMS)
|
39
|
-
smell = SmellWarning.new(
|
40
|
-
"yields #{num_params} parameters",
|
41
|
-
@source,
|
42
|
+
smell = SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [yield_node.line],
|
43
|
+
"yields #{num_params} parameters",
|
44
|
+
@source, SMELL_SUBCLASS, {'parameter_count' => num_params})
|
42
45
|
@smells_found << smell
|
43
46
|
#SMELL: serious duplication
|
44
47
|
end
|
@@ -47,10 +47,10 @@ module Reek
|
|
47
47
|
@source = source
|
48
48
|
@config = Core::SmellConfiguration.new(config)
|
49
49
|
@smells_found = Set.new
|
50
|
-
@masked = false
|
51
50
|
end
|
52
51
|
|
53
|
-
def
|
52
|
+
def register(hooks)
|
53
|
+
return unless @config.enabled?
|
54
54
|
self.class.contexts.each { |ctx| hooks[ctx] << self }
|
55
55
|
end
|
56
56
|
|
@@ -63,17 +63,6 @@ module Reek
|
|
63
63
|
@config.adopt!(config)
|
64
64
|
end
|
65
65
|
|
66
|
-
def copy
|
67
|
-
self.class.new(@source, @config.deep_copy)
|
68
|
-
end
|
69
|
-
|
70
|
-
def supersede_with(config)
|
71
|
-
clone = self.copy
|
72
|
-
@masked = true
|
73
|
-
clone.configure_with(config)
|
74
|
-
clone
|
75
|
-
end
|
76
|
-
|
77
66
|
def examine(context)
|
78
67
|
examine_context(context) if @config.enabled? and !exception?(context)
|
79
68
|
end
|
@@ -87,34 +76,17 @@ module Reek
|
|
87
76
|
|
88
77
|
def found(context, message, subclass = '', parameters = {}, lines = nil)
|
89
78
|
lines ||= [context.exp.line] # SMELL: nil?!?!?! Yuk
|
90
|
-
smell = SmellWarning.new(self.class.name.split(/::/)[-1], context.full_name,
|
79
|
+
smell = SmellWarning.new(self.class.name.split(/::/)[-1], context.full_name,
|
80
|
+
lines, message,
|
91
81
|
@source, subclass, parameters)
|
92
82
|
@smells_found << smell
|
93
83
|
smell
|
94
84
|
end
|
95
85
|
|
96
|
-
def has_smell?(patterns)
|
97
|
-
return false if @masked
|
98
|
-
@smells_found.each { |warning| return true if warning.contains_all?(patterns) }
|
99
|
-
false
|
100
|
-
end
|
101
|
-
|
102
|
-
def smell_type
|
103
|
-
self.class.name.split(/::/)[-1]
|
104
|
-
end
|
105
|
-
|
106
86
|
def report_on(report)
|
107
87
|
@smells_found.each { |smell| smell.report_on(report) }
|
108
88
|
end
|
109
89
|
|
110
|
-
def num_smells
|
111
|
-
@masked ? 0 : @smells_found.length
|
112
|
-
end
|
113
|
-
|
114
|
-
def smelly?
|
115
|
-
(not @masked) and (@smells_found.length > 0)
|
116
|
-
end
|
117
|
-
|
118
90
|
def value(key, ctx, fall_back)
|
119
91
|
@config.value(key, ctx, fall_back)
|
120
92
|
end
|
@@ -60,7 +60,7 @@ module Reek
|
|
60
60
|
return false if accept?(method_ctx)
|
61
61
|
return false unless is_bad_name?(name, method_ctx)
|
62
62
|
smell = SmellWarning.new('UncommunicativeName', method_ctx.full_name, [method_ctx.exp.line],
|
63
|
-
"has the name '#{name}'",
|
63
|
+
"has the name '#{name}'",
|
64
64
|
@source, 'UncommunicativeMethodName', {METHOD_NAME_KEY => name.to_s})
|
65
65
|
@smells_found << smell
|
66
66
|
#SMELL: serious duplication
|
@@ -56,7 +56,7 @@ module Reek
|
|
56
56
|
return false if accept?(module_ctx)
|
57
57
|
return false unless is_bad_name?(name, module_ctx)
|
58
58
|
smell = SmellWarning.new('UncommunicativeName', module_ctx.full_name, [module_ctx.exp.line],
|
59
|
-
"has the name '#{name}'",
|
59
|
+
"has the name '#{name}'",
|
60
60
|
@source, 'UncommunicativeModuleName', {'module_name' => name.to_s})
|
61
61
|
@smells_found << smell
|
62
62
|
#SMELL: serious duplication
|
@@ -30,7 +30,7 @@ module Reek
|
|
30
30
|
# uncommunicative.
|
31
31
|
ACCEPT_KEY = 'accept'
|
32
32
|
|
33
|
-
DEFAULT_ACCEPT_SET = [
|
33
|
+
DEFAULT_ACCEPT_SET = []
|
34
34
|
|
35
35
|
def self.default_config
|
36
36
|
super.adopt(
|
@@ -55,7 +55,7 @@ module Reek
|
|
55
55
|
context.exp.parameter_names.each do |name|
|
56
56
|
next unless is_bad_name?(name, context)
|
57
57
|
smell = SmellWarning.new('UncommunicativeName', context.full_name, [context.exp.line],
|
58
|
-
"has the parameter name '#{name}'",
|
58
|
+
"has the parameter name '#{name}'",
|
59
59
|
@source, 'UncommunicativeParameterName', {'parameter_name' => name.to_s})
|
60
60
|
@smells_found << smell
|
61
61
|
#SMELL: serious duplication
|
@@ -30,7 +30,7 @@ module Reek
|
|
30
30
|
# uncommunicative.
|
31
31
|
ACCEPT_KEY = 'accept'
|
32
32
|
|
33
|
-
DEFAULT_ACCEPT_SET = [
|
33
|
+
DEFAULT_ACCEPT_SET = []
|
34
34
|
|
35
35
|
def self.default_config
|
36
36
|
super.adopt(
|
@@ -52,10 +52,10 @@ module Reek
|
|
52
52
|
# Remembers any smells found.
|
53
53
|
#
|
54
54
|
def examine_context(context)
|
55
|
-
variable_names(context).each do |name, lines|
|
55
|
+
variable_names(context.exp).each do |name, lines|
|
56
56
|
next unless is_bad_name?(name, context)
|
57
57
|
smell = SmellWarning.new('UncommunicativeName', context.full_name, lines,
|
58
|
-
"has the variable name '#{name}'",
|
58
|
+
"has the variable name '#{name}'",
|
59
59
|
@source, 'UncommunicativeVariableName', {'variable_name' => name.to_s})
|
60
60
|
@smells_found << smell
|
61
61
|
#SMELL: serious duplication
|
@@ -64,25 +64,18 @@ module Reek
|
|
64
64
|
|
65
65
|
def is_bad_name?(name, context) # :nodoc:
|
66
66
|
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
67
|
-
return false if
|
67
|
+
return false if value(ACCEPT_KEY, context, DEFAULT_ACCEPT_SET).include?(var)
|
68
68
|
value(REJECT_KEY, context, DEFAULT_REJECT_SET).detect {|patt| patt === var}
|
69
69
|
end
|
70
70
|
|
71
|
-
def variable_names(
|
72
|
-
|
73
|
-
case
|
74
|
-
when
|
75
|
-
|
76
|
-
result[asgn[1]].push(asgn.line)
|
77
|
-
end
|
78
|
-
else
|
79
|
-
context.local_nodes(:iasgn).each do |asgn|
|
80
|
-
result[asgn[1]].push(asgn.line)
|
81
|
-
end
|
82
|
-
context.each_node(:lasgn, [:class, :module, :defs, :defn]).each do |asgn|
|
83
|
-
result[asgn[1]].push(asgn.line)
|
84
|
-
end
|
71
|
+
def variable_names(exp)
|
72
|
+
assignment_nodes = exp.each_node(:lasgn, [:class, :module, :defs, :defn])
|
73
|
+
case exp.first
|
74
|
+
when :class, :module
|
75
|
+
assignment_nodes += exp.each_node(:iasgn, [:class, :module])
|
85
76
|
end
|
77
|
+
result = Hash.new {|hash,key| hash[key] = []}
|
78
|
+
assignment_nodes.each {|asgn| result[asgn[1]].push(asgn.line) }
|
86
79
|
result
|
87
80
|
end
|
88
81
|
end
|