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('')))