mirror of
https://github.com/rectorphp/rector.git
synced 2024-05-28 23:10:51 +00:00
[PHP 8.0] Remove NullsafeOperatorRector, as often breaking and promoting non-early returns and losen types (#900)
This commit is contained in:
parent
b5516368d4
commit
ab182ff80b
|
@ -1,4 +1,4 @@
|
|||
# 477 Rules Overview
|
||||
# 476 Rules Overview
|
||||
|
||||
<br>
|
||||
|
||||
|
@ -72,7 +72,7 @@
|
|||
|
||||
- [Php74](#php74) (15)
|
||||
|
||||
- [Php80](#php80) (17)
|
||||
- [Php80](#php80) (16)
|
||||
|
||||
- [Php81](#php81) (5)
|
||||
|
||||
|
@ -7848,30 +7848,6 @@ Change ternary type resolve to `get_debug_type()`
|
|||
|
||||
<br>
|
||||
|
||||
### NullsafeOperatorRector
|
||||
|
||||
Change if null check with nullsafe operator ?-> with full short circuiting
|
||||
|
||||
- class: [`Rector\Php80\Rector\If_\NullsafeOperatorRector`](../rules/Php80/Rector/If_/NullsafeOperatorRector.php)
|
||||
|
||||
```diff
|
||||
class SomeClass
|
||||
{
|
||||
public function run($someObject)
|
||||
{
|
||||
- $someObject2 = $someObject->mayFail1();
|
||||
- if ($someObject2 === null) {
|
||||
- return null;
|
||||
- }
|
||||
-
|
||||
- return $someObject2->mayFail2();
|
||||
+ return $someObject->mayFail1()?->mayFail2();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
### OptionalParametersAfterRequiredRector
|
||||
|
||||
Move required parameters after optional ones
|
||||
|
|
|
@ -86,7 +86,6 @@ parameters:
|
|||
paths:
|
||||
- rules/Php70/EregToPcreTransformer.php
|
||||
- packages/NodeTypeResolver/NodeTypeResolver.php
|
||||
- rules/Php80/Rector/If_/NullsafeOperatorRector.php
|
||||
- rules/Renaming/NodeManipulator/ClassRenamer.php
|
||||
- rules/DowngradePhp72/Rector/FuncCall/DowngradePregUnmatchedAsNullConstantRector.php
|
||||
|
||||
|
@ -172,12 +171,6 @@ parameters:
|
|||
|
||||
- '#Access to an undefined property PhpParser\\Node\\Expr\\Error\|PhpParser\\Node\\Expr\\Variable\:\:\$name#'
|
||||
|
||||
# @todo loop magic, resolve later
|
||||
-
|
||||
message: '#Access to an undefined property PhpParser\\Node\:\:\$expr#'
|
||||
paths:
|
||||
- rules/Php80/Rector/If_/NullsafeOperatorRector.php
|
||||
|
||||
-
|
||||
message: '#Comparison operation "<" between 0 and 2 is always true#'
|
||||
paths:
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class DefaultValue
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
if ($this->endWeekDate !== null) {
|
||||
$param = $this->endWeekDate->format();
|
||||
} else {
|
||||
$param = "No data";
|
||||
}
|
||||
return $param;
|
||||
}
|
||||
}
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class DefaultValue
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$param = $this->endWeekDate?->format() ?? "No data";
|
||||
return $param;
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class Fixture
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $o2->mayFail2();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class Fixture
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
return $o->mayFail1()?->mayFail2();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureMultipleIf
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail2 = $o2->mayFail2();
|
||||
if ($mayFail2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail3 = $mayFail2->mayFail3();
|
||||
if ($mayFail3 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail4 = $mayFail3->mayFail4();
|
||||
if ($mayFail4 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $mayFail4->mayFail5();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureMultipleIf
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
return $o->mayFail1()?->mayFail2()?->mayFail3()?->mayFail4()?->mayFail5();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNextIsProperty
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->property1;
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $o2->property2;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNextIsProperty
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
return $o->property1?->property2;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNoReturnLast
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$o2->mayFail2();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNoReturnLast
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o->mayFail1()?->mayFail2();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNotIdentical
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
if ($o !== null) {
|
||||
$user = $o->user;
|
||||
if ($user !== null) {
|
||||
$address = $user->getAddress();
|
||||
if ($address !== null) {
|
||||
$country = $address->getCountry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNotIdentical
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$country = $o?->user?->getAddress()?->getCountry();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNotIdenticalProcessOnlyDirectIfAfterAssign
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
if ($o !== null) {
|
||||
$user = $o->user;
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
if ($user !== null) {
|
||||
$address = $user->getAddress();
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
if ($address !== null) {
|
||||
$country = $address->getCountry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNotIdenticalProcessOnlyDirectIfAfterAssign
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
if ($o !== null) {
|
||||
$user = $o->user;
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
if ($user !== null) {
|
||||
$address = $user->getAddress();
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
$country = $address?->getCountry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNotIdenticalYoda
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
if (null !== $o) {
|
||||
$user = $o->user;
|
||||
if (null !== $user) {
|
||||
$address = $user->getAddress();
|
||||
if (null !== $address) {
|
||||
$country = $address->getCountry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureNotIdenticalYoda
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$country = $o?->user?->getAddress()?->getCountry();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureProcessOnlyDirectUsageAfterIf
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
$mayFail2 = $o2->mayFail2();
|
||||
if ($mayFail2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail3 = $mayFail2->mayFail3();
|
||||
if ($mayFail3 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $mayFail3->mayFail4();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureProcessOnlyDirectUsageAfterIf
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
return $o2->mayFail2()?->mayFail3()?->mayFail4();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureTernaryIsNotNull {
|
||||
public function ternary() {
|
||||
$b = $a !== null ? $a->getB() : null;
|
||||
$c = $a !== null ? $a->c : null;
|
||||
}
|
||||
}
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureTernaryIsNotNull {
|
||||
public function ternary() {
|
||||
$b = $a?->getB();
|
||||
$c = $a?->c;
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureTernaryIsNull {
|
||||
public function ternary() {
|
||||
$b = $a === null ? null : $a->getB();
|
||||
$c = $a === null ? null : $a->c;
|
||||
}
|
||||
}
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureTernaryIsNull {
|
||||
public function ternary() {
|
||||
$b = $a?->getB();
|
||||
$c = $a?->c;
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureYoda
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if (null === $o2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $o2->mayFail2();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureYoda
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
return $o->mayFail1()?->mayFail2();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,61 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
abstract class MaybeNull
|
||||
{
|
||||
public function addCover(Date $date, Money $amount = null): void
|
||||
{
|
||||
/** @var Money|null $currentAmount */
|
||||
$currentAmount = $this->covers->get($date, null);
|
||||
|
||||
if ($currentAmount !== null) {
|
||||
$amount = $currentAmount->add($amount);
|
||||
}
|
||||
|
||||
$this->covers->put($date, $amount);
|
||||
}
|
||||
|
||||
public function addCover2(Date $date, ?Money $amount): void
|
||||
{
|
||||
/** @var Money|null $currentAmount */
|
||||
$currentAmount = $this->covers->get($date, null);
|
||||
|
||||
if ($currentAmount !== null) {
|
||||
$amount = $currentAmount->add($amount);
|
||||
}
|
||||
|
||||
$this->covers->put($date, $amount);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
abstract class MaybeNull
|
||||
{
|
||||
public function addCover(Date $date, Money $amount = null): void
|
||||
{
|
||||
/** @var Money|null $currentAmount */
|
||||
$currentAmount = $this->covers->get($date, null);
|
||||
|
||||
$amount = $currentAmount?->add($amount);
|
||||
|
||||
$this->covers->put($date, $amount);
|
||||
}
|
||||
|
||||
public function addCover2(Date $date, ?Money $amount): void
|
||||
{
|
||||
/** @var Money|null $currentAmount */
|
||||
$currentAmount = $this->covers->get($date, null);
|
||||
|
||||
$amount = $currentAmount?->add($amount);
|
||||
|
||||
$this->covers->put($date, $amount);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class MethodArgs
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
if ($this->endWeekDate !== null) {
|
||||
$param = $this->endWeekDate->format('Y-m-d');
|
||||
} else {
|
||||
$param = null;
|
||||
}
|
||||
return $param;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class MethodArgs
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$param = $this->endWeekDate?->format('Y-m-d');
|
||||
return $param;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class MethodArgsMultiCall
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1('a');
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $o2->mayFail2('b');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class MethodArgsMultiCall
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
return $o->mayFail1('a')?->mayFail2('b');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class SkipDefaultValueMoreStmt
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
if ($this->endWeekDate !== null) {
|
||||
$param = $this->endWeekDate->format();
|
||||
} else {
|
||||
$param = "No data";
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
}
|
||||
return $param;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class SkipIfNoDirectUsage
|
||||
{
|
||||
private $myService;
|
||||
|
||||
public function withReturn($prop1, $prop2)
|
||||
{
|
||||
$prop3 = $this->getProp3($prop1);
|
||||
|
||||
if (null === $prop3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->myService->myMethod($prop1, $prop2, $prop3);
|
||||
}
|
||||
|
||||
public function withoutReturn($prop1, $prop2)
|
||||
{
|
||||
$prop3 = $this->getProp3($prop1);
|
||||
|
||||
if (null === $prop3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->myService->myMethod($prop1, $prop2, $prop3);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
abstract class SkipNeverNullValue
|
||||
{
|
||||
public function addCover(Date $date, Money $amount): void
|
||||
{
|
||||
/** @var Money|null $currentAmount */
|
||||
$currentAmount = $this->covers->get($date, null);
|
||||
|
||||
if ($currentAmount !== null) {
|
||||
$amount = $currentAmount->add($amount);
|
||||
}
|
||||
|
||||
$this->covers->put($date, $amount);
|
||||
}
|
||||
|
||||
public function addCover2(Date $date): void
|
||||
{
|
||||
$amount = new Money();
|
||||
|
||||
/** @var Money|null $currentAmount */
|
||||
$currentAmount = $this->covers->get($date, null);
|
||||
|
||||
if ($currentAmount !== null) {
|
||||
$amount = $currentAmount->add($amount);
|
||||
}
|
||||
|
||||
$this->covers->put($date, $amount);
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SkipNoDirectIfAfterAssignment
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail2 = $o2->mayFail2();
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
if ($mayFail2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail3 = $mayFail2->mayFail3();
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
if ($mayFail3 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail4 = $mayFail3->mayFail4();
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
if ($mayFail4 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $mayFail4->mayFail5();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SkipNoDirectUsageAfterIf
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
$mayFail2 = $o2->mayFail2();
|
||||
if ($mayFail2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
$mayFail3 = $mayFail2->mayFail3();
|
||||
if ($mayFail3 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
$mayFail4 = $mayFail3->mayFail4();
|
||||
if ($mayFail4 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
return $mayFail4->mayFail5();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SkipNoDirectUsageAfterIfInNext
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail2 = $o2->mayFail2();
|
||||
if ($mayFail2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail3 = $mayFail2->mayFail3();
|
||||
if ($mayFail3 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
return $mayFail3->mayFail4();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SkipNoDirectUsageAfterIfInPrevAndNext
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
$o2 = $o->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
$mayFail2 = $o2->mayFail2();
|
||||
if ($mayFail2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayFail3 = $mayFail2->mayFail3();
|
||||
if ($mayFail3 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
return $mayFail3->mayFail4();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SkipNoResultUsage
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $baseField;
|
||||
|
||||
public function f($o)
|
||||
{
|
||||
$value = $this->baseField->getValue($o);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->baseField->select($value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SkipNotIdenticalNoDirectAssignAfterIf
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
if ($o !== null) {
|
||||
|
||||
echo 'STATEMENT HERE';
|
||||
|
||||
$user = $o->user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SkipNotIdenticalNotNull
|
||||
{
|
||||
public function f($o)
|
||||
{
|
||||
if ($o !== 'abc') {
|
||||
$user = $o->user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class SkipNotIdentifierAndReturnBooleanOp
|
||||
{
|
||||
private static function foo(string $f, ?string $b, bool $c): ?string
|
||||
{
|
||||
$e = self::check($f, $c);
|
||||
if ($e === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($b !== null) {
|
||||
$a = self::check($b, $c);
|
||||
if ($a === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $a . $d;
|
||||
}
|
||||
|
||||
return $d;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php declare(strict_types = 1);
|
||||
|
||||
function test(?string $argument): void
|
||||
{
|
||||
/** @var string[] $array */
|
||||
$array = [];
|
||||
|
||||
if ($argument !== null) {
|
||||
$array[] = $argument;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class SkipReturnNullInEitherCase
|
||||
{
|
||||
public function f()
|
||||
{
|
||||
$otherVar = null;
|
||||
|
||||
if ($otherVar === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class FixtureTernaryIsNull {
|
||||
public function ternary() {
|
||||
$b = $a === null ?: $a->getB();
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class SkipTernaryNoDirectUsage
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
return $someObject === null ? null : \Carbon\Carbon::parse($someObject)->toDateTimeString();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
abstract class SkipTernaryNoDirectUsage2
|
||||
{
|
||||
public ?string $value;
|
||||
|
||||
public function run(): ?string
|
||||
{
|
||||
return $this->value !== null ? $this->doSomething($this->value) : null;
|
||||
}
|
||||
|
||||
abstract public function doSomething(string $value);
|
||||
}
|
||||
?>
|
|
@ -1,15 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
final class SkipTernaryReturnsNonNullInIfAndElse
|
||||
{
|
||||
public function ternary()
|
||||
{
|
||||
$b = $a === null ? 'if case' : 'else case';
|
||||
$b = $a !== null ? 'if case 2' : 'else case 2';
|
||||
$b = $a === null ? 'is null' : $a->getB();
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SoooManyNullChecks
|
||||
{
|
||||
public function f($main)
|
||||
{
|
||||
$o2 = $main->mayFail1();
|
||||
if ($o2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$o3 = $o2->mayFail2();
|
||||
if (null === $o3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$o4 = $o3->mayFail3();
|
||||
if (null === $o4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $o4->mayFail4();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\Fixture;
|
||||
|
||||
class SoooManyNullChecks
|
||||
{
|
||||
public function f($main)
|
||||
{
|
||||
return $main->mayFail1()?->mayFail2()?->mayFail3()?->mayFail4();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class NullsafeOperatorRectorTest 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';
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Php80\Rector\If_\NullsafeOperatorRector;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(NullsafeOperatorRector::class);
|
||||
};
|
|
@ -1,600 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php80\Rector\If_;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\BinaryOp\Coalesce;
|
||||
use PhpParser\Node\Expr\BinaryOp\Identical;
|
||||
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\NullsafeMethodCall;
|
||||
use PhpParser\Node\Expr\NullsafePropertyFetch;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Ternary;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Else_;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use Rector\Core\NodeAnalyzer\ParamAnalyzer;
|
||||
use Rector\Core\NodeManipulator\IfManipulator;
|
||||
use Rector\Core\NodeManipulator\NullsafeManipulator;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @changelog https://wiki.php.net/rfc/nullsafe_operator
|
||||
* @see \Rector\Tests\Php80\Rector\If_\NullsafeOperatorRector\NullsafeOperatorRectorTest
|
||||
*/
|
||||
final class NullsafeOperatorRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var array<class-string<Expr>>
|
||||
*/
|
||||
private const ALLOWED_NEXT_NODE_EXPRS = [
|
||||
MethodCall::class,
|
||||
PropertyFetch::class,
|
||||
NullsafeMethodCall::class,
|
||||
NullsafePropertyFetch::class,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private IfManipulator $ifManipulator,
|
||||
private NullsafeManipulator $nullsafeManipulator,
|
||||
private ParamAnalyzer $paramAnalyzer
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition(
|
||||
'Change if null check with nullsafe operator ?-> with full short circuiting',
|
||||
[
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function run($someObject)
|
||||
{
|
||||
$someObject2 = $someObject->mayFail1();
|
||||
if ($someObject2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $someObject2->mayFail2();
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function run($someObject)
|
||||
{
|
||||
return $someObject->mayFail1()?->mayFail2();
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [If_::class, Ternary::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param If_|Ternary $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node instanceof If_) {
|
||||
return $this->handleIfNode($node);
|
||||
}
|
||||
|
||||
return $this->handleTernaryNode($node);
|
||||
}
|
||||
|
||||
private function handleIfNode(If_ $if): ?Node
|
||||
{
|
||||
$processNullSafeOperator = $this->processNullSafeOperatorIdentical($if);
|
||||
if ($processNullSafeOperator !== null) {
|
||||
/** @var Expression $prevNode */
|
||||
$prevNode = $if->getAttribute(AttributeKey::PREVIOUS_NODE);
|
||||
$this->removeNode($prevNode);
|
||||
|
||||
return $processNullSafeOperator;
|
||||
}
|
||||
|
||||
return $this->processNullSafeOperatorNotIdentical($if);
|
||||
}
|
||||
|
||||
private function processNullSafeOperatorIdentical(If_ $if, bool $isStartIf = true): ?Node
|
||||
{
|
||||
$comparedNode = $this->ifManipulator->matchIfValueReturnValue($if);
|
||||
if (! $comparedNode instanceof Expr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$prevNode = $if->getAttribute(AttributeKey::PREVIOUS_NODE);
|
||||
$nextNode = $if->getAttribute(AttributeKey::NEXT_NODE);
|
||||
|
||||
if (! $nextNode instanceof Node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $prevNode instanceof Expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$prevExpr = $prevNode->expr;
|
||||
if (! $prevExpr instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->ifManipulator->isIfCondUsingAssignIdenticalVariable($if, $prevExpr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->hasIndirectUsageOnNextOfIf($prevExpr->var, $nextNode)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->processAssign($prevExpr, $prevNode, $nextNode, $isStartIf);
|
||||
}
|
||||
|
||||
private function hasIndirectUsageOnNextOfIf(Expr $expr, Node $nextNode): bool
|
||||
{
|
||||
if (! $nextNode instanceof Return_ && ! $nextNode instanceof Expression) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($nextNode->expr instanceof PropertyFetch) {
|
||||
return ! $this->nodeComparator->areNodesEqual($expr, $nextNode->expr->var);
|
||||
}
|
||||
|
||||
if ($nextNode->expr instanceof MethodCall) {
|
||||
return ! $this->nodeComparator->areNodesEqual($expr, $nextNode->expr->var);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function shouldSkipNeverNullDefinedBefore(Assign $assign): bool
|
||||
{
|
||||
$variable = $assign->var;
|
||||
$isReassign = (bool) $this->betterNodeFinder->findFirstPreviousOfNode(
|
||||
$assign,
|
||||
fn (Node $subNode): bool => $subNode instanceof Assign && $this->nodeComparator->areNodesEqual(
|
||||
$subNode->var,
|
||||
$variable
|
||||
) && ! $this->valueResolver->isNull($subNode->expr)
|
||||
);
|
||||
|
||||
if ($isReassign) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$functionLike = $this->betterNodeFinder->findParentType($assign, FunctionLike::class);
|
||||
if (! $functionLike instanceof FunctionLike) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$params = $functionLike->getParams();
|
||||
foreach ($params as $param) {
|
||||
if ($this->nodeComparator->areNodesEqual($param->var, $variable)) {
|
||||
return $this->isNotNullableAndNotHasDefaultNullValue($param);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isNotNullableAndNotHasDefaultNullValue(Param $param): bool
|
||||
{
|
||||
if ($this->paramAnalyzer->isNullable($param)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->paramAnalyzer->hasDefaultNull($param);
|
||||
}
|
||||
|
||||
private function verifyAssignMatchIfNotNullNextAssignment(If_ $if): ?Assign
|
||||
{
|
||||
$assign = $this->ifManipulator->matchIfNotNullNextAssignment($if);
|
||||
if (! $assign instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->shouldSkipNeverNullDefinedBefore($assign)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $assign;
|
||||
}
|
||||
|
||||
private function processNullSafeOperatorNotIdentical(If_ $if, ?Expr $expr = null): ?Node
|
||||
{
|
||||
$assign = $this->verifyAssignMatchIfNotNullNextAssignment($if);
|
||||
if (! $assign instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$assignExpr = $assign->expr;
|
||||
if ($this->ifManipulator->isIfCondUsingAssignNotIdenticalVariable($if, $assignExpr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expression = $assign->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if (! $expression instanceof Expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nextNode = $expression->getAttribute(AttributeKey::NEXT_NODE);
|
||||
$nullSafe = $this->nullsafeManipulator->processNullSafeExpr($assignExpr);
|
||||
if ($nullSafe === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($expr !== null) {
|
||||
/** @var Identifier $nullSafeIdentifier */
|
||||
$nullSafeIdentifier = $nullSafe->name;
|
||||
/** @var NullsafeMethodCall|NullsafePropertyFetch $nullSafe */
|
||||
$nullSafe = $this->nullsafeManipulator->processNullSafeExprResult($expr, $nullSafeIdentifier);
|
||||
}
|
||||
|
||||
$nextOfNextNode = $this->processIfMayInNextNode($nextNode);
|
||||
if ($nextOfNextNode !== null) {
|
||||
return $nextOfNextNode;
|
||||
}
|
||||
|
||||
if (! $nextNode instanceof If_) {
|
||||
$nullSafe = $this->verifyDefaultValueInElse($if, $nullSafe, $assign);
|
||||
if ($nullSafe === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Assign($assign->var, $nullSafe);
|
||||
}
|
||||
|
||||
return $this->processNullSafeOperatorNotIdentical($nextNode, $nullSafe);
|
||||
}
|
||||
|
||||
private function verifyDefaultValueInElse(
|
||||
If_ $if,
|
||||
NullsafeMethodCall|NullsafePropertyFetch $nullSafe,
|
||||
Assign $assign
|
||||
): NullsafeMethodCall|NullsafePropertyFetch|Coalesce|null {
|
||||
if (! $if->else instanceof Else_) {
|
||||
return $nullSafe;
|
||||
}
|
||||
|
||||
if (count($if->else->stmts) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expression = $if->else->stmts[0];
|
||||
if (! $expression instanceof Expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expressionAssign = $expression->expr;
|
||||
if (! $expressionAssign instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->nodeComparator->areNodesEqual($expressionAssign->var, $assign->var)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->valueResolver->isNull($expressionAssign->expr)) {
|
||||
return $nullSafe;
|
||||
}
|
||||
|
||||
return new Coalesce($nullSafe, $expressionAssign->expr);
|
||||
}
|
||||
|
||||
private function processAssign(Assign $assign, Expression $prevExpression, Node $nextNode, bool $isStartIf): ?Node
|
||||
{
|
||||
if ($this->shouldProcessAssignInCurrentNode($assign, $nextNode)) {
|
||||
return $this->processAssignInCurrentNode($assign, $prevExpression, $nextNode, $isStartIf);
|
||||
}
|
||||
|
||||
return $this->processAssignMayInNextNode($nextNode);
|
||||
}
|
||||
|
||||
private function shouldProcessAssignInCurrentNode(Assign $assign, Node $nextNode): bool
|
||||
{
|
||||
if (! $nextNode instanceof Return_ && ! $nextNode instanceof Expression) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($nextNode->expr instanceof Assign) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $nextNode->expr instanceof Node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->isMethodCallOrPropertyFetch($assign->expr)) {
|
||||
return ! $this->valueResolver->isNull($nextNode->expr);
|
||||
}
|
||||
|
||||
/** @var MethodCall|PropertyFetch $expr */
|
||||
$expr = $assign->expr;
|
||||
if (! $this->isMethodCallOrPropertyFetch($expr->var)) {
|
||||
return ! $this->valueResolver->isNull($expr);
|
||||
}
|
||||
|
||||
if (! $this->isMethodCallOrPropertyFetch($nextNode->expr)) {
|
||||
return ! $this->valueResolver->isNull($nextNode->expr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function processIfMayInNextNode(?Node $nextNode = null): ?Node
|
||||
{
|
||||
if (! $nextNode instanceof Node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nextOfNextNode = $nextNode->getAttribute(AttributeKey::NEXT_NODE);
|
||||
while ($nextOfNextNode) {
|
||||
if ($nextOfNextNode instanceof If_) {
|
||||
/** @var If_ $beforeIf */
|
||||
$beforeIf = $nextOfNextNode->getAttribute(AttributeKey::PARENT_NODE);
|
||||
$nullSafe = $this->processNullSafeOperatorNotIdentical($nextOfNextNode);
|
||||
if (! $nullSafe instanceof NullsafeMethodCall && ! $nullSafe instanceof PropertyFetch) {
|
||||
return $beforeIf;
|
||||
}
|
||||
|
||||
$beforeIf->stmts[count($beforeIf->stmts) - 1] = new Expression($nullSafe);
|
||||
return $beforeIf;
|
||||
}
|
||||
|
||||
$nextOfNextNode = $nextOfNextNode->getAttribute(AttributeKey::NEXT_NODE);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function processAssignInCurrentNode(
|
||||
Assign $assign,
|
||||
Expression $expression,
|
||||
Node $nextNode,
|
||||
bool $isStartIf
|
||||
): ?Node {
|
||||
if (! in_array($nextNode->expr::class, self::ALLOWED_NEXT_NODE_EXPRS, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$assignNullSafe = $isStartIf
|
||||
? $assign->expr
|
||||
: $this->nullsafeManipulator->processNullSafeExpr($assign->expr);
|
||||
$nullSafe = $this->nullsafeManipulator->processNullSafeExprResult($assignNullSafe, $nextNode->expr->name);
|
||||
|
||||
$prevAssign = $expression->getAttribute(AttributeKey::PREVIOUS_NODE);
|
||||
if ($prevAssign instanceof If_) {
|
||||
$nullSafe = $this->getNullSafeOnPrevAssignIsIf($prevAssign, $nextNode, $nullSafe);
|
||||
}
|
||||
|
||||
$this->removeNode($nextNode);
|
||||
|
||||
if ($nextNode instanceof Return_) {
|
||||
$nextNode->expr = $nullSafe;
|
||||
return $nextNode;
|
||||
}
|
||||
|
||||
return $nullSafe;
|
||||
}
|
||||
|
||||
private function processAssignMayInNextNode(Node $nextNode): ?Node
|
||||
{
|
||||
if (! $nextNode instanceof Expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $nextNode->expr instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mayNextIf = $nextNode->getAttribute(AttributeKey::NEXT_NODE);
|
||||
if (! $mayNextIf instanceof If_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->ifManipulator->isIfCondUsingAssignIdenticalVariable($mayNextIf, $nextNode->expr)) {
|
||||
return $this->processNullSafeOperatorIdentical($mayNextIf, false);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getNullSafeOnPrevAssignIsIf(If_ $if, Node $nextNode, ?Expr $expr): ?Expr
|
||||
{
|
||||
$prevIf = $if->getAttribute(AttributeKey::PREVIOUS_NODE);
|
||||
if (! $prevIf instanceof Expression) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
if (! $this->ifManipulator->isIfCondUsingAssignIdenticalVariable($if, $prevIf->expr)) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
$start = $prevIf;
|
||||
|
||||
while ($prevIf instanceof Expression) {
|
||||
$expressionNode = $prevIf->expr;
|
||||
if (! $expressionNode instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expr = $this->nullsafeManipulator->processNullSafeExpr($expressionNode->expr);
|
||||
|
||||
/** @var Node $prevPrevIf */
|
||||
$prevPrevIf = $prevIf->getAttribute(AttributeKey::PREVIOUS_NODE);
|
||||
/** @var Node $prevPrevPrevIf */
|
||||
$prevPrevPrevIf = $prevPrevIf->getAttribute(AttributeKey::PREVIOUS_NODE);
|
||||
|
||||
if (! $prevPrevPrevIf instanceof Expression && $prevPrevPrevIf !== null) {
|
||||
$start = $this->getPreviousIf($prevPrevPrevIf);
|
||||
break;
|
||||
}
|
||||
|
||||
$prevIf = $prevPrevPrevIf;
|
||||
}
|
||||
|
||||
if (! $expr instanceof NullsafeMethodCall && ! $expr instanceof NullsafePropertyFetch) {
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/** @var Expr $expr */
|
||||
$expr = $expr->var->getAttribute(AttributeKey::PARENT_NODE);
|
||||
$expr = $this->getNullSafeAfterStartUntilBeforeEnd($start, $expr);
|
||||
|
||||
return $this->nullsafeManipulator->processNullSafeExprResult($expr, $nextNode->expr->name);
|
||||
}
|
||||
|
||||
private function getPreviousIf(Node $node): ?Node
|
||||
{
|
||||
/** @var If_ $if */
|
||||
$if = $node->getAttribute(AttributeKey::NEXT_NODE);
|
||||
|
||||
/** @var Expression $expression */
|
||||
$expression = $if->getAttribute(AttributeKey::NEXT_NODE);
|
||||
|
||||
/** @var Expression $nextExpression */
|
||||
$nextExpression = $expression->getAttribute(AttributeKey::NEXT_NODE);
|
||||
|
||||
return $nextExpression->getAttribute(AttributeKey::NEXT_NODE);
|
||||
}
|
||||
|
||||
private function getNullSafeAfterStartUntilBeforeEnd(?Node $node, ?Expr $expr): ?Expr
|
||||
{
|
||||
while ($node) {
|
||||
$expr = $this->nullsafeManipulator->processNullSafeExprResult($expr, $node->expr->expr->name);
|
||||
|
||||
$node = $node->getAttribute(AttributeKey::NEXT_NODE);
|
||||
while ($node) {
|
||||
/** @var If_ $if */
|
||||
$if = $node->getAttribute(AttributeKey::NEXT_NODE);
|
||||
if ($node instanceof Expression && $this->ifManipulator->isIfCondUsingAssignIdenticalVariable(
|
||||
$if,
|
||||
$node->expr
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$node = $node->getAttribute(AttributeKey::NEXT_NODE);
|
||||
}
|
||||
}
|
||||
|
||||
return $expr;
|
||||
}
|
||||
|
||||
private function handleTernaryNode(Ternary $ternary): ?Node
|
||||
{
|
||||
if ($this->shouldSkipTernary($ternary)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nullSafeElse = $this->nullsafeManipulator->processNullSafeExpr($ternary->else);
|
||||
|
||||
if ($nullSafeElse !== null) {
|
||||
return $nullSafeElse;
|
||||
}
|
||||
|
||||
if ($ternary->if === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->nullsafeManipulator->processNullSafeExpr($ternary->if);
|
||||
}
|
||||
|
||||
private function shouldSkipTernary(Ternary $ternary): bool
|
||||
{
|
||||
if (! $this->canTernaryReturnNull($ternary)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $ternary->cond instanceof Identical && ! $ternary->cond instanceof NotIdentical) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $this->hasNullComparison($ternary->cond)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->hasIndirectUsageOnElse($ternary->cond, $ternary->if, $ternary->else);
|
||||
}
|
||||
|
||||
private function hasIndirectUsageOnElse(Identical|NotIdentical $cond, ?Expr $if, Expr $expr): bool
|
||||
{
|
||||
$left = $cond->left;
|
||||
$right = $cond->right;
|
||||
|
||||
$object = $this->valueResolver->isNull($left)
|
||||
? $right
|
||||
: $left;
|
||||
|
||||
if ($this->valueResolver->isNull($expr)) {
|
||||
if ($this->isMethodCallOrPropertyFetch($if)) {
|
||||
/** @var MethodCall|PropertyFetch $if */
|
||||
return ! $this->nodeComparator->areNodesEqual($if->var, $object);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isMethodCallOrPropertyFetch($expr)) {
|
||||
/** @var MethodCall|PropertyFetch $expr */
|
||||
return ! $this->nodeComparator->areNodesEqual($expr->var, $object);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isMethodCallOrPropertyFetch(?Expr $expr): bool
|
||||
{
|
||||
return $expr instanceof MethodCall || $expr instanceof PropertyFetch;
|
||||
}
|
||||
|
||||
private function hasNullComparison(NotIdentical|Identical $check): bool
|
||||
{
|
||||
if ($this->valueResolver->isNull($check->left)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->valueResolver->isNull($check->right);
|
||||
}
|
||||
|
||||
private function canTernaryReturnNull(Ternary $ternary): bool
|
||||
{
|
||||
if ($this->valueResolver->isNull($ternary->else)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($ternary->if === null) {
|
||||
// $foo === null ?: 'xx' returns true if $foo is null
|
||||
// therefore it does not return null in case of the elvis operator
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->valueResolver->isNull($ternary->if);
|
||||
}
|
||||
}
|
|
@ -11,8 +11,6 @@ use PhpParser\Node\Expr\BinaryOp\Identical;
|
|||
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
|
||||
use PhpParser\Node\Expr\Exit_;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
|
@ -63,40 +61,6 @@ final class IfManipulator
|
|||
return $this->matchComparedAndReturnedNode($if->cond, $insideIfNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches:
|
||||
*
|
||||
* if (<$value> !== null) {
|
||||
* $anotherValue = $value;
|
||||
* }
|
||||
*/
|
||||
public function matchIfNotNullNextAssignment(If_ $if): ?Assign
|
||||
{
|
||||
if ($if->stmts === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $if->cond instanceof NotIdentical) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->isNotIdenticalNullCompare($if->cond)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$insideIfNode = $if->stmts[0];
|
||||
if (! $insideIfNode instanceof Expression) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$assignedExpr = $insideIfNode->expr;
|
||||
if (! $assignedExpr instanceof Assign) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $assignedExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches:
|
||||
*
|
||||
|
@ -282,32 +246,6 @@ final class IfManipulator
|
|||
return count($if->stmts) === 1;
|
||||
}
|
||||
|
||||
public function isIfCondUsingAssignIdenticalVariable(Node $if, Node $assign): bool
|
||||
{
|
||||
if (! ($if instanceof If_ && $assign instanceof Assign)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $if->cond instanceof Identical) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->nodeComparator->areNodesEqual($this->getIfCondVar($if), $assign->var);
|
||||
}
|
||||
|
||||
public function isIfCondUsingAssignNotIdenticalVariable(If_ $if, Node $node): bool
|
||||
{
|
||||
if (! $node instanceof MethodCall && ! $node instanceof PropertyFetch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $if->cond instanceof NotIdentical) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->nodeComparator->areNodesEqual($this->getIfCondVar($if), $node->var);
|
||||
}
|
||||
|
||||
public function isIfWithoutElseAndElseIfs(If_ $if): bool
|
||||
{
|
||||
if ($if->else !== null) {
|
||||
|
@ -359,19 +297,6 @@ final class IfManipulator
|
|||
return null;
|
||||
}
|
||||
|
||||
private function isNotIdenticalNullCompare(NotIdentical $notIdentical): bool
|
||||
{
|
||||
if ($this->nodeComparator->areNodesEqual($notIdentical->left, $notIdentical->right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->valueResolver->isNull($notIdentical->right)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->valueResolver->isNull($notIdentical->left);
|
||||
}
|
||||
|
||||
private function isIfWithOnlyStmtIf(If_ $if): bool
|
||||
{
|
||||
if (! $this->isIfWithoutElseAndElseIfs($if)) {
|
||||
|
@ -393,12 +318,4 @@ final class IfManipulator
|
|||
|
||||
return is_a($stmts[0], $desiredType);
|
||||
}
|
||||
|
||||
private function getIfCondVar(If_ $if): Expr
|
||||
{
|
||||
/** @var Identical|NotIdentical $ifCond */
|
||||
$ifCond = $if->cond;
|
||||
|
||||
return $this->valueResolver->isNull($ifCond->left) ? $ifCond->right : $ifCond->left;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Core\NodeManipulator;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\NullsafeMethodCall;
|
||||
use PhpParser\Node\Expr\NullsafePropertyFetch;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Identifier;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
final class NullsafeManipulator
|
||||
{
|
||||
public function processNullSafeExpr(Expr $expr): NullsafeMethodCall | NullsafePropertyFetch | null
|
||||
{
|
||||
if ($expr instanceof MethodCall) {
|
||||
return new NullsafeMethodCall($expr->var, $expr->name, $expr->args);
|
||||
}
|
||||
|
||||
if ($expr instanceof PropertyFetch) {
|
||||
return new NullsafePropertyFetch($expr->var, $expr->name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Identifier|mixed $nextExprIdentifier
|
||||
*/
|
||||
public function processNullSafeExprResult(?Expr $expr, $nextExprIdentifier): ?Expr
|
||||
{
|
||||
if (! $nextExprIdentifier instanceof Identifier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($expr === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parentIdentifier = $nextExprIdentifier->getAttribute(AttributeKey::PARENT_NODE);
|
||||
|
||||
if ($parentIdentifier instanceof MethodCall || $parentIdentifier instanceof NullsafeMethodCall) {
|
||||
return new NullsafeMethodCall($expr, $nextExprIdentifier, $parentIdentifier->args);
|
||||
}
|
||||
|
||||
return new NullsafePropertyFetch($expr, $nextExprIdentifier);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user