Skip to content

Commit

Permalink
Add Return Mutators
Browse files Browse the repository at this point in the history
  • Loading branch information
gehrisandro committed Nov 17, 2023
1 parent cb49c6a commit c1639d9
Show file tree
Hide file tree
Showing 23 changed files with 332 additions and 119 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,15 @@ This set consists of various mutators from different sets. Mutators included are

</div>

### ReturnSet

<div class="collection-method-list" markdown="1">

- [AlwaysReturnNull](#alwaysreturnnull-) (*)
- [AlwaysReturnEmptyArray](#alwaysreturnemptyarray-) (*)

</div>

### StringSet

<div class="collection-method-list" markdown="1">
Expand Down Expand Up @@ -691,6 +700,28 @@ This set consists of various mutators from different sets. Mutators included are

An alphabetical list of all mutators.

<a name="alwaysreturnemptyarray"></a>
### AlwaysReturnEmptyArray (*)
Set: Return

Mutates a return statement to an empty array

```php
return [1]; // [tl! remove]
return []; // [tl! add]
```

<a name="alwaysreturnnull"></a>
### AlwaysReturnNull (*)
Set: Return

Mutates a return statement to null if it is not null

```php
return $a; // [tl! remove]
return null; // [tl! add]
```

<a name="arraykeyfirsttoarraykeylast"></a>
### ArrayKeyFirstToArrayKeyLast (*)
Set: Array
Expand Down
10 changes: 10 additions & 0 deletions src/Mutators.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
use Pest\Mutate\Mutators\Number\DecrementInteger;
use Pest\Mutate\Mutators\Number\IncrementFloat;
use Pest\Mutate\Mutators\Number\IncrementInteger;
use Pest\Mutate\Mutators\Return\AlwaysReturnEmptyArray;
use Pest\Mutate\Mutators\Return\AlwaysReturnNull;
use Pest\Mutate\Mutators\Sets\ArithmeticSet;
use Pest\Mutate\Mutators\Sets\ArraySet;
use Pest\Mutate\Mutators\Sets\AssignmentSet;
Expand All @@ -137,6 +139,7 @@
use Pest\Mutate\Mutators\Sets\LogicalSet;
use Pest\Mutate\Mutators\Sets\MathSet;
use Pest\Mutate\Mutators\Sets\NumberSet;
use Pest\Mutate\Mutators\Sets\ReturnSet;
use Pest\Mutate\Mutators\Sets\StringSet;
use Pest\Mutate\Mutators\Sets\VisibilitySet;
use Pest\Mutate\Mutators\String\ConcatRemoveLeft;
Expand Down Expand Up @@ -201,6 +204,8 @@ class Mutators

final public const SET_NUMBER = NumberSet::class;

final public const SET_RETURN = ReturnSet::class;

final public const SET_STRING = StringSet::class;

final public const SET_VISIBILITY = VisibilitySet::class;
Expand Down Expand Up @@ -466,6 +471,11 @@ class Mutators

final public const NUMBER_INCREMENT_INTEGER = IncrementInteger::class;

/** Return */
final public const RETURN_ALWAYS_RETURN_EMPTY_ARRAY = AlwaysReturnEmptyArray::class;

final public const RETURN_ALWAYS_RETURN_NULL = AlwaysReturnNull::class;

/** String */
final public const STRING_CONCAT_REMOVE_LEFT = ConcatRemoveLeft::class;

Expand Down
57 changes: 57 additions & 0 deletions src/Mutators/Return/AlwaysReturnEmptyArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Pest\Mutate\Mutators\Return;

use Pest\Mutate\Mutators\Abstract\AbstractMutator;
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;

class AlwaysReturnEmptyArray extends AbstractMutator
{
public const SET = 'Return';

public const DESCRIPTION = 'Mutates a return statement to an empty array';

public const DIFF = <<<'DIFF'
return [1]; // [tl! remove]
return []; // [tl! add]
DIFF;

public static function nodesToHandle(): array
{
return [Return_::class];
}

public static function can(Node $node): bool
{
if (! $node instanceof Return_) {
return false;
}

$parent = $node->getAttribute('parent');

if (! $parent instanceof Function_) {
return false;
}

if ($node->expr instanceof Array_ && $node->expr->items === []) {
return false;
}

return $parent->returnType instanceof Identifier &&
$parent->returnType->name === 'array';
}

public static function mutate(Node $node): Node
{
/** @var Return_ $node */
$node->expr->items = []; // @phpstan-ignore-line

return $node;
}
}
57 changes: 57 additions & 0 deletions src/Mutators/Return/AlwaysReturnNull.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Pest\Mutate\Mutators\Return;

use Pest\Mutate\Mutators\Abstract\AbstractMutator;
use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Return_;

class AlwaysReturnNull extends AbstractMutator
{
public const SET = 'Return';

public const DESCRIPTION = 'Mutates a return statement to null if it is not null';

public const DIFF = <<<'DIFF'
return $a; // [tl! remove]
return null; // [tl! add]
DIFF;

public static function nodesToHandle(): array
{
return [Return_::class];
}

public static function can(Node $node): bool
{
if (! $node instanceof Return_) {
return false;
}

$parent = $node->getAttribute('parent');

if (! $parent instanceof Function_) {
return false;
}

if ($node->expr instanceof ConstFetch && $node->expr->name->parts[0] === 'null') {
return false;
}

return $parent->returnType === null ||
$parent->returnType->getType() === 'NullableType';
}

public static function mutate(Node $node): Node
{
/** @var Return_ $node */
$node->expr = new ConstFetch(new Name('null'));

return $node;
}
}
1 change: 1 addition & 0 deletions src/Mutators/Sets/DefaultSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static function mutators(): array
...LogicalSet::mutators(),
...MathSet::mutators(),
...NumberSet::mutators(),
...ReturnSet::mutators(),
...StringSet::mutators(),
...VisibilitySet::defaultMutators(),
];
Expand Down
26 changes: 26 additions & 0 deletions src/Mutators/Sets/ReturnSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Pest\Mutate\Mutators\Sets;

use Pest\Mutate\Contracts\MutatorSet;
use Pest\Mutate\Mutators\Concerns\HasName;
use Pest\Mutate\Mutators\Return\AlwaysReturnEmptyArray;
use Pest\Mutate\Mutators\Return\AlwaysReturnNull;

class ReturnSet implements MutatorSet
{
use HasName;

/**
* {@inheritDoc}
*/
public static function mutators(): array
{
return [
AlwaysReturnNull::class,
AlwaysReturnEmptyArray::class,
];
}
}
2 changes: 2 additions & 0 deletions src/Support/MutatorMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Pest\Mutate\Mutators\Sets\LogicalSet;
use Pest\Mutate\Mutators\Sets\MathSet;
use Pest\Mutate\Mutators\Sets\NumberSet;
use Pest\Mutate\Mutators\Sets\ReturnSet;
use Pest\Mutate\Mutators\Sets\StringSet;
use Pest\Mutate\Mutators\Sets\VisibilitySet;

Expand Down Expand Up @@ -45,6 +46,7 @@ public static function get(): array
...LaravelSet::mutators(),
...MathSet::mutators(),
...NumberSet::mutators(),
...ReturnSet::mutators(),
...StringSet::mutators(),
...VisibilitySet::mutators(),
];
Expand Down
18 changes: 15 additions & 3 deletions tests/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,35 @@ function mutateCode(string $mutator, string $code): string
{
$stmts = (new ParserFactory)->create(ParserFactory::PREFER_PHP7)->parse($code);

$mutationCount = 0;

$traverser = NodeTraverserFactory::create();
$traverser->addVisitor(new class($mutator) extends NodeVisitorAbstract
$traverser->addVisitor(new class($mutator, function () use (&$mutationCount): void {
$mutationCount++;
}) extends NodeVisitorAbstract
{
public function __construct(private readonly string $mutator)
{
public function __construct(
private readonly string $mutator,
private $incrementMutationCount,
) {
}

public function leaveNode(Node $node)
{
if ($this->mutator::can($node)) {
($this->incrementMutationCount)();

return $this->mutator::mutate($node);
}
}
});

$newStmts = $traverser->traverse($stmts);

if ($mutationCount === 0) {
throw new Exception('No mutation performed');
}

$prettyPrinter = new Standard();

return $prettyPrinter->prettyPrintFile($newStmts);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@
use PhpParser\Node\Name;

it('does not replace expression function calls', function (): void {
expect(mutateCode(MinToMax::class, <<<'CODE'
mutateCode(MinToMax::class, <<<'CODE'
<?php
$a = ($this->min)(1, 2);
CODE))->toBe(<<<'CODE'
<?php
$a = ($this->min)(1, 2);
CODE);
});
})->expectExceptionMessage('No mutation performed');

it('can not mutate non function call nodes', function (): void {
expect(MinToMax::can(new Attribute(new Name('min'))))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@
use PhpParser\Node\Name;

it('does not unwrap expression function calls', function (): void {
expect(mutateCode(UnwrapChop::class, <<<'CODE'
mutateCode(UnwrapChop::class, <<<'CODE'
<?php
$a = ($this->chop)('foo');
CODE))->toBe(<<<'CODE'
<?php
$a = ($this->chop)('foo');
CODE);
});
})->expectExceptionMessage('No mutation performed');

it('can not mutate non function call nodes', function (): void {
expect(UnwrapChop::can(new Attribute(new Name('chop'))))
Expand Down
10 changes: 2 additions & 8 deletions tests/Mutators/ControlStructures/DoWhileAlwaysFalseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,11 @@
});

it('does not mutate other statements', function (): void {
expect(mutateCode(DoWhileAlwaysFalse::class, <<<'CODE'
<?php
if ($a > $b) {
$b++;
}
CODE))->toBe(<<<'CODE'
mutateCode(DoWhileAlwaysFalse::class, <<<'CODE'
<?php
if ($a > $b) {
$b++;
}
CODE);
});
})->expectExceptionMessage('No mutation performed');
10 changes: 2 additions & 8 deletions tests/Mutators/ControlStructures/ForAlwaysFalseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,11 @@
});

it('does not mutate other statements', function (): void {
expect(mutateCode(ForAlwaysFalse::class, <<<'CODE'
<?php
if ($a > $b) {
$b++;
}
CODE))->toBe(<<<'CODE'
mutateCode(ForAlwaysFalse::class, <<<'CODE'
<?php
if ($a > $b) {
$b++;
}
CODE);
});
})->expectExceptionMessage('No mutation performed');
10 changes: 2 additions & 8 deletions tests/Mutators/ControlStructures/ForeachEmptyIterableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,11 @@
});

it('does not mutate other statements', function (): void {
expect(mutateCode(ForeachEmptyIterable::class, <<<'CODE'
<?php
if ($a > $b) {
$b++;
}
CODE))->toBe(<<<'CODE'
mutateCode(ForeachEmptyIterable::class, <<<'CODE'
<?php
if ($a > $b) {
$b++;
}
CODE);
});
})->expectExceptionMessage('No mutation performed');
Loading

0 comments on commit c1639d9

Please sign in to comment.