From 05eb9d2d395602586e6d6776482756414585bbaa Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 13 Sep 2025 20:23:41 +0700 Subject: [PATCH 1/7] [AutoImport] Skip used via @uses SomeClass::$someRef on UnusedImportRemovingPostRector --- tests/Issues/NoNamespaced/Fixture/skip_used_in_uses.php.inc | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/Issues/NoNamespaced/Fixture/skip_used_in_uses.php.inc diff --git a/tests/Issues/NoNamespaced/Fixture/skip_used_in_uses.php.inc b/tests/Issues/NoNamespaced/Fixture/skip_used_in_uses.php.inc new file mode 100644 index 00000000000..b762afca4b2 --- /dev/null +++ b/tests/Issues/NoNamespaced/Fixture/skip_used_in_uses.php.inc @@ -0,0 +1,6 @@ +setLocations('locations'); \ No newline at end of file From 16467940eceb60b0796319854f67848d40969a17 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 13 Sep 2025 20:28:38 +0700 Subject: [PATCH 2/7] Fix --- .../PhpDocInfo/PhpDocInfo.php | 6 +- .../PhpDocTagGenericUsesDecorator.php | 73 +++++++++++++++++++ .../LazyContainerFactory.php | 3 + .../Fixture/skip_used_in_used_by.php.inc | 8 ++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php create mode 100644 tests/Issues/NoNamespaced/Fixture/skip_used_in_used_by.php.inc diff --git a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php index b4f0db31db8..645ef0039c1 100644 --- a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php +++ b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php @@ -452,7 +452,11 @@ public function getGenericTagClassNames(): array foreach ($genericTagValueNodes as $genericTagValueNode) { if ($genericTagValueNode->value !== '') { - $resolvedClasses[] = $genericTagValueNode->value; + if (str_contains($genericTagValueNode->value, '::') && $genericTagValueNode->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS) !== null) { + $resolvedClasses[] = $genericTagValueNode->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS); + } else { + $resolvedClasses[] = $genericTagValueNode->value; + } } } diff --git a/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php b/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php new file mode 100644 index 00000000000..b6fe7c6cb92 --- /dev/null +++ b/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php @@ -0,0 +1,73 @@ +__toString(), '::')) { + return; + } + + $this->phpDocNodeTraverser->traverseWithCallable($phpDocNode, '', function (Node $node) use ( + $phpNode + ): Node|null { + if (! $node instanceof PhpDocTagNode) { + return null; + } + + if (! $node->value instanceof GenericTagValueNode) { + return null; + } + + if (! in_array($node->name, ['@uses', '@used-by'], true)) { + return null; + } + + $reference = $node->value->value; + if (! str_contains($reference, '::')) { + return null; + } + + if ($node->value->hasAttribute(PhpDocAttributeKey::RESOLVED_CLASS)) { + return null; + } + + $classValue = explode('::', $reference)[0]; + $className = $this->resolveFullyQualifiedClass($classValue, $phpNode); + $node->value->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); + + return $node; + }); + } + + private function resolveFullyQualifiedClass(string $classValue, PhpNode $phpNode): string + { + $nameScope = $this->nameScopeFactory->createNameScopeFromNodeWithoutTemplateTypes($phpNode); + return $nameScope->resolveStringName($classValue); + } +} diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index 6946e5cf318..a884c16a25b 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -29,6 +29,8 @@ use Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser; use Rector\BetterPhpDocParser\PhpDocParser\ConstExprClassNameDecorator; use Rector\BetterPhpDocParser\PhpDocParser\DoctrineAnnotationDecorator; +use Rector\BetterPhpDocParser\PhpDocParser\GenericAnnotationDecorator; +use Rector\BetterPhpDocParser\PhpDocParser\PhpDocTagGenericUsesDecorator; use Rector\BetterPhpDocParser\PhpDocParser\StaticDoctrineAnnotationParser; use Rector\BetterPhpDocParser\PhpDocParser\StaticDoctrineAnnotationParser\ArrayParser; use Rector\BetterPhpDocParser\PhpDocParser\StaticDoctrineAnnotationParser\PlainValueParser; @@ -316,6 +318,7 @@ final class LazyContainerFactory ConstExprClassNameDecorator::class, DoctrineAnnotationDecorator::class, ArrayItemClassNameDecorator::class, + PhpDocTagGenericUsesDecorator::class, ]; /** diff --git a/tests/Issues/NoNamespaced/Fixture/skip_used_in_used_by.php.inc b/tests/Issues/NoNamespaced/Fixture/skip_used_in_used_by.php.inc new file mode 100644 index 00000000000..53dc9960fe4 --- /dev/null +++ b/tests/Issues/NoNamespaced/Fixture/skip_used_in_used_by.php.inc @@ -0,0 +1,8 @@ + Date: Sat, 13 Sep 2025 20:29:01 +0700 Subject: [PATCH 3/7] Fix --- src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php index 645ef0039c1..defa34a1062 100644 --- a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php +++ b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php @@ -452,7 +452,7 @@ public function getGenericTagClassNames(): array foreach ($genericTagValueNodes as $genericTagValueNode) { if ($genericTagValueNode->value !== '') { - if (str_contains($genericTagValueNode->value, '::') && $genericTagValueNode->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS) !== null) { + if (str_contains($genericTagValueNode->value, '::') && $genericTagValueNode->hasAttribute(PhpDocAttributeKey::RESOLVED_CLASS)) { $resolvedClasses[] = $genericTagValueNode->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS); } else { $resolvedClasses[] = $genericTagValueNode->value; From 818df728ce20cbedeb055c40cfd8fef0939bdf1b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 13 Sep 2025 13:30:51 +0000 Subject: [PATCH 4/7] [ci-review] Rector Rectify --- ...ClassMethodArrayDocblockParamFromLocalCallsRector.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php index 2aa5a921d28..b3b407a6f40 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php @@ -4,6 +4,7 @@ namespace Rector\TypeDeclarationDocblocks\Rector\Class_; +use PHPStan\Type\Type; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; @@ -94,10 +95,12 @@ public function refactor(Node $node): ?Node $classMethodParameterTypes = $this->callTypesResolver->resolveStrictTypesFromCalls($methodCalls); foreach ($classMethod->getParams() as $parameterPosition => $param) { - if ($param->type === null || ! $this->isName($param->type, 'array')) { + if ($param->type === null) { + continue; + } + if (! $this->isName($param->type, 'array')) { continue; } - $parameterName = $this->getName($param); $parameterTagValueNode = $classMethodPhpDocInfo->getParamTagValueByName($parameterName); @@ -107,7 +110,7 @@ public function refactor(Node $node): ?Node } $resolvedParameterType = $classMethodParameterTypes[$parameterPosition] ?? null; - if (! $resolvedParameterType instanceof \PHPStan\Type\Type) { + if (! $resolvedParameterType instanceof Type) { continue; } From 634dd7c9e231ba3bf4fb640acd815dd906eb1943 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Sat, 13 Sep 2025 13:33:17 +0000 Subject: [PATCH 5/7] [ci-review] Rector Rectify --- .../ClassMethodArrayDocblockParamFromLocalCallsRector.php | 2 ++ src/DependencyInjection/LazyContainerFactory.php | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php index b3b407a6f40..429778dc8fd 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php @@ -98,9 +98,11 @@ public function refactor(Node $node): ?Node if ($param->type === null) { continue; } + if (! $this->isName($param->type, 'array')) { continue; } + $parameterName = $this->getName($param); $parameterTagValueNode = $classMethodPhpDocInfo->getParamTagValueByName($parameterName); diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index a884c16a25b..3e78fa7f42a 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -29,7 +29,6 @@ use Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser; use Rector\BetterPhpDocParser\PhpDocParser\ConstExprClassNameDecorator; use Rector\BetterPhpDocParser\PhpDocParser\DoctrineAnnotationDecorator; -use Rector\BetterPhpDocParser\PhpDocParser\GenericAnnotationDecorator; use Rector\BetterPhpDocParser\PhpDocParser\PhpDocTagGenericUsesDecorator; use Rector\BetterPhpDocParser\PhpDocParser\StaticDoctrineAnnotationParser; use Rector\BetterPhpDocParser\PhpDocParser\StaticDoctrineAnnotationParser\ArrayParser; From c25a9137230813f6054cb251e25bc014b694d8be Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 13 Sep 2025 20:34:33 +0700 Subject: [PATCH 6/7] Fix --- ...ArrayDocblockParamFromLocalCallsRector.php | 2 +- .../PhpDocInfo/PhpDocInfo.php | 21 +++++++++++++------ .../LazyContainerFactory.php | 1 - 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php index b3b407a6f40..f7d894b65fd 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php @@ -4,10 +4,10 @@ namespace Rector\TypeDeclarationDocblocks\Rector\Class_; -use PHPStan\Type\Type; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\PhpParser\NodeFinder\LocalMethodCallFinder; diff --git a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php index defa34a1062..a4bfec55866 100644 --- a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php +++ b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php @@ -451,13 +451,22 @@ public function getGenericTagClassNames(): array $resolvedClasses = []; foreach ($genericTagValueNodes as $genericTagValueNode) { - if ($genericTagValueNode->value !== '') { - if (str_contains($genericTagValueNode->value, '::') && $genericTagValueNode->hasAttribute(PhpDocAttributeKey::RESOLVED_CLASS)) { - $resolvedClasses[] = $genericTagValueNode->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS); - } else { - $resolvedClasses[] = $genericTagValueNode->value; - } + if ($genericTagValueNode->value === '') { + continue; + } + + if (! str_contains($genericTagValueNode->value, '::')) { + $resolvedClasses[] = $genericTagValueNode->value; + continue; } + + $resolvedClass = $genericTagValueNode->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS); + if ($resolvedClass === null) { + $resolvedClasses[] = $genericTagValueNode->value; + continue; + } + + $resolvedClasses[] = $resolvedClass; } return $resolvedClasses; diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index a884c16a25b..3e78fa7f42a 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -29,7 +29,6 @@ use Rector\BetterPhpDocParser\PhpDocParser\BetterPhpDocParser; use Rector\BetterPhpDocParser\PhpDocParser\ConstExprClassNameDecorator; use Rector\BetterPhpDocParser\PhpDocParser\DoctrineAnnotationDecorator; -use Rector\BetterPhpDocParser\PhpDocParser\GenericAnnotationDecorator; use Rector\BetterPhpDocParser\PhpDocParser\PhpDocTagGenericUsesDecorator; use Rector\BetterPhpDocParser\PhpDocParser\StaticDoctrineAnnotationParser; use Rector\BetterPhpDocParser\PhpDocParser\StaticDoctrineAnnotationParser\ArrayParser; From 5856909a8260b792fe17b6e00e43c1bb039fcb8b Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 13 Sep 2025 20:36:39 +0700 Subject: [PATCH 7/7] Fix --- .../PhpDocParser/PhpDocTagGenericUsesDecorator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php b/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php index b6fe7c6cb92..45f0444e9ee 100644 --- a/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php +++ b/src/BetterPhpDocParser/PhpDocParser/PhpDocTagGenericUsesDecorator.php @@ -17,6 +17,8 @@ /** * Decorate node with fully qualified class name for generic annotations for @uses * e.g. @uses Direction::* + * + * @see https://docs.phpdoc.org/guide/references/phpdoc/tags/uses.html */ final readonly class PhpDocTagGenericUsesDecorator implements PhpDocNodeDecoratorInterface {