reek 1.2.7.2 → 1.2.7.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|