mirror of
https://github.com/rectorphp/rector.git
synced 2025-04-21 16:02:23 +02:00
Added support for renaming class and it’s namespace
This commit is contained in:
parent
fac1503915
commit
9958a44ec3
@ -8,6 +8,15 @@ final class ClassNaming
|
||||
{
|
||||
public function getShortName(string $fullyQualifiedName): string
|
||||
{
|
||||
$fullyQualifiedName = trim($fullyQualifiedName, '\\');
|
||||
|
||||
return Strings::after($fullyQualifiedName, '\\', -1) ?: $fullyQualifiedName;
|
||||
}
|
||||
|
||||
public function getNamespace(string $fullyQualifiedName): ?string
|
||||
{
|
||||
$fullyQualifiedName = trim($fullyQualifiedName, '\\');
|
||||
|
||||
return Strings::before($fullyQualifiedName, '\\', -1) ?: null;
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,17 @@ namespace Rector\Rector\Class_;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Stmt\Class_;
|
||||
use PhpParser\Node\Stmt\ClassLike;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\Stmt\Namespace_;
|
||||
use PhpParser\Node\Stmt\Property;
|
||||
use PhpParser\Node\Stmt\Use_;
|
||||
use PhpParser\Node\Stmt\UseUse;
|
||||
use Rector\CodingStyle\Naming\ClassNaming;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
||||
use Rector\Rector\AbstractRector;
|
||||
@ -30,12 +34,26 @@ final class RenameClassRector extends AbstractRector
|
||||
*/
|
||||
private $docBlockManipulator;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $alreadyProcessedClasses = [];
|
||||
/**
|
||||
* @var ClassNaming
|
||||
*/
|
||||
private $classNaming;
|
||||
|
||||
/**
|
||||
* @param string[] $oldToNewClasses
|
||||
*/
|
||||
public function __construct(DocBlockManipulator $docBlockManipulator, array $oldToNewClasses = [])
|
||||
public function __construct(
|
||||
DocBlockManipulator $docBlockManipulator,
|
||||
ClassNaming $classNaming,
|
||||
array $oldToNewClasses = []
|
||||
)
|
||||
{
|
||||
$this->docBlockManipulator = $docBlockManipulator;
|
||||
$this->classNaming = $classNaming;
|
||||
$this->oldToNewClasses = $oldToNewClasses;
|
||||
}
|
||||
|
||||
@ -81,7 +99,14 @@ CODE_SAMPLE
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [Name::class, Property::class, FunctionLike::class, Expression::class];
|
||||
return [
|
||||
Name::class,
|
||||
Property::class,
|
||||
FunctionLike::class,
|
||||
Expression::class,
|
||||
ClassLike::class,
|
||||
Namespace_::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,6 +137,18 @@ CODE_SAMPLE
|
||||
return new FullyQualified($newName);
|
||||
}
|
||||
|
||||
if ($node instanceof Namespace_) {
|
||||
$node = $this->refactorNamespaceNode($node);
|
||||
}
|
||||
|
||||
if ($node instanceof ClassLike) {
|
||||
$node = $this->refactorClassLikeNode($node);
|
||||
}
|
||||
|
||||
if (! $node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->oldToNewClasses as $oldType => $newType) {
|
||||
$this->docBlockManipulator->changeType($node, $oldType, $newType);
|
||||
}
|
||||
@ -176,4 +213,77 @@ CODE_SAMPLE
|
||||
}
|
||||
return ! (in_array($node, $classNode->implements, true) && class_exists($newName));
|
||||
}
|
||||
|
||||
private function refactorNamespaceNode(Namespace_ $node): ?Node
|
||||
{
|
||||
$name = $this->getName($node);
|
||||
if ($name === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$classNode = $this->getClassOfNamespaceToRefactor($node);
|
||||
if (! $classNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newClassFqn = $this->oldToNewClasses[$this->getName($classNode)];
|
||||
$newNamespace = $this->classNaming->getNamespace($newClassFqn);
|
||||
|
||||
// Renaming to class without namespace (example MyNamespace\DateTime -> DateTimeImmutable)
|
||||
if (! $newNamespace) {
|
||||
$classNode->name = new Identifier($newClassFqn);
|
||||
|
||||
return $classNode;
|
||||
}
|
||||
|
||||
$node->name = new Name($newNamespace);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
private function getClassOfNamespaceToRefactor(Namespace_ $namespace): ?ClassLike
|
||||
{
|
||||
$foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node) {
|
||||
if (! $node instanceof ClassLike) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($this->oldToNewClasses[$this->getName($node)]);
|
||||
});
|
||||
|
||||
return $foundClass instanceof ClassLike ? $foundClass : null;
|
||||
}
|
||||
|
||||
private function refactorClassLikeNode(ClassLike $classLike): ?Node
|
||||
{
|
||||
$name = $this->getName($classLike);
|
||||
if ($name === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newName = $this->oldToNewClasses[$name] ?? null;
|
||||
if (! $newName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// prevents re-iterating same class in endless loop
|
||||
if (in_array($name, $this->alreadyProcessedClasses, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->alreadyProcessedClasses[] = $name;
|
||||
|
||||
$newName = $this->oldToNewClasses[$name];
|
||||
$newClassNamePart = $this->classNaming->getShortName($newName);
|
||||
$newNamespacePart = $this->classNaming->getNamespace($newName);
|
||||
|
||||
$classLike->name = new Identifier($newClassNamePart);
|
||||
|
||||
// Old class did not have any namespace, we need to wrap class with Namespace_ node
|
||||
if ($newNamespacePart && ! $this->classNaming->getNamespace($name)) {
|
||||
return new Namespace_(new Name($newNamespacePart), [$classLike]);
|
||||
}
|
||||
|
||||
return $classLike;
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +129,11 @@ abstract class AbstractRectorTestCase extends AbstractKernelTestCase
|
||||
$this->autoloadTestFixture = true;
|
||||
}
|
||||
|
||||
protected function doTestFile(string $file): void
|
||||
{
|
||||
$this->doTestFiles([$file]);
|
||||
}
|
||||
|
||||
protected function getTempPath(): string
|
||||
{
|
||||
return sys_get_temp_dir() . '/rector_temp_tests';
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class MyClass
|
||||
{
|
||||
/**
|
||||
* @return MyClass
|
||||
*/
|
||||
public function createSelf(): MyClass
|
||||
{
|
||||
return new MyClass;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace MyNewNamespace;
|
||||
|
||||
class MyNewClass
|
||||
{
|
||||
/**
|
||||
* @return \MyNewNamespace\MyNewClass
|
||||
*/
|
||||
public function createSelf(): \MyNewNamespace\MyNewClass
|
||||
{
|
||||
return new \MyNewNamespace\MyNewClass;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class AnotherMyClass
|
||||
{
|
||||
/**
|
||||
* @return AnotherMyClass
|
||||
*/
|
||||
public function createSelf(): AnotherMyClass
|
||||
{
|
||||
return new AnotherMyClass;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
class MyNewClassWithoutNamespace
|
||||
{
|
||||
/**
|
||||
* @return MyNewClassWithoutNamespace
|
||||
*/
|
||||
public function createSelf(): \MyNewClassWithoutNamespace
|
||||
{
|
||||
return new \MyNewClassWithoutNamespace;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
class MyOldClass
|
||||
{
|
||||
/**
|
||||
* @return MyOldClass
|
||||
*/
|
||||
public function createSelf(): MyOldClass
|
||||
{
|
||||
return new MyOldClass;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
class MyNewClass
|
||||
{
|
||||
/**
|
||||
* @return \MyNamespace\MyNewClass
|
||||
*/
|
||||
public function createSelf(): \MyNamespace\MyNewClass
|
||||
{
|
||||
return new \MyNamespace\MyNewClass;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
class AnotherMyOldClass
|
||||
{
|
||||
/**
|
||||
* @return AnotherMyOldClass
|
||||
*/
|
||||
public function createSelf(): AnotherMyOldClass
|
||||
{
|
||||
return new AnotherMyOldClass;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
class AnotherMyNewClass
|
||||
{
|
||||
/**
|
||||
* @return AnotherMyNewClass
|
||||
*/
|
||||
public function createSelf(): \AnotherMyNewClass
|
||||
{
|
||||
return new \AnotherMyNewClass;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
interface MyInterface
|
||||
{
|
||||
/**
|
||||
* @return MyInterface
|
||||
*/
|
||||
public function createSelf(): MyInterface;
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace MyNewNamespace;
|
||||
|
||||
interface MyNewInterface
|
||||
{
|
||||
/**
|
||||
* @return \MyNewNamespace\MyNewInterface
|
||||
*/
|
||||
public function createSelf(): \MyNewNamespace\MyNewInterface;
|
||||
}
|
||||
|
||||
?>
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace MyNamespace;
|
||||
|
||||
trait MyTrait
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace MyNewNamespace;
|
||||
|
||||
trait MyNewTrait
|
||||
{
|
||||
}
|
||||
|
||||
?>
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Rector\Tests\Rector\Class_\RenameClassRector;
|
||||
|
||||
use Iterator;
|
||||
use Manual\Twig\TwigFilter;
|
||||
use Manual_Twig_Filter;
|
||||
use Rector\Rector\Class_\RenameClassRector;
|
||||
@ -17,17 +18,31 @@ use Rector\Tests\Rector\Class_\RenameClassRector\Source\OldClassWithTypo;
|
||||
*/
|
||||
final class RenameClassRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
/**
|
||||
* @dataProvider provideTestFiles
|
||||
*/
|
||||
public function test(string $filePath): void
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/class_to_new.php.inc',
|
||||
__DIR__ . '/Fixture/class_to_interface.php.inc',
|
||||
__DIR__ . '/Fixture/interface_to_class.php.inc',
|
||||
__DIR__ . '/Fixture/name_insensitive.php.inc',
|
||||
__DIR__ . '/Fixture/twig_case.php.inc',
|
||||
__DIR__ . '/Fixture/underscore_doc.php.inc',
|
||||
__DIR__ . '/Fixture/keep_return_tag.php.inc',
|
||||
]);
|
||||
$this->doTestFile($filePath);
|
||||
}
|
||||
|
||||
public function provideTestFiles(): Iterator
|
||||
{
|
||||
yield [__DIR__ . '/Fixture/class_to_new.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/class_to_interface.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/interface_to_class.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/name_insensitive.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/twig_case.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/underscore_doc.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/keep_return_tag.php.inc'];
|
||||
|
||||
// Renaming class itself and its namespace
|
||||
yield [__DIR__ . '/Fixture/rename_class_without_namespace.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/rename_class.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/rename_interface.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/rename_trait.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/rename_class_without_namespace_to_class_without_namespace.php.inc'];
|
||||
yield [__DIR__ . '/Fixture/rename_class_to_class_without_namespace.php.inc'];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,6 +59,13 @@ final class RenameClassRectorTest extends AbstractRectorTestCase
|
||||
Manual_Twig_Filter::class => TwigFilter::class,
|
||||
'Twig_AbstractManualExtension' => AbstractManualExtension::class,
|
||||
'Twig_Extension_Sandbox' => 'Twig\Extension\SandboxExtension',
|
||||
// Renaming class itself and its namespace
|
||||
'MyNamespace\MyClass' => 'MyNewNamespace\MyNewClass',
|
||||
'MyNamespace\MyTrait' => 'MyNewNamespace\MyNewTrait',
|
||||
'MyNamespace\MyInterface' => 'MyNewNamespace\MyNewInterface',
|
||||
'MyOldClass' => 'MyNamespace\MyNewClass',
|
||||
'AnotherMyOldClass' => 'AnotherMyNewClass',
|
||||
'MyNamespace\AnotherMyClass' => 'MyNewClassWithoutNamespace',
|
||||
],
|
||||
]];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user