diff --git a/resources/schema.json b/resources/schema.json index bc3067d1c..85e65a5ba 100644 --- a/resources/schema.json +++ b/resources/schema.json @@ -323,7 +323,9 @@ "NewObject": { "$ref": "#/definitions/default-mutator-config" }, "This": { "$ref": "#/definitions/default-mutator-config" }, "Spaceship": { "$ref": "#/definitions/default-mutator-config" }, - "Spread": { "$ref": "#/definitions/default-mutator-config" }, + "SpreadOneItem": { "$ref": "#/definitions/default-mutator-config" }, + "SpreadAssignment": { "$ref": "#/definitions/default-mutator-config" }, + "SpreadRemoval": { "$ref": "#/definitions/default-mutator-config" }, "Foreach_": { "$ref": "#/definitions/default-mutator-config" }, "For_": { "$ref": "#/definitions/default-mutator-config" }, "DoWhile": { "$ref": "#/definitions/default-mutator-config" }, diff --git a/src/Mutator/Operator/SpreadAssignment.php b/src/Mutator/Operator/SpreadAssignment.php new file mode 100644 index 000000000..05c2b47a3 --- /dev/null +++ b/src/Mutator/Operator/SpreadAssignment.php @@ -0,0 +1,107 @@ + + */ +final class SpreadAssignment implements Mutator +{ + use GetMutatorName; + + public static function getDefinition(): ?Definition + { + return new Definition( + <<<'TXT' +Removes a spread operator in an array expression and turns it into an assignment. For example: + +```php +$x = [...$collection]; +``` + +Will be mutated to: + +```php +$x = $collection; +``` +TXT + , + MutatorCategory::SEMANTIC_REDUCTION, + null, + <<<'DIFF' +- $x = [...$collection]; ++ $x = $collection; +DIFF + ); + } + + /** + * @psalm-mutation-free + * + * @return iterable + */ + public function mutate(Node $node): iterable + { + Assert::allNotNull($node->items); + + yield $node->items[0]->value; + } + + public function canMutate(Node $node): bool + { + if (!$node instanceof Node\Expr\Array_) { + return false; + } + + if (count($node->items) !== 1) { + return false; + } + + Assert::allNotNull($node->items); + + return $node->items[0]->unpack; + } +} diff --git a/src/Mutator/Operator/Spread.php b/src/Mutator/Operator/SpreadOneItem.php similarity index 98% rename from src/Mutator/Operator/Spread.php rename to src/Mutator/Operator/SpreadOneItem.php index 42f4e6a16..a0b752d70 100644 --- a/src/Mutator/Operator/Spread.php +++ b/src/Mutator/Operator/SpreadOneItem.php @@ -46,7 +46,7 @@ * * @implements Mutator */ -final class Spread implements Mutator +final class SpreadOneItem implements Mutator { use GetMutatorName; diff --git a/src/Mutator/Operator/SpreadRemoval.php b/src/Mutator/Operator/SpreadRemoval.php new file mode 100644 index 000000000..7a92a8347 --- /dev/null +++ b/src/Mutator/Operator/SpreadRemoval.php @@ -0,0 +1,99 @@ + + */ +final class SpreadRemoval implements Mutator +{ + use GetMutatorName; + + public static function getDefinition(): ?Definition + { + return new Definition( + <<<'TXT' +Removes a spread operator in an array expression. For example: + +```php +$x = [...$collection, 4, 5]; +``` + +Will be mutated to: + +```php +$x = [$collection, 4, 5]; +``` +TXT + , + MutatorCategory::SEMANTIC_REDUCTION, + null, + <<<'DIFF' +- $x = [...$collection, 4, 5]; ++ $x = [$collection, 4, 5]; +DIFF + ); + } + + /** + * @psalm-mutation-free + * + * @return iterable + */ + public function mutate(Node $node): iterable + { + yield new Node\Expr\ArrayItem( + $node->value, + null, + false, + $node->getAttributes(), + false + ); + } + + public function canMutate(Node $node): bool + { + return $node instanceof Node\Expr\ArrayItem && $node->unpack; + } +} diff --git a/src/Mutator/ProfileList.php b/src/Mutator/ProfileList.php index 553d9f278..cb224528c 100644 --- a/src/Mutator/ProfileList.php +++ b/src/Mutator/ProfileList.php @@ -158,7 +158,9 @@ final class ProfileList Mutator\Operator\Finally_::class, Mutator\Operator\NullSafeMethodCall::class, Mutator\Operator\NullSafePropertyCall::class, - Mutator\Operator\Spread::class, + Mutator\Operator\SpreadAssignment::class, + Mutator\Operator\SpreadOneItem::class, + Mutator\Operator\SpreadRemoval::class, Mutator\Operator\Ternary::class, Mutator\Operator\Throw_::class, ]; @@ -361,7 +363,9 @@ final class ProfileList 'Finally_' => Mutator\Operator\Finally_::class, 'NullSafeMethodCall' => Mutator\Operator\NullSafeMethodCall::class, 'NullSafePropertyCall' => Mutator\Operator\NullSafePropertyCall::class, - 'Spread' => Mutator\Operator\Spread::class, + 'SpreadAssignment' => Mutator\Operator\SpreadAssignment::class, + 'SpreadOneItem' => Mutator\Operator\SpreadOneItem::class, + 'SpreadRemoval' => Mutator\Operator\SpreadRemoval::class, 'Ternary' => Mutator\Operator\Ternary::class, 'Throw_' => Mutator\Operator\Throw_::class, diff --git a/tests/phpunit/Mutator/Operator/SpreadAssignmentTest.php b/tests/phpunit/Mutator/Operator/SpreadAssignmentTest.php new file mode 100644 index 000000000..50762e768 --- /dev/null +++ b/tests/phpunit/Mutator/Operator/SpreadAssignmentTest.php @@ -0,0 +1,172 @@ += 7.4 + * @dataProvider mutationsProvider + * + * @param string|string[] $expected + */ + public function test_it_can_mutate(string $input, $expected = []): void + { + $this->doTest($input, $expected); + } + + public function mutationsProvider(): iterable + { + yield 'Spread assignment for a raw array' => [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' +getCollection()]; +PHP + , + <<<'PHP' +getCollection(); +PHP + , + ]; + + yield 'Spread assignment for a new iterator object' => [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' += 7.4 @@ -52,7 +52,7 @@ public function test_it_can_mutate(string $input, $expected = []): void public function mutationsProvider(): iterable { - yield 'Spread for a raw array' => [ + yield 'Spread one item for a raw array' => [ <<<'PHP' [ + yield 'Spread one item for a variable' => [ <<<'PHP' [ + yield 'Spread one item for a function call' => [ <<<'PHP' [ + yield 'Spread one item for a method call' => [ <<<'PHP' [ + yield 'Spread one item for a new iterator object' => [ <<<'PHP' = 7.4 + * @dataProvider mutationsProvider + * + * @param string|string[] $expected + */ + public function test_it_can_mutate(string $input, $expected = []): void + { + $this->doTest($input, $expected); + } + + public function mutationsProvider(): iterable + { + yield 'Spread removal for a raw array' => [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' + [ + <<<'PHP' +getCollection(), 4]; +PHP + , + <<<'PHP' +getCollection(), 4]; +PHP + , + ]; + + yield 'Spread removal for a new iterator object' => [ + <<<'PHP' + [ + <<<'PHP' +