Skip to content

Commit 6f3df78

Browse files
authored
Implement IsSuperTypeOfCalleeAndArgumentMutator (#26)
1 parent 968e89e commit 6f3df78

File tree

5 files changed

+287
-2
lines changed

5 files changed

+287
-2
lines changed

resources/infection.json5

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"mutators": {
1414
"@default": false,
1515
"PHPStan\\Infection\\LooseBooleanMutator": true,
16-
"PHPStan\\Infection\\TrinaryLogicMutator": true
16+
"PHPStan\\Infection\\TrinaryLogicMutator": true,
17+
"PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true
1718
},
1819
"bootstrap": "build-infection/vendor/autoload.php"
1920
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Infection;
4+
5+
use Infection\Mutator\Definition;
6+
use Infection\Mutator\Mutator;
7+
use Infection\Mutator\MutatorCategory;
8+
use PhpParser\Node;
9+
use RuntimeException;
10+
use function count;
11+
use function in_array;
12+
13+
/**
14+
* @implements Mutator<Node\Expr\MethodCall>
15+
*/
16+
final class IsSuperTypeOfCalleeAndArgumentMutator implements Mutator
17+
{
18+
19+
public static function getDefinition(): Definition
20+
{
21+
return new Definition(
22+
<<<'TXT'
23+
Replaces the callee and the argument of a isSuperTypeOf() method call.
24+
TXT
25+
,
26+
MutatorCategory::ORTHOGONAL_REPLACEMENT,
27+
null,
28+
<<<'DIFF'
29+
- $a->isSuperTypeOf($b);
30+
+ $b->isSuperTypeOf($a);
31+
DIFF,
32+
);
33+
}
34+
35+
public function getName(): string
36+
{
37+
return self::class;
38+
}
39+
40+
public function canMutate(Node $node): bool
41+
{
42+
if (!$node instanceof Node\Expr\MethodCall) {
43+
return false;
44+
}
45+
46+
if (
47+
!$node->var instanceof Node\Expr\Variable
48+
&& !$node->var instanceof Node\Expr\PropertyFetch
49+
&& !$node->var instanceof Node\Expr\MethodCall
50+
&& !$node->var instanceof Node\Expr\StaticCall
51+
&& !$node->var instanceof Node\Expr\New_
52+
) {
53+
return false;
54+
}
55+
56+
if (!$node->name instanceof Node\Identifier) {
57+
return false;
58+
}
59+
60+
if (!in_array($node->name->name, ['isSuperTypeOf'], true)) {
61+
return false;
62+
}
63+
64+
$args = $node->getArgs();
65+
if (count($args) !== 1) {
66+
return false;
67+
}
68+
69+
if (
70+
!$args[0]->value instanceof Node\Expr\Variable
71+
&& !$args[0]->value instanceof Node\Expr\PropertyFetch
72+
&& !$args[0]->value instanceof Node\Expr\MethodCall
73+
&& !$args[0]->value instanceof Node\Expr\StaticCall
74+
&& !$args[0]->value instanceof Node\Expr\New_
75+
) {
76+
return false;
77+
}
78+
79+
return true;
80+
}
81+
82+
public function mutate(Node $node): iterable
83+
{
84+
$args = $node->getArgs();
85+
if (count($args) !== 1) {
86+
throw new RuntimeException();
87+
}
88+
89+
yield new Node\Expr\MethodCall(
90+
$args[0]->value,
91+
$node->name,
92+
[new Node\Arg($node->var)],
93+
);
94+
}
95+
96+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Infection;
4+
5+
use Infection\Testing\BaseMutatorTestCase;
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
9+
#[CoversClass(IsSuperTypeOfCalleeAndArgumentMutator::class)]
10+
final class IsSuperTypeOfCalleeAndArgumentMutatorTest extends BaseMutatorTestCase
11+
{
12+
13+
/**
14+
* @param string|string[] $expected
15+
*/
16+
#[DataProvider('mutationsProvider')]
17+
public function testMutator(string $input, $expected = []): void
18+
{
19+
$this->assertMutatesInput($input, $expected);
20+
}
21+
22+
/**
23+
* @return iterable<string, array{0: string, 1?: string}>
24+
*/
25+
public static function mutationsProvider(): iterable
26+
{
27+
yield 'It mutates isSuperTypeOf' => [
28+
<<<'PHP'
29+
<?php
30+
31+
$a->isSuperTypeOf($b);
32+
PHP
33+
,
34+
<<<'PHP'
35+
<?php
36+
37+
$b->isSuperTypeOf($a);
38+
PHP
39+
,
40+
];
41+
42+
yield 'It mutates isSuperTypeOf with property fetch' => [
43+
<<<'PHP'
44+
<?php
45+
46+
$this->a->isSuperTypeOf($b);
47+
PHP
48+
,
49+
<<<'PHP'
50+
<?php
51+
52+
$b->isSuperTypeOf($this->a);
53+
PHP
54+
,
55+
];
56+
57+
yield 'It mutates isSuperTypeOf with property fetch (reversed)' => [
58+
<<<'PHP'
59+
<?php
60+
61+
$a->isSuperTypeOf($this->b);
62+
PHP
63+
,
64+
<<<'PHP'
65+
<?php
66+
67+
$this->b->isSuperTypeOf($a);
68+
PHP
69+
,
70+
];
71+
72+
yield 'It mutates isSuperTypeOf with method-call' => [
73+
<<<'PHP'
74+
<?php
75+
76+
$a->isSuperTypeOf($this->call());
77+
PHP
78+
,
79+
<<<'PHP'
80+
<?php
81+
82+
$this->call()->isSuperTypeOf($a);
83+
PHP
84+
,
85+
];
86+
87+
yield 'It mutates isSuperTypeOf with method-call (reversed)' => [
88+
<<<'PHP'
89+
<?php
90+
91+
$this->call()->isSuperTypeOf($a);
92+
PHP
93+
,
94+
<<<'PHP'
95+
<?php
96+
97+
$a->isSuperTypeOf($this->call());
98+
PHP
99+
,
100+
];
101+
102+
yield 'It mutates isSuperTypeOf with static method call' => [
103+
<<<'PHP'
104+
<?php
105+
106+
IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($a);
107+
PHP
108+
,
109+
<<<'PHP'
110+
<?php
111+
112+
$a->isSuperTypeOf(IntegerRangeType::fromInterval(0, null));
113+
PHP
114+
,
115+
];
116+
117+
yield 'It mutates isSuperTypeOf with static method call (reversed)' => [
118+
<<<'PHP'
119+
<?php
120+
121+
$a->isSuperTypeOf(IntegerRangeType::fromInterval(0, null));
122+
PHP
123+
,
124+
<<<'PHP'
125+
<?php
126+
127+
IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($a);
128+
PHP
129+
,
130+
];
131+
132+
yield 'It mutates isSuperTypeOf with new' => [
133+
<<<'PHP'
134+
<?php
135+
136+
(new ConstantStringType(''))->isSuperTypeOf($delimiterType);
137+
PHP
138+
,
139+
<<<'PHP'
140+
<?php
141+
142+
$delimiterType->isSuperTypeOf(new ConstantStringType(''));
143+
PHP
144+
,
145+
];
146+
147+
yield 'It mutates isSuperTypeOf with new (reversed)' => [
148+
<<<'PHP'
149+
<?php
150+
151+
$delimiterType->isSuperTypeOf(new ConstantStringType(''));
152+
PHP
153+
,
154+
<<<'PHP'
155+
<?php
156+
157+
(new ConstantStringType(''))->isSuperTypeOf($delimiterType);
158+
PHP
159+
,
160+
];
161+
162+
yield 'skip isSuperTypeOf with more arguments' => [
163+
<<<'PHP'
164+
<?php
165+
166+
$a->isSuperTypeOf($b, $c);
167+
PHP
168+
,
169+
];
170+
171+
yield 'skip other method calls' => [
172+
<<<'PHP'
173+
<?php
174+
175+
$a->isConstantValue($b);
176+
PHP
177+
,
178+
];
179+
}
180+
181+
protected function getTestedMutatorClassName(): string
182+
{
183+
return IsSuperTypeOfCalleeAndArgumentMutator::class;
184+
}
185+
186+
}

tests/phpt/infection-config-default.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ echo shell_exec($bin);
2020
"mutators": {
2121
"@default": false,
2222
"PHPStan\\Infection\\LooseBooleanMutator": true,
23-
"PHPStan\\Infection\\TrinaryLogicMutator": true
23+
"PHPStan\\Infection\\TrinaryLogicMutator": true,
24+
"PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true
2425
},
2526
"bootstrap": "build-infection\/vendor\/autoload.php"
2627
}

tests/phpt/infection-config.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ echo shell_exec($bin." --source-directory='more/files/' --timeout=180 --mutator-
2222
"@default": false,
2323
"PHPStan\\Infection\\LooseBooleanMutator": true,
2424
"PHPStan\\Infection\\TrinaryLogicMutator": true,
25+
"PHPStan\\Infection\\IsSuperTypeOfCalleeAndArgumentMutator": true,
2526
"My\\Class": true
2627
},
2728
"bootstrap": "build-infection\/vendor\/autoload.php"

0 commit comments

Comments
 (0)