/
send.rb
234 lines (199 loc) · 6.42 KB
/
send.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# frozen_string_literal: true
module Mutant
class Mutator
class Node
# Namespace for send mutators
# rubocop:disable Metrics/ClassLength
class Send < self
include AST::Types
handle(:send)
children :receiver, :selector
SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
reverse_map: %i[map each],
kind_of?: %i[instance_of?],
is_a?: %i[instance_of?],
reverse_each: %i[each],
reverse_merge: %i[merge],
map: %i[each],
flat_map: %i[map],
send: %i[public_send __send__],
__send__: %i[public_send],
method: %i[public_method],
gsub: %i[sub],
eql?: %i[equal?],
to_s: %i[to_str],
to_i: %i[to_int],
to_a: %i[to_ary],
to_h: %i[to_hash],
at: %i[fetch key?],
fetch: %i[key?],
values_at: %i[fetch_values],
:== => %i[eql? equal?],
:>= => %i[> == eql? equal?],
:<= => %i[< == eql? equal?],
:> => %i[== >= eql? equal?],
:< => %i[== <= eql? equal?]
)
RECEIVER_SELECTOR_REPLACEMENTS = IceNine.deep_freeze(
Date: {
parse: %i[jd civil strptime iso8601 rfc3339 xmlschema rfc2822 rfc822 httpdate jisx0301]
}
)
private
# Emit mutations
#
# @return [undefined]
def dispatch
emit_singletons
if meta.binary_method_operator?
run(Binary)
elsif meta.attribute_assignment?
run(AttributeAssignment)
else
normal_dispatch
end
end
# AST metadata for node
#
# @return [AST::Meta::Send]
def meta
AST::Meta::Send.new(node)
end
memoize :meta
# Arguments being send
#
# @return [Enumerable<Parser::AST::Node>]
alias_method :arguments, :remaining_children
private :arguments
# Perform normal, non special case dispatch
#
# @return [undefined]
def normal_dispatch
emit_naked_receiver
emit_selector_replacement
emit_selector_specific_mutations
emit_argument_propagation
emit_receiver_selector_mutations
mutate_receiver
mutate_arguments
end
# Emit mutations which only correspond to one selector
#
# @return [undefined]
def emit_selector_specific_mutations
emit_const_get_mutation
emit_integer_mutation
emit_dig_mutation
emit_double_negation_mutation
emit_lambda_mutation
end
# Emit selector mutations specific to top level constants
#
# @return [undefined]
def emit_receiver_selector_mutations
return unless meta.receiver_possible_top_level_const?
RECEIVER_SELECTOR_REPLACEMENTS
.fetch(receiver.children.last, EMPTY_HASH)
.fetch(selector, EMPTY_ARRAY)
.each(&method(:emit_selector))
end
# Emit mutation from `!!foo` to `foo`
#
# @return [undefined]
def emit_double_negation_mutation
return unless selector.equal?(:!) && n_send?(receiver)
negated = AST::Meta::Send.new(meta.receiver)
emit(negated.receiver) if negated.selector.equal?(:!)
end
# Emit mutation from proc definition to lambda
#
# @return [undefined]
def emit_lambda_mutation
emit(s(:send, nil, :lambda)) if meta.proc?
end
# Emit mutation for `#dig`
#
# - Mutates `foo.dig(a, b)` to `foo.fetch(a).dig(b)`
# - Mutates `foo.dig(a)` to `foo.fetch(a)`
#
# @return [undefined]
def emit_dig_mutation
return if !selector.equal?(:dig) || arguments.none?
head, *tail = arguments
fetch_mutation = s(:send, receiver, :fetch, head)
return emit(fetch_mutation) if tail.empty?
emit(s(:send, fetch_mutation, :dig, *tail))
end
# Emit mutation from `to_i` to `Integer(...)`
#
# @return [undefined]
def emit_integer_mutation
return unless selector.equal?(:to_i)
emit(s(:send, nil, :Integer, receiver))
end
# Emit mutation from `const_get` to const literal
#
# @return [undefined]
def emit_const_get_mutation
return unless selector.equal?(:const_get) && n_sym?(arguments.first)
emit(s(:const, receiver, AST::Meta::Symbol.new(arguments.first).name))
end
# Emit selector replacement
#
# @return [undefined]
def emit_selector_replacement
SELECTOR_REPLACEMENTS.fetch(selector, EMPTY_ARRAY).each(&method(:emit_selector))
end
# Emit naked receiver mutation
#
# @return [undefined]
def emit_naked_receiver
emit(receiver) if receiver
end
# Mutate arguments
#
# @return [undefined]
def mutate_arguments
emit_type(receiver, selector)
remaining_children_with_index.each do |_node, index|
mutate_argument_index(index)
delete_child(index)
end
end
# Mutate argument
#
# @param [Integer] index
#
# @return [undefined]
def mutate_argument_index(index)
mutate_child(index) { |node| !n_begin?(node) }
end
# Emit argument propagation
#
# @return [undefined]
def emit_argument_propagation
emit_propagation(Mutant::Util.one(arguments)) if arguments.one?
end
# Emit receiver mutations
#
# @return [undefined]
def mutate_receiver
return unless receiver
emit_implicit_self
emit_receiver_mutations do |node|
!n_nil?(node)
end
end
# Emit implicit self mutation
#
# @return [undefined]
def emit_implicit_self
emit_receiver(nil) if n_self?(receiver) && !(
KEYWORDS.include?(selector) ||
meta.attribute_assignment?
)
end
end # Send
end # Node
end # Mutator
end # Mutant