Appearance
date: 2026-06-25 tags: [mutation-testing, pest-mutate, footgun] status: graduated graduated_to: CLAUDE.md # Mutation Testing Policy
A targeted @pest-mutate-ignore silently no-ops on trailing prose or the wrong line
Symptom — the diff-scoped mutation gate on PR #783 stalled at 94.7% despite an @pest-mutate-ignore: <Mutator> on every equivalent mutant. The ignored mutants kept showing as survivors; no error, no warning.
Root cause — two separate placement traps, both in pest-mutate's parser:
- Exact-string match.
NodeVisitorcompares$ignore === $mutator::name()afterexplode(',')+trim. A directive like// @pest-mutate-ignore: RemoveArrayItem — checked defaults to falseyields the string"RemoveArrayItem — checked defaults to false", which!== "RemoveArrayItem"→ the ignore is parsed but never matches. - Leading vs trailing. A leading comment is only honoured when PHP-Parser attaches it to the mutated node — which happens for whole statements, not array-item nodes. For a
RemoveArrayItem(acasts()entry, acreate([...])key, a validation-rule element) the leading form no-ops; the directive must be a trailing same-line comment, which pest-mutate catches via its line-map (mutatorsToIgnoreByLine[node->getStartLine()], keyed on the comment's own line).
Fix — clean, correctly-placed directives across the packing files (commit d8f4097): mutator name(s) only, prose on a preceding line, trailing for array-item mutants. Took the gate 94.7% → 100%.
Guard — graduated into CLAUDE.md's Mutation Testing Policy (the @pest-mutate-ignore paragraph), which now states the exact-match rule and the leading-statement / trailing-array-item placement. The old "must be on its own preceding line" claim there was wrong for array-item mutants and has been corrected.