diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index bb1e9e88..088c3a4f 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -18,7 +18,7 @@ jobs: name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.3 - name: Install dependencies run: composer install --no-progress --prefer-dist --no-interaction diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 74422207..c1d6c65e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - name: Install dependencies - run: composer install --no-progress --no-interaction + run: composer update --no-progress --no-interaction - name: Lint diff --git a/README.md b/README.md index 41c7a992..ab48527b 100644 --- a/README.md +++ b/README.md @@ -17,49 +17,48 @@ includes: ## Configuration: -- You need to mark all entrypoints of your code to get proper results. -- This is typically long whitelist of all code that is called by your framework and libraries. +- All entrypoints of your code (controllers, consumers, commands, ...) need to be known to the detector to get proper results +- By default, all overridden methods which declaration originates inside vendor are considered entrypoints +- Also, there are some basic entrypoint providers for `symfony` and `phpunit` +- For everything else, you can implement your own entrypoint provider, just tag it with `shipmonk.deadCode.entrypointProvider` ```neon +parameters: + deadCode: + entrypoints: + vendor: + enabled: true # enabled by default + symfony: + enabled: true + phpunit: + enabled: true + services: - - class: App\SymfonyEntrypointProvider + class: App\MyEntrypointProvider tags: - shipmonk.deadCode.entrypointProvider ``` ```php use ReflectionMethod; -use PHPStan\Reflection\ReflectionProvider; use ShipMonk\PHPStan\DeadCode\Provider\EntrypointProvider; -class SymfonyEntrypointProvider implements EntrypointProvider +class MyEntrypointProvider implements EntrypointProvider { - public function __construct( - private ReflectionProvider $reflectionProvider - ) {} - public function isEntrypoint(ReflectionMethod $method): bool { - $methodName = $method->getName(); - $reflection = $this->reflectionProvider->getClass($method->getDeclaringClass()->getName()); - - return $reflection->is(\Symfony\Bundle\FrameworkBundle\Controller\AbstractController::class) - || $reflection->is(\Symfony\Component\EventDispatcher\EventSubscriberInterface::class) - || $reflection->is(\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface::class) - || ($reflection->is(\Symfony\Component\Console\Command\Command::class) && in_array($methodName, ['execute', 'initialize', ...], true) - // and many more + return $method->getDeclaringClass()->implementsInterface(ApiOutput::class)); } } ``` -## Limitations -This project is currently a working prototype (we are using it since 2022) with limited functionality: +## Limitations: - Only method calls are detected - Including static methods, trait methods, interface methods, first class callables, etc. - - Callbacks like `[$this, 'method']` are mostly not detected + - Callbacks like `[$this, 'method']` are mostly not detected; prefer first class callables `$this->method(...)` - Any calls on mixed types are not detected, e.g. `$unknownClass->method()` - Expression method calls are not detected, e.g. `$this->$methodName()` - Anonymous classes are ignored diff --git a/composer.json b/composer.json index 079db572..ba2c508b 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^1.10.30" + "phpstan/phpstan": "^1.11.0" }, "require-dev": { "editorconfig-checker/editorconfig-checker": "^10.3.0", @@ -22,8 +22,11 @@ "phpstan/phpstan-strict-rules": "^1.2.3", "phpunit/phpunit": "^9.5.20", "shipmonk/name-collision-detector": "^2.0.0", - "shipmonk/phpstan-rules": "^2.11", - "slevomat/coding-standard": "^8.0.1" + "shipmonk/phpstan-rules": "^3.1", + "slevomat/coding-standard": "^8.0.1", + "symfony/contracts": "^2.5 || ^3.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/routing": "^5.4 || ^6.0 || ^7.0" }, "autoload": { "psr-4": { @@ -65,7 +68,7 @@ "check:composer": "composer normalize --dry-run --no-check-lock --no-update-lock", "check:cs": "phpcs", "check:ec": "ec src tests", - "check:tests": "phpunit -vvv tests", + "check:tests": "phpunit tests", "check:types": "phpstan analyse -vvv --ansi", "fix:cs": "phpcbf" } diff --git a/composer.lock b/composer.lock index 9d5ac9d3..93a2a837 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d87f5b9caf4c88330c483279e2df227d", + "content-hash": "d66846b46f5d747a4668aa797e39b7e9", "packages": [ { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.11.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", "shasum": "" }, "require": { @@ -62,7 +62,7 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2024-07-06T11:17:41+00:00" } ], "packages-dev": [ @@ -146,30 +146,30 @@ }, { "name": "doctrine/instantiator", - "version": "1.5.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -196,7 +196,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -212,7 +212,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "editorconfig-checker/editorconfig-checker", @@ -677,20 +677,20 @@ }, { "name": "justinrainbow/json-schema", - "version": "v5.2.13", + "version": "5.3.0", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", - "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", + "reference": "feb2ca6dd1cebdaf1ed60a4c8de2e53ce11c4fd8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", @@ -701,11 +701,6 @@ "bin/validate-json" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, "autoload": { "psr-4": { "JsonSchema\\": "src/JsonSchema/" @@ -741,9 +736,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/v5.2.13" + "source": "https://github.com/jsonrainbow/json-schema/tree/5.3.0" }, - "time": "2023-09-26T02:20:38+00:00" + "time": "2024-07-06T21:00:26+00:00" }, { "name": "localheinz/diff", @@ -807,16 +802,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -824,11 +819,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -854,7 +850,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -862,35 +858,35 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nette/schema", - "version": "v1.2.5", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", + "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": "7.1 - 8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.3" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", + "nette/tester": "^2.4", "phpstan/phpstan-nette": "^1.0", - "tracy/tracy": "^2.7" + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -922,35 +918,36 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.5" + "source": "https://github.com/nette/schema/tree/v1.3.0" }, - "time": "2023-10-05T20:37:59+00:00" + "time": "2023-12-11T11:54:22+00:00" }, { "name": "nette/utils", - "version": "v3.2.10", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2" + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/a4175c62652f2300c8017fb7e640f9ccb11648d2", - "reference": "a4175c62652f2300c8017fb7e640f9ccb11648d2", + "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", "shasum": "" }, "require": { - "php": ">=7.2 <8.4" + "php": ">=8.0 <8.4" }, "conflict": { - "nette/di": "<3.0.6" + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "~2.0", + "nette/tester": "^2.5", "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.9" }, "suggest": { "ext-gd": "to use Image", @@ -958,13 +955,12 @@ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1008,22 +1004,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.10" + "source": "https://github.com/nette/utils/tree/v4.0.4" }, - "time": "2023-07-30T15:38:18+00:00" + "time": "2024-01-17T16:50:36+00:00" }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -1034,7 +1030,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -1066,9 +1062,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "phar-io/manifest", @@ -1190,16 +1186,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.27.0", + "version": "1.29.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "86e4d5a4b036f8f0be1464522f4c6b584c452757" + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/86e4d5a4b036f8f0be1464522f4c6b584c452757", - "reference": "86e4d5a4b036f8f0be1464522f4c6b584c452757", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", "shasum": "" }, "require": { @@ -1231,9 +1227,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.27.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" }, - "time": "2024-03-21T13:14:53+00:00" + "time": "2024-05-31T08:52:43+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -1657,45 +1653,45 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.19", + "version": "9.6.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + "reference": "49d7820565836236411f5dc002d16dd689cde42f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", - "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/49d7820565836236411f5dc002d16dd689cde42f", + "reference": "49d7820565836236411f5dc002d16dd689cde42f", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", + "doctrine/instantiator": "^1.5.0 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-code-coverage": "^9.2.31", + "phpunit/php-file-iterator": "^3.0.6", "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", + "phpunit/php-text-template": "^2.0.4", + "phpunit/php-timer": "^5.0.3", + "sebastian/cli-parser": "^1.0.2", + "sebastian/code-unit": "^1.0.8", "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", + "sebastian/diff": "^4.0.6", + "sebastian/environment": "^5.1.5", + "sebastian/exporter": "^4.0.6", + "sebastian/global-state": "^5.0.7", + "sebastian/object-enumerator": "^4.0.4", + "sebastian/resource-operations": "^3.0.4", + "sebastian/type": "^3.2.1", "sebastian/version": "^3.0.2" }, "suggest": { @@ -1740,7 +1736,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.20" }, "funding": [ { @@ -1756,7 +1752,159 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:35:58+00:00" + "time": "2024-07-10T11:45:39+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" }, { "name": "sebastian/cli-parser", @@ -2781,28 +2929,28 @@ }, { "name": "shipmonk/phpstan-rules", - "version": "2.12.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/shipmonk-rnd/phpstan-rules.git", - "reference": "db342d88dc4e28aa0a50af658b682025f19ee03c" + "reference": "8ace7eaadb0622707f7a9284cfbc9496193edf8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/shipmonk-rnd/phpstan-rules/zipball/db342d88dc4e28aa0a50af658b682025f19ee03c", - "reference": "db342d88dc4e28aa0a50af658b682025f19ee03c", + "url": "https://api.github.com/repos/shipmonk-rnd/phpstan-rules/zipball/8ace7eaadb0622707f7a9284cfbc9496193edf8e", + "reference": "8ace7eaadb0622707f7a9284cfbc9496193edf8e", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^1.10.51" + "phpstan/phpstan": "^1.11.0" }, "require-dev": { "editorconfig-checker/editorconfig-checker": "^10.6.0", "ergebnis/composer-normalize": "^2.28", "nette/neon": "^3.3.1", - "phpstan/phpstan-phpunit": "^1.1.1", - "phpstan/phpstan-strict-rules": "^1.2.3", + "phpstan/phpstan-phpunit": "^1.4.0", + "phpstan/phpstan-strict-rules": "^1.6.0", "phpunit/phpunit": "^9.5.20", "shipmonk/composer-dependency-analyser": "^1.3.0", "shipmonk/name-collision-detector": "^2.0.0", @@ -2832,9 +2980,9 @@ ], "support": { "issues": "https://github.com/shipmonk-rnd/phpstan-rules/issues", - "source": "https://github.com/shipmonk-rnd/phpstan-rules/tree/2.12.0" + "source": "https://github.com/shipmonk-rnd/phpstan-rules/tree/3.1.0" }, - "time": "2024-04-02T13:12:14+00:00" + "time": "2024-07-10T15:23:53+00:00" }, { "name": "slevomat/coding-standard", @@ -2903,16 +3051,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.1", + "version": "3.10.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "267a4405fff1d9c847134db3a3c92f1ab7f77909" + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/267a4405fff1d9c847134db3a3c92f1ab7f77909", - "reference": "267a4405fff1d9c847134db3a3c92f1ab7f77909", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", "shasum": "" }, "require": { @@ -2979,7 +3127,263 @@ "type": "open_collective" } ], - "time": "2024-03-31T21:03:09+00:00" + "time": "2024-05-22T21:24:41+00:00" + }, + { + "name": "symfony/contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "f8cd5313753cfac8329ebc4033e2013b874208e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/f8cd5313753cfac8329ebc4033e2013b874208e1", + "reference": "f8cd5313753cfac8329ebc4033e2013b874208e1", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0", + "psr/container": "^1.1|^2.0", + "psr/event-dispatcher": "^1.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "replace": { + "symfony/cache-contracts": "self.version", + "symfony/deprecation-contracts": "self.version", + "symfony/event-dispatcher-contracts": "self.version", + "symfony/http-client-contracts": "self.version", + "symfony/service-contracts": "self.version", + "symfony/translation-contracts": "self.version" + }, + "require-dev": { + "symfony/polyfill-intl-idn": "^1.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "files": [ + "Deprecation/function.php" + ], + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "dev", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "60c31bab5c45af7f13091b87deb708830f3c96c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/60c31bab5c45af7f13091b87deb708830f3c96c0", + "reference": "60c31bab5c45af7f13091b87deb708830f3c96c0", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" }, { "name": "theseer/tokenizer", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index cdd2d222..cc487aa0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -5,6 +5,7 @@ includes: - ./vendor/phpstan/phpstan-phpunit/extension.neon - ./vendor/phpstan/phpstan-phpunit/rules.neon - ./vendor/shipmonk/phpstan-rules/rules.neon + - ./rules.neon parameters: paths: @@ -35,10 +36,12 @@ parameters: superclassToSuffixMapping: PHPStan\Rules\Rule: Rule PHPStan\Collectors\Collector: Collector - ShipMonk\PHPStan\DeadCode\Rule: RuleTest - forbidAssignmentNotMatchingVarDoc: - enabled: false # native check is better now; this rule will be dropped / reworked in 3.0 - enforceClosureParamNativeTypehint: - enabled: false # we support even PHP 7.4, some typehints cannot be used + ShipMonk\PHPStan\DeadCode\Rule\RuleTestCase: RuleTest + ShipMonk\PHPStan\DeadCode\Provider\EntrypointProvider: EntrypointProvider enforceReadonlyPublicProperty: enabled: false # we support even PHP 7.4 + + deadCode: + entrypoints: + phpunit: + enabled: true diff --git a/rules.neon b/rules.neon index af3dd345..8fe542a6 100644 --- a/rules.neon +++ b/rules.neon @@ -1,4 +1,25 @@ services: + - + class: ShipMonk\PHPStan\DeadCode\Provider\VendorEntrypointProvider + tags: + - shipmonk.deadCode.entrypointProvider + arguments: + enabled: %deadCode.entrypoints.vendor.enabled% + + - + class: ShipMonk\PHPStan\DeadCode\Provider\PhpUnitEntrypointProvider + tags: + - shipmonk.deadCode.entrypointProvider + arguments: + enabled: %deadCode.entrypoints.phpunit.enabled% + + - + class: ShipMonk\PHPStan\DeadCode\Provider\SymfonyEntrypointProvider + tags: + - shipmonk.deadCode.entrypointProvider + arguments: + enabled: %deadCode.entrypoints.symfony.enabled% + - class: ShipMonk\PHPStan\DeadCode\Collector\MethodCallCollector tags: @@ -15,3 +36,29 @@ services: class: ShipMonk\PHPStan\DeadCode\Rule\DeadMethodRule tags: - phpstan.rules.rule + + +parameters: + deadCode: + entrypoints: + vendor: + enabled: true + phpunit: + enabled: false + symfony: + enabled: false + +parametersSchema: + deadCode: structure([ + entrypoints: structure([ + vendor: structure([ + enabled: bool() + ]) + phpunit: structure([ + enabled: bool() + ]) + symfony: structure([ + enabled: bool() + ]) + ]) + ]) diff --git a/src/Provider/PhpUnitEntrypointProvider.php b/src/Provider/PhpUnitEntrypointProvider.php new file mode 100644 index 00000000..d09e4d85 --- /dev/null +++ b/src/Provider/PhpUnitEntrypointProvider.php @@ -0,0 +1,144 @@ +> + */ + private array $dataProviders = []; + + private bool $enabled; + + private PhpDocParser $phpDocParser; + + private Lexer $lexer; + + public function __construct(bool $enabled, PhpDocParser $phpDocParser, Lexer $lexer) + { + $this->enabled = $enabled; + $this->lexer = $lexer; + $this->phpDocParser = $phpDocParser; + } + + public function isEntrypoint(ReflectionMethod $method): bool + { + if (!$this->enabled) { + return false; + } + + $this->gatherDataProviders($method); + + return $this->isTestCaseMethod($method) + || $this->isDataProviderMethod($method); + } + + private function isTestCaseMethod(ReflectionMethod $method): bool + { + if (!$method->getDeclaringClass()->isSubclassOf(TestCase::class)) { + return false; + } + + return strpos($method->getName(), 'test') === 0 + || $this->hasAttribute($method, 'PHPUnit\Framework\Attributes\Test') + || $this->hasAttribute($method, 'PHPUnit\Framework\Attributes\After') + || $this->hasAttribute($method, 'PHPUnit\Framework\Attributes\AfterClass') + || $this->hasAttribute($method, 'PHPUnit\Framework\Attributes\Before') + || $this->hasAttribute($method, 'PHPUnit\Framework\Attributes\BeforeClass') + || $this->hasAttribute($method, 'PHPUnit\Framework\Attributes\PostCondition') + || $this->hasAttribute($method, 'PHPUnit\Framework\Attributes\PreCondition'); + } + + private function isDataProviderMethod(ReflectionMethod $originalMethod): bool + { + if (!$originalMethod->getDeclaringClass()->isSubclassOf(TestCase::class)) { + return false; + } + + $declaringClass = $originalMethod->getDeclaringClass(); + $declaringClassName = $declaringClass->getName(); + + return $this->dataProviders[$declaringClassName][$originalMethod->getName()] ?? false; + } + + private function gatherDataProviders(ReflectionMethod $originalMethod): void + { + if (!$originalMethod->getDeclaringClass()->isSubclassOf(TestCase::class)) { + return; + } + + $declaringClass = $originalMethod->getDeclaringClass(); + $declaringClassName = $declaringClass->getName(); + + if (isset($this->dataProviders[$declaringClassName])) { + return; + } + + foreach ($declaringClass->getMethods() as $method) { + if ($method->getDeclaringClass()->getName() !== $declaringClassName) { + continue; // dont iterate parents + } + + foreach ($this->getDataProvidersFromAnnotations($method->getDocComment()) as $dataProvider) { + $this->dataProviders[$declaringClassName][$dataProvider] = true; + } + + foreach ($this->getDataProvidersFromAttributes($method) as $dataProvider) { + $this->dataProviders[$declaringClassName][$dataProvider] = true; + } + } + } + + /** + * @param false|string $rawPhpDoc + * @return iterable + */ + private function getDataProvidersFromAnnotations($rawPhpDoc): iterable + { + if ($rawPhpDoc === false) { + return; + } + + $tokens = new TokenIterator($this->lexer->tokenize($rawPhpDoc)); + $phpDoc = $this->phpDocParser->parse($tokens); + + foreach ($phpDoc->getTagsByName('@dataProvider') as $tag) { + yield (string) $tag->value; + } + } + + /** + * @return iterable + */ + private function getDataProvidersFromAttributes(ReflectionMethod $method): iterable + { + if (PHP_VERSION_ID < 8_00_00) { + return; + } + + foreach ($method->getAttributes('PHPUnit\Framework\Attributes\DataProvider') as $providerAttributeReflection) { + $methodName = $providerAttributeReflection->getArguments()[0] ?? null; + + if (is_string($methodName)) { + yield $methodName; + } + } + } + + private function hasAttribute(ReflectionMethod $method, string $attributeClass): bool + { + return PHP_VERSION_ID >= 8_00_00 && $method->getAttributes($attributeClass) !== []; + } + +} diff --git a/src/Provider/SymfonyEntrypointProvider.php b/src/Provider/SymfonyEntrypointProvider.php new file mode 100644 index 00000000..e2726faf --- /dev/null +++ b/src/Provider/SymfonyEntrypointProvider.php @@ -0,0 +1,75 @@ +reflectionProvider = $reflectionProvider; + $this->enabled = $enabled; + } + + public function isEntrypoint(ReflectionMethod $method): bool + { + if (!$this->enabled) { + return false; + } + + $methodName = $method->getName(); + $class = $method->getDeclaringClass(); + + return $class->implementsInterface('Symfony\Component\EventDispatcher\EventSubscriberInterface') + || $this->hasAttribute($class, 'Symfony\Component\EventDispatcher\Attribute\AsEventListener') + || $this->hasAttribute($method, 'Symfony\Component\EventDispatcher\Attribute\AsEventListener') + || $this->hasAttribute($method, 'Symfony\Contracts\Service\Attribute\Required') + || $this->hasAttribute($method, 'Symfony\Component\Routing\Attribute\Route', ReflectionAttribute::IS_INSTANCEOF) + || $this->hasAttribute($method, 'Symfony\Component\Routing\Annotation\Route', ReflectionAttribute::IS_INSTANCEOF) + || $this->isProbablySymfonyListener($methodName); + } + + /** + * Ideally, we would need to parse DIC xml to know this for sure just like phpstan-symfony does. + */ + private function isProbablySymfonyListener(string $methodName): bool + { + return $methodName === 'onKernelResponse' + || $methodName === 'onKernelException' + || $methodName === 'onKernelRequest' + || $methodName === 'onConsoleError' + || $methodName === 'onConsoleCommand' + || $methodName === 'onConsoleSignal' + || $methodName === 'onConsoleTerminate'; + } + + /** + * @param ReflectionClass|ReflectionMethod $classOrMethod + * @param ReflectionAttribute::IS_*|0 $flags + */ + private function hasAttribute(Reflector $classOrMethod, string $attributeClass, int $flags = 0): bool + { + if (PHP_VERSION_ID < 8_00_00) { + return false; + } + + if ($classOrMethod->getAttributes($attributeClass) !== []) { + return true; + } + + return $this->reflectionProvider->hasClass($attributeClass) // prevent https://github.com/phpstan/phpstan/issues/9618 + && $classOrMethod->getAttributes($attributeClass, $flags) !== []; + } + +} diff --git a/src/Provider/VendorEntrypointProvider.php b/src/Provider/VendorEntrypointProvider.php new file mode 100644 index 00000000..f93a2a19 --- /dev/null +++ b/src/Provider/VendorEntrypointProvider.php @@ -0,0 +1,69 @@ + + */ + private array $vendorDirs; + + private bool $enabled; + + public function __construct(bool $enabled) + { + $this->vendorDirs = array_keys(ClassLoader::getRegisteredLoaders()); + $this->enabled = $enabled; + } + + public function isEntrypoint(ReflectionMethod $method): bool + { + if (!$this->enabled) { + return false; + } + + try { + $methodPrototype = $method->getPrototype(); + } catch (ReflectionException $e) { + return false; // hasPrototype available since PHP 8.2 + } + + return $this->isForeignMethod($methodPrototype); + } + + private function isForeignMethod(ReflectionMethod $methodPrototype): bool + { + $filePath = $methodPrototype->getDeclaringClass()->getFileName(); + + if ($filePath === false) { + return true; // php core or extension + } + + $pharPrefix = 'phar://'; + + if (strpos($filePath, $pharPrefix) === 0) { + /** @var string $filePath Cannot resolve to false */ + $filePath = substr($filePath, strlen($pharPrefix)); + } + + foreach ($this->vendorDirs as $vendorDir) { + if (str_starts_with($filePath, $vendorDir)) { + return true; + } + } + + return false; + } + +} diff --git a/src/Rule/DeadMethodRule.php b/src/Rule/DeadMethodRule.php index 6282c3dc..1c597c77 100644 --- a/src/Rule/DeadMethodRule.php +++ b/src/Rule/DeadMethodRule.php @@ -6,8 +6,8 @@ use PHPStan\Analyser\Scope; use PHPStan\Node\CollectedDataNode; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleError; use PHPStan\Rules\RuleErrorBuilder; use ShipMonk\PHPStan\DeadCode\Collector\MethodCallCollector; use ShipMonk\PHPStan\DeadCode\Collector\MethodDefinitionCollector; @@ -39,7 +39,7 @@ class DeadMethodRule implements Rule private array $detectedTraitUsages = []; /** - * @var array + * @var array */ private array $errors = []; @@ -55,9 +55,9 @@ public function getNodeType(): string /** * @param CollectedDataNode $node - * @return list + * @return list */ - public function processNode( // @phpstan-ignore method.childReturnType (Do not yet raise prod dependency of phpstan to 1.11, but use it in CI) + public function processNode( Node $node, Scope $scope ): array diff --git a/tests/Rule/DeadMethodRuleTest.php b/tests/Rule/DeadMethodRuleTest.php index a6330007..8e91e17d 100644 --- a/tests/Rule/DeadMethodRuleTest.php +++ b/tests/Rule/DeadMethodRuleTest.php @@ -4,11 +4,17 @@ use PhpParser\Node; use PHPStan\Collectors\Collector; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\PhpDocParser; use PHPStan\Reflection\ReflectionProvider; +use PHPUnit\Framework\Attributes\DataProvider; use ReflectionMethod; use ShipMonk\PHPStan\DeadCode\Collector\MethodCallCollector; use ShipMonk\PHPStan\DeadCode\Collector\MethodDefinitionCollector; use ShipMonk\PHPStan\DeadCode\Provider\EntrypointProvider; +use ShipMonk\PHPStan\DeadCode\Provider\PhpUnitEntrypointProvider; +use ShipMonk\PHPStan\DeadCode\Provider\SymfonyEntrypointProvider; +use ShipMonk\PHPStan\DeadCode\Provider\VendorEntrypointProvider; use const PHP_VERSION_ID; /** @@ -37,6 +43,18 @@ public function isEntrypoint(ReflectionMethod $method): bool } }, + new VendorEntrypointProvider( + true, + ), + new PhpUnitEntrypointProvider( + true, + self::getContainer()->getByType(PhpDocParser::class), + self::getContainer()->getByType(Lexer::class), + ), + new SymfonyEntrypointProvider( + self::getContainer()->getByType(ReflectionProvider::class), + true, + ), ]; return [ new MethodDefinitionCollector($entrypointProviders), @@ -59,25 +77,26 @@ public function testDead(string $file, ?int $lowestPhpVersion = null): void /** * @return array */ - public static function provideFiles(): array + public static function provideFiles(): iterable { - return [ - 'enum' => [__DIR__ . '/data/DeadMethodRule/basic.php', 80_000], - 'code' => [__DIR__ . '/data/DeadMethodRule/basic.php'], - 'entrypoint' => [__DIR__ . '/data/DeadMethodRule/entrypoint.php'], - 'first-class-callable' => [__DIR__ . '/data/DeadMethodRule/first-class-callable.php'], - 'overwriting-1' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-1.php'], - 'overwriting-2' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-2.php'], - 'overwriting-3' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-3.php'], - 'overwriting-4' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-4.php'], - 'overwriting-5' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-5.php'], - 'trait-1' => [__DIR__ . '/data/DeadMethodRule/traits-1.php'], - 'trait-2' => [__DIR__ . '/data/DeadMethodRule/traits-2.php'], - 'trait-3' => [__DIR__ . '/data/DeadMethodRule/traits-3.php'], - 'dead-in-parent-1' => [__DIR__ . '/data/DeadMethodRule/dead-in-parent-1.php'], - 'indirect-interface' => [__DIR__ . '/data/DeadMethodRule/indirect-interface.php'], - 'array-map-1' => [__DIR__ . '/data/DeadMethodRule/array-map-1.php'], - ]; + yield 'enum' => [__DIR__ . '/data/DeadMethodRule/basic.php', 80_000]; + yield 'code' => [__DIR__ . '/data/DeadMethodRule/basic.php']; + yield 'entrypoint' => [__DIR__ . '/data/DeadMethodRule/entrypoint.php']; + yield 'first-class-callable' => [__DIR__ . '/data/DeadMethodRule/first-class-callable.php']; + yield 'overwriting-1' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-1.php']; + yield 'overwriting-2' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-2.php']; + yield 'overwriting-3' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-3.php']; + yield 'overwriting-4' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-4.php']; + yield 'overwriting-5' => [__DIR__ . '/data/DeadMethodRule/overwriting-methods-5.php']; + yield 'trait-1' => [__DIR__ . '/data/DeadMethodRule/traits-1.php']; + yield 'trait-2' => [__DIR__ . '/data/DeadMethodRule/traits-2.php']; + yield 'trait-3' => [__DIR__ . '/data/DeadMethodRule/traits-3.php']; + yield 'dead-in-parent-1' => [__DIR__ . '/data/DeadMethodRule/dead-in-parent-1.php']; + yield 'indirect-interface' => [__DIR__ . '/data/DeadMethodRule/indirect-interface.php']; + yield 'array-map-1' => [__DIR__ . '/data/DeadMethodRule/array-map-1.php']; + yield 'provider-default' => [__DIR__ . '/data/DeadMethodRule/providers/default.php']; + yield 'provider-symfony' => [__DIR__ . '/data/DeadMethodRule/providers/symfony.php', 80_000]; + yield 'provider-phpunit' => [__DIR__ . '/data/DeadMethodRule/providers/phpunit.php', 80_000]; } } diff --git a/tests/Rule/RuleTestCase.php b/tests/Rule/RuleTestCase.php index 1338bbab..33dd1c25 100644 --- a/tests/Rule/RuleTestCase.php +++ b/tests/Rule/RuleTestCase.php @@ -43,7 +43,7 @@ protected function processActualErrors(array $actualErrors): array $resultToAssert[] = $this->formatErrorForAssert($error->getMessage(), $error->getLine()); self::assertNotNull($error->getIdentifier(), "Missing error identifier for error: {$error->getMessage()}"); - self::assertStringStartsWith('shipmonk.', $error->getIdentifier()); + self::assertStringStartsWith('shipmonk.', $error->getIdentifier(), $error->getMessage()); } return $resultToAssert; diff --git a/tests/Rule/data/DeadMethodRule/providers/default.php b/tests/Rule/data/DeadMethodRule/providers/default.php new file mode 100644 index 00000000..71cce4a7 --- /dev/null +++ b/tests/Rule/data/DeadMethodRule/providers/default.php @@ -0,0 +1,26 @@ + [['onKernelRequest', 0]], + ]; + } + +}