[DowngradePhp54] Add DowngradeThisInClosureRector (#1995)

This commit is contained in:
Abdul Malik Ikhsan 2022-04-02 22:40:58 +07:00 committed by GitHub
parent e077d7c3fb
commit 9f9b29c741
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 437 additions and 3 deletions

View File

@ -1,4 +1,4 @@
# 506 Rules Overview
# 508 Rules Overview
<br>
@ -20,7 +20,7 @@
- [DowngradePhp53](#downgradephp53) (1)
- [DowngradePhp54](#downgradephp54) (6)
- [DowngradePhp54](#downgradephp54) (7)
- [DowngradePhp55](#downgradephp55) (4)
@ -38,7 +38,7 @@
- [DowngradePhp80](#downgradephp80) (28)
- [DowngradePhp81](#downgradephp81) (8)
- [DowngradePhp81](#downgradephp81) (9)
- [EarlyReturn](#earlyreturn) (11)
@ -3916,6 +3916,33 @@ Remove static from closure
<br>
### DowngradeThisInClosureRector
Downgrade `$this->` inside Closure to use assigned `$self` = `$this` before Closure
- class: [`Rector\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector`](../rules/DowngradePhp54/Rector/Closure/DowngradeThisInClosureRector.php)
```diff
class SomeClass
{
public $property = 'test';
public function run()
{
- $function = function () {
- echo $this->property;
+ $self = $this;
+ $function = function () use ($self) {
+ echo $self->property;
};
$function();
}
}
```
<br>
### ShortArrayToLongArrayRector
Replace short arrays by long arrays
@ -5826,6 +5853,35 @@ Removes union type property type definition, adding `@var` annotations instead.
## DowngradePhp81
### DowngradeArrayIsListRector
Replace `array_is_list()` function
- class: [`Rector\DowngradePhp81\Rector\FuncCall\DowngradeArrayIsListRector`](../rules/DowngradePhp81/Rector/FuncCall/DowngradeArrayIsListRector.php)
```diff
-array_is_list([1 => 'apple', 'orange']);
+$arrayIsList = function (array $array) : bool {
+ if (function_exists('array_is_list')) {
+ return array_is_list($array);
+ }
+ if ($array === []) {
+ return true;
+ }
+ $current_key = 0;
+ foreach ($array as $key => $noop) {
+ if ($key !== $current_key) {
+ return false;
+ }
+ ++$current_key;
+ }
+ return true;
+};
+$arrayIsList([1 => 'apple', 'orange']);
```
<br>
### DowngradeArraySpreadStringKeyRector
Replace array spread with string key to array_merge function

View File

@ -6,6 +6,7 @@ use Rector\Core\Configuration\Option;
use Rector\Core\ValueObject\PhpVersion;
use Rector\DowngradePhp54\Rector\Array_\ShortArrayToLongArrayRector;
use Rector\DowngradePhp54\Rector\Closure\DowngradeStaticClosureRector;
use Rector\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector;
use Rector\DowngradePhp54\Rector\FuncCall\DowngradeIndirectCallByArrayRector;
use Rector\DowngradePhp54\Rector\FunctionLike\DowngradeCallableTypeDeclarationRector;
use Rector\DowngradePhp54\Rector\LNumber\DowngradeBinaryNotationRector;
@ -23,4 +24,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(DowngradeCallableTypeDeclarationRector::class);
$services->set(DowngradeBinaryNotationRector::class);
$services->set(DowngradeInstanceMethodCallRector::class);
$services->set(DowngradeThisInClosureRector::class);
};

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
final class DowngradeThisInClosureRectorTest 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,46 @@
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class DeepClosure
{
public $property = 'test';
public function run()
{
$function = function () {
$f = function () {
echo $this->property;
};
$f();
};
$function();
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class DeepClosure
{
public $property = 'test';
public function run()
{
$self = $this;
$function = function () use($self) {
$f = function () use($self) {
echo $self->property;
};
$f();
};
$function();
}
}
?>

View File

@ -0,0 +1,40 @@
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class Fixture
{
public $property = 'test';
public function run()
{
$function = function () {
echo $this->property;
};
$function();
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class Fixture
{
public $property = 'test';
public function run()
{
$self = $this;
$function = function () use($self) {
echo $self->property;
};
$function();
}
}
?>

View File

@ -0,0 +1,42 @@
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class SelfVariableExists
{
public $property = 'test';
public function run()
{
$self = 'test';
$function = function () {
echo $this->property;
};
$function();
}
}
?>
-----
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class SelfVariableExists
{
public $property = 'test';
public function run()
{
$self = 'test';
$self2 = $this;
$function = function () use($self2) {
echo $self2->property;
};
$function();
}
}
?>

View File

@ -0,0 +1,22 @@
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class SkipInAnonymousClass
{
public $property = 'test';
public function run()
{
$function = function () {
new class {
public function test()
{
echo $this->property;
}
};
};
$function();
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\Fixture;
class SkipPrivateProperty
{
private $property = 'test';
public function run()
{
$function = function () {
echo $this->property;
};
$function();
}
}

View File

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

View File

@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp54\Rector\Closure;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ClosureUse;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Function_;
use PHPStan\Reflection\Php\PhpPropertyReflection;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\Reflection\ReflectionResolver;
use Rector\Naming\Naming\VariableNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @changelog https://wiki.php.net/rfc/closures/object-extension
*
* @see \Rector\Tests\DowngradePhp54\Rector\Closure\DowngradeThisInClosureRector\DowngradeThisInClosureRectorTest
*/
final class DowngradeThisInClosureRector extends AbstractRector
{
public function __construct(
private readonly VariableNaming $variableNaming,
private readonly ReflectionResolver $reflectionResolver
) {
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Downgrade $this-> inside Closure to use assigned $self = $this before Closure', [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass
{
public $property = 'test';
public function run()
{
$function = function () {
echo $this->property;
};
$function();
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
{
public $property = 'test';
public function run()
{
$self = $this;
$function = function () use ($self) {
echo $self->property;
};
$function();
}
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Closure::class];
}
/**
* @param Closure $node
*/
public function refactor(Node $node): ?Node
{
$closureParentFunctionLike = $this->betterNodeFinder->findParentByTypes(
$node,
[ClassMethod::class, Function_::class]
);
/** @var PropertyFetch[] $propertyFetches */
$propertyFetches = $this->resolvePropertyFetches($node, $closureParentFunctionLike);
if ($propertyFetches === []) {
return null;
}
$scope = $node->getAttribute(AttributeKey::SCOPE);
$selfVariable = new Variable($this->variableNaming->createCountedValueName('self', $scope));
$expression = new Expression(new Assign($selfVariable, new Variable('this')));
$currentStmt = $node->getAttribute(AttributeKey::CURRENT_STATEMENT);
$this->nodesToAddCollector->addNodeBeforeNode($expression, $currentStmt);
$this->traverseNodesWithCallable($node, function (Node $subNode) use ($selfVariable): ?Closure {
if (! $subNode instanceof Closure) {
return null;
}
$subNode->uses = array_merge($subNode->uses, [new ClosureUse($selfVariable)]);
return $subNode;
});
foreach ($propertyFetches as $propertyFetch) {
$propertyFetch->var = $selfVariable;
}
return $node;
}
/**
* @return PropertyFetch[]
*/
private function resolvePropertyFetches(Closure $node, ?FunctionLike $closureParentFunctionLike): array
{
/** @var PropertyFetch[] $propertyFetches */
$propertyFetches = $this->betterNodeFinder->find($node->stmts, function (Node $subNode) use (
$closureParentFunctionLike
): bool {
// multiple deep Closure may access $this, unless its parent is not Closure
$parent = $this->betterNodeFinder->findParentByTypes($subNode, [ClassMethod::class, Function_::class]);
if ($parent instanceof FunctionLike && $parent !== $closureParentFunctionLike) {
return false;
}
if (! $subNode instanceof PropertyFetch) {
return false;
}
if (! $subNode->var instanceof Variable) {
return false;
}
if (! $this->nodeNameResolver->isName($subNode->var, 'this')) {
return false;
}
$phpPropertyReflection = $this->reflectionResolver->resolvePropertyReflectionFromPropertyFetch($subNode);
if (! $phpPropertyReflection instanceof PhpPropertyReflection) {
return false;
}
return $phpPropertyReflection->isPublic();
});
return $propertyFetches;
}
}