mirror of https://github.com/rectorphp/rector.git
[TypeDeclaration] Add ParamTypeByParentCallTypeRector (#519)
This commit is contained in:
parent
a467f764ec
commit
3cf3e1fc25
|
@ -20,4 +20,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
$services->set(AddArrayParamDocTypeRector::class);
|
||||
$services->set(AddArrayReturnDocTypeRector::class);
|
||||
// $services->set(AddParamTypeFromCallersRector::class);
|
||||
$services->set(\Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector::class);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Fixture;
|
||||
|
||||
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Source\SomeControl;
|
||||
|
||||
final class FlipPosition extends SomeControl
|
||||
{
|
||||
public function __construct($hey, $name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Fixture;
|
||||
|
||||
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Source\SomeControl;
|
||||
|
||||
final class FlipPosition extends SomeControl
|
||||
{
|
||||
public function __construct($hey, string $name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Fixture;
|
||||
|
||||
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Source\SomeControl;
|
||||
|
||||
final class SkipSetType extends SomeControl
|
||||
{
|
||||
public function __construct(int $name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Fixture;
|
||||
|
||||
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Source\SomeControl;
|
||||
|
||||
final class VideoControl extends SomeControl
|
||||
{
|
||||
public function __construct($name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Fixture;
|
||||
|
||||
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Source\SomeControl;
|
||||
|
||||
final class VideoControl extends SomeControl
|
||||
{
|
||||
public function __construct(string $name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class ParamTypeByParentCallTypeRectorTest 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';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\Source;
|
||||
|
||||
abstract class SomeControl
|
||||
{
|
||||
public function __construct(string $name)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
$services = $containerConfigurator->services();
|
||||
$services->set(ParamTypeByParentCallTypeRector::class);
|
||||
};
|
|
@ -14,7 +14,6 @@ use PhpParser\Node\Identifier;
|
|||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\If_;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\NodeManipulator\IfManipulator;
|
||||
use Rector\Core\NodeManipulator\NullsafeManipulator;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
|
|
|
@ -67,9 +67,7 @@ CODE_SAMPLE
|
|||
|
||||
,
|
||||
[
|
||||
self::METHOD_CALL_WRAPS => [
|
||||
new MethodCallToReturn('SomeClass', 'deny'),
|
||||
]
|
||||
self::METHOD_CALL_WRAPS => [new MethodCallToReturn('SomeClass', 'deny')],
|
||||
]
|
||||
),
|
||||
]);
|
||||
|
|
|
@ -9,7 +9,6 @@ use PhpParser\Node\Expr\MethodCall;
|
|||
use PhpParser\Node\Identifier;
|
||||
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Renaming\ValueObject\MethodCallRenameWithArrayKey;
|
||||
use Rector\Transform\ValueObject\MethodCallToAnotherMethodCallWithArguments;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
@ -46,7 +45,12 @@ CODE_SAMPLE
|
|||
,
|
||||
[
|
||||
self::METHOD_CALL_RENAMES_WITH_ADDED_ARGUMENTS => [
|
||||
new MethodCallToAnotherMethodCallWithArguments('Nette\DI\ServiceDefinition', 'setInject', 'addTag', ['inject']),
|
||||
new MethodCallToAnotherMethodCallWithArguments(
|
||||
'Nette\DI\ServiceDefinition',
|
||||
'setInject',
|
||||
'addTag',
|
||||
['inject']
|
||||
),
|
||||
],
|
||||
]
|
||||
),
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use Rector\Core\PhpParser\AstResolver;
|
||||
use Rector\NodeNameResolver\NodeNameResolver;
|
||||
|
||||
final class ParentParamMatcher
|
||||
{
|
||||
public function __construct(
|
||||
private NodeNameResolver $nodeNameResolver,
|
||||
private AstResolver $astResolver
|
||||
) {
|
||||
}
|
||||
|
||||
public function matchParentParam(StaticCall $parentStaticCall, Param $param, Scope $scope): ?Param
|
||||
{
|
||||
$methodName = $this->nodeNameResolver->getName($parentStaticCall->name);
|
||||
if ($methodName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// match current param to parent call position
|
||||
$parentStaticCallArgPosition = $this->matchParentStaticCallArgPosition($parentStaticCall, $param);
|
||||
if ($parentStaticCallArgPosition === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->resolveParentMethodParam($scope, $methodName, $parentStaticCallArgPosition);
|
||||
}
|
||||
|
||||
private function matchParentStaticCallArgPosition(StaticCall $parentStaticCall, Param $param): int | null
|
||||
{
|
||||
$paramName = $this->nodeNameResolver->getName($param);
|
||||
|
||||
foreach ($parentStaticCall->args as $argPosition => $arg) {
|
||||
if (! $arg->value instanceof Variable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $this->nodeNameResolver->isName($arg->value, $paramName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $argPosition;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function resolveParentMethodParam(Scope $scope, string $methodName, int $paramPosition): ?Param
|
||||
{
|
||||
/** @var ClassReflection $classReflection */
|
||||
$classReflection = $scope->getClassReflection();
|
||||
|
||||
foreach ($classReflection->getParents() as $parnetClassReflection) {
|
||||
if (! $parnetClassReflection->hasMethod($methodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentClassMethod = $this->astResolver->resolveClassMethod($parnetClassReflection->getName(), $methodName);
|
||||
if (! $parentClassMethod instanceof ClassMethod) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $parentClassMethod->params[$paramPosition] ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\TypeDeclaration\Rector\ClassMethod;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\ClassMethod;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\ClassReflection;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\TypeDeclaration\NodeAnalyzer\ParentParamMatcher;
|
||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
|
||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||
|
||||
/**
|
||||
* @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector\ParamTypeByParentCallTypeRectorTest
|
||||
*/
|
||||
final class ParamTypeByParentCallTypeRector extends AbstractRector
|
||||
{
|
||||
public function __construct(
|
||||
private ParentParamMatcher $parentParamMatcher
|
||||
) {
|
||||
}
|
||||
|
||||
public function getRuleDefinition(): RuleDefinition
|
||||
{
|
||||
return new RuleDefinition('Change param type based on parent param type', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeControl
|
||||
{
|
||||
public function __construct(string $name)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class VideoControl extends SomeControl
|
||||
{
|
||||
public function __construct($name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeControl
|
||||
{
|
||||
public function __construct(string $name)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class VideoControl extends SomeControl
|
||||
{
|
||||
public function __construct(string $name)
|
||||
{
|
||||
parent::__construct($name);
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<class-string<Node>>
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [ClassMethod::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMethod $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($this->shouldSkip($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parentStaticCall = $this->findParentStaticCall($node);
|
||||
if (! $parentStaticCall instanceof StaticCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var Scope $scope */
|
||||
$scope = $node->getAttribute(AttributeKey::SCOPE);
|
||||
|
||||
$hasChanged = false;
|
||||
|
||||
foreach ($node->params as $param) {
|
||||
// already has type, skip
|
||||
if ($param->type !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentParam = $this->parentParamMatcher->matchParentParam($parentStaticCall, $param, $scope);
|
||||
if (! $parentParam instanceof Param) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($parentParam->type === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// mimic type
|
||||
$paramType = $parentParam->type;
|
||||
|
||||
// original attributes have to removed to avoid tokens crashing from origin positions
|
||||
$paramType->setAttributes([]);
|
||||
|
||||
$param->type = $paramType;
|
||||
$hasChanged = true;
|
||||
}
|
||||
|
||||
if ($hasChanged) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findParentStaticCall(ClassMethod $classMethod): ?StaticCall
|
||||
{
|
||||
$classMethodName = $this->getName($classMethod);
|
||||
|
||||
/** @var StaticCall[] $staticCalls */
|
||||
$staticCalls = $this->betterNodeFinder->findInstanceOf($classMethod, StaticCall::class);
|
||||
|
||||
foreach ($staticCalls as $staticCall) {
|
||||
if (! $this->isName($staticCall->class, 'parent')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $this->isName($staticCall->name, $classMethodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $staticCall;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function shouldSkip(ClassMethod $classMethod): bool
|
||||
{
|
||||
if ($classMethod->params === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$scope = $classMethod->getAttribute(AttributeKey::SCOPE);
|
||||
if (! $scope instanceof Scope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$classReflection = $scope->getClassReflection();
|
||||
if (! $classReflection instanceof ClassReflection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! $classReflection->isClass();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (interface_exists('UnitEnum')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 8.1
|
||||
*/
|
||||
interface UnitEnum
|
||||
{
|
||||
public string $name;
|
||||
|
||||
/**
|
||||
* @return static[]
|
||||
*/
|
||||
public static function cases(): array;
|
||||
}
|
Loading…
Reference in New Issue