Skip to content

Commit

Permalink
[TASK] Add FunctionArgumentDefaultValueReplacerRector (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
sabbelasichon committed May 17, 2021
1 parent eb82c40 commit e961ed1
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 117 deletions.
17 changes: 17 additions & 0 deletions config/set/php80.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
declare(strict_types=1);

use Rector\Arguments\Rector\ClassMethod\ArgumentAdderRector;
use Rector\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector;
use Rector\Arguments\ValueObject\ArgumentAdder;
use Rector\Arguments\ValueObject\FuncCallArgumentDefaultValueReplacer;
use Rector\DeadCode\Rector\StaticCall\RemoveParentCallWithoutParentRector;
use Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
Expand Down Expand Up @@ -91,4 +93,19 @@
],
]]);
$services->set(OptionalParametersAfterRequiredRector::class);

$services->set(Rector\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector::class)
->call('configure', [[
FunctionArgumentDefaultValueReplacerRector::REPLACED_ARGUMENTS => ValueObjectInliner::inline([
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'gte', 'ge'),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'lte', 'le'),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, '', '!='),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, '!', '!='),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'g', 'gt'),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'l', 'lt'),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'gte', 'ge'),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'lte', 'le'),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'n', 'ne'),
]),
]]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Rector\Tests\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector\Fixture;

class SomeClass
{
public function run()
{
some_function(true);
}
}

?>
-----
<?php

namespace Rector\Tests\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector\Fixture;

class SomeClass
{
public function run()
{
some_function(\Symfony\Component\Yaml\Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE);
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\Tests\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector\Fixture;

class VersionCompare
{
public function run()
{
version_compare(1, 2);
version_compare(1, 2, '');
version_compare(PHP_VERSION, '5.6', 'lte');
version_compare(PHP_VERSION, '5.6', 'le');
}
}

?>
-----
<?php

namespace Rector\Tests\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector\Fixture;

class VersionCompare
{
public function run()
{
version_compare(1, 2);
version_compare(1, 2, '!=');
version_compare(PHP_VERSION, '5.6', 'le');
version_compare(PHP_VERSION, '5.6', 'le');
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector;

use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;

final class FunctionArgumentDefaultValueReplacerRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(SmartFileInfo $fileInfo): void
{
$this->doTestFileInfo($fileInfo);
}

/**
* @return Iterator<SmartFileInfo>
*/
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

use Rector\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector;
use Rector\Arguments\ValueObject\FuncCallArgumentDefaultValueReplacer;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\SymfonyPhpConfig\ValueObjectInliner;

return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(Rector\Arguments\Rector\FuncCall\FunctionArgumentDefaultValueReplacerRector::class)
->call('configure', [[
FunctionArgumentDefaultValueReplacerRector::REPLACED_ARGUMENTS => ValueObjectInliner::inline([
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, 'lte', 'le'),
new FuncCallArgumentDefaultValueReplacer('version_compare', 2, '', '!='),
new FuncCallArgumentDefaultValueReplacer(
'some_function',
0,
true,
'Symfony\Component\Yaml\Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE'
),
]),
]]);
};
137 changes: 137 additions & 0 deletions rules/Arguments/ArgumentDefaultValueReplacer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

declare(strict_types=1);

namespace Rector\Arguments;

use Nette\Utils\Strings;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Arguments\Contract\ArgumentDefaultValueReplacerInterface;
use Rector\Core\PhpParser\Node\NodeFactory;
use Rector\Core\PhpParser\Node\Value\ValueResolver;

final class ArgumentDefaultValueReplacer
{
public function __construct(
private NodeFactory $nodeFactory,
private ValueResolver $valueResolver
) {
}

/**
* @param MethodCall|StaticCall|ClassMethod|Expr\FuncCall $node
*/
public function processReplaces(
Node $node,
ArgumentDefaultValueReplacerInterface $argumentDefaultValueReplacer
): ?Node {
if ($node instanceof ClassMethod) {
if (! isset($node->params[$argumentDefaultValueReplacer->getPosition()])) {
return null;
}
} elseif (isset($node->args[$argumentDefaultValueReplacer->getPosition()])) {
$this->processArgs($node, $argumentDefaultValueReplacer);
}

return $node;
}

/**
* @param MethodCall|StaticCall|FuncCall $expr
*/
private function processArgs(Expr $expr, ArgumentDefaultValueReplacerInterface $argumentDefaultValueReplacer): void
{
$position = $argumentDefaultValueReplacer->getPosition();

$argValue = $this->valueResolver->getValue($expr->args[$position]->value);

if (is_scalar(
$argumentDefaultValueReplacer->getValueBefore()
) && $argValue === $argumentDefaultValueReplacer->getValueBefore()) {
$expr->args[$position] = $this->normalizeValueToArgument($argumentDefaultValueReplacer->getValueAfter());
} elseif (is_array($argumentDefaultValueReplacer->getValueBefore())) {
$newArgs = $this->processArrayReplacement($expr->args, $argumentDefaultValueReplacer);

if ($newArgs) {
$expr->args = $newArgs;
}
}
}

/**
* @param mixed $value
*/
private function normalizeValueToArgument($value): Arg
{
// class constants → turn string to composite
if (is_string($value) && Strings::contains($value, '::')) {
[$class, $constant] = explode('::', $value);
$classConstFetch = $this->nodeFactory->createClassConstFetch($class, $constant);

return new Arg($classConstFetch);
}

return new Arg(BuilderHelpers::normalizeValue($value));
}

/**
* @param Arg[] $argumentNodes
* @return Arg[]|null
*/
private function processArrayReplacement(
array $argumentNodes,
ArgumentDefaultValueReplacerInterface $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()
);

// 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[]
*/
private function resolveArgumentValuesToBeforeRecipe(
array $argumentNodes,
ArgumentDefaultValueReplacerInterface $argumentDefaultValueReplacer
): array {
$argumentValues = [];

/** @var mixed[] $valueBefore */
$valueBefore = $argumentDefaultValueReplacer->getValueBefore();
$beforeArgumentCount = count($valueBefore);

for ($i = 0; $i < $beforeArgumentCount; ++$i) {
if (! isset($argumentNodes[$argumentDefaultValueReplacer->getPosition() + $i])) {
continue;
}

$nextArg = $argumentNodes[$argumentDefaultValueReplacer->getPosition() + $i];
$argumentValues[] = $this->valueResolver->getValue($nextArg->value);
}

return $argumentValues;
}
}
18 changes: 18 additions & 0 deletions rules/Arguments/Contract/ArgumentDefaultValueReplacerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Rector\Arguments\Contract;

interface ArgumentDefaultValueReplacerInterface
{
public function getPosition(): int;

/**
* @return mixed
*/
public function getValueBefore();

/**
* @return mixed
*/
public function getValueAfter();
}

0 comments on commit e961ed1

Please sign in to comment.