Selection replacement
(:replace) modifies the source code of a matched node by substituting it with a literal string. It's designed for automated refactoring tasks where you want to transform code in a predictable, pattern-driven way.
(:replace [val?] [sub?])(:replace) must enclose its target. It cannot reach across to replace something elsewhere in the query -- it replaces exactly what it wraps. This constraint keeps the mental model simple: what you see is what gets replaced.
; replaces the string "foo" with "bar"
(:replace (str "foo") "bar")
; replaces any call to `x` with the literal `y()`
(:replace (call x) "y()")Dropping a value
When (:replace) is given no substitution string, it drops the matched value entirely:
; drop the second argument
(call x (arg 2 (:replace)))Given x(a, b, c), this would produce x(a, c).
Capturing for interpolation
Replacements can incorporate dynamic fragments through captures. Captures are recorded either from pattern literals with regex groups, or via the (:capture) operator.
Pattern captures
A regex pattern with capture groups makes those groups available for interpolation in the substitution string:
(:replace (str /load_(.+)/) "load$1Async")Given "load_user", this produces "loaduserAsync". Named groups work too:
(:replace (id /get(?<name>.+)/) "fetch$name")Given getUser, this produces fetchUser.
Node captures
[(:capture)] records the span of a matched node for use in substitution. This is useful when you want to move or duplicate an entire expression:
(call x
(:and
(arg 1 (:capture first (str)))
(arg 2 (:replace _ "$first"))))Given x("a", "b"), this replaces the second argument with the first, producing x("a", "a"). The capture includes the quotes because it captured the whole string node -- exactly what we want here.
When you need the content of a node rather than its full span, use pattern captures instead:
(call
(:replace load "load_$mode")
(arg 1 (str /(?<mode>.+)/)))Given load("sync"), this produces load_sync("sync"). The pattern extracts just sync without the surrounding quotes.
Capturing across branches
Captures are visible across sibling branches of a query. The example above relies on this: the (:capture) in one (:and) branch is available to the (:replace) in another. This enables transformations where one part of the match informs another's replacement.
Capturing contiguous ranges
When capturing multiple list elements, the spans are merged to preserve the original delimiters:
(call _
(:and
(arg 1 (:replace _ "$rest, $first"))
(arg 1 (:capture first))
(arg 2.. (:capture rest (:into)))))Given x("a", "b", "c"), this swaps the first argument to the end: x("b", "c", "a"). The capture $rest spans from "b" to "c", including the comma and whitespace between them.
Limitations
(:replace) performs literal text substitution. It doesn't understand syntax, so it's possible to produce invalid code if you're not careful. Always review the output.
Structural transformations -- like moving an argument from one position to another while respecting syntax -- require careful use of captures and may not always be possible with (:replace) alone.