Small constructors and predicates for Sourceror AST nodes.
These helpers are the supported way for custom mutators to build AST. literal/1 is the most important one: it builds literal nodes with fresh metadata, so Sourceror renders the new value rather than stale source text. It also handles string delimiters, numeric token metadata, and negative-number shape correctly.
literal_value/1 reads supported literal nodes back to their values. The sentinel_* helpers return the same survivor markers used by the built-in families, and numeric_alternatives/3 the same off-by-one/zero alternatives (with their variant labels and collapse rule) the numeric literal families produce. absolute_alias/1, absolute_call/3, and remote_call/3 build references that are not affected by aliases or imports in the target source. keyword_key/1 and clean_var/1 cover the remaining node shapes a mutator emits into existing source: fresh keyword keys and re-declared bindings.
parse!/1 and to_string/1 expose the Sourceror round trip without requiring custom mutators to depend on Sourceror directly.
Summary
Functions
Builds an absolute-qualified module alias.
Builds an absolute-qualified remote call.
Strips a variable node's metadata, keeping its name and hygiene context.
Whether node is an always-empty enumerable literal admitted on the right of
in in a guard: List → [], WordListLiteral → ~w(), or
CharlistLiteral → ~c"".
The bare keyword atom of a key node, whether plain (:do) or Sourceror-wrapped
({:__block__, _, [:do]}); nil for anything that isn't an atom key.
Builds a keyword-list key node that renders as key: rather than {:key, …}.
Returns whether node is the key side of an inline keyword pair.
Builds a scalar-literal node with fresh metadata.
Reads the scalar value from a literal node.
Whether node is a nil literal in either bare or Sourceror-wrapped form.
The "off-by-one + zero sentinel" alternatives for a numeric literal value, exactly as the
built-in Mutare.Mutators.IntegerLiteral (step 1, zero 0) and Mutare.Mutators.FloatLiteral
(step 1.0, zero 0.0) produce them: value + step (labelled "succ"), value - step
("pred"), and zero ("zero"), as ordered {value, labels} pairs.
Returns the value bound to key in a Sourceror-form keyword list.
Parse a source string into a Sourceror AST node, raising on a syntax error.
Builds a remote call mod.fun(args) around a pre-built callee node.
Sourceror.to_string/2 options with the formatter lookup pinned off — to_string/2's
defaults, for the few places that render through Sourceror directly.
The survivor sentinel as a module-alias path (Mutare.Mutant).
The survivor sentinel as an atom (:mutare).
The survivor sentinel as a string: a value distinctive enough to flag a
surviving mutant in a report, yet unlikely to occur in real code. The literal
families (StringLiteral, ReturnValue, …) all substitute it, so the single
word lives here.
Whether a {:<<>>, meta, _} node is really a string, not a <<…>> bitstring.
Drops Sourceror's :leading_comments/:trailing_comments from every node of ast.
Render an AST node back to formatted source, the inverse of parse!/1.
Removes one Sourceror literal wrapper.
Map over a keyword list, replacing the value of the :do entry with fun.(value).
update_do_block/2 threading an accumulator: fun.(value, acc) returns
{new_value, acc}, like Enum.map_reduce/3. Non-:do entries (and a non-list
argument) pass the accumulator through unchanged.
Functions
Builds an absolute-qualified module alias.
The result is the Elixir.-prefixed form that alias and import resolution do
not rewrite. Use it when a mutation must refer to a specific module regardless of
aliases in the target source. Pair it with absolute_call/3 to build a call.
iex> Mutare.AST.absolute_alias([:Kernel])
{:__aliases__, [], [:"Elixir", :Kernel]}
Builds an absolute-qualified remote call.
The callee cannot be redirected by aliases or imports in the target source. This
is useful when a mutator replaces a call with a function from another module, such
as replacing String.length(s) with Kernel.byte_size(s).
iex> Mutare.AST.absolute_call([:Kernel], :==, [1, 2])
{{:., [], [{:__aliases__, [], [:"Elixir", :Kernel]}, :==]}, [], [1, 2]}
Strips a variable node's metadata, keeping its name and hygiene context.
Use it to re-declare a binding inside synthesized scaffolding — a wrapper call a mutator builds around existing code — where the source line/column and token metadata are stale but the context atom must survive for the variable to stay the same variable.
iex> Mutare.AST.clean_var({:user, [line: 3, column: 7], nil})
{:user, [], nil}
Whether node is an always-empty enumerable literal admitted on the right of
in in a guard: List → [], WordListLiteral → ~w(), or
CharlistLiteral → ~c"".
In a guard, x in <empty> is equivalent to the false mutant that
Mutare.Mutators.Conditional already produces on the in node: guards have no
observable side effects and a guard error is a failed guard. Mutare.Transform.Tag
therefore drops the empty-literal sibling there. Body expressions do not use this
predicate because evaluating x can be observable.
The bare keyword atom of a key node, whether plain (:do) or Sourceror-wrapped
({:__block__, _, [:do]}); nil for anything that isn't an atom key.
Builds a keyword-list key node that renders as key: rather than {:key, …}.
The format: :keyword marker is what the renderer keys on; keyword_label?/1
is the matching reader. Use it when a mutation emits a fresh keyword entry.
iex> Mutare.AST.keyword_key(:limit)
{:__block__, [format: :keyword], [:limit]}
Returns whether node is the key side of an inline keyword pair.
Sourceror represents a: as {:__block__, meta, [:a]} with
format: :keyword. Such keys are structural labels, not runtime values.
This predicate checks only the inline-keyword marker. Value-context block keys
such as do and else are handled separately.
Builds a scalar-literal node with fresh metadata.
Sourceror parses a literal as {:__block__, meta, [value]} and may render it
from cached token metadata. Reusing metadata from a parsed literal can therefore
render the original source text after the value has changed. This helper builds a
fresh literal node for replacement values.
String values include a double-quote delimiter. Without it, Sourceror may render a printable binary as a charlist.
Numbers carry a :token derived from the new value. The formatter fetches token
metadata for every numeric literal and can raise when it cannot synthesize one in
an embedded position — for example inside a call node a mutator weaves into
already-parsed source. Because the token is the new value's own text, it can never
re-render stale source.
Negative numbers use the same unary-minus AST shape the parser emits. This keeps
nested negative-number mutations renderable and parseable, for example when a
mutation inside -0.5 would otherwise format as an invalid --0.5.
iex> Mutare.AST.literal(0)
{:__block__, [token: "0"], [0]}
iex> Mutare.AST.literal("mutare")
{:__block__, [delimiter: ~s(")], ["mutare"]}
iex> Mutare.AST.literal(-1)
{:-, [], [{:__block__, [token: "1"], [1]}]}
Reads the scalar value from a literal node.
Returns {:ok, value} for a number, binary, or atom in either bare scalar
form or Sourceror's {:__block__, _, [value]} wrapper. Returns :error for
variables, calls, collections, unary-minus literal expressions such as
Mutare.AST.literal(-1), and other non-scalar nodes.
Booleans and nil are atoms and are returned as values; filter them separately
when a mutator does not own them.
iex> Mutare.AST.literal_value({:__block__, [], [0]})
{:ok, 0}
iex> Mutare.AST.literal_value(:foo)
{:ok, :foo}
iex> Mutare.AST.literal_value({:x, [], nil})
:error
Whether node is a nil literal in either bare or Sourceror-wrapped form.
The "off-by-one + zero sentinel" alternatives for a numeric literal value, exactly as the
built-in Mutare.Mutators.IntegerLiteral (step 1, zero 0) and Mutare.Mutators.FloatLiteral
(step 1.0, zero 0.0) produce them: value + step (labelled "succ"), value - step
("pred"), and zero ("zero"), as ordered {value, labels} pairs.
Two rules ride along, so a custom mutator that mints the same alternatives somewhere core can't
reach (a literal inside a DSL fragment it hosts) stays in step with the built-ins and their
# mutare:ignore variant vocabulary: an alternative equal to value is dropped (0 is never
re-emitted for 0), and alternatives that collapse onto one value are merged into a single
pair carrying every label, positioned at the first occurrence — so 1's pred and its zero
sentinel become one {0, ["pred", "zero"]} that either qualifier suppresses.
iex> Mutare.AST.numeric_alternatives(5, 1, 0)
[{6, ["succ"]}, {4, ["pred"]}, {0, ["zero"]}]
iex> Mutare.AST.numeric_alternatives(1, 1, 0)
[{2, ["succ"]}, {0, ["pred", "zero"]}]
iex> Mutare.AST.numeric_alternatives(0.0, 1.0, 0.0)
[{1.0, ["succ"]}, {-1.0, ["pred"]}]Values, not nodes — build each with literal/1 (and filter first if a position can't take
some of them, e.g. a negative index).
Returns the value bound to key in a Sourceror-form keyword list.
Returns default when the key is absent. Keys are read with key_atom/1, so
both [as: B] and [{:as, B}] match.
Parse a source string into a Sourceror AST node, raising on a syntax error.
The inverse of to_string/1. This is the node shape every Mutare.Mutator
receives, so a mutator's tests can parse a snippet the same way the engine does.
iex> {op, _meta, _args} = Mutare.AST.parse!("a + b")
iex> op
:+
Builds a remote call mod.fun(args) around a pre-built callee node.
The general form of absolute_call/3: use it when the module reference is
already a node — an absolute_alias/1 result, a variable, or an alias taken
from the source being mutated.
iex> Mutare.AST.remote_call(Mutare.AST.absolute_alias([:Kernel]), :==, [1, 2])
{{:., [], [{:__aliases__, [], [:"Elixir", :Kernel]}, :==]}, [], [1, 2]}
Sourceror.to_string/2 options with the formatter lookup pinned off — to_string/2's
defaults, for the few places that render through Sourceror directly.
@spec sentinel_alias() :: [atom()]
The survivor sentinel as a module-alias path (Mutare.Mutant).
@spec sentinel_atom() :: atom()
The survivor sentinel as an atom (:mutare).
@spec sentinel_string() :: String.t()
The survivor sentinel as a string: a value distinctive enough to flag a
surviving mutant in a report, yet unlikely to occur in real code. The literal
families (StringLiteral, ReturnValue, …) all substitute it, so the single
word lives here.
Whether a {:<<>>, meta, _} node is really a string, not a <<…>> bitstring.
Sourceror parses an interpolated string ("a#{x}b", or an interpolated heredoc) into a
<<>> node, distinguished from a true <<…>> bitstring literal by a :delimiter key cached
in its metadata. The string- and bitstring-literal families route on it with opposite intent
— StringLiteral mutates these (an interpolated string), BitstringLiteral/BitstringSpec
skip them (they own real bitstrings). A non-<<>> node is never a string binary.
iex> Mutare.AST.string_binary?({:<<>>, [delimiter: ~s(")], ["hi"]})
true
iex> Mutare.AST.string_binary?({:<<>>, [], [1, 2]})
false
iex> Mutare.AST.string_binary?({:__block__, [], [:atom]})
false
Drops Sourceror's :leading_comments/:trailing_comments from every node of ast.
Sourceror attaches each comment to one node — a trailing # … to the leftmost leaf of
its line, a comment before end to the enclosing form — and Sourceror.to_string/1
renders whatever the subtree carries. Since a mutation reuses the original's operands,
the comment would ride into the mutated node too. to_string/1 strips it for you; call
this directly when a parsed node is re-emitted into generated code where the comment
should not repeat.
iex> {:a, meta, nil} = "# note\na" |> Mutare.AST.parse!() |> Mutare.AST.strip_comments()
iex> Keyword.take(meta, [:leading_comments, :trailing_comments])
[]
Render an AST node back to formatted source, the inverse of parse!/1.
Comment metadata is stripped first (strip_comments/1), so a node taken from parsed
source renders as its code alone — never the # … text Sourceror parked on it.
iex> Mutare.AST.to_string(Mutare.AST.literal("mutare"))
~s("mutare")
iex> "a > x # note" |> Mutare.AST.parse!() |> Mutare.AST.to_string()
"a > x"opts are passed through to Sourceror.to_string/2; line_length: :infinity renders
without fits-based line breaks (structural breaks — do/end, clauses — still apply).
Local calls always render with parentheses (locals_without_parens: []), whatever the
target project's .formatter.exs says. Left to its default, Sourceror.to_string/2
evaluates that file through Mix.Tasks.Format on every call — its import_deps
and plugins, inside Mutare's own process — and Mix can refuse it mid-run (see NOTES
"Rendering never consults the target's formatter configuration"). Both spellings are
the same code, and the metamutant only has to compile.
Removes one Sourceror literal wrapper.
A single-element {:__block__, _, [inner]} returns inner; any other node is
returned unchanged. Unlike literal_value/1, this helper does not require the
inner term to be a scalar literal and does not wrap the result in {:ok, _}.
iex> Mutare.AST.unwrap_literal({:__block__, [], [:persistent_term]})
:persistent_term
iex> Mutare.AST.unwrap_literal(:persistent_term)
:persistent_term
Map over a keyword list, replacing the value of the :do entry with fun.(value).
Recognises both Sourceror's keyword-block key ({:__block__, _, [:do]})
and a plain :do (via key_atom/1), preserves the original key node (so its
format: :keyword marker survives for the renderer), and leaves every other entry
— and a non-list argument — untouched.
@spec update_do_block_reduce(Macro.t(), acc, (Macro.t(), acc -> {Macro.t(), acc})) :: {Macro.t(), acc} when acc: var
update_do_block/2 threading an accumulator: fun.(value, acc) returns
{new_value, acc}, like Enum.map_reduce/3. Non-:do entries (and a non-list
argument) pass the accumulator through unchanged.