[Renaming] Handle rename template tag of class on RenameClassRector (#1318)

* Add failing test fixture for RenameClassRector

# Failing Test for RenameClassRector

Based on https://getrector.org/demo/1ec4e145-1672-628e-a7a2-e3aa19da6373

* Update with class from fixtures

* Update rename_generic_template_of_fixture.php.inc

* Use full generic example

* update fixture

* closes #1309

* [ci-review] Rector Rectify

* [ci-review] Rector Rectify

* phpstan

* [ci-review] Rector Rectify

* temp back

* re-use class node for method get

* phpstan

* [ci-review] Rector Rectify

* [ci-review] Rector Rectify

Co-authored-by: Alexander Schranz <alexander@sulu.io>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Tomas Votruba <tomas.vot@gmail.com>
This commit is contained in:
Abdul Malik Ikhsan 2021-11-27 00:01:30 +07:00 committed by GitHub
parent 6aeef64c72
commit 5a0947d9a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 19 deletions

View File

@ -8,6 +8,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PropertyTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
@ -22,6 +23,7 @@ final class NodeTypes
ReturnTagValueNode::class,
ThrowsTagValueNode::class,
PropertyTagValueNode::class,
TemplateTagValueNode::class,
];
/**

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector\Fixture;
use MyNamespace\MyClass;
/**
* @template T of MyClass
*/
interface MyServiceInterface
{
/**
* @return T
*/
public function get(): MyClass;
}
?>
-----
<?php
namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector\Fixture;
use MyNamespace\MyClass;
/**
* @template T of \MyNewNamespace\MyNewClass
*/
interface MyServiceInterface
{
/**
* @return T
*/
public function get(): \MyNewNamespace\MyNewClass;
}
?>

View File

@ -44,8 +44,8 @@ final class PhpAttributeAnalyzer
return false;
}
$reflectionClass = $this->reflectionProvider->getClass($className);
$ancestorClassReflections = $reflectionClass->getAncestors();
$classReflection = $this->reflectionProvider->getClass($className);
$ancestorClassReflections = $classReflection->getAncestors();
foreach ($ancestorClassReflections as $ancestorClassReflection) {
$ancestorClassName = $ancestorClassReflection->getName();
if ($ancestorClassName === $className) {

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Rector\Transform\Rector\Class_;
use PhpParser\Node;
@ -7,6 +9,7 @@ use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\ValueObject\MethodName;
use Rector\Core\ValueObject\PhpVersionFeature;
use Rector\FamilyTree\Reflection\FamilyRelationsAnalyzer;
use Rector\Php80\NodeAnalyzer\PhpAttributeAnalyzer;
@ -56,7 +59,9 @@ CODE_SAMPLE
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
@ -67,11 +72,7 @@ CODE_SAMPLE
*/
public function refactor(Node $node): ?Node
{
if (
$this->isDescendantOfStdclass($node) ||
$this->hasNeededAttributeAlready($node) ||
$this->hasMagicSetMethod($node)
) {
if ($this->shouldSkip($node)) {
return null;
}
@ -83,14 +84,14 @@ CODE_SAMPLE
return PhpVersionFeature::DEPRECATE_DYNAMIC_PROPERTIES;
}
private function isDescendantOfStdclass(Class_ $node): bool
private function isDescendantOfStdclass(Class_ $class): bool
{
if (! $node->extends instanceof FullyQualified) {
if (! $class->extends instanceof FullyQualified) {
return false;
}
$ancestorClassNames = $this->familyRelationsAnalyzer->getClassLikeAncestorNames($node);
return in_array('stdClass', $ancestorClassNames);
$ancestorClassNames = $this->familyRelationsAnalyzer->getClassLikeAncestorNames($class);
return in_array('stdClass', $ancestorClassNames, true);
}
private function hasNeededAttributeAlready(Class_ $class): bool
@ -100,19 +101,13 @@ CODE_SAMPLE
return true;
}
if (!$class->extends instanceof FullyQualified) {
if (! $class->extends instanceof FullyQualified) {
return false;
}
return $this->phpAttributeAnalyzer->hasInheritedPhpAttribute($class, self::ATTRIBUTE);
}
private function hasMagicSetMethod(Class_ $class): bool
{
$classReflection = $this->reflectionProvider->getClass($class->namespacedName);
return $classReflection->hasMethod('__set');
}
private function addAllowDynamicPropertiesAttribute(Class_ $class): Class_
{
$attributeGroup = $this->phpAttributeGroupFactory->createFromClass(self::ATTRIBUTE);
@ -120,4 +115,24 @@ CODE_SAMPLE
return $class;
}
private function shouldSkip(Class_ $class): bool
{
if ($this->isDescendantOfStdclass($class)) {
return true;
}
if ($this->hasNeededAttributeAlready($class)) {
return true;
}
return $this->hasMagicSetMethod($class);
}
private function hasMagicSetMethod(Class_ $class): bool
{
$className = (string) $this->getName($class);
$classReflection = $this->reflectionProvider->getClass($className);
return $classReflection->hasMethod(MethodName::__SET);
}
}