Skip to content

Commit

Permalink
[TypeDeclaration] Add BinaryOpNullableToInstanceofRector (#3631)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba committed Apr 19, 2023
1 parent e3e5146 commit a4c1d7e
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 2 deletions.
24 changes: 22 additions & 2 deletions build/target-repository/docs/rector_rules_overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 418 Rules Overview
# 419 Rules Overview

<br>

Expand Down Expand Up @@ -64,7 +64,7 @@

- [Transform](#transform) (34)

- [TypeDeclaration](#typedeclaration) (38)
- [TypeDeclaration](#typedeclaration) (39)

- [Visibility](#visibility) (3)

Expand Down Expand Up @@ -9533,6 +9533,26 @@ Add array shape exact types based on constant keys of array

<br>

### BinaryOpNullableToInstanceofRector

Change && and || between nullable objects to instanceof compares

- class: [`Rector\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector`](../rules/TypeDeclaration/Rector/BooleanAnd/BinaryOpNullableToInstanceofRector.php)

```diff
function someFunction(?SomeClass $someClass)
{
- if ($someClass && $someClass->someMethod()) {
+ if ($someClass instanceof SomeClass && $someClass->someMethod()) {
return 'yes';
}

return 'no';
}
```

<br>

### DeclareStrictTypesRector

Add declare(strict_types=1) if missing
Expand Down
2 changes: 2 additions & 0 deletions config/set/instanceof.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Rector\Config\RectorConfig;
use Rector\DeadCode\Rector\BinaryOp\RemoveDuplicatedInstanceOfRector;
use Rector\DeadCode\Rector\If_\RemoveDeadInstanceOfRector;
use Rector\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector;
use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector;
use Rector\TypeDeclaration\Rector\Ternary\FlipNegatedTernaryInstanceofRector;

Expand All @@ -20,5 +21,6 @@
RemoveDuplicatedInstanceOfRector::class,
RemoveDeadInstanceOfRector::class,
FlipNegatedTernaryInstanceofRector::class,
BinaryOpNullableToInstanceofRector::class,
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class BinaryOpNullableToInstanceofRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

function binaryOr(?SomeInstance $someClass)
{
if ($someClass || $someClass->someMethod()) {
return 'yes';
}

return 'no';
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

function binaryOr(?SomeInstance $someClass)
{
if ($someClass instanceof \Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance || $someClass->someMethod()) {
return 'yes';
}

return 'no';
}

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

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

function otherDirection(?SomeInstance $someClass)
{
if (mt_rand(0,1) && $someClass) {
return $someClass->someMethod();

}

return 'no';
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

function otherDirection(?SomeInstance $someClass)
{
if (mt_rand(0,1) && $someClass instanceof \Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance) {
return $someClass->someMethod();

}

return 'no';
}

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

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

function someFunction(?SomeInstance $someClass)
{
if ($someClass && $someClass->someMethod()) {
return 'yes';
}

return 'no';
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

function someFunction(?SomeInstance $someClass)
{
if ($someClass instanceof \Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance && $someClass->someMethod()) {
return 'yes';
}

return 'no';
}

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

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

final class WithProperty
{
private ?SomeInstance $someClass;

public function run()
{
if ($this->someClass && $this->someClass->someMethod()) {
return 'yes';
}

return 'no';
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Fixture;

use Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance;

final class WithProperty
{
private ?SomeInstance $someClass;

public function run()
{
if ($this->someClass instanceof \Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source\SomeInstance && $this->someClass->someMethod()) {
return 'yes';
}

return 'no';
}
}

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

namespace Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\Source;

final class SomeInstance
{
public function someMethod()
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->rule(BinaryOpNullableToInstanceofRector::class);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

declare(strict_types=1);

namespace Rector\TypeDeclaration\Rector\BooleanAnd;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp\BooleanAnd;
use PhpParser\Node\Expr\BinaryOp\BooleanOr;
use PhpParser\Node\Expr\Instanceof_;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeCombinator;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see \Rector\Tests\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector\BinaryOpNullableToInstanceofRectorTest
*/
final class BinaryOpNullableToInstanceofRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Change && and || between nullable objects to instanceof compares', [
new CodeSample(
<<<'CODE_SAMPLE'
function someFunction(?SomeClass $someClass)
{
if ($someClass && $someClass->someMethod()) {
return 'yes';
}
return 'no';
}
CODE_SAMPLE

,
<<<'CODE_SAMPLE'
function someFunction(?SomeClass $someClass)
{
if ($someClass instanceof SomeClass && $someClass->someMethod()) {
return 'yes';
}
return 'no';
}
CODE_SAMPLE
),
]);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [BooleanAnd::class, BooleanOr::class];
}

/**
* @param BooleanAnd|BooleanOr $node
*/
public function refactor(Node $node): ?Node
{
$nullableObjectType = $this->returnNullableObjectType($node->left);

if ($nullableObjectType instanceof ObjectType) {
$node->left = $this->createExprInstanceof($node->left, $nullableObjectType);

return $node;
}

$nullableObjectType = $this->returnNullableObjectType($node->right);

if ($nullableObjectType instanceof ObjectType) {
$node->right = $this->createExprInstanceof($node->right, $nullableObjectType);

return $node;
}

return null;
}

private function returnNullableObjectType(Expr $expr): ObjectType|null
{
$exprType = $this->getType($expr);

$baseType = TypeCombinator::removeNull($exprType);
if (! $baseType instanceof ObjectType) {
return null;
}

return $baseType;
}

private function createExprInstanceof(Expr $expr, ObjectType $objectType): Instanceof_
{
$fullyQualified = new FullyQualified($objectType->getClassName());
return new Instanceof_($expr, $fullyQualified);
}
}
2 changes: 2 additions & 0 deletions utils/Command/MissingInSetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Contract\Rector\DeprecatedRectorInterface;
use Rector\DeadCode\Rector\StmtsAwareInterface\RemoveJustPropertyFetchRector;
use Rector\TypeDeclaration\Rector\BooleanAnd\BinaryOpNullableToInstanceofRector;
use Rector\TypeDeclaration\Rector\ClassMethod\FalseReturnClassMethodToNullableRector;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;
use Rector\TypeDeclaration\Rector\Ternary\FlipNegatedTernaryInstanceofRector;
Expand Down Expand Up @@ -49,6 +50,7 @@ final class MissingInSetCommand extends Command
// optional
DataProviderArrayItemsNewlinedRector::class,
FlipNegatedTernaryInstanceofRector::class,
BinaryOpNullableToInstanceofRector::class,
];

/**
Expand Down

0 comments on commit a4c1d7e

Please sign in to comment.