diff --git a/rules-tests/DeadCode/Rector/ClassMethod/RemoveParentDelegatingConstructorRector/Fixture/skip_param_refining_docblock.php.inc b/rules-tests/DeadCode/Rector/ClassMethod/RemoveParentDelegatingConstructorRector/Fixture/skip_param_refining_docblock.php.inc new file mode 100644 index 00000000000..d68d498ad9a --- /dev/null +++ b/rules-tests/DeadCode/Rector/ClassMethod/RemoveParentDelegatingConstructorRector/Fixture/skip_param_refining_docblock.php.inc @@ -0,0 +1,26 @@ + $config + * + * @phpstan-param CustomScalarConfig $config + */ + public function __construct(array $config) + { + parent::__construct($config); + } +} diff --git a/rules/DeadCode/Rector/ClassMethod/RemoveParentDelegatingConstructorRector.php b/rules/DeadCode/Rector/ClassMethod/RemoveParentDelegatingConstructorRector.php index a8ce2bd02f6..34250539caa 100644 --- a/rules/DeadCode/Rector/ClassMethod/RemoveParentDelegatingConstructorRector.php +++ b/rules/DeadCode/Rector/ClassMethod/RemoveParentDelegatingConstructorRector.php @@ -16,6 +16,7 @@ use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; +use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Enum\ObjectReference; use Rector\PhpParser\Node\Value\ValueResolver; use Rector\PHPStan\ScopeFetcher; @@ -33,7 +34,8 @@ final class RemoveParentDelegatingConstructorRector extends AbstractRector { public function __construct( private readonly StaticTypeMapper $staticTypeMapper, - private readonly ValueResolver $valueResolver + private readonly ValueResolver $valueResolver, + private readonly PhpDocInfoFactory $phpDocInfoFactory ) { } @@ -131,9 +133,23 @@ public function refactor(Node $node): ?int return null; } + // keep when the docblock refines parameter types beyond the native signature, + // e.g. @param array or @phpstan-param SomeShape $config — removing + // the constructor would drop that type information + if ($this->hasParamRefiningDocblock($node)) { + return null; + } + return NodeVisitor::REMOVE_NODE; } + private function hasParamRefiningDocblock(ClassMethod $classMethod): bool + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod); + + return $phpDocInfo->hasByNames(['@param', '@phpstan-param', '@psalm-param']); + } + private function matchParentConstructorReflection(ClassMethod $classMethod): ?ExtendedMethodReflection { $scope = ScopeFetcher::fetch($classMethod);