Skip to content

Commit

Permalink
Refactor away PARENT_NODE from AssignArrayToStringRector (#3983)
Browse files Browse the repository at this point in the history
Co-authored-by: GitHub Action <actions@github.com>
  • Loading branch information
TomasVotruba and actions-user committed May 27, 2023
1 parent c70a5b8 commit 18d8df6
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 169 deletions.

This file was deleted.

Expand Up @@ -2,15 +2,15 @@

namespace Rector\Tests\Php71\Rector\Assign\AssignArrayToStringRector\Fixture;

class SkipCastUndefinedVar
final class SkipAlreadyArray
{
public function fun()
{
$array = [];

$array[] = 'foo';
$array[] = 'bar';

return $array;
}
}

?>

This file was deleted.

@@ -0,0 +1,15 @@
<?php

namespace Rector\Tests\Php71\Rector\Assign\AssignArrayToStringRector\Fixture;

final class SkipDirectStringAssign
{
public function fun()
{
$array = '';

$array = 'foo';

return $array;
}
}
179 changes: 128 additions & 51 deletions rules/Php71/Rector/Assign/AssignArrayToStringRector.php
Expand Up @@ -9,14 +9,17 @@
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use Rector\Core\PhpParser\NodeFinder\PropertyFetchFinder;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
Expand All @@ -28,6 +31,11 @@
*/
final class AssignArrayToStringRector extends AbstractRector implements MinPhpVersionInterface
{
public function __construct(
private readonly PropertyFetchFinder $propertyFetchFinder,
) {
}

public function provideMinPhpVersion(): int
{
return PhpVersionFeature::NO_ASSIGN_ARRAY_TO_STRING;
Expand Down Expand Up @@ -56,89 +64,158 @@ public function getRuleDefinition(): RuleDefinition
*/
public function getNodeTypes(): array
{
return [Assign::class, Property::class];
return [Assign::class, Class_::class];
}

/**
* @param Assign|Property $node
* @param Assign|Class_ $node
*/
public function refactor(Node $node): ?Node
{
$defaultExpr = $this->resolveDefaultValueExpr($node);
if (! $defaultExpr instanceof Expr) {
return null;
if ($node instanceof Class_) {
return $this->refactorClass($node);
}

if (! $this->isEmptyString($defaultExpr)) {
return null;
}

$assignedVar = $this->resolveAssignedVar($node);
return $this->refactorAssign($node);
}

// 1. variable!
$shouldRetype = false;
private function isEmptyString(Expr $expr): bool
{
if (! $expr instanceof String_) {
return false;
}

/** @var array<Variable|PropertyFetch|StaticPropertyFetch> $exprUsages */
$exprUsages = $this->betterNodeFinder->findSameNamedExprs($assignedVar);
return $expr->value === '';
}

// detect if is part of variable assign?
foreach ($exprUsages as $exprUsage) {
$parentNode = $exprUsage->getAttribute(AttributeKey::PARENT_NODE);
if (! $parentNode instanceof ArrayDimFetch) {
continue;
}
private function refactorClass(Class_ $class): ?Class_
{
$hasChanged = false;

$firstAssign = $this->betterNodeFinder->findParentType($parentNode, Assign::class);
if (! $firstAssign instanceof Assign) {
foreach ($class->getProperties() as $property) {
if (! $this->hasPropertyDefaultEmptyString($property)) {
continue;
}

// skip explicit assigns
if ($parentNode->dim instanceof Expr) {
continue;
}
$arrayDimFetches = $this->propertyFetchFinder->findLocalPropertyArrayDimFetchesAssignsByName(
$class,
$property
);

$shouldRetype = true;
break;
}
foreach ($arrayDimFetches as $arrayDimFetch) {
if ($arrayDimFetch->dim instanceof Expr) {
continue;
}

if (! $shouldRetype) {
return null;
$property->props[0]->default = new Array_();
$hasChanged = true;
}
}

if ($node instanceof Property) {
$node->props[0]->default = new Array_();
return $node;
if ($hasChanged) {
return $class;
}

$node->expr = new Array_();
return $node;
return null;
}

private function resolveDefaultValueExpr(Assign | Property $node): ?Expr
private function hasPropertyDefaultEmptyString(Property $property): bool
{
if ($node instanceof Property) {
return $node->props[0]->default;
$defaultExpr = $property->props[0]->default;
if (! $defaultExpr instanceof Expr) {
return false;
}

return $node->expr;
return $this->isEmptyString($defaultExpr);
}

private function isEmptyString(Expr $expr): bool
/**
* @return ArrayDimFetch[]
*/
private function findSameNamedVariableAssigns(Variable $variable): array
{
if (! $expr instanceof String_) {
return false;
// assign of empty string to something
$scopeStmt = $this->findParentScope($variable);
if (! $scopeStmt instanceof Stmt) {
return [];
}

return $expr->value === '';
$variableName = $this->nodeNameResolver->getName($variable);
if ($variableName === null) {
return [];
}

$assignedArrayDimFetches = [];

$this->traverseNodesWithCallable($scopeStmt, function (Node $node) use (
$variableName,
&$assignedArrayDimFetches
) {
if (! $node instanceof Assign) {
return null;
}

if (! $node->var instanceof ArrayDimFetch) {
return null;
}

$arrayDimFetch = $node->var;
if (! $arrayDimFetch->var instanceof Variable) {
return null;
}

if (! $this->isName($arrayDimFetch->var, $variableName)) {
return null;
}

$assignedArrayDimFetches[] = $arrayDimFetch;
});

return $assignedArrayDimFetches;
}

private function resolveAssignedVar(Assign | Property $node): Expr | Property
/**
* @return Function_|ClassMethod|Class_|Namespace_|null
*/
private function findParentScope(Variable $variable): Stmt|null
{
if ($node instanceof Assign) {
return $node->var;
return $this->betterNodeFinder->findParentByTypes($variable, [
Function_::class,
ClassMethod::class,
Class_::class,
Namespace_::class,
]);
}

private function refactorAssign(Assign $assign): ?Assign
{
if (! $this->isEmptyString($assign->expr)) {
return null;
}

if (! $assign->var instanceof Variable) {
return null;
}

$variableAssignArrayDimFetches = $this->findSameNamedVariableAssigns($assign->var);

$shouldRetype = false;

// detect if is part of variable assign?
foreach ($variableAssignArrayDimFetches as $variableAssignArrayDimFetch) {
if ($variableAssignArrayDimFetch->dim instanceof Expr) {
continue;
}

$shouldRetype = true;
break;
}

if (! $shouldRetype) {
return null;
}

return $node;
$assign->expr = new Array_();
return $assign;
}
}
2 changes: 1 addition & 1 deletion src/Kernel/RectorKernel.php
Expand Up @@ -17,7 +17,7 @@ final class RectorKernel
/**
* @var string
*/
private const CACHE_KEY = 'v35';
private const CACHE_KEY = 'v36';

private ContainerInterface|null $container = null;

Expand Down

0 comments on commit 18d8df6

Please sign in to comment.