to_robust 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +39 -0
- data/lib/global/fix.rb +72 -0
- data/lib/global/fix/add_atom.rb +28 -0
- data/lib/global/fix/atom.rb +51 -0
- data/lib/global/fix/init.rb +4 -0
- data/lib/global/fix/remove_atom.rb +16 -0
- data/lib/global/fix/swap_atom.rb +26 -0
- data/lib/global/global.rb +155 -0
- data/lib/global/init.rb +10 -0
- data/lib/global/kernel/argument_error.rb +22 -0
- data/lib/global/kernel/exception.rb +9 -0
- data/lib/global/kernel/init.rb +4 -0
- data/lib/global/kernel/no_method_error.rb +15 -0
- data/lib/global/kernel/zero_division_error.rb +21 -0
- data/lib/global/report.rb +42 -0
- data/lib/global/robust_proc.rb +48 -0
- data/lib/global/strategies/divide_by_zero_error_strategy.rb +277 -0
- data/lib/global/strategies/init.rb +5 -0
- data/lib/global/strategies/no_method_error_strategy.rb +92 -0
- data/lib/global/strategies/wrong_arguments_error_strategy.rb +174 -0
- data/lib/global/strategy.rb +67 -0
- data/lib/local/init.rb +6 -0
- data/lib/local/kernel/init.rb +1 -0
- data/lib/local/kernel/module.rb +43 -0
- data/lib/local/local.rb +43 -0
- data/lib/local/strategies/bignum_division_strategy.rb +11 -0
- data/lib/local/strategies/bignum_modulus_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_coerce_strategy.rb +11 -0
- data/lib/local/strategies/fixnum_division_strategy.rb +12 -0
- data/lib/local/strategies/fixnum_modulus_strategy.rb +12 -0
- data/lib/local/strategies/float_division_strategy.rb +11 -0
- data/lib/local/strategies/float_modulus_strategy.rb +12 -0
- data/lib/local/strategies/init.rb +37 -0
- data/lib/local/strategies/numeric_division_strategy.rb +11 -0
- data/lib/local/strategies/numeric_modulus_strategy.rb +12 -0
- data/lib/local/strategies/soft_binding_strategy.rb +108 -0
- data/lib/local/strategies/swap_method_strategy.rb +32 -0
- data/lib/local/strategy.rb +18 -0
- data/lib/to_robust.rb +4 -0
- data/test/local/float_division.rb +28 -0
- data/test/local/integer_division.rb +28 -0
- data/test/local/method_binding.rb +45 -0
- metadata +109 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
# Augments the functionality of NoMethodError instances so that the name of the missing method
|
2
|
+
# and the name of its context (if it has one) can be quickly and easily inspected.
|
3
|
+
class NoMethodError
|
4
|
+
|
5
|
+
# Returns the name of the missing method.
|
6
|
+
def method_name
|
7
|
+
message[/\`(.*?)'/, 1]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the details of the method owner (if it has one).
|
11
|
+
def method_owner
|
12
|
+
message.partition('for ')[2]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Augments the ZeroDivisionError class to provide the line number that the ZeroDivisionError was encountered
|
2
|
+
# by some given method.
|
3
|
+
class ZeroDivisionError
|
4
|
+
|
5
|
+
# Finds the line that the zero division error was first thrown on by
|
6
|
+
# a given method (not the first line that it occurs at, since this may
|
7
|
+
# be within a non-robust method).
|
8
|
+
#
|
9
|
+
# *Parameters:*
|
10
|
+
# * method, the method to extract the line number for.
|
11
|
+
#
|
12
|
+
# *Returns:*
|
13
|
+
# The line that the error occurred on within the provided method.
|
14
|
+
def line_no(method)
|
15
|
+
backtrace.each do |b|
|
16
|
+
return b[/:(\d+):/][1...-1].to_i if b.start_with?(method.code)
|
17
|
+
end
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# A report is used to hold the details of a successful repair attempt along with
|
2
|
+
# the (partially) fixed method.
|
3
|
+
#
|
4
|
+
# If a repair was partially successful (i.e. it removed the cause of the original
|
5
|
+
# error but not all errors) then the report holds the next error that should
|
6
|
+
# be resolved.
|
7
|
+
#
|
8
|
+
# If a repair was completely succesful (i.e. the method executed without throwing
|
9
|
+
# any exceptions) then the report is used to hold the result of the method call.
|
10
|
+
#
|
11
|
+
# The storage of error and result by the report is exploited by the repair-cycle
|
12
|
+
# to prevent redundant method calls.
|
13
|
+
class ToRobust::Global::Report
|
14
|
+
|
15
|
+
attr_reader :fixed_method,
|
16
|
+
:result,
|
17
|
+
:error
|
18
|
+
|
19
|
+
# Creates a new report.
|
20
|
+
#
|
21
|
+
# *Parameters:*
|
22
|
+
# * fixed_method, the fixed form of the method.
|
23
|
+
# * opts, a hash of keyword options for this constructor.
|
24
|
+
# -> result, the result of the method call (if the repair was a complete success).
|
25
|
+
# -> error, the error encountered when calling the method after repair (if the repair was a partial success).
|
26
|
+
def initialize(fixed_method, opts = {})
|
27
|
+
@fixed_method = fixed_method
|
28
|
+
@result = opts[:result]
|
29
|
+
@error = opts[:error]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns true if the repair completely fixed the method (removing all errors).
|
33
|
+
def complete?
|
34
|
+
@error.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns true if there were still further (unrelated) errors after fixing the method.
|
38
|
+
def partial?
|
39
|
+
not complete?
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# A robust procedure is a type of object which mimics the functionality of a standard lambda
|
2
|
+
# function but also adds line-specific error debugging information to exception traces.
|
3
|
+
class ToRobust::Global::RobustProc
|
4
|
+
|
5
|
+
attr_reader :headers,
|
6
|
+
:body,
|
7
|
+
:source
|
8
|
+
|
9
|
+
# Constructs a new Robust procedure.
|
10
|
+
#
|
11
|
+
# *Parameters:*
|
12
|
+
# * headers, the arguments to the procedure (as an array of argument names).
|
13
|
+
# * body, the body of the procedure.
|
14
|
+
def initialize(headers, body)
|
15
|
+
|
16
|
+
@headers = headers.freeze
|
17
|
+
@body = body.freeze
|
18
|
+
|
19
|
+
# Dynamically create the procedure as the "call" method of this object,
|
20
|
+
# and supply file and line debugging information.
|
21
|
+
@source = "def call(#{@headers.join(', ')})
|
22
|
+
#{body}
|
23
|
+
end"
|
24
|
+
|
25
|
+
instance_eval(@source, code, 0)
|
26
|
+
|
27
|
+
# Store the lines of the body in the source property as an array (using chomp
|
28
|
+
# to remove /n from the end of each line).
|
29
|
+
@source = @source.lines.map(&:chomp).freeze
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# Every robust procedure has its own unique code that is used as its source
|
34
|
+
# file and is used to identify error information specific to it.
|
35
|
+
def code
|
36
|
+
"ROBUST_PROC_#{object_id}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Makes this robust procedure object take on the function of another
|
40
|
+
# robust procedure. This allows repairs to be optionally saved.
|
41
|
+
def become(other)
|
42
|
+
@headers = other.headers
|
43
|
+
@body = other.body
|
44
|
+
@source = other.source
|
45
|
+
instance_eval(@source.join("\n"), code, 0)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
# Handles zero division errors.
|
2
|
+
class ToRobust::Global::Strategies::DivideByZeroStrategy < ToRobust::Global::Strategy
|
3
|
+
|
4
|
+
# Wraps all method calls in a safety barrier, preventing them from raising
|
5
|
+
# a ZeroDivisionError and instead causing them to return zero when a ZeroDivisionError
|
6
|
+
# is thrown.
|
7
|
+
#
|
8
|
+
# *Parameters:*
|
9
|
+
# * line, the line to wrap the method calls for.
|
10
|
+
#
|
11
|
+
# *Returns:*
|
12
|
+
# The provided line with all method calls wrapped.
|
13
|
+
def self.wrap_calls(line)
|
14
|
+
|
15
|
+
# Extract all the method calls in the line.
|
16
|
+
calls = extract_calls(line)
|
17
|
+
|
18
|
+
# Wrap each of the method calls in the original string.
|
19
|
+
calls.each_index do |x|
|
20
|
+
|
21
|
+
# Retrieve the co-ordinates of the call.
|
22
|
+
start_at, end_at = calls[x]
|
23
|
+
|
24
|
+
# Transform the method call.
|
25
|
+
line.insert start_at, "ToRobust::Global.prevent_dbz{"
|
26
|
+
line.insert end_at+30, "}"
|
27
|
+
|
28
|
+
# Modify the coordinates of all other method calls.
|
29
|
+
(x...calls.length).each do |y|
|
30
|
+
|
31
|
+
# If this method ends before another begins, shift
|
32
|
+
# both the start and end of that method.
|
33
|
+
if end_at < calls[y][0]
|
34
|
+
calls[y][0] += 30
|
35
|
+
calls[y][1] += 30
|
36
|
+
|
37
|
+
# If this X starts after Y but finishes before, then shift
|
38
|
+
# the end of Y by 13.
|
39
|
+
elsif start_at > calls[y][0] and end_at < calls[y][1]
|
40
|
+
calls[y][1] += 30
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the transformed line.
|
48
|
+
return line
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# Wraps all inline division operators in a safety barrier, preventing them from raising
|
53
|
+
# a ZeroDivisionError and instead causing them to return zero when a ZeroDivisionError
|
54
|
+
# is thrown.
|
55
|
+
#
|
56
|
+
# *Parameters:*
|
57
|
+
# * line, the line to wrap the inline division operators for.
|
58
|
+
#
|
59
|
+
# *Returns:*
|
60
|
+
# The provided line with all inline division operators wrapped.
|
61
|
+
def self.wrap_ops(line)
|
62
|
+
|
63
|
+
# Find the index of each division operator in the string.
|
64
|
+
last_index = -1
|
65
|
+
operations = []
|
66
|
+
until last_index.nil?
|
67
|
+
last_index = line.index('/', last_index+1)
|
68
|
+
operations << last_index unless last_index.nil?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Find the start index of the left argument and the end index of
|
72
|
+
# the right argument.
|
73
|
+
operations.map! do |location|
|
74
|
+
|
75
|
+
# Find the index of the start of the left argument.
|
76
|
+
start_at = index = location
|
77
|
+
started = finished = false
|
78
|
+
bracket_depth = 0
|
79
|
+
until finished or start_at == 0
|
80
|
+
|
81
|
+
# Move to the next character.
|
82
|
+
index -= 1
|
83
|
+
char = line[index]
|
84
|
+
|
85
|
+
# Increase the bracket depth if a bracket is closed.
|
86
|
+
if char == ")"
|
87
|
+
started = true
|
88
|
+
bracket_depth += 1
|
89
|
+
|
90
|
+
# Decrease the bracket depth if a bracket is opened.
|
91
|
+
# If the resulting bracket depth is zero or less then
|
92
|
+
# the argument is finished.
|
93
|
+
elsif char == "("
|
94
|
+
started = true
|
95
|
+
bracket_depth -= 1
|
96
|
+
finished = bracket_depth <= 0
|
97
|
+
end_at = index if bracket_depth == 0
|
98
|
+
|
99
|
+
# Spaces are only accepted if they are enclosed within brackets
|
100
|
+
# or they trail the argument.
|
101
|
+
elsif char == " "
|
102
|
+
finished = (bracket_depth == 0 and started)
|
103
|
+
|
104
|
+
# Letters, digits and dots are accepted without question.
|
105
|
+
# Ensure that the argument is marked as "started" when they
|
106
|
+
# are encountered.
|
107
|
+
elsif char.match(/^[[:alnum:]]$/) or char == "."
|
108
|
+
started = true
|
109
|
+
|
110
|
+
# All other characters are only accepted if they are
|
111
|
+
# enclosed within brackets.
|
112
|
+
else
|
113
|
+
started = true
|
114
|
+
finished = true if bracket_depth <= 0
|
115
|
+
end
|
116
|
+
|
117
|
+
# Move back the starting point unless it has been found.
|
118
|
+
start_at = index unless finished
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
# Find the index of the end of the right argument.
|
123
|
+
index = end_at = location
|
124
|
+
started = finished = false
|
125
|
+
bracket_depth = 0
|
126
|
+
until finished or end_at == line.length-1
|
127
|
+
|
128
|
+
# Look at the character at the next index.
|
129
|
+
index += 1
|
130
|
+
char = line[index]
|
131
|
+
|
132
|
+
# Increase the bracket depth if a bracket is opened.
|
133
|
+
if char == "("
|
134
|
+
started = true
|
135
|
+
bracket_depth += 1
|
136
|
+
|
137
|
+
# Decrease the bracket depth if a bracket is closed.
|
138
|
+
# If the bracket closed is the last bracket left,
|
139
|
+
# then this marks the end of the argument.
|
140
|
+
elsif char == ")"
|
141
|
+
started = true
|
142
|
+
bracket_depth -= 1
|
143
|
+
finished = bracket_depth <= 0
|
144
|
+
end_at = index if bracket_depth == 0
|
145
|
+
|
146
|
+
# Spaces are only accepted if they are enclosed within brackets
|
147
|
+
# or they lead the argument.
|
148
|
+
elsif char == " "
|
149
|
+
finished = (bracket_depth == 0 and started)
|
150
|
+
|
151
|
+
# Letters, digits and dots are accepted without question.
|
152
|
+
# Ensure that the argument is marked as "started" when they
|
153
|
+
# are encountered.
|
154
|
+
elsif char.match(/^[[:alnum:]]$/) or char == "."
|
155
|
+
started = true
|
156
|
+
|
157
|
+
# All other characters are only accepted if they are
|
158
|
+
# enclosed within brackets.
|
159
|
+
else
|
160
|
+
started = true
|
161
|
+
finished = true if bracket_depth <= 0
|
162
|
+
end
|
163
|
+
|
164
|
+
# Move back the end point unless it has been found.
|
165
|
+
end_at = index unless finished
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
# Convert the index of the division operator to the start and
|
170
|
+
# end indices of the operation.
|
171
|
+
[start_at, end_at]
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
# Merge any overlapping operations into a single wrapper.
|
176
|
+
temp = []
|
177
|
+
operations.each do |x_start, x_end|
|
178
|
+
merged = false
|
179
|
+
temp.each_index do |y|
|
180
|
+
y_start, y_end = temp[y]
|
181
|
+
if x_start == y_end
|
182
|
+
temp[y][1] = x_end
|
183
|
+
merged = true
|
184
|
+
elsif x_end == y_start
|
185
|
+
temp[y][0] = x_start
|
186
|
+
merged = true
|
187
|
+
end
|
188
|
+
break if merged
|
189
|
+
end
|
190
|
+
temp << [x_start, x_end] unless merged
|
191
|
+
end
|
192
|
+
operations = temp
|
193
|
+
|
194
|
+
# Wrap each of the operations in the original string.
|
195
|
+
operations.each_index do |x|
|
196
|
+
|
197
|
+
# Retrieve the co-ordinates of the operation.
|
198
|
+
start_at, end_at = operations[x]
|
199
|
+
|
200
|
+
# Wrap the operation
|
201
|
+
line.insert start_at, "(ToRobust::Global.prevent_dbz{"
|
202
|
+
line.insert end_at+31, "})"
|
203
|
+
|
204
|
+
# Modify the coordinates of all other method calls.
|
205
|
+
#
|
206
|
+
#
|
207
|
+
#
|
208
|
+
# SURELY THIS SHOULD BE X+1?
|
209
|
+
#
|
210
|
+
#
|
211
|
+
#
|
212
|
+
(x...operations.length).each do |y|
|
213
|
+
|
214
|
+
# If this operation ends before another begins, shift
|
215
|
+
# both the start and end of that operation.
|
216
|
+
if end_at < operations[y][0]
|
217
|
+
operations[y][0] += 32
|
218
|
+
operations[y][1] += 32
|
219
|
+
|
220
|
+
# If this X starts after Y but finishes before, then shift
|
221
|
+
# the end of Y.
|
222
|
+
elsif start_at > operations[y][0] and end_at < operations[y][1]
|
223
|
+
operations[y][1] += 32
|
224
|
+
|
225
|
+
# If this Y is within X, shift the start and end of Y by the
|
226
|
+
# size of the prevention call opening.
|
227
|
+
elsif start_at < operations[y][0] and end_at > operations[y][1]
|
228
|
+
operations[y][0] += 30
|
229
|
+
operations[y][1] += 30
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
# Return the transformed line.
|
237
|
+
return line
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
# Finds all method calls and division operators that could yield a ZeroDivisionError
|
242
|
+
# on the line where the error occurred and prevents them from doing so again by
|
243
|
+
# wrapping them in a DbZ exception catcher.
|
244
|
+
#
|
245
|
+
# *Parameters:*
|
246
|
+
# * method, the method which encountered the error.
|
247
|
+
# * error, the error which occurred.
|
248
|
+
#
|
249
|
+
# *Returns:*
|
250
|
+
# A list of candidate solutions to fix the root of the error affecting the method.
|
251
|
+
def generate_candidates(method, error)
|
252
|
+
|
253
|
+
# Ensure that the error is a ZeroDivisionError.
|
254
|
+
return [] unless error.is_a? ZeroDivisionError
|
255
|
+
|
256
|
+
# Extract the contents of the line that the exception occured on.
|
257
|
+
line_no = error.line_no(method)
|
258
|
+
line_contents = method.source[line_no]
|
259
|
+
|
260
|
+
# We validate the fix by ensuring that any further errors are not
|
261
|
+
# ZeroDivisionError exceptions thrown on this line.
|
262
|
+
validator = lambda do |method, old_error, new_error|
|
263
|
+
not (new_error.is_a? ZeroDivisionError and old_error.line_no(method) == new_error.line_no(method))
|
264
|
+
end
|
265
|
+
|
266
|
+
# Compose the sole candidate fix for this error.
|
267
|
+
line_contents = self.class.wrap_ops(self.class.wrap_calls(line_contents))
|
268
|
+
return [
|
269
|
+
ToRobust::Global::Fix.new(
|
270
|
+
[ToRobust::Global::Fix::SwapAtom.new(line_no, line_contents)],
|
271
|
+
validator
|
272
|
+
)
|
273
|
+
]
|
274
|
+
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'levenshtein'
|
2
|
+
|
3
|
+
# Handles NoMethodError exceptions by mapping missing method calls to valid replacement methods within
|
4
|
+
# the same module/class whose name is within a given Levenshtein distance of the missing method name.
|
5
|
+
class ToRobust::Global::Strategies::NoMethodErrorStrategy < ToRobust::Global::Strategy
|
6
|
+
|
7
|
+
attr_accessor :max_distance
|
8
|
+
|
9
|
+
# Constructs a new NoMethodErrorStrategy.
|
10
|
+
#
|
11
|
+
# *Parameters:*
|
12
|
+
# * max_distance, the maximum allowed distance between an attempted and candidate method call.
|
13
|
+
def initialize(max_distance = 5)
|
14
|
+
@max_distance = max_distance
|
15
|
+
end
|
16
|
+
|
17
|
+
# Used to fix NoMethodError exceptions by replacing calls to missing methods with calls to
|
18
|
+
# valid methods whose name is within a given Levenshtein distance of the missing method name.
|
19
|
+
#
|
20
|
+
# *Parameters:*
|
21
|
+
# * method, the affected method.
|
22
|
+
# * error, the error whose root cause should be fixed.
|
23
|
+
#
|
24
|
+
# *Returns:*
|
25
|
+
# A (possibly empty) array of candidate fixes to the root cause of the error.
|
26
|
+
def generate_candidates(method, error)
|
27
|
+
|
28
|
+
# Ensure that the error is a NoMethodError.
|
29
|
+
return [] unless error.is_a? NoMethodError
|
30
|
+
|
31
|
+
# Retrieve details of the missing method and the contents of the line that the error
|
32
|
+
# occurred on.
|
33
|
+
line_no = error.line_no
|
34
|
+
line_contents = method.source[line_no]
|
35
|
+
missing_method_name = error.method_name
|
36
|
+
missing_method_owner = error.method_owner
|
37
|
+
|
38
|
+
# "main" methods aren't dealt with since there are too many dangerous replacement methods
|
39
|
+
# contained within main.
|
40
|
+
return [] if missing_method_owner.empty?
|
41
|
+
|
42
|
+
# Check if the missing method belongs to a module or a class.
|
43
|
+
missing_method_owner = missing_method_owner.partition(':')
|
44
|
+
missing_method_owner_type = missing_method_owner[2]
|
45
|
+
|
46
|
+
# Check if the missing method belongs to neither a module nor class (this
|
47
|
+
# shouldn't happen).
|
48
|
+
return [] unless ['Class', 'Module'].include? missing_method_owner_type
|
49
|
+
|
50
|
+
# Retrieve the object for the class / module.
|
51
|
+
missing_method_owner = Kernel.const_get(missing_method_owner[0])
|
52
|
+
|
53
|
+
# Extract a list of candidate methods from the class / module and calculate
|
54
|
+
# their levenshtein distance to the missing method. Before calculating the levenshtein
|
55
|
+
# distance to candidates, throw away any candidates whose difference in length with
|
56
|
+
# the requested method is greater than the maximum distance (since it is impossible
|
57
|
+
# that their levenshtein distance can be less or equal to the maximum distance).
|
58
|
+
if missing_method_owner_type == 'Module'
|
59
|
+
candidates = missing_method_owner.singleton_class.public_instance_methods(false)
|
60
|
+
else
|
61
|
+
candidates = missing_method_owner.public_instance_methods(false)
|
62
|
+
end
|
63
|
+
|
64
|
+
candidates.reject!{|c| (c.length - missing_method_name.length).abs > @max_distance}
|
65
|
+
candidates.map!{|c| [c.to_s, Levenshtein.distance(missing_method_name, c.to_s)]}
|
66
|
+
|
67
|
+
# Only keep candidates whose distance with the missing
|
68
|
+
# method is less or equal to the threshold. Order the remaining candidates
|
69
|
+
# before throwing away the distance information.
|
70
|
+
candidates.reject!{|c| c[1] > @max_distance}
|
71
|
+
candidates.sort {|x,y| x[1] <=> y[1]}
|
72
|
+
candidates.map!{|c| c[0]}
|
73
|
+
|
74
|
+
# We validate the exception by ensuring that any further exceptions aren't
|
75
|
+
# NoMethodError exceptions for the fixed method on the fixed line.
|
76
|
+
validator = lambda do |method, old_error, new_error|
|
77
|
+
return (not (new_error.is_a? NoMethodError and new_error.method_name == old_error.method_name and new_error.line_no == old_error.line_no))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Compose each candidate into a fix by replacing each occurence of the
|
81
|
+
# missing method name in the string with the name of the candidate method.
|
82
|
+
return candidates.map! do |c|
|
83
|
+
fixed_line = line_contents.gsub(/(\(|^|::|\.|\s|,)#{missing_method_name}\(/) {|s| s[missing_method_name] = c; s}
|
84
|
+
ToRobust::Global::Fix.new(
|
85
|
+
[ToRobust::Global::Fix::SwapAtom.new(line_no, fixed_line)],
|
86
|
+
validator
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|