diff --git a/README.md b/README.md
index 70c9938..6f67932 100644
--- a/README.md
+++ b/README.md
@@ -626,6 +626,15 @@ This set consists of various mutators from different sets. Mutators included are
+### ReturnSet
+
+
+
+- [AlwaysReturnNull](#alwaysreturnnull-) (*)
+- [AlwaysReturnEmptyArray](#alwaysreturnemptyarray-) (*)
+
+
+
### StringSet
@@ -691,6 +700,28 @@ This set consists of various mutators from different sets. Mutators included are
An alphabetical list of all mutators.
+
+### AlwaysReturnEmptyArray (*)
+Set: Return
+
+Mutates a return statement to an empty array
+
+```php
+return [1]; // [tl! remove]
+return []; // [tl! add]
+```
+
+
+### AlwaysReturnNull (*)
+Set: Return
+
+Mutates a return statement to null if it is not null
+
+```php
+return $a; // [tl! remove]
+return null; // [tl! add]
+```
+
### ArrayKeyFirstToArrayKeyLast (*)
Set: Array
diff --git a/src/Mutators.php b/src/Mutators.php
index 54350f0..1bff89a 100644
--- a/src/Mutators.php
+++ b/src/Mutators.php
@@ -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;
@@ -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;
@@ -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;
@@ -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;
diff --git a/src/Mutators/Return/AlwaysReturnEmptyArray.php b/src/Mutators/Return/AlwaysReturnEmptyArray.php
new file mode 100644
index 0000000..123d1cc
--- /dev/null
+++ b/src/Mutators/Return/AlwaysReturnEmptyArray.php
@@ -0,0 +1,57 @@
+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;
+ }
+}
diff --git a/src/Mutators/Return/AlwaysReturnNull.php b/src/Mutators/Return/AlwaysReturnNull.php
new file mode 100644
index 0000000..01713d8
--- /dev/null
+++ b/src/Mutators/Return/AlwaysReturnNull.php
@@ -0,0 +1,57 @@
+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;
+ }
+}
diff --git a/src/Mutators/Sets/DefaultSet.php b/src/Mutators/Sets/DefaultSet.php
index c57994f..06a409f 100644
--- a/src/Mutators/Sets/DefaultSet.php
+++ b/src/Mutators/Sets/DefaultSet.php
@@ -26,6 +26,7 @@ public static function mutators(): array
...LogicalSet::mutators(),
...MathSet::mutators(),
...NumberSet::mutators(),
+ ...ReturnSet::mutators(),
...StringSet::mutators(),
...VisibilitySet::defaultMutators(),
];
diff --git a/src/Mutators/Sets/ReturnSet.php b/src/Mutators/Sets/ReturnSet.php
new file mode 100644
index 0000000..785df66
--- /dev/null
+++ b/src/Mutators/Sets/ReturnSet.php
@@ -0,0 +1,26 @@
+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);
}
}
@@ -29,6 +37,10 @@ public function leaveNode(Node $node)
$newStmts = $traverser->traverse($stmts);
+ if ($mutationCount === 0) {
+ throw new Exception('No mutation performed');
+ }
+
$prettyPrinter = new Standard();
return $prettyPrinter->prettyPrintFile($newStmts);
diff --git a/tests/Mutators/Abstract/AbstractFunctionCallReplaceMutatorTest.php b/tests/Mutators/Abstract/AbstractFunctionCallReplaceMutatorTest.php
index d4fe99d..23fddd5 100644
--- a/tests/Mutators/Abstract/AbstractFunctionCallReplaceMutatorTest.php
+++ b/tests/Mutators/Abstract/AbstractFunctionCallReplaceMutatorTest.php
@@ -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'
min)(1, 2);
- CODE))->toBe(<<<'CODE'
- 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'))))
diff --git a/tests/Mutators/Abstract/AbstractFunctionCallUnwrapMutatorTest.php b/tests/Mutators/Abstract/AbstractFunctionCallUnwrapMutatorTest.php
index 552b806..71c8913 100644
--- a/tests/Mutators/Abstract/AbstractFunctionCallUnwrapMutatorTest.php
+++ b/tests/Mutators/Abstract/AbstractFunctionCallUnwrapMutatorTest.php
@@ -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'
chop)('foo');
- CODE))->toBe(<<<'CODE'
- 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'))))
diff --git a/tests/Mutators/ControlStructures/DoWhileAlwaysFalseTest.php b/tests/Mutators/ControlStructures/DoWhileAlwaysFalseTest.php
index 222b835..290d773 100644
--- a/tests/Mutators/ControlStructures/DoWhileAlwaysFalseTest.php
+++ b/tests/Mutators/ControlStructures/DoWhileAlwaysFalseTest.php
@@ -21,17 +21,11 @@
});
it('does not mutate other statements', function (): void {
- expect(mutateCode(DoWhileAlwaysFalse::class, <<<'CODE'
- $b) {
- $b++;
- }
- CODE))->toBe(<<<'CODE'
+ mutateCode(DoWhileAlwaysFalse::class, <<<'CODE'
$b) {
$b++;
}
CODE);
-});
+})->expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/ControlStructures/ForAlwaysFalseTest.php b/tests/Mutators/ControlStructures/ForAlwaysFalseTest.php
index 6f7d39a..9f4e28e 100644
--- a/tests/Mutators/ControlStructures/ForAlwaysFalseTest.php
+++ b/tests/Mutators/ControlStructures/ForAlwaysFalseTest.php
@@ -21,17 +21,11 @@
});
it('does not mutate other statements', function (): void {
- expect(mutateCode(ForAlwaysFalse::class, <<<'CODE'
- $b) {
- $b++;
- }
- CODE))->toBe(<<<'CODE'
+ mutateCode(ForAlwaysFalse::class, <<<'CODE'
$b) {
$b++;
}
CODE);
-});
+})->expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/ControlStructures/ForeachEmptyIterableTest.php b/tests/Mutators/ControlStructures/ForeachEmptyIterableTest.php
index ddc17ef..6d88a1c 100644
--- a/tests/Mutators/ControlStructures/ForeachEmptyIterableTest.php
+++ b/tests/Mutators/ControlStructures/ForeachEmptyIterableTest.php
@@ -21,17 +21,11 @@
});
it('does not mutate other statements', function (): void {
- expect(mutateCode(ForeachEmptyIterable::class, <<<'CODE'
- $b) {
- $b++;
- }
- CODE))->toBe(<<<'CODE'
+ mutateCode(ForeachEmptyIterable::class, <<<'CODE'
$b) {
$b++;
}
CODE);
-});
+})->expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/ControlStructures/WhileAlwaysFalseTest.php b/tests/Mutators/ControlStructures/WhileAlwaysFalseTest.php
index 7e58dd5..0e2980d 100644
--- a/tests/Mutators/ControlStructures/WhileAlwaysFalseTest.php
+++ b/tests/Mutators/ControlStructures/WhileAlwaysFalseTest.php
@@ -21,17 +21,11 @@
});
it('does not mutate other statements', function (): void {
- expect(mutateCode(WhileAlwaysFalse::class, <<<'CODE'
- $b) {
- $b++;
- }
- CODE))->toBe(<<<'CODE'
+ mutateCode(WhileAlwaysFalse::class, <<<'CODE'
$b) {
$b++;
}
CODE);
-});
+})->expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/Equality/SpaceshipSwitchSidesTest.php b/tests/Mutators/Equality/SpaceshipSwitchSidesTest.php
index 573cde4..8aabc75 100644
--- a/tests/Mutators/Equality/SpaceshipSwitchSidesTest.php
+++ b/tests/Mutators/Equality/SpaceshipSwitchSidesTest.php
@@ -16,13 +16,9 @@
});
it('does not mutate other operators', function (): void {
- expect(mutateCode(SpaceshipSwitchSides::class, <<<'CODE'
+ mutateCode(SpaceshipSwitchSides::class, <<<'CODE'
toBe(<<<'CODE'
- expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/Laravel/Remove/RemoveStringableUpperTest.php b/tests/Mutators/Laravel/Remove/RemoveStringableUpperTest.php
index c58165e..c45bc89 100644
--- a/tests/Mutators/Laravel/Remove/RemoveStringableUpperTest.php
+++ b/tests/Mutators/Laravel/Remove/RemoveStringableUpperTest.php
@@ -56,13 +56,9 @@
});
it('does not mutate if the str method is called without a parameter', function (): void {
- expect(mutateCode(LaravelRemoveStringableUpper::class, <<<'CODE'
- upper();
- CODE))->toBe(<<<'CODE'
+ mutateCode(LaravelRemoveStringableUpper::class, <<<'CODE'
upper();
CODE);
-});
+})->expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/Logical/TrueToFalseTest.php b/tests/Mutators/Logical/TrueToFalseTest.php
index 5584f9a..7b69001 100644
--- a/tests/Mutators/Logical/TrueToFalseTest.php
+++ b/tests/Mutators/Logical/TrueToFalseTest.php
@@ -29,40 +29,28 @@
});
it('does not mutate anything else', function (): void {
- expect(mutateCode(TrueToFalse::class, <<<'CODE'
+ mutateCode(TrueToFalse::class, <<<'CODE'
toBe(<<<'CODE'
- expectExceptionMessage('No mutation performed');
it('does not mutate if true is the third parameter of in_array', function (): void {
- expect(mutateCode(TrueToFalse::class, <<<'CODE'
+ mutateCode(TrueToFalse::class, <<<'CODE'
toBe(<<<'CODE'
- expectExceptionMessage('No mutation performed');
it('does not mutate if true is the third parameter of array_search', function (): void {
- expect(mutateCode(TrueToFalse::class, <<<'CODE'
+ mutateCode(TrueToFalse::class, <<<'CODE'
toBe(<<<'CODE'
- expectExceptionMessage('No mutation performed');
it('mutates on expression function calls', function (): void {
expect(mutateCode(TrueToFalse::class, <<<'CODE'
diff --git a/tests/Mutators/Number/DecrementIntegerTest.php b/tests/Mutators/Number/DecrementIntegerTest.php
index 6d02197..85d2e7e 100644
--- a/tests/Mutators/Number/DecrementIntegerTest.php
+++ b/tests/Mutators/Number/DecrementIntegerTest.php
@@ -28,25 +28,17 @@
});
it('does not mutate declare strict types 1', function (): void {
- expect(mutateCode(DecrementInteger::class, <<<'CODE'
- toBe(<<<'CODE'
+ mutateCode(DecrementInteger::class, <<<'CODE'
expectExceptionMessage('No mutation performed');
it('does not mutate int min', function (): void {
- expect(mutateCode(DecrementInteger::class, <<<'CODE'
- toBe(<<<'CODE'
+ mutateCode(DecrementInteger::class, <<<'CODE'
expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/Number/IncrementIntegerTest.php b/tests/Mutators/Number/IncrementIntegerTest.php
index bddde30..2b23411 100644
--- a/tests/Mutators/Number/IncrementIntegerTest.php
+++ b/tests/Mutators/Number/IncrementIntegerTest.php
@@ -28,25 +28,17 @@
});
it('does not mutate declare strict types 1', function (): void {
- expect(mutateCode(IncrementInteger::class, <<<'CODE'
- toBe(<<<'CODE'
+ mutateCode(IncrementInteger::class, <<<'CODE'
expectExceptionMessage('No mutation performed');
it('does not mutate int max', function (): void {
- expect(mutateCode(IncrementInteger::class, <<<'CODE'
- toBe(<<<'CODE'
+ mutateCode(IncrementInteger::class, <<<'CODE'
expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/Return/AlwaysReturnEmptyArrayTest.php b/tests/Mutators/Return/AlwaysReturnEmptyArrayTest.php
new file mode 100644
index 0000000..f9a94c4
--- /dev/null
+++ b/tests/Mutators/Return/AlwaysReturnEmptyArrayTest.php
@@ -0,0 +1,34 @@
+toBe(<<<'CODE'
+ expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/Return/AlwaysReturnNullTest.php b/tests/Mutators/Return/AlwaysReturnNullTest.php
new file mode 100644
index 0000000..b1803f3
--- /dev/null
+++ b/tests/Mutators/Return/AlwaysReturnNullTest.php
@@ -0,0 +1,63 @@
+toBe(<<<'CODE'
+ toBe(<<<'CODE'
+ expectExceptionMessage('No mutation performed');
+
+it('does not mutate if null is not a valid return type', function (): void {
+ mutateCode(AlwaysReturnNull::class, <<<'CODE'
+ expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/String/ConcatSwitchSidesTest.php b/tests/Mutators/String/ConcatSwitchSidesTest.php
index 5ed08a5..2912543 100644
--- a/tests/Mutators/String/ConcatSwitchSidesTest.php
+++ b/tests/Mutators/String/ConcatSwitchSidesTest.php
@@ -17,13 +17,9 @@
});
it('does not mutate other operators', function (): void {
- expect(mutateCode(ConcatSwitchSides::class, <<<'CODE'
+ mutateCode(ConcatSwitchSides::class, <<<'CODE'
toBe(<<<'CODE'
- expectExceptionMessage('No mutation performed');
diff --git a/tests/Mutators/String/EmptyStringToNotEmptyTest.php b/tests/Mutators/String/EmptyStringToNotEmptyTest.php
index 0a24e05..e695620 100644
--- a/tests/Mutators/String/EmptyStringToNotEmptyTest.php
+++ b/tests/Mutators/String/EmptyStringToNotEmptyTest.php
@@ -17,28 +17,20 @@
});
it('does not mutate a not empty string', function (): void {
- expect(mutateCode(EmptyStringToNotEmpty::class, <<<'CODE'
+ mutateCode(EmptyStringToNotEmpty::class, <<<'CODE'
toBe(<<<'CODE'
- expectExceptionMessage('No mutation performed');
it('does not mutate a not string element', function (): void {
- expect(mutateCode(EmptyStringToNotEmpty::class, <<<'CODE'
+ mutateCode(EmptyStringToNotEmpty::class, <<<'CODE'
toBe(<<<'CODE'
- expectExceptionMessage('No mutation performed');
it('can not mutate non string nodes', function (): void {
expect(EmptyStringToNotEmpty::can(new InlineHTML('')))