rector/packages/Php/src/Rector/Assign/AssignArrayToStringRector.php

181 lines
5.0 KiB
PHP
Raw Normal View History

2018-10-09 10:18:42 +00:00
<?php declare(strict_types=1);
namespace Rector\Php\Rector\Assign;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Cast\Array_ as ArrayCast;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\PropertyProperty;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use Rector\Utils\BetterNodeFinder;
use Rector\Utils\NodeTraverser\CallableNodeTraverser;
/**
* This depends on the context. We need more real app datas.
*
* @see https://3v4l.org/ABDNv
* @see https://stackoverflow.com/a/41000866/1348344
*/
final class AssignArrayToStringRector extends AbstractRector
{
/**
2018-10-31 11:53:59 +00:00
* @var PropertyProperty[]
2018-10-09 10:18:42 +00:00
*/
2018-10-31 11:53:59 +00:00
private $emptyStringPropertyNodes = [];
2018-10-09 10:18:42 +00:00
/**
2018-10-31 11:53:59 +00:00
* @var CallableNodeTraverser
2018-10-09 10:18:42 +00:00
*/
2018-10-31 11:53:59 +00:00
private $callableNodeTraverser;
2018-10-09 10:18:42 +00:00
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(CallableNodeTraverser $callableNodeTraverser, BetterNodeFinder $betterNodeFinder)
{
2018-10-09 10:18:42 +00:00
$this->callableNodeTraverser = $callableNodeTraverser;
$this->betterNodeFinder = $betterNodeFinder;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'String cannot be turned into array by assignment anymore',
[new CodeSample(
<<<'CODE_SAMPLE'
$string = '';
$string[] = 1;
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
$string = [];
$string[] = 1;
CODE_SAMPLE
)]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Assign::class];
}
/**
* @param Assign $node
2018-10-09 10:18:42 +00:00
*/
public function refactor(Node $node): ?Node
2018-10-09 10:18:42 +00:00
{
// only array with no explicit key assign, e.g. "$value[] = 5";
if (! $node->var instanceof ArrayDimFetch || $node->var->dim !== null) {
return null;
2018-10-09 10:18:42 +00:00
}
$arrayDimFetchNode = $node->var;
2018-10-09 10:18:42 +00:00
/** @var Variable|PropertyFetch|StaticPropertyFetch|Expr $variableNode */
$variableNode = $arrayDimFetchNode->var;
// set default value to property
if ($variableNode instanceof PropertyFetch || $variableNode instanceof StaticPropertyFetch) {
if ($this->processProperty($variableNode)) {
return $node;
2018-10-09 10:18:42 +00:00
}
}
// fallback to variable, property or static property = '' set
if ($this->processVariable($node, $variableNode)) {
return $node;
2018-10-09 10:18:42 +00:00
}
// there is "$string[] = ...;", which would cause error in PHP 7+
// fallback - if no array init found, retype to (array)
$retypeArrayAssignNode = new Assign($arrayDimFetchNode->var, new ArrayCast($arrayDimFetchNode->var));
$this->addNodeAfterNode(clone $node, $node);
2018-10-09 10:18:42 +00:00
return $retypeArrayAssignNode;
}
/**
* @param Node[] $nodes
*/
public function beforeTraverse(array $nodes): void
{
// collect all known "{anything} = '';" assigns
$this->callableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node): void {
if ($node instanceof PropertyProperty && $node->default && $this->isEmptyStringNode($node->default)) {
$this->emptyStringPropertyNodes[] = $node;
}
});
}
private function isEmptyStringNode(Node $node): bool
{
return $node instanceof String_ && $node->value === '';
}
/**
* @param Variable|PropertyFetch|StaticPropertyFetch|Expr $variableNode
*/
private function processVariable(Assign $assignNode, Expr $variableNode): bool
{
if (! $this->isStringType($variableNode)) {
2018-10-09 10:18:42 +00:00
return false;
}
$variableNodeContent = $this->print($variableNode);
2018-10-09 10:18:42 +00:00
$variableAssign = $this->betterNodeFinder->findFirstPrevious($assignNode, function (Node $node) use (
$variableNodeContent
) {
if (! $node instanceof Assign) {
return false;
}
if ($this->print($node->var) !== $variableNodeContent) {
2018-10-09 10:18:42 +00:00
return false;
}
// we look for variable assign = string
return $this->isEmptyStringNode($node->expr);
2018-10-09 10:18:42 +00:00
});
if ($variableAssign instanceof Assign) {
$variableAssign->expr = new Array_();
return true;
}
return false;
}
/**
* @param PropertyFetch|StaticPropertyFetch $propertyNode
*/
private function processProperty(Node $propertyNode): bool
{
foreach ($this->emptyStringPropertyNodes as $emptyStringPropertyNode) {
2018-10-22 10:43:10 +00:00
if ($this->getName($emptyStringPropertyNode) === $this->getName($propertyNode)) {
2018-10-09 10:18:42 +00:00
$emptyStringPropertyNode->default = new Array_();
return true;
}
}
return false;
}
}