Skip to content

Commit 968e89e

Browse files
authored
Implement LooseBooleanMutator (#27)
* Implement LooseBooleanMutator * ignore isTrue()/isFalse() with arguments * Update LooseBooleanMutatorTest.php * cs
1 parent 8a6991c commit 968e89e

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed

resources/infection.json5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"mutators": {
1414
"@default": false,
15+
"PHPStan\\Infection\\LooseBooleanMutator": true,
1516
"PHPStan\\Infection\\TrinaryLogicMutator": true
1617
},
1718
"bootstrap": "build-infection/vendor/autoload.php"
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 function count;
10+
use function in_array;
11+
12+
/**
13+
* @implements Mutator<Node\Expr\MethodCall>
14+
*/
15+
final class LooseBooleanMutator implements Mutator
16+
{
17+
18+
public static function getDefinition(): Definition
19+
{
20+
return new Definition(
21+
<<<'TXT'
22+
Replaces boolean Type->isTrue()/isFalse() with Type->toBoolean()->isTrue()/isFalse() to check loose comparisons coverage.
23+
TXT
24+
,
25+
MutatorCategory::ORTHOGONAL_REPLACEMENT,
26+
null,
27+
<<<'DIFF'
28+
- $type->isFalse()->yes();
29+
+ $type->isBoolean()->isFalse()->yes();
30+
DIFF,
31+
);
32+
}
33+
34+
public function getName(): string
35+
{
36+
return self::class;
37+
}
38+
39+
public function canMutate(Node $node): bool
40+
{
41+
if (!$node instanceof Node\Expr\MethodCall) {
42+
return false;
43+
}
44+
45+
if (!$node->name instanceof Node\Identifier) {
46+
return false;
47+
}
48+
49+
if (!in_array($node->name->name, ['isTrue', 'isFalse'], true)) {
50+
return false;
51+
}
52+
53+
if (count($node->getArgs()) !== 0) {
54+
return false;
55+
}
56+
57+
if ($node->var instanceof Node\Expr\MethodCall) {
58+
if (
59+
$node->var->name instanceof Node\Identifier
60+
&& in_array($node->var->name->name, ['toBoolean'], true)
61+
) {
62+
return false;
63+
}
64+
}
65+
66+
return true;
67+
}
68+
69+
public function mutate(Node $node): iterable
70+
{
71+
$node->var = new Node\Expr\MethodCall($node->var, 'toBoolean');
72+
yield $node;
73+
}
74+
75+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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(LooseBooleanMutator::class)]
10+
final class LooseBooleanMutatorTest 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 isFalse() into loose comparison' => [
28+
<<<'PHP'
29+
<?php
30+
31+
$type->isFalse()->yes();
32+
PHP
33+
,
34+
<<<'PHP'
35+
<?php
36+
37+
$type->toBoolean()->isFalse()->yes();
38+
PHP
39+
,
40+
];
41+
42+
yield 'It mutates isTrue() into loose comparison' => [
43+
<<<'PHP'
44+
<?php
45+
46+
$type->isTrue()->yes();
47+
PHP
48+
,
49+
<<<'PHP'
50+
<?php
51+
52+
$type->toBoolean()->isTrue()->yes();
53+
PHP
54+
,
55+
];
56+
57+
yield 'It skips already toBoolean() calls to prevent double repetition' => [
58+
<<<'PHP'
59+
<?php
60+
61+
$type->toBoolean()->isTrue()->yes();
62+
PHP
63+
,
64+
];
65+
66+
yield 'It skips non boolean Type calls' => [
67+
<<<'PHP'
68+
<?php
69+
70+
$type->isObject()->yes();
71+
PHP
72+
,
73+
];
74+
75+
yield 'skip isTrue() with arguments' => [
76+
<<<'PHP'
77+
<?php
78+
79+
$a->isTrue($b);
80+
PHP
81+
,
82+
];
83+
84+
yield 'skip isFalse() with arguments' => [
85+
<<<'PHP'
86+
<?php
87+
88+
$a->isFalse($b, $c);
89+
PHP
90+
,
91+
];
92+
}
93+
94+
protected function getTestedMutatorClassName(): string
95+
{
96+
return LooseBooleanMutator::class;
97+
}
98+
99+
}

tests/phpt/infection-config-default.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ echo shell_exec($bin);
1919
},
2020
"mutators": {
2121
"@default": false,
22+
"PHPStan\\Infection\\LooseBooleanMutator": true,
2223
"PHPStan\\Infection\\TrinaryLogicMutator": true
2324
},
2425
"bootstrap": "build-infection\/vendor\/autoload.php"

tests/phpt/infection-config.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ echo shell_exec($bin." --source-directory='more/files/' --timeout=180 --mutator-
2020
},
2121
"mutators": {
2222
"@default": false,
23+
"PHPStan\\Infection\\LooseBooleanMutator": true,
2324
"PHPStan\\Infection\\TrinaryLogicMutator": true,
2425
"My\\Class": true
2526
},

0 commit comments

Comments
 (0)