mirror of
https://github.com/rectorphp/rector.git
synced 2025-03-14 12:29:43 +01:00
[TypeDeclaration] Add FlipTypeControlToUseExclusiveTypeRector (#5214)
* [TypeDeclaration] Fixes #5137 [WIP] Add FlipTypeControlToUseExclusiveTypeRector * adding test * checking types * define instanceof * define instanceof * refactor use Identical node * functional * phpstan * rectify * BooleanNot * phpstan * skip not null compare * skip no assign * skip no var tag * skip multi types * skip assign different var * test interface type * skip not null doc * skip array * skip no doc * skip not union * cs fix * cs fix * fix example Co-authored-by: Tomas Votruba <tomas.vot@gmail.com> * remove doc as well * cs * cs * cs * phpstan * clean up * use instanceof IdentifierTypeNode check * failing fixture skip string type * Revert "use instanceof IdentifierTypeNode check" This reverts commit aa046dff740b621e881d27e3a51e3a3a3c73a59c. Co-authored-by: Tomas Votruba <tomas.vot@gmail.com>
This commit is contained in:
parent
e8ec95fe85
commit
3c2f4eae47
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\Rector\Identical;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\BinaryOp\Identical;
|
||||
use PhpParser\Node\Expr\BooleanNot;
|
||||
use PhpParser\Node\Expr\Instanceof_;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
|
||||
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
|
||||
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareIdentifierTypeNode;
|
||||
use Rector\AttributeAwarePhpDoc\Ast\Type\AttributeAwareUnionTypeNode;
|
||||
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @see \Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\FlipTypeControlToUseExclusiveTypeRectorTest
|
||||
*/
|
||||
final class FlipTypeControlToUseExclusiveTypeRector extends AbstractRector
|
||||
{
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition(
|
||||
'Flip type control to use exclusive type',
|
||||
[
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function __construct(array $values)
|
||||
{
|
||||
/** @var PhpDocInfo|null $phpDocInfo */
|
||||
$phpDocInfo = $functionLike->getAttribute(AttributeKey::PHP_DOC_INFO);
|
||||
if ($phpDocInfo === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function __construct(array $values)
|
||||
{
|
||||
$phpDocInfo = $functionLike->getAttribute(AttributeKey::PHP_DOC_INFO);
|
||||
if (! $phpDocInfo instanceof PhpDocInfo) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Identical::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Identical $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if (! $this->isNull($node->left) && ! $this->isNull($node->right)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$variable = $this->isNull($node->left)
|
||||
? $node->right
|
||||
: $node->left;
|
||||
|
||||
$assign = $this->getVariableAssign($node, $variable);
|
||||
if (! $assign instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expression = $assign->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if (! $expression instanceof Expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$phpDocInfo = $expression->getAttribute(AttributeKey::PHP_DOC_INFO);
|
||||
if (! $phpDocInfo instanceof PhpDocInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var AttributeAwareIdentifierTypeNode[] $types */
|
||||
$types = $this->getTypes($phpDocInfo);
|
||||
if ($this->skipNotNullOneOf($types)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->processConvertToExclusiveType($types, $variable, $phpDocInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttributeAwareIdentifierTypeNode[] $types
|
||||
*/
|
||||
private function processConvertToExclusiveType(array $types, Expr $expr, PhpDocInfo $phpDocInfo): ?BooleanNot
|
||||
{
|
||||
$type = $types[0]->name === 'null'
|
||||
? $types[1]->name
|
||||
: $types[0]->name;
|
||||
|
||||
if (! class_exists($type) && ! interface_exists($type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var VarTagValueNode $tagValueNode */
|
||||
$tagValueNode = $phpDocInfo->getVarTagValueNode();
|
||||
$phpDocInfo->removeTagValueNodeFromNode($tagValueNode);
|
||||
return new BooleanNot(new Instanceof_($expr, new FullyQualified($type)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TypeNode[]
|
||||
*/
|
||||
private function getTypes(PhpDocInfo $phpDocInfo): array
|
||||
{
|
||||
$tagValueNode = $phpDocInfo->getVarTagValueNode();
|
||||
if (! $tagValueNode instanceof VarTagValueNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (! $tagValueNode->type instanceof AttributeAwareUnionTypeNode) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (count($tagValueNode->type->types) > 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $tagValueNode->type->types;
|
||||
}
|
||||
|
||||
private function getVariableAssign(Identical $identical, Expr $expr): ?Node
|
||||
{
|
||||
return $this->betterNodeFinder->findFirstPrevious($identical, function (Node $node) use ($expr): bool {
|
||||
if (! $node instanceof Assign) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->areNodesEqual($node->var, $expr);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AttributeAwareIdentifierTypeNode[] $types
|
||||
*/
|
||||
private function skipNotNullOneOf(array $types): bool
|
||||
{
|
||||
if ($types === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($types as $type) {
|
||||
if (! $type instanceof AttributeAwareIdentifierTypeNode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($types[0]->name === $types[1]->name) {
|
||||
return true;
|
||||
}
|
||||
if ($types[0]->name === 'null') {
|
||||
return false;
|
||||
}
|
||||
return $types[1]->name !== 'null';
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Fixture
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var stdClass|null $stdClass */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var null|stdClass $stdClass */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Fixture
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$stdClass = $this->getStdClass();
|
||||
if (!$stdClass instanceof \stdClass) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stdClass = $this->getStdClass();
|
||||
if (!$stdClass instanceof \stdClass) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
class InterfaceType
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var DateTimeInterface|null $dateTimeInterface */
|
||||
$dateTimeInterface = $this->getdateTimeInterface();
|
||||
if ($dateTimeInterface === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var null|DateTimeInterface $dateTimeInterface */
|
||||
$dateTimeInterface = $this->getdateTimeInterface();
|
||||
if ($dateTimeInterface === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
class InterfaceType
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$dateTimeInterface = $this->getdateTimeInterface();
|
||||
if (!$dateTimeInterface instanceof \DateTimeInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$dateTimeInterface = $this->getdateTimeInterface();
|
||||
if (!$dateTimeInterface instanceof \DateTimeInterface) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipArray
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var stdClass[]|null $stdClass */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipAssignDifferentVar
|
||||
{
|
||||
public function run($stdClass)
|
||||
{
|
||||
/** @var stdClass|null $foo */
|
||||
$foo = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipMultiTypes
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var stdClass|null|Foo|Bar $stdClass */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipNoAssign
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipNoDoc
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipNoVarTag
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** a comment */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipNotNullCompare
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var stdClass|null $stdClass */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === $variable) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use DateTime;
|
||||
use stdClass;
|
||||
|
||||
class SkipNotNullDoc
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var stdClass|DateTime $stdClass */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipNotUnion
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var stdClass $stdClass */
|
||||
$stdClass = $this->getStdClass();
|
||||
if ($stdClass === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class SkipScalarType
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
/** @var string|null $string */
|
||||
$string = $this->getString();
|
||||
if ($string === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\Tests\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Rector\TypeDeclaration\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class FlipTypeControlToUseExclusiveTypeRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
}
|
||||
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return FlipTypeControlToUseExclusiveTypeRector::class;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user