Skip to content

Commit

Permalink
[CodeQuality] Add empty() check to FlipTypeControlToUseExclusiveTypeR…
Browse files Browse the repository at this point in the history
…ector (#3224)

* [CodeQuality] Add empty() check to FlipTypeControlToUseExclusiveTypeRector

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
  • Loading branch information
TomasVotruba and actions-user committed Dec 20, 2022
1 parent 980dc83 commit 36fcdc1
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Rector\Tests\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;

use Rector\Tests\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Source\SomeExactType;

final class EmptyOnExactType
{
public function run()
{
$exactType = $this->getExactType();
if (empty($exactType)) {
return;
}
}

private function getExactType(): ?SomeExactType
{
if (mt_rand(0, 1)) {
return new SomeExactType();
}

return null;
}
}

?>
-----
<?php

namespace Rector\Tests\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Fixture;

use Rector\Tests\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Source\SomeExactType;

final class EmptyOnExactType
{
public function run()
{
$exactType = $this->getExactType();
if (!$exactType instanceof \Rector\Tests\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Source\SomeExactType) {
return;
}
}

private function getExactType(): ?SomeExactType
{
if (mt_rand(0, 1)) {
return new SomeExactType();
}

return null;
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Rector\Tests\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector\Source;

final class SomeExactType
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BooleanNot;
use PhpParser\Node\Expr\Empty_;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Expression;
Expand All @@ -32,7 +33,7 @@
final class FlipTypeControlToUseExclusiveTypeRector extends AbstractRector
{
public function __construct(
private readonly PhpDocTagRemover $phpDocTagRemover
private readonly PhpDocTagRemover $phpDocTagRemover,
) {
}

Expand All @@ -43,29 +44,17 @@ public function getRuleDefinition(): RuleDefinition
[
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;
}
}
/** @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;
}
}
$phpDocInfo = $functionLike->getAttribute(AttributeKey::PHP_DOC_INFO);
if (! $phpDocInfo instanceof PhpDocInfo) {
return;
}
CODE_SAMPLE
),
Expand All @@ -78,48 +67,37 @@ public function __construct(array $values)
*/
public function getNodeTypes(): array
{
return [Identical::class];
return [Identical::class, Empty_::class];
}

/**
* @param Identical $node
* @param Identical|Empty_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->valueResolver->isNull($node->left) && ! $this->valueResolver->isNull($node->right)) {
return null;
if ($node instanceof Identical) {
return $this->refactorIdentical($node);
}

$variable = $this->valueResolver->isNull($node->left) ? $node->right : $node->left;

$assign = $this->getVariableAssign($node, $variable);
if (! $assign instanceof Assign) {
$exprType = $this->getType($node->expr);
if (! $exprType instanceof ObjectType) {
return null;
}

$expression = $assign->getAttribute(AttributeKey::PARENT_NODE);
if (! $expression instanceof Expression) {
// the empty false positively reports epxr, even if nullable
$assign = $this->betterNodeFinder->findPreviousAssignToExpr($node->expr);
if (! $assign instanceof Expr) {
return null;
}

$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($expression);
$type = $phpDocInfo->getVarType();

if (! $type instanceof UnionType) {
$type = $this->getType($assign->expr);
}
$previousAssignToExprType = $this->getType($assign);

if (! $type instanceof UnionType) {
return null;
}

/** @var Type[] $types */
$types = $this->getTypes($type);
$types = $this->getExactlyTwoUnionedTypes($previousAssignToExprType);
if ($this->isNotNullOneOf($types)) {
return null;
}

return $this->processConvertToExclusiveType($types, $variable, $phpDocInfo);
return $this->processConvertToExclusiveType($types, $node->expr);
}

private function getVariableAssign(Identical $identical, Expr $expr): ?Node
Expand All @@ -136,9 +114,13 @@ private function getVariableAssign(Identical $identical, Expr $expr): ?Node
/**
* @return Type[]
*/
private function getTypes(UnionType $unionType): array
private function getExactlyTwoUnionedTypes(Type $type): array
{
$types = $unionType->getTypes();
if (! $type instanceof UnionType) {
return [];
}

$types = $type->getTypes();
if (count($types) > 2) {
return [];
}
Expand Down Expand Up @@ -169,20 +151,75 @@ private function isNotNullOneOf(array $types): bool
/**
* @param Type[] $types
*/
private function processConvertToExclusiveType(array $types, Expr $expr, PhpDocInfo $phpDocInfo): ?BooleanNot
{
private function processConvertToExclusiveType(
array $types,
Expr $expr,
?PhpDocInfo $phpDocInfo = null
): ?BooleanNot {
$type = $types[0] instanceof NullType ? $types[1] : $types[0];
if (! $type instanceof FullyQualifiedObjectType && ! $type instanceof ObjectType) {
return null;
}

$varTagValueNode = $phpDocInfo->getVarTagValueNode();
if ($varTagValueNode instanceof VarTagValueNode) {
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $varTagValueNode);
if ($phpDocInfo instanceof PhpDocInfo) {
$varTagValueNode = $phpDocInfo->getVarTagValueNode();
if ($varTagValueNode instanceof VarTagValueNode) {
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $varTagValueNode);
}
}

$fullyQualifiedType = $type instanceof ShortenedObjectType ? $type->getFullyQualifiedName() : $type->getClassName();

return new BooleanNot(new Instanceof_($expr, new FullyQualified($fullyQualifiedType)));
}

private function refactorIdentical(Identical $identical): ?BooleanNot
{
$expr = $this->matchNullComparedExpr($identical);
if (! $expr instanceof Expr) {
return null;
}

$node = $this->getVariableAssign($identical, $expr);
if (! $node instanceof Assign) {
return null;
}

$expression = $node->getAttribute(AttributeKey::PARENT_NODE);
if (! $expression instanceof Expression) {
return null;
}

$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($expression);
$type = $phpDocInfo->getVarType();

if (! $type instanceof UnionType) {
$type = $this->getType($node->expr);
}

if (! $type instanceof UnionType) {
return null;
}

/** @var Type[] $types */
$types = $this->getExactlyTwoUnionedTypes($type);
if ($this->isNotNullOneOf($types)) {
return null;
}

return $this->processConvertToExclusiveType($types, $expr, $phpDocInfo);
}

private function matchNullComparedExpr(Identical $identical): ?Expr
{
if ($this->valueResolver->isNull($identical->left)) {
return $identical->right;
}

if ($this->valueResolver->isNull($identical->right)) {
return $identical->left;
}

return null;
}
}

0 comments on commit 36fcdc1

Please sign in to comment.