Skip to content

fix: honor an ignore marker on the annotated statement - #51

Open
joshdaugherty wants to merge 1 commit into
pestphp:5.xfrom
joshdaugherty:fix-ignore-annotated-statement
Open

joshdaugherty wants to merge 1 commit into
pestphp:5.xfrom
joshdaugherty:fix-ignore-annotated-statement

Conversation

@joshdaugherty

Copy link
Copy Markdown
Contributor

What:

  • Bug Fix
  • New Feature

Description:

Fixes pestphp/pest#1993.

A // @pest-mutate-ignore: <Mutator> comment on the line above a statement does not suppress that mutator when the mutator changes the statement itself. For example:

// @pest-mutate-ignore: RemoveFunctionCall
ksort($a);

still produces a RemoveFunctionCall mutation for ksort(). A marker naming a mutator that changes something inside the statement (such as a comparison in a for loop) already works.

Cause: when a node's comment carries a matching marker, NodeVisitor::enterNode() returns NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN. nikic/php-parser skips the node's children for that value but still calls leaveNode() on the node itself, and leaveNode() is where the mutation is applied. RemoveFunctionCall and RemoveMethodCall target Stmt\Expression and RemoveEarlyReturn targets Stmt\Return_, the node the comment is attached to, so they are not suppressed.

Fix: enterNode() records the node (by spl_object_id()) before returning DONT_TRAVERSE_CURRENT_AND_CHILDREN, and leaveNode() returns early for a recorded node. A new NodeVisitor is created for each traversal, so the record does not carry over between mutators or offsets.

Tests added to tests/Unit/MutationGeneratorTest.php:

  • a statement with a marker naming the mutator: no mutation;
  • a statement with a bare marker: no mutation;
  • an early return with a marker naming RemoveEarlyReturn: no mutation;
  • a statement with a marker naming a different mutator: still mutated;
  • the statement after an annotated one: still mutated;
  • a statement with no marker: mutated (control).

Without the change to NodeVisitor, the first three and the fifth fail; with it, all six pass.

CI: this repository's workflows install with the branch name as the root version, which does not satisfy pestphp/pest's ^5.0.1 requirement on this package, so a fork branch fails at Install Dependencies. Run on the fork with COMPOSER_ROOT_VERSION=5.x-dev set, this branch and unchanged 5.x give the same result: on PHP 8.4 with prefer-stable, the same three Tests\Visual snapshot tests fail on both (316 passed on 5.x, 322 with this change), and the PHPStan step reports the same two errors on both, in files this change does not touch. The prefer-lowest jobs that ran pass on both.

This touches the same two return statements in enterNode() as #46, so whichever merges second needs a small rebase.

- `NodeVisitor::enterNode()` returns `DONT_TRAVERSE_CURRENT_AND_CHILDREN` for a node whose comment carries a matching `@pest-mutate-ignore` marker, but `nikic/php-parser` still calls `leaveNode()` on that node, so a mutator that targets the statement itself (`RemoveFunctionCall`, `RemoveMethodCall`, `RemoveEarlyReturn`) still mutated it
- remember such nodes by object id in `enterNode()` and skip them in `leaveNode()`
- add tests for a statement with a specific marker, a bare marker, an early return, a marker naming another mutator, and the statement after an annotated one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: [mutate] A @pest-mutate-ignore marker above a statement does not suppress a mutator that targets the statement

1 participant