From 4dcb1da7eac471c25c84fa2d2b071f7c08464643 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 6 Oct 2026 10:45:49 +0200 Subject: [PATCH 1/5] Replace tomasvotruba/unused-public with shipmonk/dead-code-detector --- composer.json | 2 +- phpstan-baseline-dead-code.neon | 31 +++++++++++++++++++ phpstan.neon | 10 +++--- .../AbstractPhpDocInfoPrinterTestCase.php | 4 --- .../Storage/FileCacheStorageTest.php | 5 --- 5 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 phpstan-baseline-dead-code.neon diff --git a/composer.json b/composer.json index 879870963e8..38b96ad6737 100644 --- a/composer.json +++ b/composer.json @@ -50,13 +50,13 @@ "phpunit/phpunit": "^13.3", "rector/swiss-knife": "^2.7", "shipmonk/composer-dependency-analyser": "^1.8", + "shipmonk/dead-code-detector": "^1.4", "symfony/process": "^8.1", "symplify/easy-coding-standard": "^13.2.13", "symplify/phpstan-rules": "^14.18", "tomasvotruba/class-leak": "^2.1", "tomasvotruba/fast-unit": "^0.1", "tomasvotruba/type-coverage": "^2.3.6", - "tomasvotruba/unused-public": "^2.2", "tracy/tracy": "^2.12" }, "replace": { diff --git a/phpstan-baseline-dead-code.neon b/phpstan-baseline-dead-code.neon new file mode 100644 index 00000000000..28caabf89d6 --- /dev/null +++ b/phpstan-baseline-dead-code.neon @@ -0,0 +1,31 @@ +parameters: + ignoreErrors: + # unused public methods + - + message: '#^Unused Rector\\Php80\\Contract\\StrStartWithMatchAndRefactorInterface\:\:(.*?)#' + identifier: shipmonk.deadMethod + path: rules/Php80/Contract/StrStartWithMatchAndRefactorInterface.php + + - + message: '#^Unused Rector\\BetterPhpDocParser\\Contract\\PhpDocParser\\PhpDocNodeDecoratorInterface\:\:decorate$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/BetterPhpDocParser/Contract/PhpDocParser/PhpDocNodeDecoratorInterface.php + + - + message: '#^Unused Rector\\Parallel\\Contract\\SerializableInterface\:\:decode$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Parallel/Contract/SerializableInterface.php + + # called magically by parent + - + message: '#^Unused Rector\\PhpParser\\Printer\\BetterStandardPrinter\:\:(pInterpolatedStringPart|pStmt_FileNode)$#' + identifier: shipmonk.deadMethod + path: src/PhpParser/Printer/BetterStandardPrinter.php + + # deprecated, to be removed + - + message: '#^Unused Rector\\Set\\Contract\\SetInterface\:\:(.*?)$#' + identifier: shipmonk.deadMethod + path: src/Set/Contract/SetInterface.php diff --git a/phpstan.neon b/phpstan.neon index 4d738e20e15..80168690141 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,6 @@ includes: - vendor/symplify/phpstan-rules/config/rector-rules.neon + - phpstan-baseline-dead-code.neon rules: - Rector\Utils\PHPStan\Rule\RegisterRelatedPolyfillRectorRule @@ -67,11 +68,10 @@ parameters: narrow_param: true narrow_return: true - # see https://github.com/TomasVotruba/unused-public - unused_public: - methods: true - properties: true - constants: true + # offload breaks cold-cache parallel runs, keep collector data in result cache + shipmonkDeadCode: + cache: + offloadCollectorData: false ignoreErrors: # the deprecated set objects are still resolved internally, until every extension bonds its rules diff --git a/tests/BetterPhpDocParser/PhpDocInfo/PhpDocInfoPrinter/AbstractPhpDocInfoPrinterTestCase.php b/tests/BetterPhpDocParser/PhpDocInfo/PhpDocInfoPrinter/AbstractPhpDocInfoPrinterTestCase.php index 997b88b4c5c..b93c336d408 100644 --- a/tests/BetterPhpDocParser/PhpDocInfo/PhpDocInfoPrinter/AbstractPhpDocInfoPrinterTestCase.php +++ b/tests/BetterPhpDocParser/PhpDocInfo/PhpDocInfoPrinter/AbstractPhpDocInfoPrinterTestCase.php @@ -9,13 +9,10 @@ use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter; -use Rector\FileSystem\FilePathHelper; use Rector\Testing\PHPUnit\AbstractLazyTestCase; abstract class AbstractPhpDocInfoPrinterTestCase extends AbstractLazyTestCase { - protected FilePathHelper $filePathHelper; - protected PhpDocInfoPrinter $phpDocInfoPrinter; private PhpDocInfoFactory $phpDocInfoFactory; @@ -24,7 +21,6 @@ protected function setUp(): void { parent::setUp(); - $this->filePathHelper = $this->make(FilePathHelper::class); $this->phpDocInfoFactory = $this->make(PhpDocInfoFactory::class); $this->phpDocInfoPrinter = $this->make(PhpDocInfoPrinter::class); } diff --git a/tests/Caching/ValueObject/Storage/FileCacheStorageTest.php b/tests/Caching/ValueObject/Storage/FileCacheStorageTest.php index 3eb792855cb..54411bd6767 100644 --- a/tests/Caching/ValueObject/Storage/FileCacheStorageTest.php +++ b/tests/Caching/ValueObject/Storage/FileCacheStorageTest.php @@ -77,9 +77,4 @@ public function testSaveLeavesConcurrentReaderOnCompleteFile(): void $this->fileCacheStorage->clean('aaK1STfY'); } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config.php'; - } } From 7e82783bcfe89f72312807e2f3a247341e9f6ce7 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 10 Oct 2026 11:23:11 +0200 Subject: [PATCH 2/5] mark interface methods as @api instead of baseline ignore --- phpstan-baseline-dead-code.neon | 12 ------------ .../PhpDocParser/PhpDocNodeDecoratorInterface.php | 3 +++ src/Parallel/Contract/SerializableInterface.php | 1 + 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/phpstan-baseline-dead-code.neon b/phpstan-baseline-dead-code.neon index 28caabf89d6..4f755b03910 100644 --- a/phpstan-baseline-dead-code.neon +++ b/phpstan-baseline-dead-code.neon @@ -6,18 +6,6 @@ parameters: identifier: shipmonk.deadMethod path: rules/Php80/Contract/StrStartWithMatchAndRefactorInterface.php - - - message: '#^Unused Rector\\BetterPhpDocParser\\Contract\\PhpDocParser\\PhpDocNodeDecoratorInterface\:\:decorate$#' - identifier: shipmonk.deadMethod - count: 1 - path: src/BetterPhpDocParser/Contract/PhpDocParser/PhpDocNodeDecoratorInterface.php - - - - message: '#^Unused Rector\\Parallel\\Contract\\SerializableInterface\:\:decode$#' - identifier: shipmonk.deadMethod - count: 1 - path: src/Parallel/Contract/SerializableInterface.php - # called magically by parent - message: '#^Unused Rector\\PhpParser\\Printer\\BetterStandardPrinter\:\:(pInterpolatedStringPart|pStmt_FileNode)$#' diff --git a/src/BetterPhpDocParser/Contract/PhpDocParser/PhpDocNodeDecoratorInterface.php b/src/BetterPhpDocParser/Contract/PhpDocParser/PhpDocNodeDecoratorInterface.php index 7d556956015..c95b4bc56b4 100644 --- a/src/BetterPhpDocParser/Contract/PhpDocParser/PhpDocNodeDecoratorInterface.php +++ b/src/BetterPhpDocParser/Contract/PhpDocParser/PhpDocNodeDecoratorInterface.php @@ -9,5 +9,8 @@ interface PhpDocNodeDecoratorInterface { + /** + * @api implemented by child classes + */ public function decorate(PhpDocNode $phpDocNode, Node $phpNode): void; } diff --git a/src/Parallel/Contract/SerializableInterface.php b/src/Parallel/Contract/SerializableInterface.php index 250986e4c66..a08a6a9e890 100644 --- a/src/Parallel/Contract/SerializableInterface.php +++ b/src/Parallel/Contract/SerializableInterface.php @@ -9,6 +9,7 @@ interface SerializableInterface extends JsonSerializable { /** + * @api implemented by child classes * @param array $json */ public static function decode(array $json): self; From 23b8287aa03965e2ab8292b4cc6773f344f3a1e2 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 10 Oct 2026 11:25:49 +0200 Subject: [PATCH 3/5] move remaining dead-code baseline into phpstan.neon, @api interface methods --- phpstan-baseline-dead-code.neon | 19 ------------------- phpstan.neon | 7 ++++++- .../StrStartWithMatchAndRefactorInterface.php | 4 ++++ src/Set/Contract/SetInterface.php | 9 +++++++++ 4 files changed, 19 insertions(+), 20 deletions(-) delete mode 100644 phpstan-baseline-dead-code.neon diff --git a/phpstan-baseline-dead-code.neon b/phpstan-baseline-dead-code.neon deleted file mode 100644 index 4f755b03910..00000000000 --- a/phpstan-baseline-dead-code.neon +++ /dev/null @@ -1,19 +0,0 @@ -parameters: - ignoreErrors: - # unused public methods - - - message: '#^Unused Rector\\Php80\\Contract\\StrStartWithMatchAndRefactorInterface\:\:(.*?)#' - identifier: shipmonk.deadMethod - path: rules/Php80/Contract/StrStartWithMatchAndRefactorInterface.php - - # called magically by parent - - - message: '#^Unused Rector\\PhpParser\\Printer\\BetterStandardPrinter\:\:(pInterpolatedStringPart|pStmt_FileNode)$#' - identifier: shipmonk.deadMethod - path: src/PhpParser/Printer/BetterStandardPrinter.php - - # deprecated, to be removed - - - message: '#^Unused Rector\\Set\\Contract\\SetInterface\:\:(.*?)$#' - identifier: shipmonk.deadMethod - path: src/Set/Contract/SetInterface.php diff --git a/phpstan.neon b/phpstan.neon index 80168690141..5cb2738bbfa 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,6 +1,5 @@ includes: - vendor/symplify/phpstan-rules/config/rector-rules.neon - - phpstan-baseline-dead-code.neon rules: - Rector\Utils\PHPStan\Rule\RegisterRelatedPolyfillRectorRule @@ -74,6 +73,12 @@ parameters: offloadCollectorData: false ignoreErrors: + # called magically by parent + - + message: '#^Unused Rector\\PhpParser\\Printer\\BetterStandardPrinter\:\:(pInterpolatedStringPart|pStmt_FileNode)$#' + identifier: shipmonk.deadMethod + path: src/PhpParser/Printer/BetterStandardPrinter.php + # the deprecated set objects are still resolved internally, until every extension bonds its rules - message: '#deprecated (class|interface) Rector\\(Set|Bridge)\\#' diff --git a/rules/Php80/Contract/StrStartWithMatchAndRefactorInterface.php b/rules/Php80/Contract/StrStartWithMatchAndRefactorInterface.php index 5a9eadde45f..639de06ce29 100644 --- a/rules/Php80/Contract/StrStartWithMatchAndRefactorInterface.php +++ b/rules/Php80/Contract/StrStartWithMatchAndRefactorInterface.php @@ -15,9 +15,13 @@ interface StrStartWithMatchAndRefactorInterface { + /** + * @api implemented by child classes + */ public function match(Identical|NotIdentical|Equal|NotEqual $binaryOp): ?StrStartsWith; /** + * @api implemented by child classes * @return FuncCall|BooleanNot|null */ public function refactorStrStartsWith(StrStartsWith $strStartsWith): ?Node; diff --git a/src/Set/Contract/SetInterface.php b/src/Set/Contract/SetInterface.php index f32334a6e36..ca6825e3f95 100644 --- a/src/Set/Contract/SetInterface.php +++ b/src/Set/Contract/SetInterface.php @@ -14,9 +14,18 @@ */ interface SetInterface { + /** + * @api implemented by child classes + */ public function getGroupName(): string; + /** + * @api implemented by child classes + */ public function getName(): string; + /** + * @api implemented by child classes + */ public function getSetFilePath(): string; } From 379b989c93395c607f4d7fc959379be668a1956f Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 10 Oct 2026 11:26:43 +0200 Subject: [PATCH 4/5] keep deprecated SetInterface in ignore, drop @api --- phpstan.neon | 6 ++++++ src/Set/Contract/SetInterface.php | 9 --------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 5cb2738bbfa..dd1e3bf83b4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -79,6 +79,12 @@ parameters: identifier: shipmonk.deadMethod path: src/PhpParser/Printer/BetterStandardPrinter.php + # deprecated, to be removed + - + message: '#^Unused Rector\\Set\\Contract\\SetInterface\:\:(.*?)$#' + identifier: shipmonk.deadMethod + path: src/Set/Contract/SetInterface.php + # the deprecated set objects are still resolved internally, until every extension bonds its rules - message: '#deprecated (class|interface) Rector\\(Set|Bridge)\\#' diff --git a/src/Set/Contract/SetInterface.php b/src/Set/Contract/SetInterface.php index ca6825e3f95..f32334a6e36 100644 --- a/src/Set/Contract/SetInterface.php +++ b/src/Set/Contract/SetInterface.php @@ -14,18 +14,9 @@ */ interface SetInterface { - /** - * @api implemented by child classes - */ public function getGroupName(): string; - /** - * @api implemented by child classes - */ public function getName(): string; - /** - * @api implemented by child classes - */ public function getSetFilePath(): string; } From 31fffee902c2a88f34683c7755be1b09c5d37df3 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 10 Oct 2026 11:27:21 +0200 Subject: [PATCH 5/5] cs --- .../src/Rule/NoScopeFetchBeforeCheapGuardRule.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/utils/phpstan/src/Rule/NoScopeFetchBeforeCheapGuardRule.php b/utils/phpstan/src/Rule/NoScopeFetchBeforeCheapGuardRule.php index b891ccca98a..6f4ed34c916 100644 --- a/utils/phpstan/src/Rule/NoScopeFetchBeforeCheapGuardRule.php +++ b/utils/phpstan/src/Rule/NoScopeFetchBeforeCheapGuardRule.php @@ -7,8 +7,11 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Assign; +use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Identifier; +use PhpParser\Node\Name; use PhpParser\Node\Stmt; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; @@ -102,7 +105,7 @@ private function matchScopeFetchVariableName(Stmt $stmt): ?string } $staticCall = $assign->expr; - if (! $staticCall->class instanceof Node\Name) { + if (! $staticCall->class instanceof Name) { return null; } @@ -110,7 +113,7 @@ private function matchScopeFetchVariableName(Stmt $stmt): ?string return null; } - if (! $staticCall->name instanceof Node\Identifier || $staticCall->name->toString() !== 'fetch') { + if (! $staticCall->name instanceof Identifier || $staticCall->name->toString() !== 'fetch') { return null; } @@ -128,17 +131,17 @@ private function isReturnNullGuard(If_ $if): bool return false; } - return $onlyStmt->expr === null || $this->isNullConstant($onlyStmt->expr); + return ! $onlyStmt->expr instanceof Expr || $this->isNullConstant($onlyStmt->expr); } private function isNullConstant(Expr $expr): bool { - return $expr instanceof Node\Expr\ConstFetch && $expr->name->toLowerString() === 'null'; + return $expr instanceof ConstFetch && $expr->name->toLowerString() === 'null'; } private function usesVariable(Expr $expr, string $variableName): bool { - $foundVariable = (new NodeFinder())->findFirst( + $foundVariable = new NodeFinder()->findFirst( [$expr], static fn (Node $subNode): bool => $subNode instanceof Variable && $subNode->name === $variableName );