Skip to content

Commit c1fbb73

Browse files
committed
Add MutationVisitor#remove API to remove nodes from the tree
1 parent c4c78b3 commit c1fbb73

File tree

3 files changed

+46
-5
lines changed

3 files changed

+46
-5
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -578,14 +578,17 @@ visitor.mutate("IfNode[predicate: Assign | OpAssign]") do |node|
578578
node.copy(predicate: predicate)
579579
end
580580

581-
source = "if a = 1; end"
581+
# remove `do_more_work` method call node
582+
visitor.remove("SyntaxTree::VCall[value: SyntaxTree::Ident[value: 'do_more_work']]")
583+
584+
source = "if a = 1; perform_work; do_more_work; end"
582585
program = SyntaxTree.parse(source)
583586

584587
SyntaxTree::Formatter.format(source, program)
585-
# => "if a = 1\nend\n"
588+
# => "if a = 1\n perform_work\n do_more_work\nend\n"
586589

587590
SyntaxTree::Formatter.format(source, program.accept(visitor))
588-
# => "if (a = 1)\nend\n"
591+
# => "if (a = 1)\n perform_work\nend\n"
589592
```
590593

591594
### WithEnvironment

lib/syntax_tree/visitor/mutation_visitor.rb

+11-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ class Visitor
55
# This visitor walks through the tree and copies each node as it is being
66
# visited. This is useful for mutating the tree before it is formatted.
77
class MutationVisitor < BasicVisitor
8-
attr_reader :mutations
8+
attr_reader :mutations, :removals
99

1010
def initialize
1111
@mutations = []
12+
@removals = []
1213
end
1314

1415
# Create a new mutation based on the given query that will mutate the node
@@ -20,6 +21,10 @@ def mutate(query, &block)
2021
mutations << [Pattern.new(query).compile, block]
2122
end
2223

24+
def remove(query)
25+
@removals << Pattern.new(query).compile
26+
end
27+
2328
# This is the base visit method for each node in the tree. It first
2429
# creates a copy of the node using the visit_* methods defined below. Then
2530
# it checks each mutation in sequence and calls it if it finds a match.
@@ -31,6 +36,10 @@ def visit(node)
3136
result = mutation.call(result) if pattern.call(result)
3237
end
3338

39+
removals.each do |removal_pattern|
40+
return if removal_pattern.call(result)
41+
end
42+
3443
result
3544
end
3645

@@ -712,7 +721,7 @@ def visit_sclass(node)
712721

713722
# Visit a Statements node.
714723
def visit_statements(node)
715-
node.copy(body: visit_all(node.body))
724+
node.copy(body: visit_all(node.body).compact)
716725
end
717726

718727
# Visit a StringContent node.

test/mutation_test.rb

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

24+
def test_removes_node
25+
source = <<~RUBY
26+
App.configure do |config|
27+
config.config_value_a = 1
28+
config.config_value_b = 2
29+
config.config_value_c = 2
30+
end
31+
RUBY
32+
33+
expected = <<~RUBY
34+
App.configure do |config|
35+
config.config_value_a = 1
36+
37+
config.config_value_c = 2
38+
end
39+
RUBY
40+
41+
mutation_visitor = SyntaxTree.mutation do |mutation|
42+
mutation.remove("SyntaxTree::Assign[
43+
target: SyntaxTree::Field[
44+
name: SyntaxTree::Ident[value: 'config_value_b']
45+
],
46+
]")
47+
end
48+
49+
program = SyntaxTree.parse(source).accept(mutation_visitor)
50+
assert_equal(expected, SyntaxTree::Formatter.format(source, program))
51+
end
52+
2453
private
2554

2655
def build_mutation

0 commit comments

Comments
 (0)