# `Mutare.Report`
[🔗](https://github.com/foxbenjaminfox/mutare/blob/v0.5.1/lib/mutare/report.ex#L1)

Renders mutation results for the human report.

Diffs are patched against the original source via `Sourceror.patch_string` at the site's recorded range, so untouched source stays byte-identical and a survivor reads as a precise source-range change. A replacement is parenthesized where its position would otherwise reread it (`!(a == b)` → `(a == b)` beside a `|>`, a `-1` for the `0.75` of `-0.75`), on the assumption that binary operators are spaced as `mix format` writes them: in unformatted `a-0`, a `-1` replacement is patched as `a--1`.

The mutation score and tallies in the report come from `Mutare.Score`, which also implements the CI gates; this module only renders.

# `broken_partition`

```elixir
@spec broken_partition(Mutare.Run.BrokenPartition.t()) :: String.t()
```

One line for a partition whose environment failed the run's tests with no mutant
active (`Mutare.Run.BrokenPartition`), e.g.
`partition 2  BROKEN  — with no mutant active, the tests that killed mutant 12 failed: ** (RuntimeError) no database`.

# `diff`

```elixir
@spec diff(Mutare.Site.t(), String.t()) :: String.t()
```

A `-`/`+` diff of the lines touched by the mutation.

The diff is line-based within the site's source range. Changed lines are shown
as deletions and insertions; unchanged lines inside a multi-line fragment are
shown as context. This keeps multi-line replacements aligned when a mutation adds
or removes a line in the middle of the fragment.

# `harness_error`

```elixir
@spec harness_error(Mutare.Result.t()) :: String.t()
```

One line for a harness-errored mutant, including a compact diagnostic.

# `header`

```elixir
@spec header(Mutare.Site.t()) :: String.t()
```

Header line for a surviving mutant, e.g.
`lib/x.ex:42:7  [relational, in-place]  SURVIVED`.

A mutation with a note appends it as a trailing `— note`, matching the ignored
mutant reason format.

# `ignored`

```elixir
@spec ignored(Mutare.Site.t()) :: String.t()
```

One line for an ignored mutant, e.g.
`lib/x.ex:42:7  [arithmetic]  IGNORED  — off-by-one is intentional`.

The trailing `— reason` is present only when the directive carried one, so a
bare `# mutare:ignore` reads as `… IGNORED` with nothing after it.

# `no_coverage`

```elixir
@spec no_coverage([Mutare.Result.t()]) :: String.t()
```

The lines holding mutants no test ran, one line per file, with runs of
consecutive lines collapsed into a range. Files keep the order of `results`.
Returns `""` when every mutant was covered.

Listing each uncovered mutant would flood a large run, since a line can hold
many; collapsing to lines keeps the section proportional to the untested
regions. Only strictly consecutive lines merge: a line with no uncovered
mutant between two that have one may be covered or outside the run's scope,
so a range never spans it.

    iex> results = [
    ...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/a.ex", line: 13}},
    ...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/a.ex", line: 12}},
    ...>   %Mutare.Result{status: :killed, site: %Mutare.Site{file: "lib/a.ex", line: 20}},
    ...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/a.ex", line: 30}},
    ...>   %Mutare.Result{status: :no_coverage, site: %Mutare.Site{file: "lib/b.ex", line: 7}}
    ...> ]
    iex> Mutare.Report.no_coverage(results)
    "no test ran the mutants on:\n  lib/a.ex:12-13, 30\n  lib/b.ex:7"

# `patch`

```elixir
@spec patch(Mutare.Site.t(), String.t()) :: String.t()
```

Apply a single mutation to the original source string.

# `render`

```elixir
@spec render([Mutare.Result.t()], %{optional(String.t()) =&gt; String.t()}) :: String.t()
```

Render the whole report from results and a `%{file => original_source}` map.

The report lists each survivor as a diff, then the lines holding mutants no
test ran (`no_coverage/1`), then ignored mutants and harness errors, one per
line, then the partitions whose kills may be false, and ends with the
`summary/2` tally.

# `render`

```elixir
@spec render([Mutare.Result.t()], %{optional(String.t()) =&gt; String.t()}, keyword()) ::
  String.t()
```

Renders the human report with the same arity as machine reporters.

`opts[:scope]` is the `summary/2` scope note, and `opts[:broken_partitions]`
(`Mutare.Run`'s) the partitions whose kills may be false; the other options are
ignored (score gating is handled by the caller).

# `summary`

```elixir
@spec summary([Mutare.Result.t()], String.t() | nil) :: String.t()
```

One-line tally, e.g. `mutation score: 66.7%  (2 killed, 1 survived, 3 total)`.

A run that tested only part of the project (`--since`, `--line`, …) passes the
flags that chose that part as `scope`, appended as a trailing `— scoped by …`:
its score is over those mutants alone, not the project's.

    iex> Mutare.Report.summary([
    ...>   %Mutare.Result{status: :killed},
    ...>   %Mutare.Result{status: :survived}
    ...> ])
    "mutation score: 50.0%  (1 killed, 1 survived, 2 total)"

    iex> Mutare.Report.summary([%Mutare.Result{status: :killed}], "--since main")
    "mutation score: 100.0%  (1 killed, 0 survived, 1 total)  — scoped by --since main"

# `survivor`

```elixir
@spec survivor(Mutare.Site.t(), String.t()) :: String.t()
```

Full diff block (header + diff) for one survivor.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
