mirror of
https://github.com/rectorphp/rector.git
synced 2025-02-21 01:41:00 +01:00
[PHP 8.0] Add new support to RequireOptionalParamResolver (#5279)
This commit is contained in:
parent
f866e25b1f
commit
3e96fab69f
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Rector\NodeCollector\Reflection;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
@ -93,14 +92,6 @@ final class MethodReflectionProvider
|
||||
return $this->provideParameterTypesFromMethodReflection($methodReflection);
|
||||
}
|
||||
|
||||
public function provideByNew(New_ $new): ?MethodReflection
|
||||
{
|
||||
$objectType = $this->nodeTypeResolver->resolve($new->class);
|
||||
$classes = TypeUtils::getDirectClassNames($objectType);
|
||||
|
||||
return $this->provideByClassNamesAndMethodName($classes, MethodName::CONSTRUCT, $new);
|
||||
}
|
||||
|
||||
public function provideByStaticCall(StaticCall $staticCall): ?MethodReflection
|
||||
{
|
||||
$objectType = $this->nodeTypeResolver->resolve($staticCall->class);
|
||||
@ -184,10 +175,13 @@ final class MethodReflectionProvider
|
||||
/**
|
||||
* @param string[] $classes
|
||||
*/
|
||||
private function provideByClassNamesAndMethodName(array $classes, string $methodName, Node $node): ?MethodReflection
|
||||
{
|
||||
private function provideByClassNamesAndMethodName(
|
||||
array $classes,
|
||||
string $methodName,
|
||||
StaticCall $staticCall
|
||||
): ?MethodReflection {
|
||||
/** @var Scope|null $scope */
|
||||
$scope = $node->getAttribute(AttributeKey::SCOPE);
|
||||
$scope = $staticCall->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
@ -66,7 +66,6 @@ final class NameTypeResolver implements NodeTypeResolverInterface
|
||||
private function resolveFullyQualifiedName(Name $name): string
|
||||
{
|
||||
$nameValue = $name->toString();
|
||||
|
||||
if (in_array($nameValue, ['self', 'static', 'this'], true)) {
|
||||
/** @var string|null $class */
|
||||
$class = $name->getAttribute(AttributeKey::CLASS_NAME);
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php80\NodeResolver;
|
||||
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
|
||||
final class RequireOptionalParamResolver
|
||||
{
|
||||
/**
|
||||
* @param ClassMethod $functionLike
|
||||
* @return array<int, Param>
|
||||
*/
|
||||
public function resolve(FunctionLike $functionLike): array
|
||||
{
|
||||
$optionalParams = [];
|
||||
$requireParams = [];
|
||||
foreach ($functionLike->getParams() as $position => $param) {
|
||||
if ($param->default === null) {
|
||||
$requireParams[$position] = $param;
|
||||
} else {
|
||||
$optionalParams[$position] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
return $requireParams + $optionalParams;
|
||||
}
|
||||
}
|
@ -5,9 +5,15 @@ declare(strict_types=1);
|
||||
namespace Rector\Php80\Rector\ClassMethod;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Type\TypeWithClassName;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\ValueObject\MethodName;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\Php80\NodeResolver\RequireOptionalParamResolver;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
@ -18,6 +24,16 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
*/
|
||||
final class OptionalParametersAfterRequiredRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var RequireOptionalParamResolver
|
||||
*/
|
||||
private $requireOptionalParamResolver;
|
||||
|
||||
public function __construct(RequireOptionalParamResolver $requireOptionalParamResolver)
|
||||
{
|
||||
$this->requireOptionalParamResolver = $requireOptionalParamResolver;
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('Move required parameters after optional ones', [
|
||||
@ -50,62 +66,107 @@ CODE_SAMPLE
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [ClassMethod::class];
|
||||
return [ClassMethod::class, New_::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod $node
|
||||
* @param ClassMethod|New_ $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node->params === []) {
|
||||
if ($node instanceof ClassMethod) {
|
||||
return $this->refactorClassMethod($node);
|
||||
}
|
||||
|
||||
return $this->refactorNew($node);
|
||||
}
|
||||
|
||||
private function refactorClassMethod(ClassMethod $classMethod): ?ClassMethod
|
||||
{
|
||||
if ($classMethod->params === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$requireParams = $this->resolveRequiredParams($node);
|
||||
$optionalParams = $this->resolveOptionalParams($node);
|
||||
|
||||
$expectedOrderParams = array_merge($requireParams, $optionalParams);
|
||||
if ($node->params === $expectedOrderParams) {
|
||||
$expectedOrderParams = $this->requireOptionalParamResolver->resolve($classMethod);
|
||||
if ($classMethod->params === $expectedOrderParams) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$node->params = $expectedOrderParams;
|
||||
$classMethod->params = $expectedOrderParams;
|
||||
|
||||
return $node;
|
||||
return $classMethod;
|
||||
}
|
||||
|
||||
private function refactorNew(New_ $new): ?New_
|
||||
{
|
||||
if ($new->args === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$constructorClassMethod = $this->findClassMethodConstructorByNew($new);
|
||||
if (! $constructorClassMethod instanceof ClassMethod) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we need orignal node, as the order might have already hcanged
|
||||
$originalClassMethod = $constructorClassMethod->getAttribute(AttributeKey::ORIGINAL_NODE);
|
||||
if (! $originalClassMethod instanceof ClassMethod) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expectedOrderedParams = $this->requireOptionalParamResolver->resolve($originalClassMethod);
|
||||
if ($expectedOrderedParams === $originalClassMethod->getParams()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newArgs = $this->resolveNewArgsOrderedByRequiredParams($expectedOrderedParams, $new);
|
||||
if ($new->args === $newArgs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$new->args = $newArgs;
|
||||
return $new;
|
||||
}
|
||||
|
||||
private function findClassMethodConstructorByNew(New_ $new): ?ClassMethod
|
||||
{
|
||||
$className = $this->getObjectType($new->class);
|
||||
if (! $className instanceof TypeWithClassName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$constructorClassMethod = $this->nodeRepository->findClassMethod(
|
||||
$className->getClassName(),
|
||||
MethodName::CONSTRUCT
|
||||
);
|
||||
if (! $constructorClassMethod instanceof ClassMethod) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($constructorClassMethod->getParams() === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $constructorClassMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, Param>
|
||||
* @param array<int, Param> $expectedOrderedParams
|
||||
* @return array<int, Arg>
|
||||
*/
|
||||
private function resolveOptionalParams(ClassMethod $classMethod): array
|
||||
private function resolveNewArgsOrderedByRequiredParams(array $expectedOrderedParams, New_ $new): array
|
||||
{
|
||||
$paramsByPosition = [];
|
||||
foreach ($classMethod->params as $position => $param) {
|
||||
if ($param->default === null) {
|
||||
$oldToNewPositions = array_keys($expectedOrderedParams);
|
||||
|
||||
$newArgs = [];
|
||||
foreach (array_keys($new->args) as $position) {
|
||||
$newPosition = $oldToNewPositions[$position] ?? null;
|
||||
if ($newPosition === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paramsByPosition[$position] = $param;
|
||||
$newArgs[$position] = $new->args[$newPosition];
|
||||
}
|
||||
|
||||
return $paramsByPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Param[]
|
||||
*/
|
||||
private function resolveRequiredParams(ClassMethod $classMethod): array
|
||||
{
|
||||
$paramsByPosition = [];
|
||||
foreach ($classMethod->params as $param) {
|
||||
if ($param->default !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paramsByPosition[] = $param;
|
||||
}
|
||||
|
||||
return $paramsByPosition;
|
||||
return $newArgs;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
|
||||
|
||||
final class NewTheConstructor
|
||||
{
|
||||
public function __construct($optional = 1, $required)
|
||||
{
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return new self(1, 5);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php80\Tests\Rector\ClassMethod\OptionalParametersAfterRequiredRector\Fixture;
|
||||
|
||||
final class NewTheConstructor
|
||||
{
|
||||
public function __construct($required, $optional = 1)
|
||||
{
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return new self(5, 1);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
Loading…
x
Reference in New Issue
Block a user