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;
|
|
|
|
|
2018-10-17 10:24:00 +00:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-10-15 04:36:58 +00:00
|
|
|
* @param Assign $node
|
2018-10-09 10:18:42 +00:00
|
|
|
*/
|
2018-10-15 04:36:58 +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";
|
2018-10-15 04:36:58 +00:00
|
|
|
if (! $node->var instanceof ArrayDimFetch || $node->var->dim !== null) {
|
2018-10-21 10:19:14 +00:00
|
|
|
return null;
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
|
|
|
|
2018-10-15 04:36:58 +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)) {
|
2018-10-15 04:36:58 +00:00
|
|
|
return $node;
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// fallback to variable, property or static property = '' set
|
2018-10-15 04:36:58 +00:00
|
|
|
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));
|
|
|
|
|
2018-10-15 04:36:58 +00:00
|
|
|
$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
|
|
|
|
{
|
2018-10-17 10:24:00 +00:00
|
|
|
if (! $this->isStringType($variableNode)) {
|
2018-10-09 10:18:42 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-15 10:01:21 +00:00
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2018-10-15 10:01:21 +00:00
|
|
|
if ($this->print($node->var) !== $variableNodeContent) {
|
2018-10-09 10:18:42 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// we look for variable assign = string
|
2018-10-27 12:54:27 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|