[Strict] Add rules to resolve PHPStan Strict rule set (#955)

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2021-10-05 10:39:10 +02:00 committed by GitHub
parent 687f9e9de9
commit c4de350289
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1651 additions and 72 deletions

View File

@ -1,4 +1,4 @@
# 477 Rules Overview
# 482 Rules Overview
<br>
@ -92,6 +92,8 @@
- [Restoration](#restoration) (6)
- [Strict](#strict) (5)
- [Transform](#transform) (34)
- [TypeDeclaration](#typedeclaration) (20)
@ -9774,6 +9776,111 @@ Rename file to respect class name
<br>
## Strict
### BooleanInBooleanNotRuleFixerRector
Fixer for PHPStan reports by strict type rule - "PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule"
- class: [`Rector\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector`](../rules/Strict/Rector/BooleanNot/BooleanInBooleanNotRuleFixerRector.php)
```diff
class SomeClass
{
public function run(string $name)
{
- if (! $name) {
+ if ($name !== '') {
return 'name';
}
return 'no name';
}
}
```
<br>
### BooleanInIfConditionRuleFixerRector
Fixer for PHPStan reports by strict type rule - "PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule"
- class: [`Rector\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector`](../rules/Strict/Rector/If_/BooleanInIfConditionRuleFixerRector.php)
```diff
final class NegatedString
{
public function run(string $name)
{
- if ($name) {
+ if ($name !== '') {
return 'name';
}
return 'no name';
}
}
```
<br>
### BooleanInTernaryOperatorRuleFixerRector
Fixer for PHPStan reports by strict type rule - "PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule"
- class: [`Rector\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector`](../rules/Strict/Rector/Ternary/BooleanInTernaryOperatorRuleFixerRector.php)
```diff
final class ArrayCompare
{
public function run(array $data)
{
- return $data ? 1 : 2;
+ return $data !== [] ? 1 : 2;
}
}
```
<br>
### DisallowedEmptyRuleFixerRector
Fixer for PHPStan reports by strict type rule - "PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule"
- class: [`Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector`](../rules/Strict/Rector/Empty_/DisallowedEmptyRuleFixerRector.php)
```diff
final class SomeEmptyArray
{
public function run(array $items)
{
- return empty($items);
+ return $items === [];
}
}
```
<br>
### DisallowedShortTernaryRuleFixerRector
Fixer for PHPStan reports by strict type rule - "PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule"
- class: [`Rector\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector`](../rules/Strict/Rector/Ternary/DisallowedShortTernaryRuleFixerRector.php)
```diff
final class ShortTernaryArray
{
public function run(array $array)
{
- return $array ?: 2;
+ return $array !== [] ? $array : 2;
}
}
```
<br>
## Transform
### AddInterfaceByParentRector

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class BooleanInBooleanNotRuleFixerRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class NegatedInteger
{
private int $age;
public function run()
{
if (! $this->age) {
return 'age';
}
return 'no age';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class NegatedInteger
{
private int $age;
public function run()
{
if ($this->age === 0) {
return 'age';
}
return 'no age';
}
}
?>

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class NegatedString
{
private string $name = '';
public function run()
{
if (!$this->name) {
return 'name';
}
return 'no name';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class NegatedString
{
private string $name = '';
public function run()
{
if ($this->name === '') {
return 'name';
}
return 'no name';
}
}
?>

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class UnionNullBoolean
{
public function run(bool|null $maye)
{
if (! $maye) {
return true;
}
return false;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class UnionNullBoolean
{
public function run(bool|null $maye)
{
if ($maye === true) {
return true;
}
return false;
}
}
?>

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class UnionWithNull
{
/**
* @var string|null
*/
private $value;
public function run()
{
if (! $this->value) {
return 'empty';
}
return 'set';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\Fixture;
final class UnionWithNull
{
/**
* @var string|null
*/
private $value;
public function run()
{
if ($this->value === null) {
return 'empty';
}
return 'set';
}
}
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(BooleanInBooleanNotRuleFixerRector::class);
};

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class DisallowedEmptyRuleFixerRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;
final class NegatedEmptyArray
{
public function run(array $items)
{
return ! empty($items);
}
}
?>
-----
<?php
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;
final class NegatedEmptyArray
{
public function run(array $items)
{
return $items !== [];
}
}
?>

View File

@ -0,0 +1,14 @@
<?php
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;
final class SkipMultiUnionTypes
{
/**
* @param int|int[]|null|string $id
*/
public function get($id)
{
return empty($id);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;
final class SomeEmptyArray
{
public function run(array $items)
{
return empty($items);
}
}
?>
-----
<?php
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;
final class SomeEmptyArray
{
public function run(array $items)
{
return $items === [];
}
}
?>

View File

@ -0,0 +1,27 @@
<?php
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;
final class UnionObjectNullable
{
public function run(null|\DateTime $dateTime)
{
return empty($dateTime);
}
}
?>
-----
<?php
namespace Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\Fixture;
final class UnionObjectNullable
{
public function run(null|\DateTime $dateTime)
{
return $dateTime === null;
}
}
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DisallowedEmptyRuleFixerRector::class);
};

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class BooleanInIfConditionRuleFixerRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class ArrayFromFilter
{
public function run(array $items)
{
if ($items) {
return true;
}
return false;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class ArrayFromFilter
{
public function run(array $items)
{
if ($items !== []) {
return true;
}
return false;
}
}
?>

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class ElseIfNegatedString
{
private string $name = '';
public function run(bool $boolValue)
{
if ($boolValue) {
return 'bool';
} elseif ($this->name) {
return 'name';
}
return 'no name';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class ElseIfNegatedString
{
private string $name = '';
public function run(bool $boolValue)
{
if ($boolValue) {
return 'bool';
} elseif ($this->name !== '') {
return 'name';
}
return 'no name';
}
}
?>

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class NegatedString
{
private string $name = '';
public function run()
{
if ($this->name) {
return 'name';
}
return 'no name';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class NegatedString
{
private string $name = '';
public function run()
{
if ($this->name !== '') {
return 'name';
}
return 'no name';
}
}
?>

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class NullableBool
{
public function run(bool|null $value)
{
if ($value) {
return 'yes';
}
return 'no';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class NullableBool
{
public function run(bool|null $value)
{
if ($value === true) {
return 'yes';
}
return 'no';
}
}
?>

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class NullableObject
{
public function run(\stdClass|null $value)
{
if ($value) {
return 'yes';
}
return 'no';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\Fixture;
final class NullableObject
{
public function run(\stdClass|null $value)
{
if ($value instanceof \stdClass) {
return 'yes';
}
return 'no';
}
}
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(BooleanInIfConditionRuleFixerRector::class);
};

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class BooleanInTernaryOperatorRuleFixerRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector\Fixture;
final class ArrayCompare
{
public function run(array $data)
{
return $data ? 1 : 2;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector\Fixture;
final class ArrayCompare
{
public function run(array $data)
{
return $data !== [] ? 1 : 2;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector\Fixture;
final class StringSilentCompare
{
public function run(string $string)
{
return $string ? 1 : 2;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector\Fixture;
final class StringSilentCompare
{
public function run(string $string)
{
return $string !== '' ? 1 : 2;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector\Fixture;
final class StrlenNumber
{
public function run(string $string)
{
return strlen($string) ? 1 : 2;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector\Fixture;
final class StrlenNumber
{
public function run(string $string)
{
return strlen($string) !== 0 ? 1 : 2;
}
}
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(BooleanInTernaryOperatorRuleFixerRector::class);
};

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class DisallowedShortTernaryRuleFixerRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}
/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector\Fixture;
trait ResetTrait
{
public function run(array $albums)
{
return \reset($albums) ?: null;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector\Fixture;
trait ResetTrait
{
public function run(array $albums)
{
return $albums !== [] ? \reset($albums) : null;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector\Fixture;
final class ShortTernaryArray
{
public function run(array $array)
{
return $array ?: 2;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector\Fixture;
final class ShortTernaryArray
{
public function run(array $array)
{
return $array !== [] ? $array : 2;
}
}
?>

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector\Fixture;
final class ShortTernaryString
{
public function run(string $string)
{
return $string ?: 2;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector\Fixture;
final class ShortTernaryString
{
public function run(string $string)
{
return $string !== '' ? $string : 2;
}
}
?>

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Rector\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DisallowedShortTernaryRuleFixerRector::class);
};

View File

@ -1,41 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQualityStrict\NodeFactory;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\StaticTypeMapper\ValueObject\Type\ShortenedObjectType;
final class ClassConstFetchFactory
{
/**
* @return ClassConstFetch[]
*/
public function createFromType(ObjectType | UnionType $type): array
{
$classConstTypes = [];
if ($type instanceof ShortenedObjectType) {
$classConstTypes[] = new ClassConstFetch(new FullyQualified($type->getFullyQualifiedName()), 'class');
} elseif ($type instanceof ObjectType) {
$classConstTypes[] = new ClassConstFetch(new FullyQualified($type->getClassName()), 'class');
}
if ($type instanceof UnionType) {
foreach ($type->getTypes() as $unionedType) {
if (! $unionedType instanceof TypeWithClassName) {
throw new ShouldNotHappenException();
}
$classConstTypes[] = new ClassConstFetch(new FullyQualified($unionedType->getClassName()), 'class');
}
}
return $classConstTypes;
}
}

View File

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\CodeQualityStrict\TypeAnalyzer;
use PHPStan\Type\Type;
use PHPStan\Type\TypeWithClassName;
final class SubTypeAnalyzer
{
public function isObjectSubType(Type $checkedType, Type $mainType): bool
{
if (! $checkedType instanceof TypeWithClassName) {
return false;
}
if (! $mainType instanceof TypeWithClassName) {
return false;
}
// parent type to all objects
if ($mainType->getClassName() === 'stdClass') {
return true;
}
return $mainType->isSuperTypeOf($checkedType)
->yes();
}
}

View File

@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace Rector\Strict\NodeFactory;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use PHPStan\Type\UnionType;
final class ExactCompareFactory
{
public function createIdenticalFalsyCompare(Type $exprType, Expr $expr): ?Identical
{
if ($exprType instanceof StringType) {
return new Identical($expr, new String_(''));
}
if ($exprType instanceof IntegerType) {
return new Identical($expr, new LNumber(0));
}
if ($exprType instanceof ArrayType) {
return new Identical($expr, new Array_([]));
}
if (! $exprType instanceof UnionType) {
return null;
}
if (! TypeCombinator::containsNull($exprType)) {
return null;
}
return $this->createTruthyFromUnionType($exprType, $expr);
}
public function createNotIdenticalFalsyCompare(Type $exprType, Expr $expr): NotIdentical|Identical|Instanceof_|null
{
if ($exprType instanceof StringType) {
return new NotIdentical($expr, new String_(''));
}
if ($exprType instanceof IntegerType) {
return new NotIdentical($expr, new LNumber(0));
}
if ($exprType instanceof ArrayType) {
return new NotIdentical($expr, new Array_([]));
}
if (! $exprType instanceof UnionType) {
return null;
}
if (! TypeCombinator::containsNull($exprType)) {
return null;
}
return $this->createFromUnionType($exprType, $expr);
}
private function createFromUnionType(Type|UnionType $exprType, Expr $expr): Identical|Instanceof_|NotIdentical
{
$exprType = TypeCombinator::removeNull($exprType);
if ($exprType instanceof BooleanType) {
$trueConstFetch = new ConstFetch(new Name('true'));
return new Identical($expr, $trueConstFetch);
}
if ($exprType instanceof TypeWithClassName) {
return new Instanceof_($expr, new FullyQualified($exprType->getClassName()));
}
$nullConstFetch = new ConstFetch(new Name('null'));
return new NotIdentical($expr, $nullConstFetch);
}
private function resolveFalsyTypesCount(UnionType $unionType): int
{
$falsyTypesCount = 0;
foreach ($unionType->getTypes() as $unionedType) {
if ($unionedType instanceof StringType) {
++$falsyTypesCount;
}
if ($unionedType instanceof IntegerType) {
++$falsyTypesCount;
}
if ($unionedType instanceof FloatType) {
++$falsyTypesCount;
}
if ($unionedType instanceof ArrayType) {
++$falsyTypesCount;
}
}
return $falsyTypesCount;
}
private function createTruthyFromUnionType(UnionType $unionType, Expr $expr): ?Identical
{
$unionType = TypeCombinator::removeNull($unionType);
if ($unionType instanceof BooleanType) {
$trueConstFetch = new ConstFetch(new Name('true'));
return new Identical($expr, $trueConstFetch);
}
if ($unionType instanceof UnionType) {
$falsyTypesCount = $this->resolveFalsyTypesCount($unionType);
// impossible to refactor to string value compare, as many falsy values can be provided
if ($falsyTypesCount > 1) {
return null;
}
}
$nullConstFetch = new ConstFetch(new Name('null'));
return new Identical($expr, $nullConstFetch);
}
}

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Rector\Strict\Rector\BooleanNot;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BooleanNot;
use PHPStan\Analyser\Scope;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Strict\NodeFactory\ExactCompareFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* Fixer Rector for PHPStan rule:
* https://github.com/phpstan/phpstan-strict-rules/blob/0705fefc7c9168529fd130e341428f5f10f4f01d/src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php
*
* @see \Rector\Tests\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector\BooleanInBooleanNotRuleFixerRectorTest
*/
final class BooleanInBooleanNotRuleFixerRector extends AbstractRector
{
public function __construct(
private ExactCompareFactory $exactCompareFactory
) {
}
public function getRuleDefinition(): RuleDefinition
{
$errorMessage = \sprintf(
'Fixer for PHPStan reports by strict type rule - "%s"',
'PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule'
);
return new RuleDefinition($errorMessage, [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass
{
public function run(string $name)
{
if (! $name) {
return 'name';
}
return 'no name';
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
{
public function run(string $name)
{
if ($name !== '') {
return 'name';
}
return 'no name';
}
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [BooleanNot::class];
}
/**
* @param BooleanNot $node
*/
public function refactor(Node $node): ?Identical
{
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
$exprType = $scope->getType($node->expr);
return $this->exactCompareFactory->createIdenticalFalsyCompare($exprType, $node->expr);
}
}

View File

@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Rector\Strict\Rector\Empty_;
use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\Instanceof_;
use PHPStan\Analyser\Scope;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Strict\NodeFactory\ExactCompareFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\Tests\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector\DisallowedEmptyRuleFixerRectorTest
*/
final class DisallowedEmptyRuleFixerRector extends AbstractRector
{
public function __construct(
private ExactCompareFactory $exactCompareFactory,
) {
}
public function getRuleDefinition(): RuleDefinition
{
$errorMessage = \sprintf(
'Fixer for PHPStan reports by strict type rule - "%s"',
'PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule'
);
return new RuleDefinition($errorMessage, [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeEmptyArray
{
public function run(array $items)
{
return empty($items);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class SomeEmptyArray
{
public function run(array $items)
{
return $items === [];
}
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Empty_::class, BooleanNot::class];
}
/**
* @param Empty_|BooleanNot $node
*/
public function refactor(Node $node)
{
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
if ($node instanceof BooleanNot) {
return $this->refactorBooleanNot($node, $scope);
}
return $this->refactorEmpty($node, $scope);
}
private function refactorBooleanNot(BooleanNot $booleanNot, Scope $scope): NotIdentical|Identical|Instanceof_|null
{
if (! $booleanNot->expr instanceof Empty_) {
return null;
}
$empty = $booleanNot->expr;
$emptyExprType = $scope->getType($empty->expr);
return $this->exactCompareFactory->createNotIdenticalFalsyCompare($emptyExprType, $empty->expr);
}
private function refactorEmpty(Empty_ $empty, Scope $scope): ?Identical
{
$exprType = $scope->getType($empty->expr);
return $this->exactCompareFactory->createIdenticalFalsyCompare($exprType, $empty->expr);
}
}

View File

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Rector\Strict\Rector\If_;
use PhpParser\Node;
use PhpParser\Node\Stmt\If_;
use PHPStan\Analyser\Scope;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Strict\NodeFactory\ExactCompareFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* Fixer Rector for PHPStan rules:
* https://github.com/phpstan/phpstan-strict-rules/blob/master/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php
* https://github.com/phpstan/phpstan-strict-rules/blob/master/src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php
*
* @see \Rector\Tests\Strict\Rector\If_\BooleanInIfConditionRuleFixerRector\BooleanInIfConditionRuleFixerRectorTest
*/
final class BooleanInIfConditionRuleFixerRector extends AbstractRector
{
public function __construct(
private ExactCompareFactory $exactCompareFactory
) {
}
public function getRuleDefinition(): RuleDefinition
{
$errorMessage = \sprintf(
'Fixer for PHPStan reports by strict type rule - "%s"',
'PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule'
);
return new RuleDefinition($errorMessage, [
new CodeSample(
<<<'CODE_SAMPLE'
final class NegatedString
{
public function run(string $name)
{
if ($name) {
return 'name';
}
return 'no name';
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class NegatedString
{
public function run(string $name)
{
if ($name !== '') {
return 'name';
}
return 'no name';
}
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [If_::class];
}
/**
* @param If_ $node
*/
public function refactor(Node $node): ?If_
{
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
// 1. if
$ifCondExprType = $scope->getType($node->cond);
$notIdentical = $this->exactCompareFactory->createNotIdenticalFalsyCompare($ifCondExprType, $node->cond);
if ($notIdentical !== null) {
$node->cond = $notIdentical;
}
// 2. elseifs
foreach ($node->elseifs as $elseif) {
$elseifCondExprType = $scope->getType($elseif->cond);
$notIdentical = $this->exactCompareFactory->createNotIdenticalFalsyCompare(
$elseifCondExprType,
$elseif->cond
);
if ($notIdentical === null) {
continue;
}
$elseif->cond = $notIdentical;
}
return $node;
}
}

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Rector\Strict\Rector\Ternary;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Ternary;
use PHPStan\Analyser\Scope;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Strict\NodeFactory\ExactCompareFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* Fixer Rector for PHPStan rule:
* https://github.com/phpstan/phpstan-strict-rules/blob/master/src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php
*
* @see \Rector\Tests\Strict\Rector\Ternary\BooleanInTernaryOperatorRuleFixerRector\BooleanInTernaryOperatorRuleFixerRectorTest
*/
final class BooleanInTernaryOperatorRuleFixerRector extends AbstractRector
{
public function __construct(
private ExactCompareFactory $exactCompareFactory
) {
}
public function getRuleDefinition(): RuleDefinition
{
$errorMessage = \sprintf(
'Fixer for PHPStan reports by strict type rule - "%s"',
'PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule'
);
return new RuleDefinition($errorMessage, [
new CodeSample(
<<<'CODE_SAMPLE'
final class ArrayCompare
{
public function run(array $data)
{
return $data ? 1 : 2;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class ArrayCompare
{
public function run(array $data)
{
return $data !== [] ? 1 : 2;
}
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Ternary::class];
}
/**
* @param Ternary $node
*/
public function refactor(Node $node): ?Ternary
{
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
// skip short ternary
if ($node->if === null) {
return null;
}
$exprType = $scope->getType($node->cond);
$falsyIdentical = $this->exactCompareFactory->createNotIdenticalFalsyCompare($exprType, $node->cond);
if (! $falsyIdentical instanceof Expr) {
return null;
}
$node->cond = $falsyIdentical;
return $node;
}
}

View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Rector\Strict\Rector\Ternary;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Ternary;
use PHPStan\Analyser\Scope;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Strict\NodeFactory\ExactCompareFactory;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* Fixer Rector for PHPStan rule:
* https://github.com/phpstan/phpstan-strict-rules/blob/master/src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php
*
* @see \Rector\Tests\Strict\Rector\Ternary\DisallowedShortTernaryRuleFixerRector\DisallowedShortTernaryRuleFixerRectorTest
*/
final class DisallowedShortTernaryRuleFixerRector extends AbstractRector
{
public function __construct(
private ExactCompareFactory $exactCompareFactory,
) {
}
public function getRuleDefinition(): RuleDefinition
{
$errorMessage = sprintf(
'Fixer for PHPStan reports by strict type rule - "%s"',
'PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule'
);
return new RuleDefinition($errorMessage, [
new CodeSample(
<<<'CODE_SAMPLE'
final class ShortTernaryArray
{
public function run(array $array)
{
return $array ?: 2;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
final class ShortTernaryArray
{
public function run(array $array)
{
return $array !== [] ? $array : 2;
}
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Ternary::class];
}
/**
* @param Ternary $node
*/
public function refactor(Node $node): ?Ternary
{
$scope = $node->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return null;
}
// skip non-short ternary
if ($node->if !== null) {
return null;
}
// special case for reset() function
if ($node->cond instanceof FuncCall && $this->isName($node->cond, 'reset')) {
$this->refactorResetFuncCall($node, $node->cond, $scope);
return $node;
}
$exprType = $scope->getType($node->cond);
$falsyIdentical = $this->exactCompareFactory->createNotIdenticalFalsyCompare($exprType, $node->cond);
if ($falsyIdentical === null) {
return null;
}
$node->if = $node->cond;
$node->cond = $falsyIdentical;
return $node;
}
private function refactorResetFuncCall(Ternary $ternary, FuncCall $resetFuncCall, Scope $scope): void
{
$ternary->if = $ternary->cond;
$firstArgValue = $resetFuncCall->args[0]->value;
$firstArgType = $scope->getType($firstArgValue);
$falsyCompareExpr = $this->exactCompareFactory->createNotIdenticalFalsyCompare(
$firstArgType,
$firstArgValue
);
if ($falsyCompareExpr === null) {
return;
}
$ternary->cond = $falsyCompareExpr;
}
}