From 8ae06eb3eb70155343534b46604d55138c7434e8 Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Tue, 16 Jun 2026 08:36:50 +0200 Subject: [PATCH] [BetterPhpDocParser] Keep import referenced by @see/@uses tag with a trailing description When `removeUnusedImports()` is enabled, UnusedImportRemovingPostRector collects the class names used inside doc blocks through PhpDocInfo::getGenericTagClassNames(). For generic tags such as @see, @uses and @link it only registered the full tag value. A tag like `@see Foo some description` therefore produced the single candidate "Foo some description", which never matches the imported short name "Foo". As a result the `use` import for Foo was considered unused and removed, leaving an unresolved reference (or, for a class in another namespace, one that silently resolves to the wrong class). Per the phpDocumentor syntax `@see [FQSEN] []`, the class reference is the leading token of the value. Register that leading token in addition to the full value, so the import is correctly kept when a description follows the class name. --- .../PhpDocInfo/PhpDocInfo.php | 14 +++++++++++ .../see_with_non_class_description.php.inc | 23 +++++++++++++++++++ .../skip_used_see_with_description.php.inc | 12 ++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tests/Issues/NamespacedUse/Fixture/see_with_non_class_description.php.inc create mode 100644 tests/Issues/NamespacedUse/Fixture/skip_used_see_with_description.php.inc diff --git a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php index a49e14f8841..e004b3c55db 100644 --- a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php +++ b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php @@ -35,6 +35,8 @@ use Rector\Exception\ShouldNotHappenException; use Rector\PhpDocParser\PhpDocParser\PhpDocNodeTraverser; use Rector\StaticTypeMapper\StaticTypeMapper; +use Rector\Validation\RectorAssert; +use Webmozart\Assert\InvalidArgumentException; /** * @see \Rector\Tests\BetterPhpDocParser\PhpDocInfo\PhpDocInfo\PhpDocInfoTest @@ -461,7 +463,19 @@ public function getGenericTagClassNames(): array // add default original value $resolvedClasses[] = $genericTagValueNode->value; + if (! str_contains($genericTagValueNode->value, '::')) { + // keep the import for the leading class reference in "@see Foo some description" + $leadingReference = strtok($genericTagValueNode->value, " \t\n\r"); + if ($leadingReference !== false && $leadingReference !== $genericTagValueNode->value) { + try { + RectorAssert::className($leadingReference); + $resolvedClasses[] = $leadingReference; + } catch (InvalidArgumentException) { + continue; + } + } + continue; } diff --git a/tests/Issues/NamespacedUse/Fixture/see_with_non_class_description.php.inc b/tests/Issues/NamespacedUse/Fixture/see_with_non_class_description.php.inc new file mode 100644 index 00000000000..2f016659a44 --- /dev/null +++ b/tests/Issues/NamespacedUse/Fixture/see_with_non_class_description.php.inc @@ -0,0 +1,23 @@ +