From 3a7b2f36c9baf1861728235194c285b1db088f0b Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 18 Jan 2025 22:44:39 +0700 Subject: [PATCH] feat: add AddNamedArgumentsRector --- .../AddNamedArgumentsRectorTest.php | 34 +++ .../Fixture/construct.php.inc | 11 + .../Fixture/function.php.inc | 11 + .../Fixture/method.php.inc | 11 + .../Fixture/skip_function.php.inc | 6 + .../Fixture/static_method.php.inc | 11 + .../AddNamedArgumentsRector/config/rector.php | 9 + .../FuncCall/AddNamedArgumentsRector.php | 194 ++++++++++++++++++ src/Configuration/RectorConfigBuilder.php | 8 +- 9 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/AddNamedArgumentsRectorTest.php create mode 100644 rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/construct.php.inc create mode 100644 rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/function.php.inc create mode 100644 rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/method.php.inc create mode 100644 rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/skip_function.php.inc create mode 100644 rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/static_method.php.inc create mode 100644 rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/config/rector.php create mode 100644 rules/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector.php diff --git a/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/AddNamedArgumentsRectorTest.php b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/AddNamedArgumentsRectorTest.php new file mode 100644 index 0000000000..883106d6c6 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/AddNamedArgumentsRectorTest.php @@ -0,0 +1,34 @@ +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; + } +} diff --git a/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/construct.php.inc b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/construct.php.inc new file mode 100644 index 0000000000..67f74554f7 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/construct.php.inc @@ -0,0 +1,11 @@ + +----- + diff --git a/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/function.php.inc b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/function.php.inc new file mode 100644 index 0000000000..3b8dfbbe56 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/function.php.inc @@ -0,0 +1,11 @@ + +----- + diff --git a/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/method.php.inc b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/method.php.inc new file mode 100644 index 0000000000..b574ce2658 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/method.php.inc @@ -0,0 +1,11 @@ +format('Y-m-d'); + +?> +----- +format(format: 'Y-m-d'); + +?> diff --git a/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/skip_function.php.inc b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/skip_function.php.inc new file mode 100644 index 0000000000..adc55969d0 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/skip_function.php.inc @@ -0,0 +1,6 @@ + + diff --git a/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/static_method.php.inc b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/static_method.php.inc new file mode 100644 index 0000000000..7eb5675f3b --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/Fixture/static_method.php.inc @@ -0,0 +1,11 @@ + +----- + diff --git a/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/config/rector.php b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/config/rector.php new file mode 100644 index 0000000000..9371bff442 --- /dev/null +++ b/rules-tests/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector/config/rector.php @@ -0,0 +1,9 @@ +withRules([AddNamedArgumentsRector::class]); diff --git a/rules/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector.php b/rules/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector.php new file mode 100644 index 0000000000..d5e917ddb0 --- /dev/null +++ b/rules/CodingStyle/Rector/FuncCall/AddNamedArgumentsRector.php @@ -0,0 +1,194 @@ +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()); + } + } +} diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 208570ee15..493fc5ff75 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -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,