rector/rules/Php80/NodeManipulator/TokenManipulator.php

305 lines
12 KiB
PHP
Raw Normal View History

<?php
declare (strict_types=1);
2022-06-06 16:43:29 +00:00
namespace RectorPrefix20220606\Rector\Php80\NodeManipulator;
2022-06-06 16:43:29 +00:00
use RectorPrefix20220606\PhpParser\Node;
use RectorPrefix20220606\PhpParser\Node\Arg;
use RectorPrefix20220606\PhpParser\Node\Expr;
use RectorPrefix20220606\PhpParser\Node\Expr\ArrayDimFetch;
use RectorPrefix20220606\PhpParser\Node\Expr\Assign;
use RectorPrefix20220606\PhpParser\Node\Expr\BinaryOp\Identical;
use RectorPrefix20220606\PhpParser\Node\Expr\BooleanNot;
use RectorPrefix20220606\PhpParser\Node\Expr\ConstFetch;
use RectorPrefix20220606\PhpParser\Node\Expr\FuncCall;
use RectorPrefix20220606\PhpParser\Node\Expr\MethodCall;
use RectorPrefix20220606\PhpParser\Node\Expr\PropertyFetch;
use RectorPrefix20220606\PhpParser\Node\Expr\Variable;
use RectorPrefix20220606\PhpParser\Node\Stmt\If_;
use RectorPrefix20220606\PHPStan\Type\ArrayType;
use RectorPrefix20220606\Rector\Core\NodeAnalyzer\ArgsAnalyzer;
use RectorPrefix20220606\Rector\Core\PhpParser\Comparing\NodeComparator;
use RectorPrefix20220606\Rector\Core\PhpParser\Node\Value\ValueResolver;
use RectorPrefix20220606\Rector\Core\Util\StringUtils;
use RectorPrefix20220606\Rector\NodeNameResolver\NodeNameResolver;
use RectorPrefix20220606\Rector\NodeTypeResolver\Node\AttributeKey;
use RectorPrefix20220606\Rector\NodeTypeResolver\NodeTypeResolver;
use RectorPrefix20220606\Rector\Php80\ValueObject\ArrayDimFetchAndConstFetch;
use RectorPrefix20220606\Rector\PostRector\Collector\NodesToRemoveCollector;
use RectorPrefix20220606\Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;
final class TokenManipulator
{
/**
* @var \PhpParser\Node\Expr|null
*/
private $assignedNameExpr;
/**
* @readonly
* @var \Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser
*/
private $simpleCallableNodeTraverser;
/**
* @readonly
* @var \Rector\NodeNameResolver\NodeNameResolver
*/
private $nodeNameResolver;
/**
* @readonly
* @var \Rector\NodeTypeResolver\NodeTypeResolver
*/
private $nodeTypeResolver;
/**
* @readonly
* @var \Rector\PostRector\Collector\NodesToRemoveCollector
*/
private $nodesToRemoveCollector;
/**
* @readonly
* @var \Rector\Core\PhpParser\Node\Value\ValueResolver
*/
private $valueResolver;
/**
* @readonly
* @var \Rector\Core\PhpParser\Comparing\NodeComparator
*/
private $nodeComparator;
/**
* @readonly
* @var \Rector\Core\NodeAnalyzer\ArgsAnalyzer
*/
private $argsAnalyzer;
2022-06-06 16:43:29 +00:00
public function __construct(SimpleCallableNodeTraverser $simpleCallableNodeTraverser, NodeNameResolver $nodeNameResolver, NodeTypeResolver $nodeTypeResolver, NodesToRemoveCollector $nodesToRemoveCollector, ValueResolver $valueResolver, NodeComparator $nodeComparator, ArgsAnalyzer $argsAnalyzer)
{
$this->simpleCallableNodeTraverser = $simpleCallableNodeTraverser;
$this->nodeNameResolver = $nodeNameResolver;
$this->nodeTypeResolver = $nodeTypeResolver;
$this->nodesToRemoveCollector = $nodesToRemoveCollector;
$this->valueResolver = $valueResolver;
$this->nodeComparator = $nodeComparator;
$this->argsAnalyzer = $argsAnalyzer;
}
/**
* @param Node[] $nodes
*/
2022-06-06 16:43:29 +00:00
public function refactorArrayToken(array $nodes, Expr $singleTokenExpr) : void
{
$this->replaceTokenDimFetchZeroWithGetTokenName($nodes, $singleTokenExpr);
// replace "$token[1]"; with "$token->value"
2022-06-06 16:43:29 +00:00
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node) : ?PropertyFetch {
if (!$node instanceof ArrayDimFetch) {
return null;
}
if (!$this->isArrayDimFetchWithDimIntegerValue($node, 1)) {
return null;
}
$tokenStaticType = $this->nodeTypeResolver->getType($node->var);
2022-06-06 16:43:29 +00:00
if (!$tokenStaticType instanceof ArrayType) {
return null;
}
2022-06-06 16:43:29 +00:00
return new PropertyFetch($node->var, 'text');
});
}
/**
* @param Node[] $nodes
*/
2022-06-06 16:43:29 +00:00
public function refactorNonArrayToken(array $nodes, Expr $singleTokenExpr) : void
{
// replace "$content = $token;" → "$content = $token->text;"
2022-06-06 16:43:29 +00:00
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node) use($singleTokenExpr) {
if (!$node instanceof Assign) {
return null;
}
if (!$this->nodeComparator->areNodesEqual($node->expr, $singleTokenExpr)) {
return null;
}
$tokenStaticType = $this->nodeTypeResolver->getType($node->expr);
2022-06-06 16:43:29 +00:00
if ($tokenStaticType instanceof ArrayType) {
return null;
}
2022-06-06 16:43:29 +00:00
$node->expr = new PropertyFetch($singleTokenExpr, 'text');
});
// replace "$name = null;" → "$name = $token->getTokenName();"
2022-06-06 16:43:29 +00:00
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node) use($singleTokenExpr) : ?Assign {
if (!$node instanceof Assign) {
return null;
}
$tokenStaticType = $this->nodeTypeResolver->getType($node->expr);
2022-06-06 16:43:29 +00:00
if ($tokenStaticType instanceof ArrayType) {
return null;
}
if ($this->assignedNameExpr === null) {
return null;
}
if (!$this->nodeComparator->areNodesEqual($node->var, $this->assignedNameExpr)) {
return null;
}
if (!$this->valueResolver->isValue($node->expr, 'null')) {
return null;
}
2022-06-06 16:43:29 +00:00
$node->expr = new MethodCall($singleTokenExpr, 'getTokenName');
return $node;
});
}
/**
* @param Node[] $nodes
*/
2022-06-06 16:43:29 +00:00
public function refactorTokenIsKind(array $nodes, Expr $singleTokenExpr) : void
{
2022-06-06 16:43:29 +00:00
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node) use($singleTokenExpr) : ?MethodCall {
if (!$node instanceof Identical) {
return null;
}
$arrayDimFetchAndConstFetch = $this->matchArrayDimFetchAndConstFetch($node);
2022-06-06 16:43:29 +00:00
if (!$arrayDimFetchAndConstFetch instanceof ArrayDimFetchAndConstFetch) {
return null;
}
if (!$this->isArrayDimFetchWithDimIntegerValue($arrayDimFetchAndConstFetch->getArrayDimFetch(), 0)) {
return null;
}
$arrayDimFetch = $arrayDimFetchAndConstFetch->getArrayDimFetch();
$constFetch = $arrayDimFetchAndConstFetch->getConstFetch();
if (!$this->nodeComparator->areNodesEqual($arrayDimFetch->var, $singleTokenExpr)) {
return null;
}
$constName = $this->nodeNameResolver->getName($constFetch);
if ($constName === null) {
return null;
}
2022-06-06 16:43:29 +00:00
if (!StringUtils::isMatch($constName, '#^T_#')) {
return null;
}
return $this->createIsTConstTypeMethodCall($arrayDimFetch, $arrayDimFetchAndConstFetch->getConstFetch());
});
}
/**
* @param Node[] $nodes
*/
2022-06-06 16:43:29 +00:00
public function removeIsArray(array $nodes, Variable $singleTokenVariable) : void
{
2022-06-06 16:43:29 +00:00
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node) use($singleTokenVariable) {
if (!$node instanceof FuncCall) {
return null;
}
if (!$this->nodeNameResolver->isName($node, 'is_array')) {
return null;
}
if (!$this->argsAnalyzer->isArgInstanceInArgsPosition($node->args, 0)) {
return null;
}
/** @var Arg $firstArg */
$firstArg = $node->args[0];
if (!$this->nodeComparator->areNodesEqual($firstArg->value, $singleTokenVariable)) {
return null;
}
if ($this->shouldSkipNodeRemovalForPartOfIf($node)) {
return null;
}
// remove correct node
$nodeToRemove = $this->matchParentNodeInCaseOfIdenticalTrue($node);
$this->nodesToRemoveCollector->addNodeToRemove($nodeToRemove);
});
}
/**
* Replace $token[0] with $token->getTokenName() call
*
* @param Node[] $nodes
*/
2022-06-06 16:43:29 +00:00
private function replaceTokenDimFetchZeroWithGetTokenName(array $nodes, Expr $singleTokenExpr) : void
{
2022-06-06 16:43:29 +00:00
$this->simpleCallableNodeTraverser->traverseNodesWithCallable($nodes, function (Node $node) use($singleTokenExpr) : ?MethodCall {
if (!$node instanceof FuncCall) {
return null;
}
if (!$this->nodeNameResolver->isName($node, 'token_name')) {
return null;
}
if (!$this->argsAnalyzer->isArgInstanceInArgsPosition($node->args, 0)) {
return null;
}
/** @var Arg $firstArg */
$firstArg = $node->args[0];
$possibleTokenArray = $firstArg->value;
2022-06-06 16:43:29 +00:00
if (!$possibleTokenArray instanceof ArrayDimFetch) {
return null;
}
$tokenStaticType = $this->nodeTypeResolver->getType($possibleTokenArray->var);
2022-06-06 16:43:29 +00:00
if (!$tokenStaticType instanceof ArrayType) {
return null;
}
if ($possibleTokenArray->dim === null) {
return null;
}
if (!$this->valueResolver->isValue($possibleTokenArray->dim, 0)) {
return null;
}
if (!$this->nodeComparator->areNodesEqual($possibleTokenArray->var, $singleTokenExpr)) {
return null;
}
// save token variable name for later
2022-06-06 16:43:29 +00:00
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Assign) {
$this->assignedNameExpr = $parentNode->var;
}
2022-06-06 16:43:29 +00:00
return new MethodCall($singleTokenExpr, 'getTokenName');
});
}
2022-06-06 16:43:29 +00:00
private function isArrayDimFetchWithDimIntegerValue(ArrayDimFetch $arrayDimFetch, int $value) : bool
{
2021-01-19 23:29:52 +00:00
if ($arrayDimFetch->dim === null) {
return \false;
}
2021-01-19 23:29:52 +00:00
return $this->valueResolver->isValue($arrayDimFetch->dim, $value);
}
2022-06-06 16:43:29 +00:00
private function matchArrayDimFetchAndConstFetch(Identical $identical) : ?ArrayDimFetchAndConstFetch
{
2022-06-06 16:43:29 +00:00
if ($identical->left instanceof ArrayDimFetch && $identical->right instanceof ConstFetch) {
return new ArrayDimFetchAndConstFetch($identical->left, $identical->right);
}
2022-06-06 16:43:29 +00:00
if (!$identical->right instanceof ArrayDimFetch) {
return null;
}
2022-06-06 16:43:29 +00:00
if (!$identical->left instanceof ConstFetch) {
return null;
}
2022-06-06 16:43:29 +00:00
return new ArrayDimFetchAndConstFetch($identical->right, $identical->left);
}
2022-06-06 16:43:29 +00:00
private function createIsTConstTypeMethodCall(ArrayDimFetch $arrayDimFetch, ConstFetch $constFetch) : MethodCall
{
2022-06-06 16:43:29 +00:00
return new MethodCall($arrayDimFetch->var, 'is', [new Arg($constFetch)]);
}
2022-06-06 16:43:29 +00:00
private function shouldSkipNodeRemovalForPartOfIf(FuncCall $funcCall) : bool
{
2022-06-06 16:43:29 +00:00
$parentNode = $funcCall->getAttribute(AttributeKey::PARENT_NODE);
// cannot remove x from if(x)
2022-06-06 16:43:29 +00:00
if ($parentNode instanceof If_ && $parentNode->cond === $funcCall) {
return \true;
}
2022-06-06 16:43:29 +00:00
if ($parentNode instanceof BooleanNot) {
$parentParentNode = $parentNode->getAttribute(AttributeKey::PARENT_NODE);
if ($parentParentNode instanceof If_) {
$parentParentNode->cond = $parentNode;
return \true;
}
}
return \false;
}
/**
* @return \PhpParser\Node\Expr\BinaryOp\Identical|\PhpParser\Node\Expr\FuncCall
*/
2022-06-06 16:43:29 +00:00
private function matchParentNodeInCaseOfIdenticalTrue(FuncCall $funcCall)
{
2022-06-06 16:43:29 +00:00
$parentNode = $funcCall->getAttribute(AttributeKey::PARENT_NODE);
if ($parentNode instanceof Identical) {
$isRightValueTrue = $this->valueResolver->isValue($parentNode->right, \true);
2021-01-30 23:20:05 +00:00
if ($parentNode->left === $funcCall && $isRightValueTrue) {
return $parentNode;
}
$isLeftValueTrue = $this->valueResolver->isValue($parentNode->left, \true);
2021-01-30 23:20:05 +00:00
if ($parentNode->right === $funcCall && $isLeftValueTrue) {
return $parentNode;
}
}
return $funcCall;
}
}