From f4dab54457bb468afc1a18b55fad256b6c0137ba Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 26 Jun 2026 17:43:28 +0200 Subject: [PATCH 1/2] [DeadCode] Add RemoveMixedDocblockRector to drop @param mixed / @return mixed duplicating native type --- .../remove_param_and_return_mixed.php.inc | 31 +++ .../Fixture/remove_param_mixed.php.inc | 28 +++ .../Fixture/skip_param_no_native_type.php.inc | 13 ++ .../skip_param_with_description.php.inc | 13 ++ .../skip_return_no_native_type.php.inc | 13 ++ .../RemoveMixedDocblockRectorTest.php | 28 +++ .../config/configured_rule.php | 9 + .../ClassMethod/RemoveMixedDocblockRector.php | 215 ++++++++++++++++++ src/Config/Level/DeadCodeLevel.php | 2 + 9 files changed, 352 insertions(+) create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_and_return_mixed.php.inc create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_mixed.php.inc create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/skip_param_no_native_type.php.inc create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/skip_param_with_description.php.inc create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/skip_return_no_native_type.php.inc create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/RemoveMixedDocblockRectorTest.php create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php create mode 100644 rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector.php diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_and_return_mixed.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_and_return_mixed.php.inc new file mode 100644 index 00000000000..b4d1065a98e --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_and_return_mixed.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_mixed.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_mixed.php.inc new file mode 100644 index 00000000000..139abda84fe --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_mixed.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/skip_param_no_native_type.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/skip_param_no_native_type.php.inc new file mode 100644 index 00000000000..7ec0f8b008b --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/skip_param_no_native_type.php.inc @@ -0,0 +1,13 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php new file mode 100644 index 00000000000..f217e75b60d --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([RemoveMixedDocblockRector::class]); diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector.php new file mode 100644 index 00000000000..e6287c22aec --- /dev/null +++ b/rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector.php @@ -0,0 +1,215 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class]; + } + + /** + * @param ClassMethod|Function_ $node + */ + public function refactor(Node $node): ?Node + { + // skip as no comments + if ($node->getComments() === []) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + if (! $phpDocInfo instanceof PhpDocInfo) { + return null; + } + + $hasChanged = $this->removeMixedParamTags($phpDocInfo, $node); + $hasChanged = $this->removeMixedReturnTag($phpDocInfo, $node) || $hasChanged; + + if (! $hasChanged) { + return null; + } + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } + + /** + * @param ClassMethod|Function_ $functionLike + */ + private function removeMixedParamTags(PhpDocInfo $phpDocInfo, Node $functionLike): bool + { + $declaredParamNames = $this->resolveDeclaredParamNames($functionLike); + if ($declaredParamNames === []) { + return false; + } + + $hasChanged = false; + + $phpDocNodeTraverser = new PhpDocNodeTraverser(); + $phpDocNodeTraverser->traverseWithCallable($phpDocInfo->getPhpDocNode(), '', function (PhpDocNode $phpDocNode) use ( + $declaredParamNames, + &$hasChanged + ): ?int { + if (! $phpDocNode instanceof PhpDocTagNode) { + return null; + } + + if ($phpDocNode->name !== '@param') { + return null; + } + + if (! $phpDocNode->value instanceof ParamTagValueNode) { + return null; + } + + $paramTagValueNode = $phpDocNode->value; + + // keep description, it is the only useful info on a mixed param + if ($paramTagValueNode->description !== '') { + return null; + } + + if (! $this->isMixedType($paramTagValueNode->type)) { + return null; + } + + $parameterName = ltrim($paramTagValueNode->parameterName, '$'); + if (! in_array($parameterName, $declaredParamNames, true)) { + return null; + } + + $hasChanged = true; + return PhpDocNodeTraverser::NODE_REMOVE; + }); + + return $hasChanged; + } + + /** + * @param ClassMethod|Function_ $functionLike + */ + private function removeMixedReturnTag(PhpDocInfo $phpDocInfo, Node $functionLike): bool + { + if ($functionLike->returnType === null) { + return false; + } + + $returnTagValueNode = $phpDocInfo->getReturnTagValue(); + if (! $returnTagValueNode instanceof ReturnTagValueNode) { + return false; + } + + if ($returnTagValueNode->description !== '') { + return false; + } + + if (! $this->isMixedType($returnTagValueNode->type)) { + return false; + } + + return $phpDocInfo->removeByType(ReturnTagValueNode::class); + } + + /** + * @param ClassMethod|Function_ $functionLike + * @return string[] + */ + private function resolveDeclaredParamNames(Node $functionLike): array + { + $declaredParamNames = []; + foreach ($functionLike->params as $param) { + if (! $param instanceof Param) { + continue; + } + + if (! $param->type instanceof Node) { + continue; + } + + if (! $param->var instanceof Variable) { + continue; + } + + if (! is_string($param->var->name)) { + continue; + } + + $declaredParamNames[] = $param->var->name; + } + + return $declaredParamNames; + } + + private function isMixedType(TypeNode $typeNode): bool + { + return $typeNode instanceof IdentifierTypeNode && $typeNode->name === 'mixed'; + } +} diff --git a/src/Config/Level/DeadCodeLevel.php b/src/Config/Level/DeadCodeLevel.php index 3422b0d49e6..a99ba262395 100644 --- a/src/Config/Level/DeadCodeLevel.php +++ b/src/Config/Level/DeadCodeLevel.php @@ -17,6 +17,7 @@ use Rector\DeadCode\Rector\ClassMethod\RemoveArgumentFromDefaultParentCallRector; use Rector\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector; use Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector; +use Rector\DeadCode\Rector\ClassMethod\RemoveMixedDocblockRector; use Rector\DeadCode\Rector\ClassMethod\RemoveNullTagValueNodeRector; use Rector\DeadCode\Rector\ClassMethod\RemoveParentDelegatingConstructorRector; use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector; @@ -118,6 +119,7 @@ final class DeadCodeLevel RemoveUselessParamTagRector::class, RemoveUselessReturnTagRector::class, RemoveDuplicatedReturnSelfDocblockRector::class, + RemoveMixedDocblockRector::class, RemoveUselessReadOnlyTagRector::class, RemoveNonExistingVarAnnotationRector::class, RemoveUselessVarTagRector::class, From 49fbd7478485e309718b85ad9efb6c2f1304e840 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 26 Jun 2026 17:55:22 +0200 Subject: [PATCH 2/2] Rename to RemoveMixedDocblockOverruledByNativeTypeRector --- .../Fixture/remove_param_and_return_mixed.php.inc | 4 ++-- .../Fixture/remove_param_mixed.php.inc | 4 ++-- .../Fixture/skip_param_no_native_type.php.inc | 2 +- .../Fixture/skip_param_with_description.php.inc | 2 +- .../Fixture/skip_return_no_native_type.php.inc | 2 +- ...moveMixedDocblockOverruledByNativeTypeRectorTest.php} | 4 ++-- .../config/configured_rule.php | 9 +++++++++ .../RemoveMixedDocblockRector/config/configured_rule.php | 9 --------- ...> RemoveMixedDocblockOverruledByNativeTypeRector.php} | 4 ++-- src/Config/Level/DeadCodeLevel.php | 4 ++-- 10 files changed, 22 insertions(+), 22 deletions(-) rename rules-tests/DeadCode/Rector/ClassMethod/{RemoveMixedDocblockRector => RemoveMixedDocblockOverruledByNativeTypeRector}/Fixture/remove_param_and_return_mixed.php.inc (84%) rename rules-tests/DeadCode/Rector/ClassMethod/{RemoveMixedDocblockRector => RemoveMixedDocblockOverruledByNativeTypeRector}/Fixture/remove_param_mixed.php.inc (84%) rename rules-tests/DeadCode/Rector/ClassMethod/{RemoveMixedDocblockRector => RemoveMixedDocblockOverruledByNativeTypeRector}/Fixture/skip_param_no_native_type.php.inc (82%) rename rules-tests/DeadCode/Rector/ClassMethod/{RemoveMixedDocblockRector => RemoveMixedDocblockOverruledByNativeTypeRector}/Fixture/skip_param_with_description.php.inc (83%) rename rules-tests/DeadCode/Rector/ClassMethod/{RemoveMixedDocblockRector => RemoveMixedDocblockOverruledByNativeTypeRector}/Fixture/skip_return_no_native_type.php.inc (81%) rename rules-tests/DeadCode/Rector/ClassMethod/{RemoveMixedDocblockRector/RemoveMixedDocblockRectorTest.php => RemoveMixedDocblockOverruledByNativeTypeRector/RemoveMixedDocblockOverruledByNativeTypeRectorTest.php} (82%) create mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockOverruledByNativeTypeRector/config/configured_rule.php delete mode 100644 rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php rename rules/DeadCode/Rector/ClassMethod/{RemoveMixedDocblockRector.php => RemoveMixedDocblockOverruledByNativeTypeRector.php} (97%) diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_and_return_mixed.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockOverruledByNativeTypeRector/Fixture/remove_param_and_return_mixed.php.inc similarity index 84% rename from rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_and_return_mixed.php.inc rename to rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockOverruledByNativeTypeRector/Fixture/remove_param_and_return_mixed.php.inc index b4d1065a98e..ebbe481c7f2 100644 --- a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/Fixture/remove_param_and_return_mixed.php.inc +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockOverruledByNativeTypeRector/Fixture/remove_param_and_return_mixed.php.inc @@ -1,6 +1,6 @@ withRules([RemoveMixedDocblockOverruledByNativeTypeRector::class]); diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php b/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php deleted file mode 100644 index f217e75b60d..00000000000 --- a/rules-tests/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector/config/configured_rule.php +++ /dev/null @@ -1,9 +0,0 @@ -withRules([RemoveMixedDocblockRector::class]); diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockOverruledByNativeTypeRector.php similarity index 97% rename from rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector.php rename to rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockOverruledByNativeTypeRector.php index e6287c22aec..dfe79ebd2d4 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveMixedDocblockOverruledByNativeTypeRector.php @@ -24,9 +24,9 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveMixedDocblockRector\RemoveMixedDocblockRectorTest + * @see \Rector\Tests\DeadCode\Rector\ClassMethod\RemoveMixedDocblockOverruledByNativeTypeRector\RemoveMixedDocblockOverruledByNativeTypeRectorTest */ -final class RemoveMixedDocblockRector extends AbstractRector +final class RemoveMixedDocblockOverruledByNativeTypeRector extends AbstractRector { public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, diff --git a/src/Config/Level/DeadCodeLevel.php b/src/Config/Level/DeadCodeLevel.php index a99ba262395..dd68b6c251a 100644 --- a/src/Config/Level/DeadCodeLevel.php +++ b/src/Config/Level/DeadCodeLevel.php @@ -17,7 +17,7 @@ use Rector\DeadCode\Rector\ClassMethod\RemoveArgumentFromDefaultParentCallRector; use Rector\DeadCode\Rector\ClassMethod\RemoveDuplicatedReturnSelfDocblockRector; use Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector; -use Rector\DeadCode\Rector\ClassMethod\RemoveMixedDocblockRector; +use Rector\DeadCode\Rector\ClassMethod\RemoveMixedDocblockOverruledByNativeTypeRector; use Rector\DeadCode\Rector\ClassMethod\RemoveNullTagValueNodeRector; use Rector\DeadCode\Rector\ClassMethod\RemoveParentDelegatingConstructorRector; use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector; @@ -119,7 +119,7 @@ final class DeadCodeLevel RemoveUselessParamTagRector::class, RemoveUselessReturnTagRector::class, RemoveDuplicatedReturnSelfDocblockRector::class, - RemoveMixedDocblockRector::class, + RemoveMixedDocblockOverruledByNativeTypeRector::class, RemoveUselessReadOnlyTagRector::class, RemoveNonExistingVarAnnotationRector::class, RemoveUselessVarTagRector::class,