[CodeQuality] Fix default array param in CallableThisArrayToAnonymousFunctionRector (#2527)

* fix default array param

* [ci-review] Rector Rectify

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Tomas Votruba 2022-06-17 21:19:34 +02:00 committed by GitHub
parent b31c654cca
commit 7129067a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 87 deletions

View File

@ -10,7 +10,6 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
use Symplify\PackageBuilder\Console\Command\CommandNaming;
require __DIR__ . '/../vendor/autoload.php';

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\Tests\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Fixture;
final class DefaultOfArray
{
public function run($values)
{
usort($values, [$this, 'sortMe']);
}
public function sortMe($values = [])
{
}
}
?>
-----
<?php
namespace Rector\Tests\CodeQuality\Rector\Array_\CallableThisArrayToAnonymousFunctionRector\Fixture;
final class DefaultOfArray
{
public function run($values)
{
usort($values, function ($values = []) {
return $this->sortMe($values);
});
}
public function sortMe($values = [])
{
}
}
?>

View File

@ -183,6 +183,7 @@ CODE_SAMPLE
$switchCases[] = $lastCase;
}
/** @var Case_ $lastCase */
$lastCase->stmts = $this->createSwitchStmts($node, $matchArm, $parentNode);
} else {
$stmts = $this->createSwitchStmts($node, $matchArm, $parentNode);

View File

@ -13,7 +13,6 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ClosureUse;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
@ -38,15 +37,14 @@ use PHPStan\Reflection\Php\PhpMethodReflection;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\VoidType;
use Rector\Core\Contract\PhpParser\NodePrinterInterface;
use Rector\Core\PhpParser\AstResolver;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\Core\PhpParser\Parser\InlineCodeParser;
use Rector\Core\PhpParser\Parser\SimplePhpParser;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Php72\NodeManipulator\ClosureNestedUsesDecorator;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind;
use Rector\StaticTypeMapper\StaticTypeMapper;
use ReflectionParameter;
@ -68,9 +66,8 @@ final class AnonymousFunctionFactory
private readonly StaticTypeMapper $staticTypeMapper,
private readonly SimpleCallableNodeTraverser $simpleCallableNodeTraverser,
private readonly SimplePhpParser $simplePhpParser,
private readonly NodeComparator $nodeComparator,
private readonly ClosureNestedUsesDecorator $closureNestedUsesDecorator,
private readonly AstResolver $astResolver,
private readonly NodePrinterInterface $nodePrinter,
private readonly PrivatesAccessor $privatesAccessor,
private readonly InlineCodeParser $inlineCodeParser
) {
@ -96,7 +93,10 @@ final class AnonymousFunctionFactory
}
foreach ($useVariables as $useVariable) {
$anonymousFunctionNode = $this->applyNestedUses($anonymousFunctionNode, $useVariable);
$anonymousFunctionNode = $this->closureNestedUsesDecorator->applyNestedUses(
$anonymousFunctionNode,
$useVariable
);
$anonymousFunctionNode->uses[] = new ClosureUse($useVariable);
}
@ -113,10 +113,10 @@ final class AnonymousFunctionFactory
/** @var FunctionVariantWithPhpDocs $functionVariantWithPhpDoc */
$functionVariantWithPhpDoc = ParametersAcceptorSelector::selectSingle($phpMethodReflection->getVariants());
$anonymousFunction = new Closure();
$newParams = $this->createParams($phpMethodReflection, $functionVariantWithPhpDoc->getParameters());
$anonymousFunction->params = $newParams;
$anonymousFunction = new Closure([
'params' => $newParams,
]);
$innerMethodCall = $this->createInnerMethodCall($phpMethodReflection, $expr, $newParams);
if ($innerMethodCall === null) {
@ -128,6 +128,7 @@ final class AnonymousFunctionFactory
$functionVariantWithPhpDoc->getReturnType(),
TypeKind::RETURN
);
$anonymousFunction->returnType = $returnType;
}
@ -189,78 +190,6 @@ final class AnonymousFunctionFactory
return $anonymousFunction;
}
/**
* @param ClosureUse[] $uses
* @return ClosureUse[]
*/
private function cleanClosureUses(array $uses): array
{
$uniqueUses = [];
foreach ($uses as $use) {
if (! is_string($use->var->name)) {
continue;
}
$variableName = $use->var->name;
if (array_key_exists($variableName, $uniqueUses)) {
continue;
}
$uniqueUses[$variableName] = $use;
}
return array_values($uniqueUses);
}
private function applyNestedUses(Closure $anonymousFunctionNode, Variable $useVariable): Closure
{
$parent = $this->betterNodeFinder->findParentType($useVariable, Closure::class);
if (! $parent instanceof Closure) {
return $anonymousFunctionNode;
}
$paramNames = $this->nodeNameResolver->getNames($parent->params);
if ($this->nodeNameResolver->isNames($useVariable, $paramNames)) {
return $anonymousFunctionNode;
}
$anonymousFunctionNode = clone $anonymousFunctionNode;
while ($parent instanceof Closure) {
$parentOfParent = $this->betterNodeFinder->findParentType($parent, Closure::class);
$uses = [];
while ($parentOfParent instanceof Closure) {
$uses = $this->collectUsesEqual($parentOfParent, $uses, $useVariable);
$parentOfParent = $this->betterNodeFinder->findParentType($parentOfParent, Closure::class);
}
$uses = array_merge($parent->uses, $uses);
$uses = $this->cleanClosureUses($uses);
$parent->uses = $uses;
$parent = $this->betterNodeFinder->findParentType($parent, Closure::class);
}
return $anonymousFunctionNode;
}
/**
* @param ClosureUse[] $uses
* @return ClosureUse[]
*/
private function collectUsesEqual(Closure $closure, array $uses, Variable $useVariable): array
{
foreach ($closure->params as $param) {
if ($this->nodeComparator->areNodesEqual($param->var, $useVariable)) {
$uses[] = new ClosureUse($param->var);
}
}
return $uses;
}
/**
* @param Param[] $paramNodes
* @return string[]
@ -377,8 +306,22 @@ final class AnonymousFunctionFactory
return;
}
$printDefaultValue = $this->nodePrinter->print($classMethod->params[$key]->default);
$param->default = new ConstFetch(new Name($printDefaultValue));
$paramDefaultExpr = $classMethod->params[$key]->default;
if (! $paramDefaultExpr instanceof Expr) {
return;
}
// reset original node, to allow the printer to re-use the expr
$paramDefaultExpr->setAttribute(AttributeKey::ORIGINAL_NODE, null);
$this->simpleCallableNodeTraverser->traverseNodesWithCallable(
$paramDefaultExpr,
function (Node $node): Node {
$node->setAttribute(AttributeKey::ORIGINAL_NODE, null);
return $node;
}
);
$param->default = $paramDefaultExpr;
}
/**
@ -427,9 +370,11 @@ final class AnonymousFunctionFactory
}
$name = new Name($className);
return $name->isSpecialClassName()
? $name
: new FullyQualified($className);
if ($name->isSpecialClassName()) {
return $name;
}
return new FullyQualified($className);
}
private function resolveExpr(Expr $expr): New_ | Expr | null

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Rector\Php72\NodeManipulator;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\ClosureUse;
use PhpParser\Node\Expr\Variable;
use Rector\Core\PhpParser\Comparing\NodeComparator;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeNameResolver\NodeNameResolver;
final class ClosureNestedUsesDecorator
{
public function __construct(
private readonly BetterNodeFinder $betterNodeFinder,
private readonly NodeNameResolver $nodeNameResolver,
private readonly NodeComparator $nodeComparator,
) {
}
public function applyNestedUses(Closure $anonymousFunctionNode, Variable $useVariable): Closure
{
$parent = $this->betterNodeFinder->findParentType($useVariable, Closure::class);
if (! $parent instanceof Closure) {
return $anonymousFunctionNode;
}
$paramNames = $this->nodeNameResolver->getNames($parent->params);
if ($this->nodeNameResolver->isNames($useVariable, $paramNames)) {
return $anonymousFunctionNode;
}
$anonymousFunctionNode = clone $anonymousFunctionNode;
while ($parent instanceof Closure) {
$parentOfParent = $this->betterNodeFinder->findParentType($parent, Closure::class);
$uses = [];
while ($parentOfParent instanceof Closure) {
$uses = $this->collectUsesEqual($parentOfParent, $uses, $useVariable);
$parentOfParent = $this->betterNodeFinder->findParentType($parentOfParent, Closure::class);
}
$uses = array_merge($parent->uses, $uses);
$uses = $this->cleanClosureUses($uses);
$parent->uses = $uses;
$parent = $this->betterNodeFinder->findParentType($parent, Closure::class);
}
return $anonymousFunctionNode;
}
/**
* @param ClosureUse[] $uses
* @return ClosureUse[]
*/
private function collectUsesEqual(Closure $closure, array $uses, Variable $useVariable): array
{
foreach ($closure->params as $param) {
if ($this->nodeComparator->areNodesEqual($param->var, $useVariable)) {
$uses[] = new ClosureUse($param->var);
}
}
return $uses;
}
/**
* @param ClosureUse[] $uses
* @return ClosureUse[]
*/
private function cleanClosureUses(array $uses): array
{
$uniqueUses = [];
foreach ($uses as $use) {
if (! is_string($use->var->name)) {
continue;
}
$variableName = $use->var->name;
if (array_key_exists($variableName, $uniqueUses)) {
continue;
}
$uniqueUses[$variableName] = $use;
}
return array_values($uniqueUses);
}
}