mirror of https://github.com/rectorphp/rector.git
204 lines
6.2 KiB
PHP
204 lines
6.2 KiB
PHP
<?php declare(strict_types=1);
|
|
|
|
namespace Rector\Rector\Dynamic;
|
|
|
|
use PhpParser\BuilderHelpers;
|
|
use PhpParser\ConstExprEvaluator;
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Arg;
|
|
use PhpParser\Node\Expr\MethodCall;
|
|
use PhpParser\Node\Expr\StaticCall;
|
|
use PhpParser\Node\Param;
|
|
use PhpParser\Node\Stmt\ClassMethod;
|
|
use Rector\NodeAnalyzer\ClassMethodAnalyzer;
|
|
use Rector\NodeAnalyzer\MethodCallAnalyzer;
|
|
use Rector\NodeAnalyzer\StaticMethodCallAnalyzer;
|
|
use Rector\Rector\AbstractRector;
|
|
use Rector\Rector\Dynamic\Configuration\ArgumentReplacerRecipe;
|
|
|
|
final class ArgumentReplacerRector extends AbstractRector
|
|
{
|
|
/**
|
|
* @var ArgumentReplacerRecipe[]
|
|
*/
|
|
private $argumentReplacerRecipes = [];
|
|
|
|
/**
|
|
* @var MethodCallAnalyzer
|
|
*/
|
|
private $methodCallAnalyzer;
|
|
|
|
/**
|
|
* @var ArgumentReplacerRecipe[]
|
|
*/
|
|
private $activeArgumentReplacerRecipes = [];
|
|
|
|
/**
|
|
* @var ClassMethodAnalyzer
|
|
*/
|
|
private $classMethodAnalyzer;
|
|
|
|
/**
|
|
* @var StaticMethodCallAnalyzer
|
|
*/
|
|
private $staticMethodCallAnalyzer;
|
|
|
|
/**
|
|
* @var ConstExprEvaluator
|
|
*/
|
|
private $constExprEvaluator;
|
|
|
|
/**
|
|
* @param mixed[] $argumentChangesByMethodAndType
|
|
*/
|
|
public function __construct(
|
|
array $argumentChangesByMethodAndType,
|
|
MethodCallAnalyzer $methodCallAnalyzer,
|
|
ClassMethodAnalyzer $classMethodAnalyzer,
|
|
StaticMethodCallAnalyzer $staticMethodCallAnalyzer,
|
|
ConstExprEvaluator $constExprEvaluator
|
|
) {
|
|
$this->loadArgumentReplacerRecipes($argumentChangesByMethodAndType);
|
|
$this->methodCallAnalyzer = $methodCallAnalyzer;
|
|
$this->classMethodAnalyzer = $classMethodAnalyzer;
|
|
$this->staticMethodCallAnalyzer = $staticMethodCallAnalyzer;
|
|
$this->constExprEvaluator = $constExprEvaluator;
|
|
}
|
|
|
|
public function isCandidate(Node $node): bool
|
|
{
|
|
$this->activeArgumentReplacerRecipes = $this->matchArgumentChanges($node);
|
|
|
|
return (bool) $this->activeArgumentReplacerRecipes;
|
|
}
|
|
|
|
/**
|
|
* @param MethodCall|StaticCall|ClassMethod $node
|
|
*/
|
|
public function refactor(Node $node): Node
|
|
{
|
|
$argumentsOrParameters = $this->getNodeArgumentsOrParameters($node);
|
|
$argumentsOrParameters = $this->processArgumentNodes($argumentsOrParameters);
|
|
|
|
$this->setNodeArgumentsOrParameters($node, $argumentsOrParameters);
|
|
|
|
return $node;
|
|
}
|
|
|
|
/**
|
|
* @return ArgumentReplacerRecipe[]
|
|
*/
|
|
private function matchArgumentChanges(Node $node): array
|
|
{
|
|
if (! $node instanceof ClassMethod && ! $node instanceof MethodCall && ! $node instanceof StaticCall) {
|
|
return [];
|
|
}
|
|
|
|
$argumentReplacerRecipes = [];
|
|
|
|
foreach ($this->argumentReplacerRecipes as $argumentReplacerRecipe) {
|
|
if ($this->isNodeToRecipeMatch($node, $argumentReplacerRecipe)) {
|
|
$argumentReplacerRecipes[] = $argumentReplacerRecipe;
|
|
}
|
|
}
|
|
|
|
return $argumentReplacerRecipes;
|
|
}
|
|
|
|
/**
|
|
* @return Arg[]|Param[]
|
|
*/
|
|
private function getNodeArgumentsOrParameters(Node $node): array
|
|
{
|
|
if ($node instanceof MethodCall || $node instanceof StaticCall) {
|
|
return $node->args;
|
|
}
|
|
|
|
if ($node instanceof ClassMethod) {
|
|
return $node->params;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param MethodCall|StaticCall|ClassMethod $node
|
|
* @param mixed[] $argumentsOrParameters
|
|
*/
|
|
private function setNodeArgumentsOrParameters(Node $node, array $argumentsOrParameters): void
|
|
{
|
|
if ($node instanceof MethodCall || $node instanceof StaticCall) {
|
|
$node->args = $argumentsOrParameters;
|
|
}
|
|
|
|
if ($node instanceof ClassMethod) {
|
|
$node->params = $argumentsOrParameters;
|
|
}
|
|
}
|
|
|
|
private function isNodeToRecipeMatch(Node $node, ArgumentReplacerRecipe $argumentReplacerRecipe): bool
|
|
{
|
|
$type = $argumentReplacerRecipe->getClass();
|
|
$method = $argumentReplacerRecipe->getMethod();
|
|
|
|
if ($this->methodCallAnalyzer->isTypeAndMethods($node, $type, [$method])) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->staticMethodCallAnalyzer->isTypeAndMethods($node, $type, [$method])) {
|
|
return true;
|
|
}
|
|
|
|
return $this->classMethodAnalyzer->isTypeAndMethods($node, $type, [$method]);
|
|
}
|
|
|
|
/**
|
|
* @param mixed[] $configurationArrays
|
|
*/
|
|
private function loadArgumentReplacerRecipes(array $configurationArrays): void
|
|
{
|
|
foreach ($configurationArrays as $configurationArray) {
|
|
$this->argumentReplacerRecipes[] = ArgumentReplacerRecipe::createFromArray($configurationArray);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param mixed[] $argumentNodes
|
|
* @return mixed[]
|
|
*/
|
|
private function processArgumentNodes(array $argumentNodes): array
|
|
{
|
|
foreach ($this->activeArgumentReplacerRecipes as $argumentReplacerRecipe) {
|
|
$type = $argumentReplacerRecipe->getType();
|
|
$position = $argumentReplacerRecipe->getPosition();
|
|
|
|
if ($type === ArgumentReplacerRecipe::TYPE_REMOVED) {
|
|
unset($argumentNodes[$position]);
|
|
} elseif ($type === ArgumentReplacerRecipe::TYPE_CHANGED) {
|
|
$argumentNodes[$position] = BuilderHelpers::normalizeValue(
|
|
$argumentReplacerRecipe->getDefaultValue()
|
|
);
|
|
} elseif ($type === ArgumentReplacerRecipe::TYPE_REPLACED_DEFAULT_VALUE) {
|
|
$argumentNodes[$position] = $this->processReplacedDefaultValue(
|
|
$argumentNodes[$position],
|
|
$argumentReplacerRecipe
|
|
);
|
|
}
|
|
}
|
|
|
|
return $argumentNodes;
|
|
}
|
|
|
|
private function processReplacedDefaultValue(Arg $argNode, ArgumentReplacerRecipe $argumentReplacerRecipe): Arg
|
|
{
|
|
$resolvedValue = $this->constExprEvaluator->evaluateDirectly($argNode->value);
|
|
|
|
$replaceMap = $argumentReplacerRecipe->getReplaceMap();
|
|
foreach ($replaceMap as $oldValue => $newValue) {
|
|
if ($resolvedValue === $oldValue) {
|
|
return new Arg(BuilderHelpers::normalizeValue($newValue));
|
|
}
|
|
}
|
|
|
|
return $argNode;
|
|
}
|
|
}
|