diff --git a/resources/infection.json5 b/resources/infection.json5 index c26e837..70d7195 100644 --- a/resources/infection.json5 +++ b/resources/infection.json5 @@ -13,7 +13,8 @@ "mutators": { "@default": false, "PHPStan\\Infection\\LooseBooleanMutator": true, - "PHPStan\\Infection\\TrinaryLogicMutator": true + "PHPStan\\Infection\\TrinaryLogicMutator": true, + "PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true }, "bootstrap": "build-infection/vendor/autoload.php" } diff --git a/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php b/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php new file mode 100644 index 0000000..a2aaecd --- /dev/null +++ b/src/Infection/IsSuperTypeOfCalleeAndArgumentMutator.php @@ -0,0 +1,96 @@ + + */ +final class IsSuperTypeOfCalleeAndArgumentMutator implements Mutator +{ + + public static function getDefinition(): Definition + { + return new Definition( + <<<'TXT' + Replaces the callee and the argument of a isSuperTypeOf() method call. + TXT + , + MutatorCategory::ORTHOGONAL_REPLACEMENT, + null, + <<<'DIFF' + - $a->isSuperTypeOf($b); + + $b->isSuperTypeOf($a); + DIFF, + ); + } + + public function getName(): string + { + return self::class; + } + + public function canMutate(Node $node): bool + { + if (!$node instanceof Node\Expr\MethodCall) { + return false; + } + + if ( + !$node->var instanceof Node\Expr\Variable + && !$node->var instanceof Node\Expr\PropertyFetch + && !$node->var instanceof Node\Expr\MethodCall + && !$node->var instanceof Node\Expr\StaticCall + && !$node->var instanceof Node\Expr\New_ + ) { + return false; + } + + if (!$node->name instanceof Node\Identifier) { + return false; + } + + if (!in_array($node->name->name, ['isSuperTypeOf'], true)) { + return false; + } + + $args = $node->getArgs(); + if (count($args) !== 1) { + return false; + } + + if ( + !$args[0]->value instanceof Node\Expr\Variable + && !$args[0]->value instanceof Node\Expr\PropertyFetch + && !$args[0]->value instanceof Node\Expr\MethodCall + && !$args[0]->value instanceof Node\Expr\StaticCall + && !$args[0]->value instanceof Node\Expr\New_ + ) { + return false; + } + + return true; + } + + public function mutate(Node $node): iterable + { + $args = $node->getArgs(); + if (count($args) !== 1) { + throw new RuntimeException(); + } + + yield new Node\Expr\MethodCall( + $args[0]->value, + $node->name, + [new Node\Arg($node->var)], + ); + } + +} diff --git a/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php new file mode 100644 index 0000000..3c361c0 --- /dev/null +++ b/tests/Infection/IsSuperTypeOfCalleeAndArgumentMutatorTest.php @@ -0,0 +1,186 @@ +assertMutatesInput($input, $expected); + } + + /** + * @return iterable + */ + public static function mutationsProvider(): iterable + { + yield 'It mutates isSuperTypeOf' => [ + <<<'PHP' + isSuperTypeOf($b); + PHP +, + <<<'PHP' + isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with property fetch' => [ + <<<'PHP' + a->isSuperTypeOf($b); + PHP +, + <<<'PHP' + isSuperTypeOf($this->a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with property fetch (reversed)' => [ + <<<'PHP' + isSuperTypeOf($this->b); + PHP +, + <<<'PHP' + b->isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with method-call' => [ + <<<'PHP' + isSuperTypeOf($this->call()); + PHP +, + <<<'PHP' + call()->isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with method-call (reversed)' => [ + <<<'PHP' + call()->isSuperTypeOf($a); + PHP +, + <<<'PHP' + isSuperTypeOf($this->call()); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with static method call' => [ + <<<'PHP' + isSuperTypeOf($a); + PHP +, + <<<'PHP' + isSuperTypeOf(IntegerRangeType::fromInterval(0, null)); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with static method call (reversed)' => [ + <<<'PHP' + isSuperTypeOf(IntegerRangeType::fromInterval(0, null)); + PHP +, + <<<'PHP' + isSuperTypeOf($a); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with new' => [ + <<<'PHP' + isSuperTypeOf($delimiterType); + PHP +, + <<<'PHP' + isSuperTypeOf(new ConstantStringType('')); + PHP +, + ]; + + yield 'It mutates isSuperTypeOf with new (reversed)' => [ + <<<'PHP' + isSuperTypeOf(new ConstantStringType('')); + PHP +, + <<<'PHP' + isSuperTypeOf($delimiterType); + PHP +, + ]; + + yield 'skip isSuperTypeOf with more arguments' => [ + <<<'PHP' + isSuperTypeOf($b, $c); + PHP +, + ]; + + yield 'skip other method calls' => [ + <<<'PHP' + isConstantValue($b); + PHP +, + ]; + } + + protected function getTestedMutatorClassName(): string + { + return IsSuperTypeOfCalleeAndArgumentMutator::class; + } + +} diff --git a/tests/phpt/infection-config-default.phpt b/tests/phpt/infection-config-default.phpt index ddb44db..aff41c7 100644 --- a/tests/phpt/infection-config-default.phpt +++ b/tests/phpt/infection-config-default.phpt @@ -20,7 +20,8 @@ echo shell_exec($bin); "mutators": { "@default": false, "PHPStan\\Infection\\LooseBooleanMutator": true, - "PHPStan\\Infection\\TrinaryLogicMutator": true + "PHPStan\\Infection\\TrinaryLogicMutator": true, + "PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true }, "bootstrap": "build-infection\/vendor\/autoload.php" } diff --git a/tests/phpt/infection-config.phpt b/tests/phpt/infection-config.phpt index 99db7ff..b6cbd04 100644 --- a/tests/phpt/infection-config.phpt +++ b/tests/phpt/infection-config.phpt @@ -22,6 +22,7 @@ echo shell_exec($bin." --source-directory='more/files/' --timeout=180 --mutator- "@default": false, "PHPStan\\Infection\\LooseBooleanMutator": true, "PHPStan\\Infection\\TrinaryLogicMutator": true, + "PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true, "My\\Class": true }, "bootstrap": "build-infection\/vendor\/autoload.php"