Skip to content

Commit

Permalink
RenameClassRector with callback refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dorrogeray committed Oct 29, 2022
1 parent ed9d665 commit b6c1c4b
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 72 deletions.

This file was deleted.

3 changes: 0 additions & 3 deletions packages/NodeTypeResolver/NodeScopeAndMetadataDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use PhpParser\NodeVisitor\CloningVisitor;
use PhpParser\NodeVisitor\NodeConnectingVisitor;
use Rector\Core\ValueObject\Application\File;
use Rector\NodeNameResolver\NodeVisitor\RenameClassCallbackVisitor;
use Rector\NodeTypeResolver\NodeVisitor\FunctionLikeParamArgPositionNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\PHPStanNodeScopeResolver;

Expand All @@ -20,7 +19,6 @@ public function __construct(
private readonly PHPStanNodeScopeResolver $phpStanNodeScopeResolver,
private readonly NodeConnectingVisitor $nodeConnectingVisitor,
private readonly FunctionLikeParamArgPositionNodeVisitor $functionLikeParamArgPositionNodeVisitor,
private readonly RenameClassCallbackVisitor $renameClassCallbackVisitor,
) {
}

Expand All @@ -39,7 +37,6 @@ public function decorateNodesFromFile(File $file, array $stmts): array
// this one has to be run again to re-connect nodes with new attributes
$nodeTraverser->addVisitor($this->nodeConnectingVisitor);
$nodeTraverser->addVisitor($this->functionLikeParamArgPositionNodeVisitor);
$nodeTraverser->addVisitor($this->renameClassCallbackVisitor);

return $nodeTraverser->traverse($stmts);
}
Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ parameters:
- rules/CodeQuality/Rector/PropertyFetch/ExplicitMethodCallOverMagicGetSetRector.php
- rules/Php72/Rector/Assign/ReplaceEachAssignmentWithKeyCurrentRector.php
- rules/PSR4/Rector/FileWithoutNamespace/NormalizeNamespaceByPSR4ComposerAutoloadRector.php
- rules/Renaming/NodeManipulator/ClassRenamer.php

