2019-10-13 05:59:52 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2018-10-09 10:18:42 +00:00
|
|
|
|
2019-09-22 18:57:03 +00:00
|
|
|
namespace Rector\Php71\Rector\Assign;
|
2018-10-09 10:18:42 +00:00
|
|
|
|
|
|
|
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;
|
2019-01-18 02:41:58 +00:00
|
|
|
use PHPStan\Type\ArrayType;
|
|
|
|
use PHPStan\Type\Constant\ConstantStringType;
|
2019-03-10 23:47:43 +00:00
|
|
|
use PHPStan\Type\ErrorType;
|
2019-01-18 02:41:58 +00:00
|
|
|
use PHPStan\Type\MixedType;
|
|
|
|
use PHPStan\Type\StringType;
|
|
|
|
use PHPStan\Type\UnionType;
|
2020-02-06 21:48:18 +00:00
|
|
|
use Rector\Core\Rector\AbstractRector;
|
|
|
|
use Rector\Core\RectorDefinition\CodeSample;
|
|
|
|
use Rector\Core\RectorDefinition\RectorDefinition;
|
2018-10-09 10:18:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @see https://3v4l.org/ABDNv
|
|
|
|
* @see https://stackoverflow.com/a/41000866/1348344
|
2019-09-23 11:43:13 +00:00
|
|
|
* @see \Rector\Php71\Tests\Rector\Assign\AssignArrayToStringRector\AssignArrayToStringRectorTest
|
2018-10-09 10:18:42 +00:00
|
|
|
*/
|
|
|
|
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
|
|
|
|
|
|
|
public function getDefinition(): RectorDefinition
|
|
|
|
{
|
|
|
|
return new RectorDefinition(
|
|
|
|
'String cannot be turned into array by assignment anymore',
|
2019-09-18 10:16:31 +00:00
|
|
|
[new CodeSample(<<<'PHP'
|
2018-10-09 10:18:42 +00:00
|
|
|
$string = '';
|
|
|
|
$string[] = 1;
|
2019-09-18 06:14:35 +00:00
|
|
|
PHP
|
2019-09-18 10:16:31 +00:00
|
|
|
, <<<'PHP'
|
2018-10-09 10:18:42 +00:00
|
|
|
$string = [];
|
|
|
|
$string[] = 1;
|
2019-09-18 06:14:35 +00:00
|
|
|
PHP
|
2018-10-09 10:18:42 +00:00
|
|
|
)]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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
|
2020-01-19 19:45:01 +00:00
|
|
|
if (($variableNode instanceof PropertyFetch || $variableNode instanceof StaticPropertyFetch) &&
|
|
|
|
$this->processProperty($variableNode)
|
|
|
|
) {
|
|
|
|
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)
|
2020-06-29 21:19:37 +00:00
|
|
|
$assign = new Assign($arrayDimFetchNode->var, new ArrayCast($arrayDimFetchNode->var));
|
2018-10-09 10:18:42 +00:00
|
|
|
|
2018-10-15 04:36:58 +00:00
|
|
|
$this->addNodeAfterNode(clone $node, $node);
|
2018-10-09 10:18:42 +00:00
|
|
|
|
2020-06-29 21:19:37 +00:00
|
|
|
return $assign;
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Node[] $nodes
|
2018-11-09 18:32:50 +00:00
|
|
|
* @return Node[]|null
|
2018-10-09 10:18:42 +00:00
|
|
|
*/
|
2018-11-09 18:32:50 +00:00
|
|
|
public function beforeTraverse(array $nodes): ?array
|
2018-10-09 10:18:42 +00:00
|
|
|
{
|
|
|
|
// collect all known "{anything} = '';" assigns
|
2019-06-04 20:34:59 +00:00
|
|
|
$this->traverseNodesWithCallable($nodes, function (Node $node): void {
|
2018-10-09 10:18:42 +00:00
|
|
|
if ($node instanceof PropertyProperty && $node->default && $this->isEmptyStringNode($node->default)) {
|
|
|
|
$this->emptyStringPropertyNodes[] = $node;
|
|
|
|
}
|
|
|
|
});
|
2018-11-09 18:32:50 +00:00
|
|
|
|
|
|
|
return $nodes;
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
|
|
|
|
2018-10-31 15:34:37 +00:00
|
|
|
/**
|
|
|
|
* @param PropertyFetch|StaticPropertyFetch $propertyNode
|
|
|
|
*/
|
|
|
|
private function processProperty(Node $propertyNode): bool
|
2018-10-09 10:18:42 +00:00
|
|
|
{
|
2018-10-31 15:34:37 +00:00
|
|
|
foreach ($this->emptyStringPropertyNodes as $emptyStringPropertyNode) {
|
2019-03-10 23:47:43 +00:00
|
|
|
if ($this->areNamesEqual($emptyStringPropertyNode, $propertyNode)) {
|
2018-10-31 15:34:37 +00:00
|
|
|
$emptyStringPropertyNode->default = new Array_();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-02-22 17:25:31 +00:00
|
|
|
* @param Variable|PropertyFetch|StaticPropertyFetch|Expr $expr
|
2018-10-09 10:18:42 +00:00
|
|
|
*/
|
2019-02-22 17:25:31 +00:00
|
|
|
private function processVariable(Assign $assign, Expr $expr): bool
|
2018-10-09 10:18:42 +00:00
|
|
|
{
|
2019-09-22 18:57:03 +00:00
|
|
|
if ($this->shouldSkipVariable($expr)) {
|
2018-11-02 16:04:35 +00:00
|
|
|
return true;
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
|
|
|
|
2019-05-09 13:30:39 +00:00
|
|
|
$variableAssign = $this->betterNodeFinder->findFirstPrevious($assign, function (Node $node) use ($expr): bool {
|
2018-10-09 10:18:42 +00:00
|
|
|
if (! $node instanceof Assign) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-02-22 17:25:31 +00:00
|
|
|
if (! $this->areNodesEqual($node->var, $expr)) {
|
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;
|
|
|
|
}
|
|
|
|
|
2018-10-31 15:34:37 +00:00
|
|
|
private function isEmptyStringNode(Node $node): bool
|
2018-10-09 10:18:42 +00:00
|
|
|
{
|
2018-10-31 15:34:37 +00:00
|
|
|
return $node instanceof String_ && $node->value === '';
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|
2019-01-18 02:41:58 +00:00
|
|
|
|
2019-09-22 18:57:03 +00:00
|
|
|
private function shouldSkipVariable(Expr $expr): bool
|
2019-01-18 02:41:58 +00:00
|
|
|
{
|
2019-09-22 18:57:03 +00:00
|
|
|
$staticType = $this->getStaticType($expr);
|
2019-03-10 23:47:43 +00:00
|
|
|
if ($staticType instanceof ErrorType) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-18 02:41:58 +00:00
|
|
|
if ($staticType instanceof UnionType) {
|
2019-02-27 13:21:11 +00:00
|
|
|
return ! ($staticType->isSuperTypeOf(new ArrayType(new MixedType(), new MixedType()))->yes() &&
|
|
|
|
$staticType->isSuperTypeOf(new ConstantStringType(''))->yes());
|
2019-01-18 02:41:58 +00:00
|
|
|
}
|
2019-09-22 18:57:03 +00:00
|
|
|
|
2019-02-27 13:21:11 +00:00
|
|
|
return ! $staticType instanceof StringType;
|
2019-01-18 02:41:58 +00:00
|
|
|
}
|
2018-10-09 10:18:42 +00:00
|
|
|
}
|