[TypeDeclaration] Add ParamTypeByParentCallTypeRector (#519)

This commit is contained in:
Tomas Votruba 2021-07-28 21:34:42 +02:00 committed by GitHub
parent a467f764ec
commit 3cf3e1fc25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 409 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{
}
}

View File

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

View File

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

View File

@ -67,9 +67,7 @@ CODE_SAMPLE
,
[
self::METHOD_CALL_WRAPS => [
new MethodCallToReturn('SomeClass', 'deny'),
]
self::METHOD_CALL_WRAPS => [new MethodCallToReturn('SomeClass', 'deny')],
]
),
]);

View File

@ -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']
),
],
]
),

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

View File

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