diff --git a/packages/post-rector/src/Rector/ClassRenamingPostRector.php b/packages/post-rector/src/Rector/ClassRenamingPostRector.php new file mode 100644 index 000000000000..9b46bb678dd4 --- /dev/null +++ b/packages/post-rector/src/Rector/ClassRenamingPostRector.php @@ -0,0 +1,44 @@ +renamedClassesCollector = $renamedClassesCollector; + $this->classRenamer = $classRenamer; + } + + public function getPriority(): int + { + return 100; + } + + public function enterNode(Node $node): ?Node + { + return $this->classRenamer->renameNode($node, $this->renamedClassesCollector->getOldToNewClasses()); + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Post Rector that renames classes'); + } +} diff --git a/rules/autodiscovery/src/FileMover/FileMover.php b/rules/autodiscovery/src/FileMover/FileMover.php index edc9ea88d54d..9d3fef249fb4 100644 --- a/rules/autodiscovery/src/FileMover/FileMover.php +++ b/rules/autodiscovery/src/FileMover/FileMover.php @@ -10,7 +10,6 @@ use Rector\Autodiscovery\Configuration\CategoryNamespaceProvider; use Rector\Autodiscovery\ValueObject\NodesWithFileDestinationValueObject; use Rector\Core\PhpParser\Node\BetterNodeFinder; -use Rector\PSR4\Collector\RenamedClassesCollector; use Rector\PSR4\FileRelocationResolver; use Symplify\SmartFileSystem\SmartFileInfo; @@ -26,11 +25,6 @@ final class FileMover */ private $fileRelocationResolver; - /** - * @var RenamedClassesCollector - */ - private $renamedClassesCollector; - /** * @var CategoryNamespaceProvider */ @@ -39,12 +33,10 @@ final class FileMover public function __construct( BetterNodeFinder $betterNodeFinder, FileRelocationResolver $fileRelocationResolver, - RenamedClassesCollector $renamedClassesCollector, CategoryNamespaceProvider $categoryNamespaceProvider ) { $this->betterNodeFinder = $betterNodeFinder; $this->fileRelocationResolver = $fileRelocationResolver; - $this->renamedClassesCollector = $renamedClassesCollector; $this->categoryNamespaceProvider = $categoryNamespaceProvider; } @@ -75,17 +67,14 @@ public function createMovedNodesAndFilePath( return null; } - // 1. create helping rename class rector.yaml + class_alias autoload file - $this->renamedClassesCollector->addClassRename($currentClassName, $newClassName); - - // 2. rename namespace + // 1. rename namespace foreach ($nodes as $node) { if ($node instanceof Namespace_) { $node->name = new Name($newNamespaceName); } } - // 3. return changed nodes and new file destination + // 2. return changed nodes and new file destination $newFileDestination = $this->fileRelocationResolver->createNewFileDestination( $smartFileInfo, $desiredGroupName, diff --git a/rules/psr4/src/Collector/RenamedClassesCollector.php b/rules/psr4/src/Collector/RenamedClassesCollector.php index 53f0dacb38e0..41b6e124366c 100644 --- a/rules/psr4/src/Collector/RenamedClassesCollector.php +++ b/rules/psr4/src/Collector/RenamedClassesCollector.php @@ -4,124 +4,23 @@ namespace Rector\PSR4\Collector; -use Rector\PSR4\ValueObject\ClassRenameValueObject; - final class RenamedClassesCollector { /** - * @var ClassRenameValueObject[] + * @var string[] */ - private $classRenames = []; + private $oldToNewClass = []; public function addClassRename(string $oldClass, string $newClass): void { - $parentClasses = $this->getParentClasses($oldClass); - - $this->classRenames[$oldClass] = new ClassRenameValueObject($oldClass, $newClass, $parentClasses); - } - - /** - * @return string[] - */ - public function getOldToNewClassesSortedByHighestParentsAsString(): array - { - $oldToNewClasses = []; - foreach ($this->getClassRenamesSortedByHighestParents() as $classRename) { - $oldToNewClasses[$classRename->getOldClass()] = $classRename->getNewClass(); - } - - return $oldToNewClasses; - } - - /** - * @return ClassRenameValueObject[] - */ - public function getClassRenamesSortedByHighestParents(): array - { - $this->filterOutParentsThatAreNotCollected(); - - $leftClassPriorityStack = $this->createLeftClassPriorityStack(); - - return $this->sortClassRenamedByLeftClassPriorityStack($this->classRenames, $leftClassPriorityStack); - } - - public function hasClassRenames(): bool - { - return $this->classRenames !== []; - } - - /** - * Returns all parent classes, implemented interfaces and used traits by specific class - * - * @return string[] - */ - private function getParentClasses(string $className): array - { - $parentClasses = class_parents($className) + class_implements($className) + class_uses($className); - - return array_values($parentClasses); - } - - /** - * We don't need to sort classes that are not here. - */ - private function filterOutParentsThatAreNotCollected(): void - { - foreach ($this->classRenames as $classRename) { - foreach ($classRename->getParentClasses() as $parent) { - if (isset($this->classRenames[$parent])) { - continue; - } - - $classRename->removeParent($parent); - } - } + $this->oldToNewClass[$oldClass] = $newClass; } /** * @return string[] */ - private function createLeftClassPriorityStack(): array + public function getOldToNewClasses(): array { - $leftClassPriorityStack = []; - foreach ($this->classRenames as $classRename) { - foreach ($classRename->getParentClasses() as $parentClass) { - $leftClassPriorityStack[$parentClass] = $classRename->getOldClass(); - } - } - - return $leftClassPriorityStack; - } - - /** - * @param ClassRenameValueObject[] $classRenames - * @param string[] $leftClassPriorityStack - * @return ClassRenameValueObject[] - */ - private function sortClassRenamedByLeftClassPriorityStack(array $classRenames, array $leftClassPriorityStack): array - { - usort($classRenames, function ( - ClassRenameValueObject $firstClassRename, - ClassRenameValueObject $secondClassRename - ) use ($leftClassPriorityStack): int { - if ($secondClassRename->getParentClasses() === []) { - // no parents, put first - return 1; - } - - if (! isset($leftClassPriorityStack[$secondClassRename->getOldClass()])) { - return -1; - } - - $lessPriorityClass = $leftClassPriorityStack[$secondClassRename->getOldClass()]; - if ($lessPriorityClass === $firstClassRename->getOldClass()) { - // put class with higher priority first - return 1; - } - - return -1; - }); - - return $classRenames; + return $this->oldToNewClass; } } diff --git a/rules/psr4/src/Extension/RenamedClassesReportExtension.php b/rules/psr4/src/Extension/RenamedClassesReportExtension.php deleted file mode 100644 index 022ebb439ad8..000000000000 --- a/rules/psr4/src/Extension/RenamedClassesReportExtension.php +++ /dev/null @@ -1,108 +0,0 @@ -renamedClassesCollector = $renamedClassesCollector; - $this->symfonyStyle = $symfonyStyle; - $this->betterStandardPrinter = $betterStandardPrinter; - } - - public function run(): void - { - if (! $this->renamedClassesCollector->hasClassRenames()) { - return; - } - - // 1. dump rector config for RenameClassRector - $rectorYamlContent = $this->createRectorYamlContent(); - FileSystem::write(getcwd() . DIRECTORY_SEPARATOR . 'renames-rector.yaml', $rectorYamlContent); - - // 2. dump class aliases - $renameClassesAliasesContent = $this->createRenameClassAliasContent(); - FileSystem::write(getcwd() . DIRECTORY_SEPARATOR . 'class-aliases.php', $renameClassesAliasesContent); - - // 3. tell user what to do next - $this->symfonyStyle->warning( - 'Now rename classes: "vendor/bin/rector process src tests -c renames-rector.yaml -a class-aliases.php"' - ); - } - - private function createRectorYamlContent(): string - { - $oldToNewClasses = $this->renamedClassesCollector->getOldToNewClassesSortedByHighestParentsAsString(); - - $data = [ - # rector.yaml - 'services' => [ - RenameClassRector::class => [ - '$oldToNewClasses' => $oldToNewClasses, - ], - ], - ]; - - return Yaml::dump($data, 10); - } - - private function createRenameClassAliasContent(): string - { - $classRenames = $this->renamedClassesCollector->getClassRenamesSortedByHighestParents(); - - return $this->betterStandardPrinter->prettyPrintFile($this->createClassAliasNodes($classRenames)); - } - - /** - * @param ClassRenameValueObject[] $classRenames - * @return Expression[] - */ - private function createClassAliasNodes(array $classRenames): array - { - $nodes = []; - foreach ($classRenames as $classRename) { - $classAlias = new FuncCall(new Name('class_alias')); - $classAlias->args[] = new Arg(new String_($classRename->getNewClass())); - $classAlias->args[] = new Arg(new String_($classRename->getOldClass())); - - $nodes[] = new Expression($classAlias); - } - - return $nodes; - } -} diff --git a/rules/psr4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php b/rules/psr4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php index 094483d73de7..9c1f28ae979b 100644 --- a/rules/psr4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php +++ b/rules/psr4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php @@ -125,11 +125,13 @@ private function getExpectedNamespace(Node $node): ?string foreach ($paths as $path) { $path = rtrim($path, '/'); - if (Strings::startsWith($smartFileInfo->getRelativeDirectoryPath(), $path)) { - $expectedNamespace = $namespace . $this->resolveExtraNamespace($smartFileInfo, $path); - - return rtrim($expectedNamespace, '\\'); + if (! Strings::startsWith($smartFileInfo->getRelativeDirectoryPath(), $path)) { + continue; } + + $expectedNamespace = $namespace . $this->resolveExtraNamespace($smartFileInfo, $path); + + return rtrim($expectedNamespace, '\\'); } } diff --git a/rules/psr4/src/ValueObject/ClassRenameValueObject.php b/rules/psr4/src/ValueObject/ClassRenameValueObject.php deleted file mode 100644 index ff0119fa9207..000000000000 --- a/rules/psr4/src/ValueObject/ClassRenameValueObject.php +++ /dev/null @@ -1,63 +0,0 @@ -oldClass = $oldClass; - $this->newClass = $newClass; - $this->parentClasses = $parentClasses; - } - - public function getOldClass(): string - { - return $this->oldClass; - } - - public function getNewClass(): string - { - return $this->newClass; - } - - /** - * @return string[] - */ - public function getParentClasses(): array - { - return $this->parentClasses; - } - - public function removeParent(string $class): void - { - foreach ($this->parentClasses as $key => $parentClass) { - if ($parentClass !== $class) { - continue; - } - - unset($this->parentClasses[$key]); - break; - } - } -} diff --git a/rules/renaming/config/config.yaml b/rules/renaming/config/config.yaml new file mode 100644 index 000000000000..6420417bc071 --- /dev/null +++ b/rules/renaming/config/config.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + public: true + autowire: true + + Rector\Renaming\: + resource: '../src' + exclude: + - '../src/Rector/**/*Rector.php' diff --git a/rules/renaming/src/NodeManipulator/ClassRenamer.php b/rules/renaming/src/NodeManipulator/ClassRenamer.php new file mode 100644 index 000000000000..25886b773859 --- /dev/null +++ b/rules/renaming/src/NodeManipulator/ClassRenamer.php @@ -0,0 +1,316 @@ +docBlockManipulator = $docBlockManipulator; + $this->nodeNameResolver = $nodeNameResolver; + $this->callableNodeTraverser = $callableNodeTraverser; + $this->phpDocClassRenamer = $phpDocClassRenamer; + $this->classNaming = $classNaming; + $this->betterNodeFinder = $betterNodeFinder; + } + + public function renameNode(Node $node, array $oldToNewClasses): ?Node + { + $this->refactorPhpDoc($node, $oldToNewClasses); + + if ($node instanceof Name) { + return $this->refactorName($node, $oldToNewClasses); + } + + if ($node instanceof Namespace_) { + return $this->refactorNamespaceNode($node, $oldToNewClasses); + } + + if ($node instanceof ClassLike) { + return $this->refactorClassLikeNode($node, $oldToNewClasses); + } + + return null; + } + + /** + * Replace types in @var/@param/@return/@throws, + * Doctrine @ORM entity targetClass, Serialize, Assert etc. + */ + private function refactorPhpDoc(Node $node, $oldToNewClasses): void + { + if (! $this->docBlockManipulator->hasNodeTypeTags($node)) { + return; + } + + foreach ($oldToNewClasses as $oldClass => $newClass) { + $oldClassType = new ObjectType($oldClass); + $newClassType = new FullyQualifiedObjectType($newClass); + + $this->docBlockManipulator->changeType($node, $oldClassType, $newClassType); + } + + $this->phpDocClassRenamer->changeTypeInAnnotationTypes($node, $oldToNewClasses); + } + + private function refactorName(Name $name, array $oldToNewClasses): ?Name + { + $stringName = $this->nodeNameResolver->getName($name); + if ($stringName === null) { + return null; + } + + $newName = $oldToNewClasses[$stringName] ?? null; + if (! $newName) { + return null; + } + + if (! $this->isClassToInterfaceValidChange($name, $newName)) { + return null; + } + + $parentNode = $name->getAttribute(AttributeKey::PARENT_NODE); + // no need to preslash "use \SomeNamespace" of imported namespace + if ($parentNode instanceof UseUse && ($parentNode->type === Use_::TYPE_NORMAL || $parentNode->type === Use_::TYPE_UNKNOWN)) { + return new Name($newName); + } + + return new FullyQualified($newName); + } + + private function refactorNamespaceNode(Namespace_ $namespace, array $oldToNewClasses): ?Node + { + $name = $this->nodeNameResolver->getName($namespace); + if ($name === null) { + return null; + } + + $classNode = $this->getClassOfNamespaceToRefactor($namespace, $oldToNewClasses); + if ($classNode === null) { + return null; + } + + $currentName = $this->nodeNameResolver->getName($classNode); + + $newClassFqn = $oldToNewClasses[$currentName]; + $newNamespace = $this->classNaming->getNamespace($newClassFqn); + + // Renaming to class without namespace (example MyNamespace\DateTime -> DateTimeImmutable) + if (! $newNamespace) { + $classNode->name = new Identifier($newClassFqn); + + return $classNode; + } + + $namespace->name = new Name($newNamespace); + + return $namespace; + } + + private function refactorClassLikeNode(ClassLike $classLike, array $oldToNewClasses): ?Node + { + $name = $this->nodeNameResolver->getName($classLike); + if ($name === null) { + return null; + } + + $newName = $oldToNewClasses[$name] ?? null; + if (! $newName) { + return null; + } + + // prevents re-iterating same class in endless loop + if (in_array($name, $this->alreadyProcessedClasses, true)) { + return null; + } + + /** @var string $name */ + $this->alreadyProcessedClasses[] = $name; + + $newName = $oldToNewClasses[$name]; + $newClassNamePart = $this->classNaming->getShortName($newName); + $newNamespacePart = $this->classNaming->getNamespace($newName); + + $this->ensureClassWillNotBeDuplicate($newName, $name); + + $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)) { + $this->changeNameToFullyQualifiedName($classLike); + + return new Namespace_(new Name($newNamespacePart), [$classLike]); + } + + return $classLike; + } + + /** + * Checks validity: + * + * - extends SomeClass + * - extends SomeInterface + * + * - new SomeClass + * - new SomeInterface + * + * - implements SomeInterface + * - implements SomeClass + */ + private function isClassToInterfaceValidChange(Node $node, string $newName): bool + { + // ensure new is not with interface + $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); + if ($parentNode instanceof New_ && interface_exists($newName)) { + return false; + } + + if ($parentNode instanceof Class_) { + 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; + } + + private function getClassOfNamespaceToRefactor(Namespace_ $namespace, array $oldToNewClasses): ?ClassLike + { + $foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node) use ( + $oldToNewClasses + ): bool { + if (! $node instanceof ClassLike) { + return false; + } + + $classLikeName = $this->nodeNameResolver->getName($node); + + return isset($oldToNewClasses[$classLikeName]); + }); + + return $foundClass instanceof ClassLike ? $foundClass : null; + } + + private function ensureClassWillNotBeDuplicate(string $newName, string $oldName): void + { + if (! ClassExistenceStaticHelper::doesClassLikeExist($newName)) { + return; + } + + $classReflection = new ReflectionClass($newName); + + throw new InvalidPhpCodeException(sprintf( + 'Renaming class "%s" to "%s" would create a duplicated class/interface/trait (already existing in "%s") and cause PHP code to be invalid.', + $oldName, + $newName, + $classReflection->getFileName() + )); + } + + private function changeNameToFullyQualifiedName(ClassLike $classLike): void + { + $this->callableNodeTraverser->traverseNodesWithCallable($classLike, function (Node $node) { + if (! $node instanceof FullyQualified) { + return null; + } + + // invoke override + $node->setAttribute(AttributeKey::ORIGINAL_NODE, null); + }); + } + + private function isValidClassNameChange(Node $node, string $newName, Class_ $classNode): bool + { + if ($classNode->extends === $node && interface_exists($newName)) { + return false; + } + return ! (in_array($node, $classNode->implements, true) && class_exists($newName)); + } + + private function isValidUseImportChange(string $newName, UseUse $useUse): bool + { + /** @var Use_[]|null $useNodes */ + $useNodes = $useUse->getAttribute(AttributeKey::USE_NODES); + if ($useNodes === null) { + return true; + } + + foreach ($useNodes as $useNode) { + if ($this->nodeNameResolver->isName($useNode, $newName)) { + // name already exists + return false; + } + } + + return true; + } +} diff --git a/rules/renaming/src/Rector/Class_/RenameClassRector.php b/rules/renaming/src/Rector/Class_/RenameClassRector.php index 977835a2b4a4..2bd95ac2cd2e 100644 --- a/rules/renaming/src/Rector/Class_/RenameClassRector.php +++ b/rules/renaming/src/Rector/Class_/RenameClassRector.php @@ -5,30 +5,17 @@ namespace Rector\Renaming\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 PHPStan\Type\ObjectType; -use Rector\CodingStyle\Naming\ClassNaming; use Rector\Core\Configuration\ChangeConfiguration; -use Rector\Core\PhpDoc\PhpDocClassRenamer; use Rector\Core\Rector\AbstractRector; use Rector\Core\RectorDefinition\ConfiguredCodeSample; use Rector\Core\RectorDefinition\RectorDefinition; -use Rector\NodeTypeResolver\ClassExistenceStaticHelper; -use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\PHPStan\Type\FullyQualifiedObjectType; -use Rector\Renaming\Exception\InvalidPhpCodeException; -use ReflectionClass; +use Rector\Renaming\NodeManipulator\ClassRenamer; /** * @see \Rector\Renaming\Tests\Rector\Class_\RenameClassRector\RenameClassRectorTest @@ -41,32 +28,20 @@ final class RenameClassRector extends AbstractRector private $oldToNewClasses = []; /** - * @var string[] - */ - private $alreadyProcessedClasses = []; - - /** - * @var ClassNaming - */ - private $classNaming; - - /** - * @var PhpDocClassRenamer + * @var ClassRenamer */ - private $phpDocClassRenamer; + private $classRenamer; /** * @param string[] $oldToNewClasses */ public function __construct( - ClassNaming $classNaming, - PhpDocClassRenamer $phpDocClassRenamer, ChangeConfiguration $changeConfiguration, + ClassRenamer $classRenamer, array $oldToNewClasses = [] ) { - $this->classNaming = $classNaming; $this->oldToNewClasses = $oldToNewClasses; - $this->phpDocClassRenamer = $phpDocClassRenamer; + $this->classRenamer = $classRenamer; $changeConfiguration->setOldToNewClasses($oldToNewClasses); } @@ -130,231 +105,6 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - $this->refactorPhpDoc($node); - - if ($node instanceof Name) { - return $this->refactorName($node); - } - - if ($node instanceof Namespace_) { - return $this->refactorNamespaceNode($node); - } - - if ($node instanceof ClassLike) { - return $this->refactorClassLikeNode($node); - } - - return null; - } - - /** - * Replace types in @var/@param/@return/@throws, - * Doctrine @ORM entity targetClass, Serialize, Assert etc. - */ - private function refactorPhpDoc(Node $node): void - { - if (! $this->docBlockManipulator->hasNodeTypeTags($node)) { - return; - } - - foreach ($this->oldToNewClasses as $oldClass => $newClass) { - $oldClassType = new ObjectType($oldClass); - $newClassType = new FullyQualifiedObjectType($newClass); - - $this->docBlockManipulator->changeType($node, $oldClassType, $newClassType); - } - - $this->phpDocClassRenamer->changeTypeInAnnotationTypes($node, $this->oldToNewClasses); - } - - private function refactorName(Name $name): ?Name - { - $stringName = $this->getName($name); - if ($stringName === null) { - return null; - } - - $newName = $this->oldToNewClasses[$stringName] ?? null; - if (! $newName) { - return null; - } - - if (! $this->isClassToInterfaceValidChange($name, $newName)) { - return null; - } - - $parentNode = $name->getAttribute(AttributeKey::PARENT_NODE); - // no need to preslash "use \SomeNamespace" of imported namespace - if ($parentNode instanceof UseUse && ($parentNode->type === Use_::TYPE_NORMAL || $parentNode->type === Use_::TYPE_UNKNOWN)) { - return new Name($newName); - } - - return new FullyQualified($newName); - } - - private function refactorNamespaceNode(Namespace_ $namespace): ?Node - { - $name = $this->getName($namespace); - if ($name === null) { - return null; - } - - $classNode = $this->getClassOfNamespaceToRefactor($namespace); - if ($classNode === null) { - 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; - } - - $namespace->name = new Name($newNamespace); - - return $namespace; - } - - 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; - } - - /** @var string $name */ - $this->alreadyProcessedClasses[] = $name; - - $newName = $this->oldToNewClasses[$name]; - $newClassNamePart = $this->getShortName($newName); - $newNamespacePart = $this->classNaming->getNamespace($newName); - - $this->ensureClassWillNotBeDuplicate($newName, $name); - - $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)) { - $this->changeNameToFullyQualifiedName($classLike); - - return new Namespace_(new Name($newNamespacePart), [$classLike]); - } - - return $classLike; - } - - /** - * Checks validity: - * - * - extends SomeClass - * - extends SomeInterface - * - * - new SomeClass - * - new SomeInterface - * - * - implements SomeInterface - * - implements SomeClass - */ - private function isClassToInterfaceValidChange(Node $node, string $newName): bool - { - // ensure new is not with interface - $parentNode = $node->getAttribute(AttributeKey::PARENT_NODE); - if ($parentNode instanceof New_ && interface_exists($newName)) { - return false; - } - - if ($parentNode instanceof Class_) { - 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; - } - - private function getClassOfNamespaceToRefactor(Namespace_ $namespace): ?ClassLike - { - $foundClass = $this->betterNodeFinder->findFirst($namespace, function (Node $node): bool { - if (! $node instanceof ClassLike) { - return false; - } - - $classLikeName = $this->getName($node); - - return isset($this->oldToNewClasses[$classLikeName]); - }); - - return $foundClass instanceof ClassLike ? $foundClass : null; - } - - private function ensureClassWillNotBeDuplicate(string $newName, string $oldName): void - { - if (! ClassExistenceStaticHelper::doesClassLikeExist($newName)) { - return; - } - - $classReflection = new ReflectionClass($newName); - - throw new InvalidPhpCodeException(sprintf( - 'Renaming class "%s" to "%s" would create a duplicated class/interface/trait (already existing in "%s") and cause PHP code to be invalid.', - $oldName, - $newName, - $classReflection->getFileName() - )); - } - - private function changeNameToFullyQualifiedName(ClassLike $classLike): void - { - $this->traverseNodesWithCallable($classLike, function (Node $node) { - if (! $node instanceof FullyQualified) { - return null; - } - - // invoke override - $node->setAttribute(AttributeKey::ORIGINAL_NODE, null); - }); - } - - private function isValidClassNameChange(Node $node, string $newName, Class_ $classNode): bool - { - if ($classNode->extends === $node && interface_exists($newName)) { - return false; - } - return ! (in_array($node, $classNode->implements, true) && class_exists($newName)); - } - - private function isValidUseImportChange(string $newName, UseUse $useUse): bool - { - /** @var Use_[]|null $useNodes */ - $useNodes = $useUse->getAttribute(AttributeKey::USE_NODES); - if ($useNodes === null) { - return true; - } - - foreach ($useNodes as $useNode) { - if ($this->isName($useNode, $newName)) { - // name already exists - return false; - } - } - - return true; + return $this->classRenamer->renameNode($node, $this->oldToNewClasses); } } diff --git a/src/Console/Command/ShowCommand.php b/src/Console/Command/ShowCommand.php index ff2288928702..63363f2ea58e 100644 --- a/src/Console/Command/ShowCommand.php +++ b/src/Console/Command/ShowCommand.php @@ -7,6 +7,7 @@ use Rector\Core\Contract\Rector\RectorInterface; use Rector\Core\Php\TypeAnalyzer; use Rector\Core\Yaml\YamlPrinter; +use Rector\PostRector\Contract\Rector\PostRectorInterface; use ReflectionClass; use ReflectionNamedType; use Symfony\Component\Console\Input\InputInterface; @@ -62,9 +63,9 @@ protected function configure(): void protected function execute(InputInterface $input, OutputInterface $output): int { - sort($this->rectors); + $rectors = $this->filterAndSortRectors($this->rectors); - foreach ($this->rectors as $rector) { + foreach ($rectors as $rector) { $this->symfonyStyle->writeln(' * ' . get_class($rector)); $configuration = $this->resolveConfiguration($rector); if ($configuration === []) { @@ -79,7 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->symfonyStyle->writeln($indentedContent); } - $this->symfonyStyle->success(sprintf('%d loaded Rectors', count($this->rectors))); + $this->symfonyStyle->success(sprintf('%d loaded Rectors', count($rectors))); return ShellCode::SUCCESS; } @@ -118,4 +119,18 @@ private function resolveConfiguration(RectorInterface $rector): array return $configuration; } + + /** + * @param RectorInterface[] $rectors + * @return RectorInterface[] + */ + private function filterAndSortRectors(array $rectors): array + { + sort($rectors); + + return array_filter($rectors, function (RectorInterface $rector) { + // skip as internal and always run + return ! $rector instanceof PostRectorInterface; + }); + } }