Skip to content

Commit

Permalink
[PostRector] Skip remove use statement on used in annotation on remov…
Browse files Browse the repository at this point in the history
…eUnusedImports() (#5657)

* [PostRector] Skip remove use statement on used in annotation on removeUnusedImports()

* Fixed 🎉

* [ci-review] Rector Rectify

* phpstan fix

---------

Co-authored-by: GitHub Action <actions@github.com>
  • Loading branch information
samsonasik and actions-user committed Feb 22, 2024
1 parent fb312e0 commit b9ea5fd
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php
Expand Up @@ -24,6 +24,7 @@
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\Annotation\AnnotationNaming;
use Rector\BetterPhpDocParser\PhpDoc\ArrayItemNode;
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
use Rector\BetterPhpDocParser\PhpDoc\SpacelessPhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocNodeFinder\PhpDocNodeByTypeFinder;
Expand Down Expand Up @@ -456,6 +457,34 @@ public function getConstFetchNodeClassNames(): array
return $classNames;
}

/**
* @return string[]
*/
public function getArrayItemNodeClassNames(): array
{
$phpDocNodeTraverser = new PhpDocNodeTraverser();

$classNames = [];

$phpDocNodeTraverser->traverseWithCallable($this->phpDocNode, '', static function (Node $node) use (
&$classNames,
): ?ArrayItemNode {
if (! $node instanceof ArrayItemNode) {
return null;
}

$resolvedClass = $node->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS);
if ($resolvedClass === null) {
return null;
}

$classNames[] = $resolvedClass;
return $node;
});

return $classNames;
}

/**
* @param class-string $desiredClass
* @return DoctrineAnnotationTagValueNode[]
Expand Down
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace Rector\BetterPhpDocParser\PhpDocParser;

use Rector\BetterPhpDocParser\PhpDoc\ArrayItemNode;
use PhpParser\Node as PhpNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use Rector\BetterPhpDocParser\Contract\PhpDocParser\PhpDocNodeDecoratorInterface;
use Rector\BetterPhpDocParser\ValueObject\PhpDocAttributeKey;
use Rector\PhpDocParser\PhpDocParser\PhpDocNodeTraverser;
use Rector\StaticTypeMapper\Naming\NameScopeFactory;

/**
* Decorate node with fully qualified class name for annotation:
* e.g. @ORM\Column(type=Types::STRING, length=100, nullable=false)
*/
final readonly class ArrayItemClassNameDecorator implements PhpDocNodeDecoratorInterface
{
public function __construct(
private NameScopeFactory $nameScopeFactory,
private PhpDocNodeTraverser $phpDocNodeTraverser
) {
}

public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void
{
// iterating all phpdocs has big overhead. peek into the phpdoc to exit early
if (! str_contains($phpDocNode->__toString(), '::')) {
return;
}

$this->phpDocNodeTraverser->traverseWithCallable($phpDocNode, '', function (Node $node) use (
$phpNode
): Node|null {
if (! $node instanceof ArrayItemNode) {
return null;
}

if (! is_string($node->value)) {
return null;
}

$splitScopeResolution = explode('::', $node->value);
if (count($splitScopeResolution) !== 2) {
return null;
}

$firstName = $splitScopeResolution[0];
$constFetchNode = new ConstFetchNode($firstName, $firstName);

$className = $this->resolveFullyQualifiedClass($constFetchNode, $phpNode);
$node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className);

return $node;
});
}

private function resolveFullyQualifiedClass(ConstFetchNode $constFetchNode, PhpNode $phpNode): string
{
$nameScope = $this->nameScopeFactory->createNameScopeFromNodeWithoutTemplateTypes($phpNode);
return $nameScope->resolveStringName($constFetchNode->className);
}
}
2 changes: 2 additions & 0 deletions src/DependencyInjection/LazyContainerFactory.php
Expand Up @@ -28,6 +28,7 @@
use Rector\BetterPhpDocParser\PhpDocNodeVisitor\IntersectionTypeNodePhpDocNodeVisitor;
use Rector\BetterPhpDocParser\PhpDocNodeVisitor\TemplatePhpDocNodeVisitor;
use Rector\BetterPhpDocParser\PhpDocNodeVisitor\UnionTypeNodePhpDocNodeVisitor;
use Rector\BetterPhpDocParser\PhpDocParser\ArrayItemClassNameDecorator;
use Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser;
use Rector\BetterPhpDocParser\PhpDocParser\BetterTypeParser;
use Rector\BetterPhpDocParser\PhpDocParser\ConstExprClassNameDecorator;
Expand Down Expand Up @@ -313,6 +314,7 @@ final class LazyContainerFactory
private const PHP_DOC_NODE_DECORATOR_CLASSES = [
ConstExprClassNameDecorator::class,
DoctrineAnnotationDecorator::class,
ArrayItemClassNameDecorator::class,
];

/**
Expand Down
3 changes: 3 additions & 0 deletions src/PostRector/Rector/UnusedImportRemovingPostRector.php
Expand Up @@ -143,6 +143,9 @@ private function findNamesInDocBlocks(Namespace_|FileWithoutNamespace $namespace

$genericTagClassNames = $phpDocInfo->getGenericTagClassNames();
$names = [...$names, ...$genericTagClassNames];

$arrayItemTagClassNames = $phpDocInfo->getArrayItemNodeClassNames();
$names = [...$names, ...$arrayItemTagClassNames];
}
});

Expand Down
16 changes: 16 additions & 0 deletions tests/Issues/NoNamespaced/Fixture/skip_used_in_annotation.php.inc
@@ -0,0 +1,16 @@
<?php

use App\Repository\DemoRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity(repositoryClass=DemoRepository::class)
*/
final class SkipUsedInAnnotation
{
/**
* @ORM\Column(type=Types::STRING, length=100, nullable=false)
*/
protected $status;
}

0 comments on commit b9ea5fd

Please sign in to comment.