Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Support/MutationGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public function generate(
$mutatorsToIgnoreByLine = [];
foreach (explode(PHP_EOL, $contents) as $lineNumber => $line) {
if (str_contains($line, '@pest-mutate-ignore')) {
$mutatorsToIgnore = null;

if (Str::after($line, '@pest-mutate-ignore:') !== $line) {
$mutatorsToIgnore = explode(',', Str::after($line, '@pest-mutate-ignore:'));
$mutatorsToIgnore = array_map(trim(...), $mutatorsToIgnore);
Expand Down
6 changes: 4 additions & 2 deletions src/Support/NodeVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ public function enterNode(Node $node): Node|int|null
foreach ($node->getAttribute('comments') as $comment) { // @phpstan-ignore-line
preg_match('/@pest-mutate-ignore(.*)/', (string) $comment->getText(), $matches); // @phpstan-ignore-line
if ($matches !== []) {
if ($matches[1] === '') {
$mutatorsToIgnore = trim($matches[1], " \t\n\r*/:");

if ($mutatorsToIgnore === '') {
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}

if (in_array($this->mutator::name(), array_map(fn (string $mutatorToIgnore): string => trim($mutatorToIgnore, ' */:'), explode(',', $matches[1])), true)) {
if (in_array($this->mutator::name(), array_map(fn (string $mutatorToIgnore): string => trim($mutatorToIgnore, ' */:'), explode(',', $mutatorsToIgnore)), true)) {
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
}
}
Expand Down
61 changes: 61 additions & 0 deletions tests/Unit/MutationGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,64 @@ function test() {
expect($mutations)
->toBeEmpty();
});

it('ignores all mutators for a statement when the bare annotation has trailing whitespace', function (): void {
// The trailing space is built explicitly so a formatter cannot strip it from the fixture.
$mutations = ($this->generate)(
"<?php\n\n// @pest-mutate-ignore \nreturn \$a >= \$b;\n",
[GreaterOrEqualToGreater::class],
);

expect($mutations)
->toBeEmpty();
});

it('ignores all mutators for a function with a bare single line doc block', function (): void {
$mutations = ($this->generate)(<<<'PHP'
<?php

/** @pest-mutate-ignore */
function test() {
if($a >= $b) {
return $a = $b;
}
}
PHP,
[GreaterOrEqualToGreater::class, IfNegated::class],
);

expect($mutations)
->toBeEmpty();
});

it('does not carry a mutator list over to a later bare annotation', function (): void {
$mutations = ($this->generate)(<<<'PHP'
<?php

if($a > $b) { // @pest-mutate-ignore: IfNegated
$a = $b;
}

return $a >= $b; // @pest-mutate-ignore
PHP,
[GreaterOrEqualToGreater::class, IfNegated::class],
);

expect($mutations)
->toBeEmpty();
});

it('does not ignore a mutator that is not named', function (string $annotation): void {
$mutations = ($this->generate)(
"<?php\n\n{$annotation}\nreturn \$a >= \$b;\n",
[GreaterOrEqualToGreater::class],
);

expect($mutations)
->toHaveCount(1)
->{0}->mutator->toBe(GreaterOrEqualToGreater::class);
})->with([
'unknown name' => ['// @pest-mutate-ignore: NotAMutator'],
'unknown name with trailing whitespace' => ['// @pest-mutate-ignore: NotAMutator '],
'unknown name in a doc block' => ['/** @pest-mutate-ignore: NotAMutator */'],
]);