Repository: infection/infection
Pull request #2090: Introduce dead code detector
Author: login: maks-rafalko
Created: 2025-05-17T08:39:21Z | Merged: 2025-05-21T13:48:15Z

See https://github.com/shipmonk-rnd/dead-code-detector

Result: no real dead code is found. A bunch of false-positives (reported to the plugin repository) and 2 edge cases - added comments to `phpstan.neon`.

----

- Related to https://github.com/infection/infection/pull/335
- [x] For now in draft as it has false-positives that [I want to ask about](https://github.com/shipmonk-rnd/dead-code-detector/issues/209)
- [x] blocked by https://github.com/infection/infection/pull/2091
- [x] Reported bug/feature request https://github.com/shipmonk-rnd/dead-code-detector/issues/211


Diff:
diff --git a/composer.json b/composer.json
index cc2d0e6acd..a54d721db2 100644
--- a/composer.json
+++ b/composer.json
@@ -83,6 +83,7 @@
         "phpstan/phpstan-webmozart-assert": "^2.0",
         "phpunit/phpunit": "^11.5",
         "rector/rector": "^2.0",
+        "shipmonk/dead-code-detector": "^0.12.0",
         "sidz/phpstan-rules": "^0.5.1",
         "symfony/yaml": "^6.4 || ^7.0",
         "thecodingmachine/phpstan-safe-rule": "^1.4"
diff --git a/composer.lock b/composer.lock
index 6327704f60..40818fc834 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "c67f4439e430e2485b7f8581e1172da9",
+    "content-hash": "67e3098fc807ebcdb7e184d702c648e4",
     "packages": [
         {
             "name": "colinodell/json5",
@@ -3984,6 +3984,81 @@
             ],
             "time": "2024-10-09T05:16:32+00:00"
         },
+        {
+            "name": "shipmonk/dead-code-detector",
+            "version": "0.12.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/shipmonk-rnd/dead-code-detector.git",
+                "reference": "12af219888e245e9f16cd36ab5fe701f2e426a97"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/shipmonk-rnd/dead-code-detector/zipball/12af219888e245e9f16cd36ab5fe701f2e426a97",
+                "reference": "12af219888e245e9f16cd36ab5fe701f2e426a97",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4 || ^8.0",
+                "phpstan/phpstan": "^2.1.9"
+            },
+            "require-dev": {
+                "composer-runtime-api": "^2.0",
+                "composer/semver": "^3.4",
+                "doctrine/orm": "^2.19 || ^3.0",
+                "editorconfig-checker/editorconfig-checker": "^10.6.0",
+                "ergebnis/composer-normalize": "^2.45.0",
+                "nette/application": "^3.1",
+                "nette/component-model": "^3.0",
+                "nette/utils": "^3.0 || ^4.0",
+                "nikic/php-parser": "^5.4.0",
+                "phpstan/phpstan-phpunit": "^2.0.4",
+                "phpstan/phpstan-strict-rules": "^2.0.3",
+                "phpstan/phpstan-symfony": "^2.0.2",
+                "phpunit/phpunit": "^9.6.22",
+                "shipmonk/composer-dependency-analyser": "^1.8.2",
+                "shipmonk/name-collision-detector": "^2.1.1",
+                "shipmonk/phpstan-rules": "^4.1.0",
+                "slevomat/coding-standard": "^8.16.0",
+                "symfony/contracts": "^2.5 || ^3.0",
+                "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
+                "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0",
+                "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
+                "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
+                "symfony/routing": "^5.4 || ^6.0 || ^7.0",
+                "symfony/validator": "^5.4 || ^6.0 || ^7.0",
+                "twig/twig": "^3.0"
+            },
+            "type": "phpstan-extension",
+            "extra": {
+                "phpstan": {
+                    "includes": [
+                        "rules.neon"
+                    ]
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "ShipMonk\\PHPStan\\DeadCode\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Dead code detector to find unused PHP code via PHPStan extension. Can automatically remove dead PHP code. Supports libraries like Symfony, Doctrine, PHPUnit etc. Detects dead cycles. Can detect dead code that is tested.",
+            "keywords": [
+                "PHPStan",
+                "dead code",
+                "static analysis",
+                "unused code"
+            ],
+            "support": {
+                "issues": "https://github.com/shipmonk-rnd/dead-code-detector/issues",
+                "source": "https://github.com/shipmonk-rnd/dead-code-detector/tree/0.12.1"
+            },
+            "time": "2025-05-20T07:02:46+00:00"
+        },
         {
             "name": "sidz/phpstan-rules",
             "version": "0.5.1",
diff --git a/devTools/phpstan-baseline.neon b/devTools/phpstan-baseline.neon
index e8a825621f..860c763b06 100644
--- a/devTools/phpstan-baseline.neon
+++ b/devTools/phpstan-baseline.neon
@@ -1728,6 +1728,18 @@ parameters:
 			count: 1
 			path: ../tests/phpunit/Reflection/AnonymousClassReflectionTest.php
 
+		-
+			message: '#^Unused Infection\\Tests\\Reflection\\ProtChild\:\:foo$#'
+			identifier: shipmonk.deadMethod
+			count: 1
+			path: ../tests/phpunit/Reflection/ClassReflectionTestCase.php
+
+		-
+			message: '#^Unused Infection\\Tests\\Reflection\\ProtParent\:\:foo$#'
+			identifier: shipmonk.deadMethod
+			count: 1
+			path: ../tests/phpunit/Reflection/ClassReflectionTestCase.php
+
 		-
 			message: '#^Parameter \#1 \$className of static method Infection\\Reflection\\CoreClassReflection\:\:fromClassName\(\) expects class\-string, string given\.$#'
 			identifier: argument.type
diff --git a/devTools/phpstan.neon b/devTools/phpstan.neon
index 6c3f66840d..1e885592fe 100644
--- a/devTools/phpstan.neon
+++ b/devTools/phpstan.neon
@@ -65,6 +65,26 @@ parameters:
             path: ../src/Mutator/Extensions/BCMath.php
         -
             identifier: arrayFilter.strict
+        -
+            identifier: shipmonk.deadMethod
+            path: ../src/Testing
+        # ProxyTrace::getRelativePathname() is used implicitly by PathFilterIterator
+        -
+            message: '#^Unused Infection\\TestFramework\\Coverage\\ProxyTrace\:\:getRelativePathname$#'
+            identifier: shipmonk.deadMethod
+            count: 1
+            path: ../src/TestFramework/Coverage/ProxyTrace.php
+        -
+            message: '#^Unused Infection\\TestFramework\\Coverage\\Trace\:\:getRelativePathname$#'
+            identifier: shipmonk.deadMethod
+            count: 1
+            path: ../src/TestFramework/Coverage/Trace.php
+        # While it's not used yet, left for consistency as we have ending column passed and used
+        -
+            message: '#^Unused Infection\\Mutant\\MutantExecutionResult\:\:getOriginalStartingColumn$#'
+            identifier: shipmonk.deadMethod
+            count: 1
+            path: ../src/Mutant/MutantExecutionResult.php
 
         # tests
         - '#Dynamic call to static method PHPUnit\\Framework\\.*::.*#'


Reviews:
user: login: maks-rafalko
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T21:52:52Z
user: login: maks-rafalko
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T07:59:54Z

Review comments:
diff_hunk: @@ -264,12 +264,24 @@ parameters:
 			count: 2
 			path: ../tests/phpunit/AutoReview/EnvVariableManipulation/EnvTestCasesProvider.php
 
+		-
+			message: '#^Unused Infection\\Tests\\AutoReview\\EnvVariableManipulation\\EnvTestCasesProvider\:\:envTestCaseTupleProvider$#'
+			identifier: shipmonk.deadMethod
+			count: 1
+			path: ../tests/phpunit/AutoReview/EnvVariableManipulation/EnvTestCasesProvider.php
+
path: devTools/phpstan-baseline.neon
user: login: maks-rafalko
body: all the issues added to baseline - false-positives, reported https://github.com/shipmonk-rnd/dead-code-detector/issues/211
created_at: 2025-05-19T21:52:52Z
author_association: MEMBER
diff_hunk: @@ -264,12 +264,24 @@ parameters:
 			count: 2
 			path: ../tests/phpunit/AutoReview/EnvVariableManipulation/EnvTestCasesProvider.php
 
+		-
+			message: '#^Unused Infection\\Tests\\AutoReview\\EnvVariableManipulation\\EnvTestCasesProvider\:\:envTestCaseTupleProvider$#'
+			identifier: shipmonk.deadMethod
+			count: 1
+			path: ../tests/phpunit/AutoReview/EnvVariableManipulation/EnvTestCasesProvider.php
+
path: devTools/phpstan-baseline.neon
user: login: maks-rafalko
body: this has been fixed, not relevant anymore
created_at: 2025-05-20T07:59:54Z
author_association: MEMBER

PR comments:
user: login: private-packagist[bot]
created_at: 2025-05-17T08:39:25Z
body: ### composer.lock
#### Dev Package changes
| Package | Operation | From | To | About |
|---------|-----------|------|----|-------|
| **shipmonk/dead-code-detector** | add | -  | 0.12.1  | [view code](https://github.com/shipmonk-rnd/dead-code-detector/releases/tag/0.12.1) - License: MIT License |

---
[Settings](https://packagist.com/orgs/infection/syncs/5829/pull-requests/settings) · [Docs](https://packagist.com/docs/update-review) · Powered by [Private Packagist](https://packagist.com/)

author_association: CONTRIBUTOR
user: login: sanmai
created_at: 2025-05-17T08:49:10Z
body: I think Psalm also had dead code detection.
author_association: MEMBER
user: login: maks-rafalko
created_at: 2025-05-19T22:02:21Z
body: > I think Psalm also had dead code detection.

- personally, I found this one is more user-friendly and flexible. For example, if you make change `findUnusedCode` to `true` in https://github.com/infection/infection/blob/8ba127848d901c76b72e5719cb157decfd4a2559/psalm.xml#L10, then it gives many false-positives like unused private constructors, unused methods (when created for tests purposes), etc. This is probably because we are ignoring many folders in `psalm.xml` but anyway
- there is a comparison with other alternatives, where we can see that this plugin is quite powerful: https://github.com/shipmonk-rnd/dead-code-detector?tab=readme-ov-file#comparison-with-tomasvotrubaunused-public
- this plugin is used by PHPStan itself, so I consider it as a good choice because of this fact
author_association: MEMBER
user: login: private-packagist[bot]
created_at: 2025-05-20T07:58:36Z
body: The [composer.lock diff comment](https://github.com/infection/infection/pull/2090#issuecomment-2888227541) has been updated to reflect new changes in this PR.

author_association: CONTRIBUTOR
user: login: janedbal
created_at: 2025-05-21T14:24:36Z
body: FYI I just added your repo into Dead Code Detector's E2E test suite: https://github.com/shipmonk-rnd/dead-code-detector/pull/213

---------

One more note:
- with `-vvv` PHPStan mode, [you can see](https://github.com/shipmonk-rnd/dead-code-detector/actions/runs/15164515717/job/42639235521?pr=213#step:5:58) that you make some **calls over unknown types**, which marks all methods with the same name as used:

![image](https://github.com/user-attachments/assets/6fc6b2d3-19ba-4fdf-bf81-2402c351158d)

author_association: CONTRIBUTOR
user: login: janedbal
created_at: 2025-05-21T14:27:43Z
body: You can deny those usages even on level 8 with [forbidMethodCallOnMixed](https://github.com/shipmonk-rnd/phpstan-rules?tab=readme-ov-file#forbidmethodcallonmixed)
author_association: CONTRIBUTOR
user: login: maks-rafalko
created_at: 2025-05-21T14:28:55Z
body: Thank you, will have a look at it.
author_association: MEMBER