mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-01 17:00:51 +00:00
[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(AddArrayParamDocTypeRector::class);
|
||||||
$services->set(AddArrayReturnDocTypeRector::class);
|
$services->set(AddArrayReturnDocTypeRector::class);
|
||||||
// $services->set(AddParamTypeFromCallersRector::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\Expression;
|
||||||
use PhpParser\Node\Stmt\If_;
|
use PhpParser\Node\Stmt\If_;
|
||||||
use PhpParser\Node\Stmt\Return_;
|
use PhpParser\Node\Stmt\Return_;
|
||||||
use Rector\Core\Exception\ShouldNotHappenException;
|
|
||||||
use Rector\Core\NodeManipulator\IfManipulator;
|
use Rector\Core\NodeManipulator\IfManipulator;
|
||||||
use Rector\Core\NodeManipulator\NullsafeManipulator;
|
use Rector\Core\NodeManipulator\NullsafeManipulator;
|
||||||
use Rector\Core\Rector\AbstractRector;
|
use Rector\Core\Rector\AbstractRector;
|
||||||
|
|
|
@ -67,9 +67,7 @@ CODE_SAMPLE
|
||||||
|
|
||||||
,
|
,
|
||||||
[
|
[
|
||||||
self::METHOD_CALL_WRAPS => [
|
self::METHOD_CALL_WRAPS => [new MethodCallToReturn('SomeClass', 'deny')],
|
||||||
new MethodCallToReturn('SomeClass', 'deny'),
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -9,7 +9,6 @@ use PhpParser\Node\Expr\MethodCall;
|
||||||
use PhpParser\Node\Identifier;
|
use PhpParser\Node\Identifier;
|
||||||
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
|
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
|
||||||
use Rector\Core\Rector\AbstractRector;
|
use Rector\Core\Rector\AbstractRector;
|
||||||
use Rector\Renaming\ValueObject\MethodCallRenameWithArrayKey;
|
|
||||||
use Rector\Transform\ValueObject\MethodCallToAnotherMethodCallWithArguments;
|
use Rector\Transform\ValueObject\MethodCallToAnotherMethodCallWithArguments;
|
||||||
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
|
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
|
||||||
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
|
||||||
|
@ -46,7 +45,12 @@ CODE_SAMPLE
|
||||||
,
|
,
|
||||||
[
|
[
|
||||||
self::METHOD_CALL_RENAMES_WITH_ADDED_ARGUMENTS => [
|
self::METHOD_CALL_RENAMES_WITH_ADDED_ARGUMENTS => [
|
||||||
new MethodCallToAnotherMethodCallWithArguments('Nette\DI\ServiceDefinition', 'setInject', 'addTag', ['inject']),
|
new MethodCallToAnotherMethodCallWithArguments(
|
||||||
|
'Nette\DI\ServiceDefinition',
|
||||||
|
'setInject',
|
||||||
|
'addTag',
|
||||||
|
['inject']
|
||||||
|
),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
79
rules/TypeDeclaration/NodeAnalyzer/ParentParamMatcher.php
Normal file
79
rules/TypeDeclaration/NodeAnalyzer/ParentParamMatcher.php
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
20
stubs/Php/UnitEnum.php
Normal file
20
stubs/Php/UnitEnum.php
Normal file
|
@ -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
Block a user