Skip to content

Commit 5959dec

Browse files
committed
Make mutation visitor visit all nodes
The test case demonstrates 3 important node types that would ignore their children: - Assign - Assoc - IfOp The fact that MutationVisitor wouldn't traverse anything that's assigned to a variable, a hash key, or inside a ternary operator was very limiting.
1 parent ea300f2 commit 5959dec

File tree

2 files changed

+64
-20
lines changed

2 files changed

+64
-20
lines changed

lib/syntax_tree/mutation_visitor.rb

+44-20
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ def visit_alias(node)
6767

6868
# Visit a ARef node.
6969
def visit_aref(node)
70-
node.copy(index: visit(node.index))
70+
node.copy(collection: visit(node.collection), index: visit(node.index))
7171
end
7272

7373
# Visit a ARefField node.
7474
def visit_aref_field(node)
75-
node.copy(index: visit(node.index))
75+
node.copy(collection: visit(node.collection), index: visit(node.index))
7676
end
7777

7878
# Visit a ArgParen node.
@@ -120,17 +120,17 @@ def visit_aryptn(node)
120120

121121
# Visit a Assign node.
122122
def visit_assign(node)
123-
node.copy(target: visit(node.target))
123+
node.copy(target: visit(node.target), value: visit(node.value))
124124
end
125125

126126
# Visit a Assoc node.
127127
def visit_assoc(node)
128-
node.copy
128+
node.copy(key: visit(node.key), value: visit(node.value))
129129
end
130130

131131
# Visit a AssocSplat node.
132132
def visit_assoc_splat(node)
133-
node.copy
133+
node.copy(value: visit(node.value))
134134
end
135135

136136
# Visit a Backref node.
@@ -155,12 +155,12 @@ def visit_begin(node)
155155

156156
# Visit a PinnedBegin node.
157157
def visit_pinned_begin(node)
158-
node.copy
158+
node.copy(statement: visit(node.statement))
159159
end
160160

161161
# Visit a Binary node.
162162
def visit_binary(node)
163-
node.copy
163+
node.copy(left: visit(node.left), right: visit(node.right))
164164
end
165165

166166
# Visit a BlockVar node.
@@ -178,6 +178,7 @@ def visit_bodystmt(node)
178178
node.copy(
179179
statements: visit(node.statements),
180180
rescue_clause: visit(node.rescue_clause),
181+
else_keyword: visit(node.else_keyword),
181182
else_clause: visit(node.else_clause),
182183
ensure_clause: visit(node.ensure_clause)
183184
)
@@ -209,7 +210,11 @@ def visit_case(node)
209210

210211
# Visit a RAssign node.
211212
def visit_rassign(node)
212-
node.copy(operator: visit(node.operator))
213+
node.copy(
214+
value: visit(node.value),
215+
operator: visit(node.operator),
216+
pattern: visit(node.pattern)
217+
)
213218
end
214219

