Skip to content

Commit

Permalink
feat: add AddNamedArgumentsRector
Browse files Browse the repository at this point in the history
  • Loading branch information
savinmikhail committed Jan 18, 2025
1 parent a26bfd9 commit 3a7b2f3
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Rector\Tests\CodingStyle\Rector\FuncCall\AddNamedArgumentsRector;

use Rector\CodingStyle\Rector\FuncCall\AddNamedArgumentsRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class AddNamedArgumentsRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideCases()
*/
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideCases(): iterable
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/rector.php';
}

protected function getRectorClass(): string
{
return AddNamedArgumentsRector::class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

new DateTimeImmutable('now');

?>
-----
<?php

new DateTimeImmutable(datetime: 'now');

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

str_contains('foo', 'bar');

?>
-----
<?php

str_contains(haystack: 'foo', needle: 'bar');

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

(new DateTimeImmutable())->format('Y-m-d');

?>
-----
<?php

(new DateTimeImmutable())->format(format: 'Y-m-d');

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

str_contains(haystack: 'foo', needle: 'bar');

?>

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

DateTime::createFromFormat('Y-m-d', '2001-01-01');

?>
-----
<?php

DateTime::createFromFormat(format: 'Y-m-d', datetime: '2001-01-01');

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

declare(strict_types=1);

use Rector\CodingStyle\Rector\FuncCall\AddNamedArgumentsRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withRules([AddNamedArgumentsRector::class]);
194 changes: 194 additions & 0 deletions rules/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<?php

declare(strict_types=1);

namespace Rector\CodingStyle\Rector\FuncCall;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use const PHP_VERSION_ID;

final class AddNamedArgumentsRector extends AbstractRector
{
public function __construct(private readonly ReflectionProvider $reflectionProvider)
{
}

public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Convert all arguments to named arguments', [
new CodeSample('$user->setPassword("123456");', '$user->changePassword(password: "123456");'),
]);
}

public function getNodeTypes(): array
{
return [FuncCall::class, StaticCall::class, MethodCall::class, New_::class];
}

public function refactor(Node $node): ?Node
{
if (PHP_VERSION_ID < 80000) {
return null;
}

$parameters = $this->getParameters($node);
$this->addNamesToArgs($node, $parameters);

return $node;
}

/**
* @param Node $node
* @return ParameterReflectionWithPhpDocs[]
*/
private function getParameters(Node $node): array
{
$parameters = [];

if ($node instanceof New_) {
$parameters = $this->getConstructorArgs($node);
} elseif ($node instanceof MethodCall) {
$parameters = $this->getMethodArgs($node);
} elseif ($node instanceof StaticCall) {
$parameters = $this->getStaticMethodArgs($node);
} elseif ($node instanceof FuncCall) {
$parameters = $this->getFuncArgs($node);
}

return $parameters;
}

/**
* @return ParameterReflectionWithPhpDocs[]
*/
private function getStaticMethodArgs(StaticCall $node): array
{
$namespaceAnswerer = $node->getAttribute(AttributeKey::SCOPE);

if (! $node->class instanceof Name) {
return [];
}

$className = $this->getName($node->class);
if (! $this->reflectionProvider->hasClass($className)) {
return [];
}

$classReflection = $this->reflectionProvider->getClass($className);
if (! $classReflection->hasMethod($node->name->toString())) {
return [];
}

$methodReflection = $classReflection->getMethod($node->name->toString(), $namespaceAnswerer);

return $methodReflection->getOnlyVariant()->getParameters();
}

/**
* @return ParameterReflectionWithPhpDocs[]
*/
private function getMethodArgs(MethodCall $node): array
{
$namespaceAnswerer = $node->getAttribute(AttributeKey::SCOPE);

$callerType = $this->nodeTypeResolver->getType($node->var);
if (! $callerType->hasMethod($node->name->toString())) {
return [];
}

$methodReflection = $callerType->getMethod($node->name->toString(), $namespaceAnswerer);

return $methodReflection->getOnlyVariant()->getParameters();
}

private function resolveCalledName(Node $node): ?string
{
if ($node instanceof FuncCall && $node->name instanceof Name) {
return (string) $node->name;
}

if ($node instanceof MethodCall && $node->name instanceof Identifier) {
return (string) $node->name;
}

if ($node instanceof StaticCall && $node->name instanceof Identifier) {
return (string) $node->name;
}

if ($node instanceof New_ && $node->class instanceof Name) {
return (string) $node->class;
}

return null;
}

/**
* @return ParameterReflectionWithPhpDocs[]
*/
private function getConstructorArgs(New_ $node): array
{
$calledName = $this->resolveCalledName($node);
if ($calledName === null) {
return [];
}

if (! $this->reflectionProvider->hasClass($calledName)) {
return [];
}
$classReflection = $this->reflectionProvider->getClass($calledName);

if (! $classReflection->hasConstructor()) {
return [];
}

$constructorReflection = $classReflection->getConstructor();

return $constructorReflection->getOnlyVariant()->getParameters();
}

/**
* @return ParameterReflectionWithPhpDocs[]
*/
private function getFuncArgs(FuncCall $node): array
{
$namespaceAnswerer = $node->getAttribute(AttributeKey::SCOPE);

$calledName = $this->resolveCalledName($node);
if ($calledName === null) {
return [];
}

if (! $this->reflectionProvider->hasFunction(new Name($calledName), $namespaceAnswerer)) {
return [];
}
$reflection = $this->reflectionProvider->getFunction(new Name($calledName), $namespaceAnswerer);

return $reflection->getOnlyVariant()->getParameters();
}

/**
* @param ParameterReflectionWithPhpDocs[] $parameters
*/
private function addNamesToArgs(Node $node, array $parameters): void
{
/** @var FuncCall|StaticCall|MethodCall|New_ $node */
foreach ($node->args as $index => $arg) {
if (! isset($parameters[$index])) {
return;
}
$arg->name = new Identifier($parameters[$index]->getName());
}
}
}
8 changes: 6 additions & 2 deletions src/Configuration/RectorConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -728,8 +728,12 @@ public function withPreparedSets(
return $this;
}

public function withComposerBased(bool $twig = false, bool $doctrine = false, bool $phpunit = false, bool $symfony = \false): self
{
public function withComposerBased(
bool $twig = false,
bool $doctrine = false,
bool $phpunit = false,
bool $symfony = \false
): self {
$setMap = [
SetGroup::TWIG => $twig,
SetGroup::DOCTRINE => $doctrine,
Expand Down

0 comments on commit 3a7b2f3

Please sign in to comment.