From a9a90cdd73d6be77514355ece528d25f2c6ad882 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 2 Sep 2026 11:03:07 +0200 Subject: [PATCH] [BetterPhpDocParser] Keep import used only in an annotation array key like @ORM\DiscriminatorMap({ SomeEnum::TOTP = ... }) The ArrayItemClassNameDecorator only resolved the class from the value side of an array item, so a class referenced solely in the key of a Doctrine annotation map was not counted as used. With removeUnusedImports() enabled the import was then wrongly removed. Claude-Session: https://claude.ai/code/session_01DnwXQN191Le8TVieRZnWv6 --- .../ArrayItemClassNameDecorator.php | 29 ++++++++++++++----- .../PhpDocInfo/PhpDocInfo.php | 10 +++++-- .../ValueObject/PhpDocAttributeKey.php | 5 ++++ .../skip_annotation_array_key_const.php.inc | 16 ++++++++++ 4 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 tests/Issues/NamespacedUseAutoImport/Fixture/skip_annotation_array_key_const.php.inc diff --git a/src/BetterPhpDocParser/NodeDecorator/ArrayItemClassNameDecorator.php b/src/BetterPhpDocParser/NodeDecorator/ArrayItemClassNameDecorator.php index f6c5cc0b313..d0096e7bfd6 100644 --- a/src/BetterPhpDocParser/NodeDecorator/ArrayItemClassNameDecorator.php +++ b/src/BetterPhpDocParser/NodeDecorator/ArrayItemClassNameDecorator.php @@ -39,22 +39,35 @@ public function decorate(PhpDocNode $phpDocNode, PhpNode $phpNode): void return null; } - if (! is_string($node->value)) { - return null; + $valueClassName = $this->resolveClassFromScopeResolution($node->value, $phpNode); + if ($valueClassName !== null) { + $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $valueClassName); } - $splitScopeResolution = explode('::', $node->value); - if (count($splitScopeResolution) !== 2) { - return null; + // e.g. @ORM\DiscriminatorMap({ SomeEnum::TOTP = "..." }), the class is in the key + $keyClassName = $this->resolveClassFromScopeResolution($node->key, $phpNode); + if ($keyClassName !== null) { + $node->setAttribute(PhpDocAttributeKey::RESOLVED_KEY_CLASS, $keyClassName); } - $className = $this->resolveFullyQualifiedClass($splitScopeResolution[0], $phpNode); - $node->setAttribute(PhpDocAttributeKey::RESOLVED_CLASS, $className); - return $node; }); } + private function resolveClassFromScopeResolution(mixed $value, PhpNode $phpNode): ?string + { + if (! is_string($value)) { + return null; + } + + $splitScopeResolution = explode('::', $value); + if (count($splitScopeResolution) !== 2) { + return null; + } + + return $this->resolveFullyQualifiedClass($splitScopeResolution[0], $phpNode); + } + private function resolveFullyQualifiedClass(string $className, PhpNode $phpNode): string { $nameScope = $this->nameScopeFactory->createNameScopeFromNodeWithoutTemplateTypes($phpNode); diff --git a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php index 5b47f9d9585..5313f86c574 100644 --- a/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php +++ b/src/BetterPhpDocParser/PhpDocInfo/PhpDocInfo.php @@ -558,11 +558,15 @@ public function getArrayItemNodeClassNames(): array } $resolvedClass = $node->getAttribute(PhpDocAttributeKey::RESOLVED_CLASS); - if ($resolvedClass === null) { - return null; + if ($resolvedClass !== null) { + $classNames[] = $resolvedClass; + } + + $resolvedKeyClass = $node->getAttribute(PhpDocAttributeKey::RESOLVED_KEY_CLASS); + if ($resolvedKeyClass !== null) { + $classNames[] = $resolvedKeyClass; } - $classNames[] = $resolvedClass; return $node; }); diff --git a/src/BetterPhpDocParser/ValueObject/PhpDocAttributeKey.php b/src/BetterPhpDocParser/ValueObject/PhpDocAttributeKey.php index 3311c8273a0..e4715cf44e6 100644 --- a/src/BetterPhpDocParser/ValueObject/PhpDocAttributeKey.php +++ b/src/BetterPhpDocParser/ValueObject/PhpDocAttributeKey.php @@ -15,6 +15,11 @@ final class PhpDocAttributeKey */ public const string RESOLVED_CLASS = 'resolved_class'; + /** + * Fully qualified name of class referenced in an array item key, e.g. SomeEnum::TOTP + */ + public const string RESOLVED_KEY_CLASS = 'resolved_key_class'; + public const string PARENT = NativePhpDocAttributeKey::PARENT; public const string LAST_PHP_DOC_TOKEN_POSITION = 'last_token_position'; diff --git a/tests/Issues/NamespacedUseAutoImport/Fixture/skip_annotation_array_key_const.php.inc b/tests/Issues/NamespacedUseAutoImport/Fixture/skip_annotation_array_key_const.php.inc new file mode 100644 index 00000000000..15c0dc0ab8e --- /dev/null +++ b/tests/Issues/NamespacedUseAutoImport/Fixture/skip_annotation_array_key_const.php.inc @@ -0,0 +1,16 @@ +