215220
# Visit a ClassDeclaration node.
@@ -238,6 +243,7 @@ def visit_command(node)
238243
# Visit a CommandCall node.
239244
def visit_command_call(node)
240245
node.copy(
246+
receiver: visit(node.receiver),
241247
operator: node.operator == :"::" ? :"::" : visit(node.operator),
242248
message: visit(node.message),
243249
arguments: visit(node.arguments),
@@ -257,12 +263,12 @@ def visit_const(node)
257263

258264
# Visit a ConstPathField node.
259265
def visit_const_path_field(node)
260-
node.copy(constant: visit(node.constant))
266+
node.copy(parent: visit(node.parent), constant: visit(node.constant))
261267
end
262268

263269
# Visit a ConstPathRef node.
264270
def visit_const_path_ref(node)
265-
node.copy(constant: visit(node.constant))
271+
node.copy(parent: visit(node.parent), constant: visit(node.constant))
266272
end
267273

268274
# Visit a ConstRef node.
@@ -288,7 +294,7 @@ def visit_def(node)
288294

289295
# Visit a Defined node.
290296
def visit_defined(node)
291-
node.copy
297+
node.copy(value: visit(node.value))
292298
end
293299

294300
# Visit a Block node.
@@ -325,6 +331,7 @@ def visit_else(node)
325331
# Visit a Elsif node.
326332
def visit_elsif(node)
327333
node.copy(
334+
predicate: visit(node.predicate),
328335
statements: visit(node.statements),
329336
consequent: visit(node.consequent)
330337
)
@@ -366,6 +373,7 @@ def visit_excessed_comma(node)
366373
# Visit a Field node.
367374
def visit_field(node)
368375
node.copy(
376+
parent: visit(node.parent),
369377
operator: node.operator == :"::" ? :"::" : visit(node.operator),
370378
name: visit(node.name)
371379
)
@@ -388,7 +396,11 @@ def visit_fndptn(node)
388396

389397
# Visit a For node.
390398
def visit_for(node)
391-
node.copy(index: visit(node.index), statements: visit(node.statements))
399+
node.copy(
400+
index: visit(node.index),
401+
collection: visit(node.collection),
402+
statements: visit(node.statements)
403+
)
392404
end
393405

394406
# Visit a GVar node.
@@ -446,7 +458,11 @@ def visit_if(node)
446458

447459
# Visit a IfOp node.
448460
def visit_if_op(node)
449-
node.copy
461+
node.copy(
462+
predicate: visit(node.predicate),
463+
truthy: visit(node.truthy),
464+
falsy: visit(node.falsy)
465+
)
450466
end
451467

452468
# Visit a Imaginary node.
@@ -457,6 +473,7 @@ def visit_imaginary(node)
457473
# Visit a In node.
458474
def visit_in(node)
459475
node.copy(
476+
pattern: visit(node.pattern),
460477
statements: visit(node.statements),
461478
consequent: visit(node.consequent)
462479
)
@@ -522,7 +539,7 @@ def visit_lparen(node)
522539

523540
# Visit a MAssign node.
524541
def visit_massign(node)
525-
node.copy(target: visit(node.target))
542+
node.copy(target: visit(node.target), value: visit(node.value))
526543
end
527544

528545
# Visit a MethodAddBlock node.
@@ -565,7 +582,11 @@ def visit_op(node)
565582

566583
# Visit a OpAssign node.
567584
def visit_opassign(node)
568-
node.copy(target: visit(node.target), operator: visit(node.operator))
585+
node.copy(
586+
target: visit(node.target),
587+
operator: visit(node.operator),
588+
value: visit(node.value)
589+
)
569590
end
570591

571592
# Visit a Params node.
@@ -667,7 +688,10 @@ def visit_regexp_literal(node)
667688

668689
# Visit a RescueEx node.
669690
def visit_rescue_ex(node)
670-
node.copy(variable: visit(node.variable))
691+
node.copy(
692+
exceptions: visit(node.exceptions),
693+
variable: visit(node.variable)
694+
)
671695
end
672696

673697
# Visit a Rescue node.
@@ -682,7 +706,7 @@ def visit_rescue(node)
682706

683707
# Visit a RescueMod node.
684708
def visit_rescue_mod(node)
685-
node.copy
709+
node.copy(statement: visit(node.statement), value: visit(node.value))
686710
end
687711

688712
# Visit a RestParam node.
@@ -707,7 +731,7 @@ def visit_rparen(node)
707731

708732
# Visit a SClass node.
709733
def visit_sclass(node)
710-
node.copy(bodystmt: visit(node.bodystmt))
734+
node.copy(target: visit(node.target), bodystmt: visit(node.bodystmt))
711735
end
712736

713737
# Visit a Statements node.
@@ -815,7 +839,7 @@ def visit_not(node)
815839

816840
# Visit a Unary node.
817841
def visit_unary(node)
818-
node.copy
842+
node.copy(statement: visit(node.statement))
819843
end
820844

821845
# Visit a Undef node.
@@ -842,7 +866,7 @@ def visit_until(node)
842866

843867
# Visit a VarField node.
844868
def visit_var_field(node)
845-
node.copy(value: visit(node.value))
869+
node.copy(value: node.value == :nil ? :nil : visit(node.value))
846870
end
847871

848872
# Visit a VarRef node.

test/mutation_test.rb

+20
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,26 @@ def test_mutates_based_on_patterns
2121
assert_equal(expected, SyntaxTree::Formatter.format(source, program))
2222
end
2323

24+
def test_deep_mutation
25+
source = <<~RUBY
26+
hash = { "key" => a ? foo : nil }
27+
RUBY
28+
29+
expected = <<~RUBY
30+
hash = { "key" => a ? bar : nil }
31+
RUBY
32+
33+
rename_foo_into_bar =
34+
SyntaxTree.mutation do |mutation|
35+
mutation.mutate("Ident[value: 'foo']") do |node|
36+
node.copy(value: "bar")
37+
end
38+
end
39+
40+
program = SyntaxTree.parse(source).accept(rename_foo_into_bar)
41+
assert_equal(expected, SyntaxTree::Formatter.format(source, program))
42+
end
43+
2444
private
2545

2646
def build_mutation

0 commit comments

Comments
 (0)