[PHP 8.0] Add new support to RequireOptionalParamResolver (#5279)

This commit is contained in:
Tomas Votruba 2021-01-22 02:41:09 +01:00 committed by GitHub
parent f866e25b1f
commit 3e96fab69f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 172 additions and 48 deletions

View File

@ -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();
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
?>