- '#Method Rector\\Core\\Application\\ApplicationFileProcessor\:\:runParallel\(\) should return array\{system_errors\: array<Rector\\Core\\ValueObject\\Error\\SystemError\>, file_diffs\: array<Rector\\Core\\ValueObject\\Reporting\\FileDiff\>\} but returns array#'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@ class BadRequest extends Exception {

$badRequest = new \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\BadRequest();

if ($badRequest instanceof \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\BadRequest) {
echo \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\BadRequest::class;
}

class CorrectlyNamedException extends Exception {

}

$correctlyNamedException = new \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\CorrectlyNamedException();

interface Processor {

}

class FruitProcessor implements \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\Processor {

}

?>
-----
<?php
Expand All @@ -30,10 +42,22 @@ class BadRequestException extends Exception {

$badRequest = new \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\BadRequestException();

if ($badRequest instanceof \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\BadRequestException) {
echo \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\BadRequestException::class;
}

class CorrectlyNamedException extends Exception {

}

$correctlyNamedException = new \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\CorrectlyNamedException();

interface ProcessorInterface {

}

class FruitProcessor implements \Rector\Tests\Renaming\Rector\Name\RenameClassRector\FixtureWithCallback\ProcessorInterface {

}

?>
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@

namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source;

use Exception;
use PhpParser\Node\Stmt\ClassLike;
use PHPStan\Reflection\ReflectionProvider;
use Rector\NodeNameResolver\NodeNameResolver;

class EnforceExceptionSuffixCallback
{
public function __invoke(ClassLike $class, NodeNameResolver $nodeNameResolver): ?string {
public function __invoke(
ClassLike $class,
NodeNameResolver $nodeNameResolver,
ReflectionProvider $reflectionProvider
): ?string {
$fullyQualifiedClassName = (string) $nodeNameResolver->getName($class);
if (
// normally here would be is_subclass_of($fullyQualifiedClassName, Exception::class) condition, but it
// would not work in the unit test, since the class itself is defined in the fixture and is not loaded
!str_ends_with($fullyQualifiedClassName, 'Exception')
) {
$classReflection = $reflectionProvider->getClass($fullyQualifiedClassName);
if (! $classReflection->isSubclassOf(Exception::class)) {
return null;
}

if (!str_ends_with($fullyQualifiedClassName, 'Exception')) {
return $fullyQualifiedClassName . 'Exception';
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types = 1);

namespace Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source;

use PhpParser\Node\Stmt\ClassLike;
use PhpParser\Node\Stmt\Interface_;
use Rector\NodeNameResolver\NodeNameResolver;

class EnforceInterfaceSuffixCallback
{
public function __invoke(ClassLike $class, NodeNameResolver $nodeNameResolver): ?string {
$fullyQualifiedClassName = (string) $nodeNameResolver->getName($class);
if (
$class instanceof Interface_ &&
!str_ends_with($fullyQualifiedClassName, 'Interface')
) {
return $fullyQualifiedClassName . 'Interface';
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\Name\RenameClassRector;
use Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\EnforceExceptionSuffixCallback;
use Rector\Tests\Renaming\Rector\Name\RenameClassRector\Source\EnforceInterfaceSuffixCallback;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig
->ruleWithConfiguration(RenameClassRector::class, [
'__callbacks__' => [new EnforceExceptionSuffixCallback()],
'__callbacks__' => [new EnforceExceptionSuffixCallback(), new EnforceInterfaceSuffixCallback()],
]);
};
75 changes: 75 additions & 0 deletions rules/Renaming/Helper/RenameClassCallbackHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);

namespace Rector\Renaming\Helper;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassLike;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\ReflectionProvider;
use Rector\Core\Configuration\RenamedClassesDataCollector;
use Rector\NodeNameResolver\NodeNameResolver;

final class RenameClassCallbackHandler extends NodeVisitorAbstract
{
/**
* @var array<callable(ClassLike, NodeNameResolver, ReflectionProvider): ?string>
*/
private array $oldToNewClassCallbacks = [];

public function __construct(
private readonly RenamedClassesDataCollector $renamedClassesDataCollector,
private readonly NodeNameResolver $nodeNameResolver,
private readonly ReflectionProvider $reflectionProvider
) {
}

public function hasOldToNewClassCallbacks(): bool
{
return $this->oldToNewClassCallbacks !== [];
}

/**
* @param array<callable(ClassLike, NodeNameResolver, ReflectionProvider): ?string> $oldToNewClassCallbacks
*/
public function addOldToNewClassCallbacks(array $oldToNewClassCallbacks): void
{
$this->oldToNewClassCallbacks = [...$this->oldToNewClassCallbacks, ...$oldToNewClassCallbacks];
}

/**
* @return array<string, string>
*/
public function getOldToNewClassesFromNode(Node $node): array
{
if ($node instanceof ClassLike) {
return $this->handleClassLike($node);
}

return [];
}

/**
* @return array<string, string>
*/
public function handleClassLike(ClassLike $node): array
{
$oldToNewClasses = [];
$className = $node->name;
if ($className === null) {
return [];
}

foreach ($this->oldToNewClassCallbacks as $oldToNewClassCallback) {
$newClassName = $oldToNewClassCallback($node, $this->nodeNameResolver, $this->reflectionProvider);
if ($newClassName !== null) {
$fullyQualifiedClassName = (string) $this->nodeNameResolver->getName($node);
$this->renamedClassesDataCollector->addOldToNewClass($fullyQualifiedClassName, $newClassName);
$oldToNewClasses[$fullyQualifiedClassName] = $newClassName;
}
}

return $oldToNewClasses;
}
}
16 changes: 14 additions & 2 deletions rules/Renaming/NodeManipulator/ClassRenamer.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockClassRenamer;
use Rector\NodeTypeResolver\ValueObject\OldToNewType;
use Rector\PhpDocParser\NodeTraverser\SimpleCallableNodeTraverser;
use Rector\Renaming\Helper\RenameClassCallbackHandler;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;

final class ClassRenamer
Expand All @@ -61,6 +62,7 @@ public function __construct(
private readonly NodeRemover $nodeRemover,
private readonly ParameterProvider $parameterProvider,
private readonly UseImportsResolver $useImportsResolver,
private readonly RenameClassCallbackHandler $renameClassCallbackHandler,
) {
}

Expand All @@ -69,7 +71,7 @@ public function __construct(
*/
public function renameNode(Node $node, array $oldToNewClasses): ?Node
{
$oldToNewTypes = $this->createOldToNewTypes($oldToNewClasses);
$oldToNewTypes = $this->createOldToNewTypes($node, $oldToNewClasses);
$this->refactorPhpDoc($node, $oldToNewTypes, $oldToNewClasses);

if ($node instanceof Name) {
Expand Down Expand Up @@ -441,8 +443,9 @@ private function shouldRemoveUseName(string $last, string $newNameLastName, bool
* @param array<string, string> $oldToNewClasses
* @return OldToNewType[]
*/
private function createOldToNewTypes(array $oldToNewClasses): array
private function createOldToNewTypes(Node $node, array $oldToNewClasses): array
{
$oldToNewClasses = $this->resolveOldToNewClassCallbacks($node, $oldToNewClasses);
$cacheKey = md5(serialize($oldToNewClasses));

if (isset($this->oldToNewTypesByCacheKey[$cacheKey])) {
Expand All @@ -461,4 +464,13 @@ private function createOldToNewTypes(array $oldToNewClasses): array

return $oldToNewTypes;
}

/**
* @param array<string, string> $oldToNewClasses
* @return array<string, string>
*/
private function resolveOldToNewClassCallbacks(Node $node, array $oldToNewClasses): array
{
return [...$oldToNewClasses, ...$this->renameClassCallbackHandler->getOldToNewClassesFromNode($node)];
}
}
15 changes: 8 additions & 7 deletions rules/Renaming/Rector/Name/RenameClassRector.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use Rector\Core\PhpParser\Node\CustomNode\FileWithoutNamespace;
use Rector\Core\Rector\AbstractRector;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeNameResolver\NodeVisitor\RenameClassCallbackVisitor;
use Rector\Renaming\Helper\RenameClassCallbackHandler;
use Rector\Renaming\NodeManipulator\ClassRenamer;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
Expand All @@ -34,7 +34,7 @@ public function __construct(
private readonly RenamedClassesDataCollector $renamedClassesDataCollector,
private readonly ClassRenamer $classRenamer,
private readonly RectorConfigProvider $rectorConfigProvider,
private readonly RenameClassCallbackVisitor $renameClassCallbackVisitor,
private readonly RenameClassCallbackHandler $renameClassCallbackHandler,
) {
}

Expand Down Expand Up @@ -98,7 +98,7 @@ public function getNodeTypes(): array
public function refactor(Node $node): ?Node
{
$oldToNewClasses = $this->renamedClassesDataCollector->getOldToNewClasses();
if ($oldToNewClasses === []) {
if ($oldToNewClasses === [] && ! $this->renameClassCallbackHandler->hasOldToNewClassCallbacks()) {
return null;
}

Expand All @@ -118,11 +118,12 @@ public function refactor(Node $node): ?Node
*/
public function configure(array $configuration): void
{
if (isset($configuration['__callbacks__'])) {
Assert::allIsCallable($configuration['__callbacks__']);
$oldToNewClassCallbacks = $configuration['__callbacks__'] ?? [];
Assert::isArray($oldToNewClassCallbacks);
if ($oldToNewClassCallbacks !== []) {
Assert::allIsCallable($oldToNewClassCallbacks);
/** @var array<callable(ClassLike, NodeNameResolver): ?string> $oldToNewClassCallbacks */
$oldToNewClassCallbacks = $configuration['__callbacks__'];
$this->renameClassCallbackVisitor->addOldToNewClassCallbacks($oldToNewClassCallbacks);
$this->renameClassCallbackHandler->addOldToNewClassCallbacks($oldToNewClassCallbacks);
unset($configuration['__callbacks__']);
}

Expand Down

0 comments on commit b6c1c4b

Please sign in to comment.