Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/infection.json5
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"mutators": {
"@default": false,
"PHPStan\\Infection\\LooseBooleanMutator": true,
"PHPStan\\Infection\\TrinaryLogicMutator": true
},
"bootstrap": "build-infection/vendor/autoload.php"
Expand Down
75 changes: 75 additions & 0 deletions src/Infection/LooseBooleanMutator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php declare(strict_types = 1);

namespace PHPStan\Infection;

use Infection\Mutator\Definition;
use Infection\Mutator\Mutator;
use Infection\Mutator\MutatorCategory;
use PhpParser\Node;
use function count;
use function in_array;

/**
* @implements Mutator<Node\Expr\MethodCall>
*/
final class LooseBooleanMutator implements Mutator
{

public static function getDefinition(): Definition
{
return new Definition(
<<<'TXT'
Replaces boolean Type->isTrue()/isFalse() with Type->toBoolean()->isTrue()/isFalse() to check loose comparisons coverage.
TXT
,
MutatorCategory::ORTHOGONAL_REPLACEMENT,
null,
<<<'DIFF'
- $type->isFalse()->yes();
+ $type->isBoolean()->isFalse()->yes();
DIFF,
);
}

public function getName(): string
{
return self::class;
}

public function canMutate(Node $node): bool
{
if (!$node instanceof Node\Expr\MethodCall) {
return false;
}

if (!$node->name instanceof Node\Identifier) {
return false;
}

if (!in_array($node->name->name, ['isTrue', 'isFalse'], true)) {
return false;
}

if (count($node->getArgs()) !== 0) {
return false;
}

if ($node->var instanceof Node\Expr\MethodCall) {
if (
$node->var->name instanceof Node\Identifier
&& in_array($node->var->name->name, ['toBoolean'], true)
) {
return false;
}
}

return true;
}

public function mutate(Node $node): iterable
{
$node->var = new Node\Expr\MethodCall($node->var, 'toBoolean');
yield $node;
}

}
99 changes: 99 additions & 0 deletions tests/Infection/LooseBooleanMutatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php declare(strict_types = 1);

namespace PHPStan\Infection;

use Infection\Testing\BaseMutatorTestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;

#[CoversClass(LooseBooleanMutator::class)]
final class LooseBooleanMutatorTest extends BaseMutatorTestCase
{

/**
* @param string|string[] $expected
*/
#[DataProvider('mutationsProvider')]
public function testMutator(string $input, $expected = []): void
{
$this->assertMutatesInput($input, $expected);
}

/**
* @return iterable<string, array{0: string, 1?: string}>
*/
public static function mutationsProvider(): iterable
{
yield 'It mutates isFalse() into loose comparison' => [
<<<'PHP'
<?php

$type->isFalse()->yes();
PHP
,
<<<'PHP'
<?php

$type->toBoolean()->isFalse()->yes();
PHP
,
];

yield 'It mutates isTrue() into loose comparison' => [
<<<'PHP'
<?php

$type->isTrue()->yes();
PHP
,
<<<'PHP'
<?php

$type->toBoolean()->isTrue()->yes();
PHP
,
];

yield 'It skips already toBoolean() calls to prevent double repetition' => [
<<<'PHP'
<?php

$type->toBoolean()->isTrue()->yes();
PHP
,
];

yield 'It skips non boolean Type calls' => [
<<<'PHP'
<?php

$type->isObject()->yes();
PHP
,
];

yield 'skip isTrue() with arguments' => [
<<<'PHP'
<?php

$a->isTrue($b);
PHP
,
];

yield 'skip isFalse() with arguments' => [
<<<'PHP'
<?php

$a->isFalse($b, $c);
PHP
,
];
}

protected function getTestedMutatorClassName(): string
{
return LooseBooleanMutator::class;
}

}
1 change: 1 addition & 0 deletions tests/phpt/infection-config-default.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ echo shell_exec($bin);
},
"mutators": {
"@default": false,
"PHPStan\\Infection\\LooseBooleanMutator": true,
"PHPStan\\Infection\\TrinaryLogicMutator": true
},
"bootstrap": "build-infection\/vendor\/autoload.php"
Expand Down
1 change: 1 addition & 0 deletions tests/phpt/infection-config.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ echo shell_exec($bin." --source-directory='more/files/' --timeout=180 --mutator-
},
"mutators": {
"@default": false,
"PHPStan\\Infection\\LooseBooleanMutator": true,
"PHPStan\\Infection\\TrinaryLogicMutator": true,
"My\\Class": true
},
Expand Down
Loading