Skip to content

Commit 595f52d

Browse files
authored
Detect dead methods (#1)
1 parent 85e1854 commit 595f52d

37 files changed

+5082
-0
lines changed

.editorconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[*]
2+
charset = utf-8
3+
end_of_line = lf
4+
trim_trailing_whitespace = true
5+
insert_final_newline = true
6+
indent_style = space
7+
indent_size = 4
8+

.gitattributes

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
* text=auto
2+
3+
/tests export-ignore
4+
/.editorconfig export-ignore
5+
/.gitattributes export-ignore
6+
/.github export-ignore
7+
/.gitignore export-ignore
8+
/phpcs.xml.dist export-ignore
9+
/phpstan.neon.dist export-ignore
10+
/phpunit.xml.dist export-ignore

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
-
4+
package-ecosystem: composer
5+
directory: "/"
6+
schedule:
7+
interval: monthly
8+
versioning-strategy: lockfile-only

.github/workflows/checks.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Checks
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- "master"
7+
- "v[0-9]"
8+
jobs:
9+
checks:
10+
runs-on: ubuntu-latest
11+
strategy:
12+
fail-fast: false
13+
steps:
14+
-
15+
name: Checkout code
16+
uses: actions/checkout@v2
17+
-
18+
name: Setup PHP
19+
uses: shivammathur/setup-php@v2
20+
with:
21+
php-version: 8.2
22+
-
23+
name: Install dependencies
24+
run: composer install --no-progress --prefer-dist --no-interaction
25+
26+
-
27+
name: Run checks
28+
run: composer check
29+
30+
tests:
31+
runs-on: ubuntu-latest
32+
strategy:
33+
fail-fast: false
34+
matrix:
35+
php-version: [ '7.4', '8.0', '8.1', '8.2', '8.3' ]
36+
dependency-version: [ prefer-lowest, prefer-stable ]
37+
steps:
38+
-
39+
name: Checkout code
40+
uses: actions/checkout@v2
41+
-
42+
name: Setup PHP
43+
uses: shivammathur/setup-php@v2
44+
with:
45+
php-version: ${{ matrix.php-version }}
46+
-
47+
name: Update dependencies
48+
run: composer update --no-progress --${{ matrix.dependency-version }} --prefer-dist --no-interaction
49+
-
50+
name: Run tests
51+
run: composer check:tests

.github/workflows/lint.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: phpcs.xml lint
2+
on:
3+
pull_request:
4+
push:
5+
branches:
6+
- "master"
7+
- "v[0-9]"
8+
9+
jobs:
10+
xml-linter:
11+
runs-on: ubuntu-latest
12+
steps:
13+
-
14+
name: Checkout code
15+
uses: actions/checkout@v3
16+
17+
-
18+
name: Install dependencies
19+
run: composer install --no-progress --no-interaction
20+
21+
-
22+
name: Lint
23+
uses: ChristophWurst/xmllint-action@v1
24+
with:
25+
xml-file: phpcs.xml.dist
26+
xml-schema-file: vendor/squizlabs/php_codesniffer/phpcs.xsd

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/vendor
2+
/cache/*
3+
!/cache/.gitkeep
4+
/.idea
5+
/phpstan.neon

README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Dead code detector
2+
3+
PHPStan rules to find dead code in your project.
4+
5+
## Installation:
6+
7+
```sh
8+
composer require --dev shipmonk/dead-code-detector
9+
```
10+
11+
Use [official extension-installer](https://phpstan.org/user-guide/extension-library#installing-extensions) or enable just load the rules:
12+
13+
```neon
14+
includes:
15+
- vendor/shipmonk/dead-code-detector/rules.neon
16+
```
17+
18+
19+
## Configuration:
20+
- You need to mark all entrypoints of your code to get proper results.
21+
- This is typically long whitelist of all code that is called by your framework and libraries.
22+
23+
```neon
24+
services:
25+
-
26+
class: App\SymfonyEntrypointProvider
27+
tags:
28+
- shipmonk.deadCode.entrypointProvider
29+
```
30+
```php
31+
32+
use ReflectionMethod;
33+
use PHPStan\Reflection\ReflectionProvider;
34+
use ShipMonk\PHPStan\DeadCode\Provider\EntrypointProvider;
35+
36+
class SymfonyEntrypointProvider implements EntrypointProvider
37+
{
38+
39+
public function __construct(
40+
private ReflectionProvider $reflectionProvider
41+
) {}
42+
43+
public function isEntrypoint(ReflectionMethod $method): bool
44+
{
45+
$methodName = $method->getName();
46+
$reflection = $this->reflectionProvider->getClass($method->getDeclaringClass()->getName());
47+
48+
return $reflection->is(\Symfony\Bundle\FrameworkBundle\Controller\AbstractController::class)
49+
|| $reflection->is(\Symfony\Component\EventDispatcher\EventSubscriberInterface::class)
50+
|| $reflection->is(\Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface::class)
51+
|| ($reflection->is(\Symfony\Component\Console\Command\Command::class) && in_array($methodName, ['execute', 'initialize', ...], true)
52+
// and many more
53+
}
54+
}
55+
```
56+
57+
## Limitations
58+
This project is currently a working prototype (we are using it since 2022) with limited functionality:
59+
60+
- Only method calls are detected
61+
- Including static methods, trait methods, interface methods, first class callables, etc.
62+
- Callbacks like `[$this, 'method']` are mostly not detected
63+
- Any calls on mixed types are not detected, e.g. `$unknownClass->method()`
64+
- Expression method calls are not detected, e.g. `$this->$methodName()`
65+
- Anonymous classes are ignored
66+
- Does not check constructor calls
67+
- Does not check magic methods
68+
- No transitive check is performed (dead method called only from dead method)
69+
- No dead cycles are detected (e.g. dead method calling itself)
70+
71+
## Contributing
72+
- Check your code by `composer check`
73+
- Autofix coding-style by `composer fix:cs`
74+
- All functionality must be tested

cache/.gitkeep

Whitespace-only changes.

composer.json

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"name": "shipmonk/dead-code-detector",
3+
"description": "PHPStan rules and collectors to find dead and unused code",
4+
"license": [
5+
"MIT"
6+
],
7+
"type": "phpstan-extension",
8+
"keywords": [
9+
"phpstan",
10+
"static analysis",
11+
"unused code",
12+
"dead code"
13+
],
14+
"require": {
15+
"php": "^7.4 || ^8.0",
16+
"phpstan/phpstan": "^1.10.30"
17+
},
18+
"require-dev": {
19+
"editorconfig-checker/editorconfig-checker": "^10.3.0",
20+
"ergebnis/composer-normalize": "^2.28",
21+
"phpstan/phpstan-phpunit": "^1.1.1",
22+
"phpstan/phpstan-strict-rules": "^1.2.3",
23+
"phpunit/phpunit": "^9.5.20",
24+
"shipmonk/name-collision-detector": "^2.0.0",
25+
"shipmonk/phpstan-rules": "^2.11",
26+
"slevomat/coding-standard": "^8.0.1"
27+
},
28+
"autoload": {
29+
"psr-4": {
30+
"ShipMonk\\PHPStan\\DeadCode\\": "src/"
31+
}
32+
},
33+
"autoload-dev": {
34+
"psr-4": {
35+
"ShipMonk\\PHPStan\\DeadCode\\": "tests/"
36+
},
37+
"classmap": [
38+
"tests/Rule/data"
39+
]
40+
},
41+
"config": {
42+
"allow-plugins": {
43+
"dealerdirect/phpcodesniffer-composer-installer": false,
44+
"ergebnis/composer-normalize": true
45+
},
46+
"sort-packages": true
47+
},
48+
"extra": {
49+
"phpstan": {
50+
"includes": [
51+
"rules.neon"
52+
]
53+
}
54+
},
55+
"scripts": {
56+
"check": [
57+
"@check:composer",
58+
"@check:ec",
59+
"@check:cs",
60+
"@check:types",
61+
"@check:tests",
62+
"@check:collisions"
63+
],
64+
"check:collisions": "detect-collisions src tests",
65+
"check:composer": "composer normalize --dry-run --no-check-lock --no-update-lock",
66+
"check:cs": "phpcs",
67+
"check:ec": "ec src tests",
68+
"check:tests": "phpunit -vvv tests",
69+
"check:types": "phpstan analyse -vvv --ansi",
70+
"fix:cs": "phpcbf"
71+
}
72+
}

0 commit comments

Comments
 (0)