---
title: "Mutator Reference and Choosing the Right Mutators"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Mutator Reference and Choosing the Right Mutators}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
source("engine.R")
```

A mutator defines one kind of code change. When you pass a mutator to `muttest_plan()`, muttest finds every matching pattern in your source file and produces one mutant per match.

## Operator mutators

### `operator()` — custom pair

The most flexible mutator. Replaces any token with any other token.

```r
operator("+", "-")   # every + becomes -
operator("==", "!=") # every == becomes !=
```

Use this when you need a specific swap not covered by the preset functions.

### `arithmetic_operators()` — preset for arithmetic

Returns a ready-made list of operator mutators covering common arithmetic mistakes:

| Original | Mutant |
| -------- | ------ |
| `+`      | `-`    |
| `-`      | `+`    |
| `*`      | `/`    |
| `/`      | `*`    |
| `^`      | `*`    |
| `%%`     | `*`    |
| `%/%`    | `/`    |

💡 **When to use:** Any function that performs calculations — finance, statistics, data transformations. Arithmetic operator swaps can happen easily and go unnoticed.

```r
plan <- muttest_plan(
  source_files = "R/finance.R",
  mutators = arithmetic_operators()
)
```

```{muttest_example}
mad
```

### `comparison_operators()` — preset for comparisons

Covers boundary and direction mistakes in comparison expressions:

| Original | Mutant |
| -------- | ------ |
| `<`      | `>`    |
| `>`      | `<`    |
| `<=`     | `>=`   |
| `>=`     | `<=`   |
| `==`     | `!=`   |
| `!=`     | `==`   |
| `<`      | `<=`   |
| `>`      | `>=`   |

💡 **When to use:** Functions with threshold logic, range checks, or filter conditions. Off-by-one and direction errors are easy to introduce and hard to catch with weak tests.

```{muttest_example}
shipping
```

### `logical_operators()` — preset for logical operators

| Original | Mutant |
| -------- | ------ |
| `&&`     | `\|\|` |
| `\|\|`   | `&&`   |
| `&`      | `\|`   |
| `\|`     | `&`    |

💡 **When to use:** Functions with compound conditions (`if (a && b)`). Swapping `&&` for `||` is a classic logic bug that coverage cannot detect.

```{muttest_example}
access
```

---

## Boolean literal mutators

### `boolean_literal()` — flip TRUE/FALSE

```r
boolean_literal("TRUE", "FALSE")  # TRUE → FALSE
boolean_literal("FALSE", "TRUE")  # FALSE → TRUE
```

💡 **When to use:** Functions with hard-coded boolean flags or default arguments like `na.rm = TRUE`, `stringsAsFactors = FALSE`. Flipping the default reveals whether tests exercise both states.

### `boolean_literals()` — preset for boolean literals

Returns a ready-made list covering all canonical flips:

| Original | Mutant  |
| -------- | ------- |
| `TRUE`   | `FALSE` |
| `FALSE`  | `TRUE`  |
| `T`      | `F`     |
| `F`      | `T`     |

```r
plan <- muttest_plan(
  source_files = "R/flags.R",
  mutators = boolean_literals()
)
```

---

## Numeric mutators

### `numeric_increment()` — add 1 to every numeric literal

```r
numeric_increment()  # 5 → 6, 0 → 1, 100 → 101
```

### `numeric_decrement()` — subtract 1 from every numeric literal

```r
numeric_decrement()  # 5 → 4, 1 → 0, 100 → 99
```

💡 **When to use:** Functions where exact numeric constants matter — thresholds, window sizes, counts, index boundaries. Off-by-one errors in constants are common and often untested.

### `numeric_literals()` — preset for numeric constants

Returns `numeric_increment()` and `numeric_decrement()` together.

```r
plan <- muttest_plan(
  source_files = "R/thresholds.R",
  mutators = numeric_literals()
)
```

---

## String mutators

### `string_empty()` — replace strings with `""`

```r
string_empty()  # "hello" → ""
```

### `string_fill()` — replace empty strings with `"mutant"`

```r
string_fill()  # "" → "mutant"
```

💡 **When to use:** `string_empty()` is useful for functions that return or compare string values — it checks whether your tests would notice a blank output. `string_fill()` tests whether your code handles non-empty strings where empty ones are expected.

### `string_literals()` — preset for string literals

Returns `string_empty()` and `string_fill()` together.

```r
plan <- muttest_plan(
  source_files = "R/labels.R",
  mutators = string_literals()
)
```

---

## Condition mutators

### `negate_condition()` — wrap condition in `!(...)`

```r
negate_condition()
# if (x > 0)   →   if (!(x > 0))
# while (done) →   while (!(done))
```

### `remove_condition_negation()` — strip leading `!`

```r
remove_condition_negation()
# if (!done)  →  if (done)
# if (!valid) →  if (valid)
```

### `remove_negation()` — remove `!` anywhere

```r
remove_negation()
# !is.na(x)    →  is.na(x)
# !is.null(y)  →  is.null(y)
```

💡 **When to use:** Functions with guard clauses and early returns. These mutators reveal whether your tests cover both branches of a condition. A surviving `negate_condition()` mutant means the test inputs never trigger the `FALSE` branch.

### `condition_mutations()` — preset for condition logic

Returns `negate_condition()` and `remove_condition_negation()` together.

```r
plan <- muttest_plan(
  source_files = "R/validation.R",
  mutators = condition_mutations()
)
```

---

## Function call mutator

### `call_name()` — swap one function name for another

```r
call_name("any", "all")   # any(x) → all(x)
call_name("min", "max")   # min(x) → max(x)
call_name("sum", "prod")  # sum(x) → prod(x)
```

💡 **When to use:** Functions that delegate to summary or aggregation helpers. `any` vs `all` and `min` vs `max` are among the easiest mistakes to make and the hardest to spot in a review.

---

---

## NA and NULL mutators

### `na_literal()` — swap NA/NULL values

```r
na_literal("NA", "NULL")          # NA → NULL
na_literal("NULL", "NA")          # NULL → NA
na_literal("NA", "NA_real_")      # NA → NA_real_
na_literal("NA_real_", "NA")      # NA_real_ → NA
```

💡 **When to use:** Functions that accept or return `NA` or `NULL`, especially any
code with `is.na()`, `is.null()`, or `na.rm` handling. Swapping `NA` for `NULL`
(and vice versa) reveals whether callers distinguish between "value is missing"
and "value is absent" — two distinct concepts that R treats very differently.

Swapping between typed NAs (`NA_real_`, `NA_integer_`, `NA_character_`) and
plain `NA` checks whether type-sensitive downstream code (e.g. `vapply`,
`dplyr::mutate`) is covered.

### `na_literals()` — preset for NA and NULL

Returns mutators for all common NA/NULL swaps:

| Original        | Mutant          |
| --------------- | --------------- |
| `NA`            | `NULL`          |
| `NULL`          | `NA`            |
| `NA`            | `NA_real_`      |
| `NA_real_`      | `NA`            |
| `NA`            | `NA_integer_`   |
| `NA_integer_`   | `NA`            |
| `NA`            | `NA_character_` |
| `NA_character_` | `NA`            |

```r
plan <- muttest_plan(
  source_files = "R/missing.R",
  mutators = na_literals()
)
```

---

## Return value mutators

### `replace_return_value()` — replace explicit return values

```r
replace_return_value()       # return(x) → return(NULL)
replace_return_value("NA")   # return(x) → return(NA)
```

💡 **When to use:** Any function with explicit `return()` calls. Tests that only
check that a function *runs without error* or *returns something* will not kill
these mutants — only tests that assert the specific value returned will.

A surviving `replace_return_value()` mutant means the caller never checks what
came back from that branch. This is especially common in functions with multiple
early-exit `return()` paths where only the happy path is tested.

Only explicit `return(expr)` calls are targeted; implicit returns (the last
expression of a function body) are not affected.

---

## Index and subscript mutators

### `index_increment()` — shift subscript indices up by one

```r
index_increment()   # x[i] → x[i + 1L],  x[[i]] → x[[i + 1L]]
```

### `index_decrement()` — shift subscript indices down by one

```r
index_decrement()   # x[i] → x[i - 1L],  x[[i]] → x[[i - 1L]]
```

💡 **When to use:** Functions that index into vectors or lists by position or by a
computed variable. Off-by-one errors in indexing are among the most common
silent bugs in R — they produce a different element rather than an error,
so they pass all tests unless a specific element value is asserted.

Only simple indices are mutated: plain identifiers (`x[i]`) and numeric
literals (`x[1]`, `x[1L]`). Complex expressions like `x[a + b]` or `x[seq_len(n)]`
are left untouched, keeping the mutant count focused and the signal-to-noise
ratio high.

Both single-bracket (`[`) and double-bracket (`[[`) indexing are covered.

### `index_mutations()` — preset for subscript indices

Returns `index_increment()` and `index_decrement()` together.

```r
plan <- muttest_plan(
  source_files = "R/selectors.R",
  mutators = index_mutations()
)
```

---

## Recommended starting set

If you are unsure where to begin, start here:

```r
p <- muttest_plan(
  source_files = "R/my_file.R",
  mutators = c(
    arithmetic_operators(),
    comparison_operators(),
    logical_operators(),
    condition_mutations(),
    numeric_literals(),
    list(remove_negation())
  )
)
```

Look at the survivors, then layer in `boolean_literals()`, `na_literals()`, `string_literals()`, or `index_mutations()` for the specific patterns in your code.

The better you know your codebase, the more you can target mutators to the patterns that are most likely to harbor bugs. For example, if your code has a lot of `if` statements but no numeric constants, condition mutators will be more effective than numeric ones.

You'll figure it out as you go.
