Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrowFunctionParamArrayWhereDimFetchRector\Fixture;

final class HandleUsort
{
public function run(array $items)
{
usort(
$items,
fn ($a, $b): int => $a['key'] <=> $b['key']
);
}
}

?>
-----
<?php

namespace Rector\Tests\TypeDeclaration\Rector\FuncCall\AddArrowFunctionParamArrayWhereDimFetchRector\Fixture;

final class HandleUsort
{
public function run(array $items)
{
usort(
$items,
fn (array $a, array $b): int => $a['key'] <=> $b['key']
);
}
}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,19 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
if ($node->isFirstClassCallable()) {
return null;
}

if (count($node->getArgs()) !== 2) {
return null;
}

foreach (NativeFuncCallPositions::ARRAY_AND_CALLBACK_POSITIONS as $functionName => $positions) {
if (! $this->isName($node, $functionName)) {
continue;
}

if ($node->isFirstClassCallable()) {
continue;
}

$arrayPosition = $positions['array'];
$callbackPosition = $positions['callback'];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
use PhpParser\Node;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\ArrowFunction;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Identifier;
use Rector\PhpParser\Node\BetterNodeFinder;
use Rector\Rector\AbstractRector;
use Rector\TypeDeclaration\Enum\NativeFuncCallPositions;
use Rector\ValueObject\PhpVersionFeature;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
Expand All @@ -20,6 +23,11 @@
*/
final class AddArrowFunctionParamArrayWhereDimFetchRector extends AbstractRector implements MinPhpVersionInterface
{
public function __construct(
private BetterNodeFinder $betterNodeFinder
) {
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add function/closure param array type, if dim fetch is inside', [
Expand Down Expand Up @@ -53,48 +61,148 @@ public function getNodeTypes(): array
*/
public function refactor(Node $node): ?Node
{
if (! $this->isName($node, 'array_map')) {
return null;
}

if ($node->isFirstClassCallable()) {
return null;
}

$firstArgExpr = $node->getArgs()[0]
->value;
if (! $firstArgExpr instanceof ArrowFunction) {
if (count($node->getArgs()) !== 2) {
return null;
}

$arrowFunction = $firstArgExpr;
$arrowFunctionParam = $arrowFunction->getParams()[0];
$hasChanged = false;

// param is known already
if ($arrowFunctionParam->type instanceof Node) {
return null;
}
foreach (NativeFuncCallPositions::ARRAY_AND_CALLBACK_POSITIONS as $functionName => $positions) {
if (! $this->isName($node, $functionName)) {
continue;
}

if (! $arrowFunction->expr instanceof ArrayDimFetch) {
return null;
}
$callbackPosition = $positions['callback'];

$closureExpr = $node->getArgs()[$callbackPosition]->value;
if (! $closureExpr instanceof ArrowFunction && ! $closureExpr instanceof Closure) {
continue;
}

$isArrayVariableNames = $this->resolveIsArrayVariables($closureExpr);
$instanceofVariableNames = $this->resolveInstanceofVariables($closureExpr);
$skippedVariableNames = array_merge($isArrayVariableNames, $instanceofVariableNames);

$dimFetchVariableNames = $this->resolveDimFetchVariableNames($closureExpr);

foreach ($closureExpr->getParams() as $closureParam) {
if ($closureParam->type instanceof \PhpParser\Node) {
// param is known already
continue;
}

$var = $arrowFunction->expr;
while ($var instanceof ArrayDimFetch) {
$var = $var->var;
// skip is_array() checked variables
if ($this->isNames($closureParam->var, $skippedVariableNames)) {
continue;
}

if (! $this->isNames($closureParam->var, $dimFetchVariableNames)) {
continue;
}

$hasChanged = true;
$closureParam->type = new Identifier('array');
}
}

if (! $this->nodeComparator->areNodesEqual($var, $arrowFunctionParam->var)) {
if ($hasChanged === false) {
return null;
}

$arrowFunctionParam->type = new Identifier('array');

return $node;
}

public function provideMinPhpVersion(): int
{
return PhpVersionFeature::SCALAR_TYPES;
}

/**
* @return string[]
*/
private function resolveDimFetchVariableNames(Closure|ArrowFunction $closureExpr): array
{
if ($closureExpr instanceof ArrowFunction) {
$closureNodes = [$closureExpr->expr];
} else {
$closureNodes = $closureExpr->stmts;
}

/** @var ArrayDimFetch[] $arrayDimFetches */
$arrayDimFetches = $this->betterNodeFinder->findInstancesOfScoped($closureNodes, ArrayDimFetch::class);

$usedDimFetchVariableNames = [];

foreach ($arrayDimFetches as $arrayDimFetch) {
if ($arrayDimFetch->var instanceof Node\Expr\Variable) {
$usedDimFetchVariableNames[] = (string) $this->getName($arrayDimFetch->var);
}
}

return $usedDimFetchVariableNames;
}

/**
* @return string[]
*/
private function resolveIsArrayVariables(Closure|ArrowFunction $closureExpr): array
{
if ($closureExpr instanceof ArrowFunction) {
$closureNodes = [$closureExpr->expr];
} else {
$closureNodes = $closureExpr->stmts;
}

/** @var FuncCall[] $funcCalls */
$funcCalls = $this->betterNodeFinder->findInstancesOfScoped($closureNodes, FuncCall::class);

$variableNames = [];

foreach ($funcCalls as $funcCall) {
if (! $this->isName($funcCall, 'is_array')) {
continue;
}

$firstArgExpr = $funcCall->getArgs()[0]
->value;
if (! $firstArgExpr instanceof Node\Expr\Variable) {
continue;
}

$variableNames[] = (string) $this->getName($firstArgExpr);
}

return $variableNames;
}

/**
* @return string[]
*/
private function resolveInstanceofVariables(Closure|ArrowFunction $closureExpr): array
{
if ($closureExpr instanceof ArrowFunction) {
$closureNodes = [$closureExpr->expr];
} else {
$closureNodes = $closureExpr->stmts;
}

/** @var Node\Expr\Instanceof_[] $instanceOfs */
$instanceOfs = $this->betterNodeFinder->findInstancesOfScoped($closureNodes, Node\Expr\Instanceof_::class);

$variableNames = [];

foreach ($instanceOfs as $instanceOf) {
if (! $instanceOf->expr instanceof Node\Expr\Variable) {
continue;
}

$variableNames[] = (string) $this->getName($instanceOf->expr);
}

return $variableNames;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

namespace Rector\TypeDeclaration\Rector\FunctionLike;

use PhpParser\Node\VariadicPlaceholder;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Param;
use PhpParser\Node\VariadicPlaceholder;
use PHPStan\Reflection\Native\NativeFunctionReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
Expand Down
Loading