2017-10-05 10:40:19 +00:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
2018-08-01 19:22:52 +00:00
|
|
|
namespace Rector\Rector\Class_;
|
2017-10-05 10:41:37 +00:00
|
|
|
|
2017-10-06 06:34:58 +00:00
|
|
|
use PhpParser\Node;
|
2019-01-14 11:46:19 +00:00
|
|
|
use PhpParser\Node\Expr\New_;
|
2019-03-15 20:12:45 +00:00
|
|
|
use PhpParser\Node\FunctionLike;
|
2017-10-06 06:34:58 +00:00
|
|
|
use PhpParser\Node\Name;
|
|
|
|
use PhpParser\Node\Name\FullyQualified;
|
2019-01-14 12:36:37 +00:00
|
|
|
use PhpParser\Node\Stmt\Class_;
|
2019-03-27 13:54:57 +00:00
|
|
|
use PhpParser\Node\Stmt\Expression;
|
2019-03-15 19:37:48 +00:00
|
|
|
use PhpParser\Node\Stmt\Property;
|
2019-02-10 21:15:51 +00:00
|
|
|
use PhpParser\Node\Stmt\Use_;
|
2019-02-10 10:01:33 +00:00
|
|
|
use PhpParser\Node\Stmt\UseUse;
|
2019-04-13 09:20:27 +00:00
|
|
|
use Rector\NodeTypeResolver\Node\AttributeKey;
|
2019-02-27 21:54:39 +00:00
|
|
|
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
|
2017-10-06 06:34:58 +00:00
|
|
|
use Rector\Rector\AbstractRector;
|
2018-08-01 19:22:52 +00:00
|
|
|
use Rector\RectorDefinition\ConfiguredCodeSample;
|
2018-04-08 11:51:26 +00:00
|
|
|
use Rector\RectorDefinition\RectorDefinition;
|
2017-10-05 10:40:19 +00:00
|
|
|
|
2019-03-09 12:51:57 +00:00
|
|
|
final class RenameClassRector extends AbstractRector
|
2017-10-05 10:40:19 +00:00
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var string[]
|
|
|
|
*/
|
|
|
|
private $oldToNewClasses = [];
|
|
|
|
|
2019-02-10 21:15:51 +00:00
|
|
|
/**
|
2019-02-27 21:54:39 +00:00
|
|
|
* @var DocBlockManipulator
|
2019-02-10 21:15:51 +00:00
|
|
|
*/
|
2019-02-27 21:54:39 +00:00
|
|
|
private $docBlockManipulator;
|
2019-02-10 21:15:51 +00:00
|
|
|
|
2017-10-05 10:40:19 +00:00
|
|
|
/**
|
|
|
|
* @param string[] $oldToNewClasses
|
|
|
|
*/
|
2019-04-26 13:32:24 +00:00
|
|
|
public function __construct(DocBlockManipulator $docBlockManipulator, array $oldToNewClasses = [])
|
2017-11-09 02:33:17 +00:00
|
|
|
{
|
2019-02-27 21:54:39 +00:00
|
|
|
$this->docBlockManipulator = $docBlockManipulator;
|
2019-04-26 13:32:24 +00:00
|
|
|
$this->oldToNewClasses = $oldToNewClasses;
|
2017-10-05 10:40:19 +00:00
|
|
|
}
|
|
|
|
|
2018-04-08 11:51:26 +00:00
|
|
|
public function getDefinition(): RectorDefinition
|
|
|
|
{
|
2018-08-01 13:34:55 +00:00
|
|
|
return new RectorDefinition('Replaces defined classes by new ones.', [
|
2018-08-01 19:22:52 +00:00
|
|
|
new ConfiguredCodeSample(
|
2018-10-17 10:51:28 +00:00
|
|
|
<<<'CODE_SAMPLE'
|
2019-05-01 18:51:58 +00:00
|
|
|
namespace App;
|
|
|
|
|
2018-10-17 10:51:28 +00:00
|
|
|
use SomeOldClass;
|
|
|
|
|
2019-03-15 19:36:58 +00:00
|
|
|
function someFunction(SomeOldClass $someOldClass): SomeOldClass
|
2018-10-17 10:51:28 +00:00
|
|
|
{
|
|
|
|
if ($someOldClass instanceof SomeOldClass) {
|
|
|
|
return new SomeOldClass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CODE_SAMPLE
|
|
|
|
,
|
|
|
|
<<<'CODE_SAMPLE'
|
2019-05-01 18:51:58 +00:00
|
|
|
namespace App;
|
|
|
|
|
2018-10-17 10:51:28 +00:00
|
|
|
use SomeNewClass;
|
|
|
|
|
2019-03-15 19:36:58 +00:00
|
|
|
function someFunction(SomeNewClass $someOldClass): SomeNewClass
|
2018-10-17 10:51:28 +00:00
|
|
|
{
|
|
|
|
if ($someOldClass instanceof SomeNewClass) {
|
|
|
|
return new SomeNewClass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
CODE_SAMPLE
|
|
|
|
,
|
2018-08-01 19:22:52 +00:00
|
|
|
[
|
2019-05-01 18:51:58 +00:00
|
|
|
'App\SomeOldClass' => 'App\SomeNewClass',
|
2018-08-01 19:22:52 +00:00
|
|
|
]
|
|
|
|
),
|
2018-04-08 11:51:26 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2018-08-14 22:12:41 +00:00
|
|
|
/**
|
|
|
|
* @return string[]
|
|
|
|
*/
|
|
|
|
public function getNodeTypes(): array
|
2017-10-06 06:34:58 +00:00
|
|
|
{
|
2019-03-27 13:54:57 +00:00
|
|
|
return [Name::class, Property::class, FunctionLike::class, Expression::class];
|
2017-10-06 06:34:58 +00:00
|
|
|
}
|
|
|
|
|
2017-10-05 10:40:19 +00:00
|
|
|
/**
|
2019-03-15 22:03:18 +00:00
|
|
|
* @param Name|FunctionLike|Property $node
|
2017-10-05 10:40:19 +00:00
|
|
|
*/
|
2018-10-22 10:43:10 +00:00
|
|
|
public function refactor(Node $node): ?Node
|
2017-10-06 06:34:58 +00:00
|
|
|
{
|
2019-03-27 13:49:41 +00:00
|
|
|
// replace on @var/@param/@return/@throws
|
|
|
|
foreach ($this->oldToNewClasses as $oldClass => $newClass) {
|
|
|
|
$this->docBlockManipulator->changeType($node, $oldClass, $newClass);
|
|
|
|
}
|
|
|
|
|
2019-02-10 21:15:51 +00:00
|
|
|
if ($node instanceof Name) {
|
|
|
|
$name = $this->getName($node);
|
2019-03-15 19:36:58 +00:00
|
|
|
if ($name === null) {
|
2019-02-10 21:15:51 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$newName = $this->oldToNewClasses[$name] ?? null;
|
|
|
|
if (! $newName) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-02-27 13:07:25 +00:00
|
|
|
if (! $this->isClassToInterfaceValidChange($node, $newName)) {
|
2019-02-10 21:15:51 +00:00
|
|
|
return null;
|
|
|
|
}
|
2018-10-22 15:53:21 +00:00
|
|
|
|
2019-02-10 21:15:51 +00:00
|
|
|
return new FullyQualified($newName);
|
2018-08-14 21:33:39 +00:00
|
|
|
}
|
2017-10-06 07:30:34 +00:00
|
|
|
|
2019-02-10 21:15:51 +00:00
|
|
|
foreach ($this->oldToNewClasses as $oldType => $newType) {
|
2019-02-27 21:54:39 +00:00
|
|
|
$this->docBlockManipulator->changeType($node, $oldType, $newType);
|
2019-01-14 12:36:37 +00:00
|
|
|
}
|
|
|
|
|
2019-02-10 21:15:51 +00:00
|
|
|
return $node;
|
2019-01-14 12:36:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks validity:
|
|
|
|
*
|
|
|
|
* - extends SomeClass
|
|
|
|
* - extends SomeInterface
|
|
|
|
*
|
|
|
|
* - new SomeClass
|
|
|
|
* - new SomeInterface
|
|
|
|
*
|
|
|
|
* - implements SomeInterface
|
|
|
|
* - implements SomeClass
|
|
|
|
*/
|
|
|
|
private function isClassToInterfaceValidChange(Node $node, string $newName): bool
|
|
|
|
{
|
2019-01-14 11:46:19 +00:00
|
|
|
// ensure new is not with interface
|
2019-04-13 09:20:27 +00:00
|
|
|
$parentNode = $node->getAttribute(AttributeKey::PARENT_NODE);
|
2019-01-14 12:36:37 +00:00
|
|
|
if ($parentNode instanceof New_ && interface_exists($newName)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($parentNode instanceof Class_) {
|
2019-02-10 10:01:33 +00:00
|
|
|
return $this->isValidClassNameChange($node, $newName, $parentNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
// prevent to change to import, that already exists
|
|
|
|
if ($parentNode instanceof UseUse) {
|
|
|
|
return $this->isValidUseImportChange($newName, $parentNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-02-22 17:25:31 +00:00
|
|
|
private function isValidUseImportChange(string $newName, UseUse $useUse): bool
|
2019-02-10 10:01:33 +00:00
|
|
|
{
|
2019-02-10 21:15:51 +00:00
|
|
|
/** @var Use_[]|null $useNodes */
|
2019-04-13 09:20:27 +00:00
|
|
|
$useNodes = $useUse->getAttribute(AttributeKey::USE_NODES);
|
2019-02-10 12:02:04 +00:00
|
|
|
if ($useNodes === null) {
|
|
|
|
return true;
|
|
|
|
}
|
2019-01-14 12:36:37 +00:00
|
|
|
|
2019-02-10 10:01:33 +00:00
|
|
|
foreach ($useNodes as $useNode) {
|
|
|
|
if ($this->isName($useNode, $newName)) {
|
|
|
|
// name already exists
|
2019-01-14 12:36:37 +00:00
|
|
|
return false;
|
2019-01-14 11:46:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-14 12:36:37 +00:00
|
|
|
return true;
|
2017-10-06 07:30:34 +00:00
|
|
|
}
|
2019-02-10 10:01:33 +00:00
|
|
|
|
|
|
|
private function isValidClassNameChange(Node $node, string $newName, Class_ $classNode): bool
|
|
|
|
{
|
|
|
|
if ($classNode->extends === $node && interface_exists($newName)) {
|
|
|
|
return false;
|
|
|
|
}
|
2019-02-27 13:21:11 +00:00
|
|
|
return ! (in_array($node, $classNode->implements, true) && class_exists($newName));
|
2019-02-10 10:01:33 +00:00
|
|
|
}
|
2017-10-05 10:40:19 +00:00
|
|
|
}
|