Mutare.Transform.NodeRange (mutare v0.3.1)

Copy Markdown View Source

Sourceror.get_range/1 with a correction for one upstream quirk that would otherwise corrupt a survivor's reported location.

The escaped-delimiter under-count in sigils and interpolated strings. Sourceror computes a sigil's end column from the stored content length (range.ex get_end_pos_for_interpolation_segments/3, String.length of the <<>> segments). The tokenizer keeps a sigil's content raw — \n/\\/\t stay two-character sequences — except it collapses an escaped closing delimiter (\/ → / in ~r/…/, \} → } in ~r{…}), so the stored content is one byte shorter per such escape and the range falls short by that many columns. A textual patch over the short range leaves the sigil's tail in place — and when the mutation only drops a trailing flag (~r/…/u → ~r/…/, RegexLiteral), the patch lands exactly on the dropped u and the diff shows no change at all (a survivor with an empty diff). get/1 adds back one column per collapsed closing delimiter. (Escaped delimiters before the last interpolation are already accounted for — their absolute closing position is baked into the segment metadata — so only the trailing binary segments are counted; an opening delimiter escape \{ keeps its backslash and never collapses, so only the closing char is counted.)

The same collapse hits the interpolated string family — "a\"#{x}\"b" and its charlist / quoted-atom cousins also range from segment lengths, and the tokenizer collapses \" → " (or \' → ') there too, so an escaped quote after the last interpolation shortens the range and the patch leaves the original closing quote behind (toast("…\"#{x}\".") mutated to "" renders as toast(""")). get/1 applies the same trailing-segment count to those three container shapes. Non-interpolated strings are immune (their raw content keeps the backslash), and heredocs never escape their fence, so only the quote-delimited (non-heredoc), single-line, interpolated forms are corrected.

Only Mutare.Report reads the range, so the quirk is invisible at runtime: the metamutant is built from the AST, never the range. It corrupts only a surviving mutant's rendered location — the human diff, and the endColumn the machine reporters emit (Mutare.Report.Json / Mutare.Report.Sarif). See NOTES "Sourceror range".

Summary

Functions

Like Sourceror.get_range/1, correcting the sigil/interpolated-string under-count.

Functions

get(node)

@spec get(Macro.t()) :: Sourceror.Range.t() | nil

Like Sourceror.get_range/1, correcting the sigil/interpolated-string under-count.