rector/rules/generic/src/Rector/ClassMethod/ArgumentDefaultValueReplacerRector.php

211 lines
6.8 KiB
PHP
Raw Normal View History

2019-10-13 05:59:52 +00:00
<?php
declare(strict_types=1);
namespace Rector\Generic\Rector\ClassMethod;
use Nette\Utils\Strings;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\ClassMethod;
2020-07-29 23:39:41 +00:00
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\Generic\ValueObject\ArgumentDefaultValueReplacer;
2020-08-25 22:21:41 +00:00
use Webmozart\Assert\Assert;
2019-09-03 09:11:45 +00:00
/**
* @see \Rector\Generic\Tests\Rector\ClassMethod\ArgumentDefaultValueReplacerRector\ArgumentDefaultValueReplacerRectorTest
2019-09-03 09:11:45 +00:00
*/
2020-07-29 23:39:41 +00:00
final class ArgumentDefaultValueReplacerRector extends AbstractRector implements ConfigurableRectorInterface
{
2020-07-29 23:39:41 +00:00
/**
* @var string
*/
2020-08-25 22:21:41 +00:00
public const REPLACED_ARGUMENTS = 'replaced_arguments';
2020-07-29 23:39:41 +00:00
2020-05-06 21:39:33 +00:00
/**
* @var ArgumentDefaultValueReplacer[]
2018-10-31 11:53:59 +00:00
*/
2020-08-25 22:21:41 +00:00
private $replacedArguments = [];
2018-10-31 11:53:59 +00:00
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Replaces defined map of arguments in defined methods and their calls.',
[
2018-08-01 19:52:44 +00:00
new ConfiguredCodeSample(
2019-09-18 06:14:35 +00:00
<<<'PHP'
$someObject = new SomeClass;
$someObject->someMethod(SomeClass::OLD_CONSTANT);
2019-09-18 06:14:35 +00:00
PHP
,
2019-09-18 06:14:35 +00:00
<<<'PHP'
$someObject = new SomeClass;
$someObject->someMethod(false);'
2019-09-18 06:14:35 +00:00
PHP
2018-08-01 19:52:44 +00:00
,
[
2020-08-25 22:21:41 +00:00
self::REPLACED_ARGUMENTS => [
new ArgumentDefaultValueReplacer(
2020-08-25 22:21:41 +00:00
'SomeExampleClass',
'someMethod',
0,
'SomeClass::OLD_CONSTANT',
'false'
),
2018-08-01 19:52:44 +00:00
],
]
),
]
);
}
2018-08-14 22:12:41 +00:00
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [MethodCall::class, StaticCall::class, ClassMethod::class];
}
/**
* @param MethodCall|StaticCall|ClassMethod $node
*/
2018-08-14 22:12:41 +00:00
public function refactor(Node $node): ?Node
{
2020-08-25 22:21:41 +00:00
foreach ($this->replacedArguments as $replacedArgument) {
if (! $this->isMethodStaticCallOrClassMethodObjectType($node, $replacedArgument->getClass())) {
continue;
}
2020-08-25 22:21:41 +00:00
if (! $this->isName($node->name, $replacedArgument->getMethod())) {
continue;
}
2020-08-25 22:21:41 +00:00
$this->processReplaces($node, $replacedArgument);
}
return $node;
}
2020-07-29 23:39:41 +00:00
public function configure(array $configuration): void
{
2020-08-25 22:21:41 +00:00
$replacedArguments = $configuration[self::REPLACED_ARGUMENTS] ?? [];
Assert::allIsInstanceOf($replacedArguments, ArgumentDefaultValueReplacer::class);
2020-08-25 22:21:41 +00:00
$this->replacedArguments = $replacedArguments;
2020-07-29 23:39:41 +00:00
}
/**
* @param MethodCall|StaticCall|ClassMethod $node
*/
private function processReplaces(Node $node, ArgumentDefaultValueReplacer $argumentDefaultValueReplacer): ?Node
{
2020-08-25 22:21:41 +00:00
if ($node instanceof ClassMethod) {
if (! isset($node->params[$argumentDefaultValueReplacer->getPosition()])) {
2020-08-25 22:21:41 +00:00
return null;
}
} elseif (isset($node->args[$argumentDefaultValueReplacer->getPosition()])) {
$this->processArgs($node, $argumentDefaultValueReplacer);
}
return $node;
}
/**
2018-10-31 15:34:37 +00:00
* @param MethodCall|StaticCall $node
*/
private function processArgs(Node $node, ArgumentDefaultValueReplacer $argumentDefaultValueReplacer): void
{
$position = $argumentDefaultValueReplacer->getPosition();
2020-08-25 22:21:41 +00:00
$argValue = $this->getValue($node->args[$position]->value);
if (is_scalar(
$argumentDefaultValueReplacer->getValueBefore()
) && $argValue === $argumentDefaultValueReplacer->getValueBefore()) {
$node->args[$position] = $this->normalizeValueToArgument($argumentDefaultValueReplacer->getValueAfter());
} elseif (is_array($argumentDefaultValueReplacer->getValueBefore())) {
$newArgs = $this->processArrayReplacement($node->args, $argumentDefaultValueReplacer);
2020-08-25 22:21:41 +00:00
if ($newArgs) {
$node->args = $newArgs;
2018-10-31 15:34:37 +00:00
}
}
}
2020-07-27 06:56:25 +00:00
/**
* @param mixed $value
*/
2018-10-31 15:34:37 +00:00
private function normalizeValueToArgument($value): Arg
{
// class constants → turn string to composite
2019-05-28 16:34:40 +00:00
if (is_string($value) && Strings::contains($value, '::')) {
2018-10-31 15:34:37 +00:00
[$class, $constant] = explode('::', $value);
2020-07-19 18:58:54 +00:00
$classConstFetch = $this->createClassConstFetch($class, $constant);
2018-10-31 15:34:37 +00:00
2020-07-19 18:58:54 +00:00
return new Arg($classConstFetch);
2018-10-31 15:34:37 +00:00
}
return new Arg(BuilderHelpers::normalizeValue($value));
}
/**
* @param Arg[] $argumentNodes
* @return Arg[]|null
*/
private function processArrayReplacement(
array $argumentNodes,
ArgumentDefaultValueReplacer $argumentDefaultValueReplacer
): ?array {
$argumentValues = $this->resolveArgumentValuesToBeforeRecipe($argumentNodes, $argumentDefaultValueReplacer);
if ($argumentValues !== $argumentDefaultValueReplacer->getValueBefore()) {
return null;
}
if (is_string($argumentDefaultValueReplacer->getValueAfter())) {
$argumentNodes[$argumentDefaultValueReplacer->getPosition()] = $this->normalizeValueToArgument(
$argumentDefaultValueReplacer->getValueAfter()
2020-08-25 22:21:41 +00:00
);
// clear following arguments
$argumentCountToClear = count($argumentDefaultValueReplacer->getValueBefore());
for ($i = $argumentDefaultValueReplacer->getPosition() + 1; $i <= $argumentDefaultValueReplacer->getPosition() + $argumentCountToClear; ++$i) {
unset($argumentNodes[$i]);
}
}
return $argumentNodes;
}
/**
* @param Arg[] $argumentNodes
* @return mixed[]
*/
2020-08-25 22:21:41 +00:00
private function resolveArgumentValuesToBeforeRecipe(
array $argumentNodes,
ArgumentDefaultValueReplacer $argumentDefaultValueReplacer
2020-08-25 22:21:41 +00:00
): array {
$argumentValues = [];
2020-08-25 22:21:41 +00:00
/** @var mixed[] $valueBefore */
$valueBefore = $argumentDefaultValueReplacer->getValueBefore();
2020-08-25 22:21:41 +00:00
$beforeArgumentCount = count($valueBefore);
for ($i = 0; $i < $beforeArgumentCount; ++$i) {
if (! isset($argumentNodes[$argumentDefaultValueReplacer->getPosition() + $i])) {
continue;
}
$nextArg = $argumentNodes[$argumentDefaultValueReplacer->getPosition() + $i];
$argumentValues[] = $this->getValue($nextArg->value);
}
return $argumentValues;
}
}