Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .ecrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"Exclude": [
"^tests/Rule/data/debug/expected_output.txt"
]
}
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ use PHPStan\Analyser\Scope;
use ReflectionMethod;
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodRef;
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodUsage;
use ShipMonk\PHPStan\DeadCode\Graph\UsageOriginDetector;
use ShipMonk\PHPStan\DeadCode\Graph\UsageOrigin;
use ShipMonk\PHPStan\DeadCode\Provider\MemberUsageProvider;
use Symfony\Component\Serializer\SerializerInterface;

Expand Down Expand Up @@ -185,13 +185,13 @@ class DeserializationUsageProvider implements MemberUsageProvider
$secondArgument = $node->getArgs()[1]->value;
$serializedClass = $scope->getType($secondArgument)->getConstantStrings()[0];

// record the method it was called from (needed for proper transitive dead code elimination)
$originRef = $this->originDetector->detectOrigin($scope);
// record the place it was called from (needed for proper transitive dead code elimination)
$usageOrigin = UsageOrigin::createRegular($node, $scope);

// record the hidden constructor call
$constructorRef = new ClassMethodRef($serializedClass->getValue(), '__construct', false);

return [new ClassMethodUsage($originRef, $constructorRef)];
return [new ClassMethodUsage($usageOrigin, $constructorRef)];
}

return [];
Expand Down Expand Up @@ -350,6 +350,34 @@ class IgnoreDeadInterfaceUsageProvider extends ReflectionBasedMemberUsageProvide
}
```

## Debugging:
- If you want to see how dead code detector evaluated usages of certain method, you do the following:

```neon
parameters:
shipmonkDeadCode:
debug:
usagesOf:
- App\User\Entity\Address::__construct
```

Then, run PHPStan with `-vvv` CLI option and you will see the output like this:

```txt
App\User\Entity\Address::__construct
|
| Marked as alive by:
| entry virtual usage from ShipMonk\PHPStan\DeadCode\Provider\SymfonyUsageProvider
| calls App\User\RegisterUserController::__invoke:36
| calls App\User\UserFacade::registerUser:142
| calls App\User\Entity\Address::__construct
|
| Found 2 usages:
| • src/User/UserFacade.php:142
| • tests/User/Entity/AddressTest.php:64 - excluded by tests excluder
```

If you set up `editorUrl` [parameter](https://phpstan.org/user-guide/output-format#opening-file-in-an-editor), you can click on the usages to open it in your IDE.

## Future scope:
- Dead class property detection
Expand Down
1 change: 0 additions & 1 deletion phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@
<exclude name="Squiz.Operators.IncrementDecrementUsage.NoBrackets"/><!-- there is no need for brackets -->
</rule>
<rule ref="Squiz.Operators.ValidLogicalOperators"/>
<rule ref="Squiz.PHP.CommentedOutCode"/>
<rule ref="Squiz.PHP.GlobalKeyword"/>
<rule ref="Squiz.PHP.InnerFunctions"/>
<rule ref="Squiz.PHP.LowercasePHPFunctions"/>
Expand Down
11 changes: 9 additions & 2 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ services:
class: ShipMonk\PHPStan\DeadCode\Transformer\FileSystem

-
class: ShipMonk\PHPStan\DeadCode\Graph\UsageOriginDetector
class: ShipMonk\PHPStan\DeadCode\Debug\DebugUsagePrinter
arguments:
trackMixedAccess: %shipmonkDeadCode.trackMixedAccess%
editorUrl: %editorUrl%

-
class: ShipMonk\PHPStan\DeadCode\Provider\VendorUsageProvider
Expand Down Expand Up @@ -105,7 +108,6 @@ services:
- phpstan.diagnoseExtension
arguments:
reportTransitivelyDeadMethodAsSeparateError: %shipmonkDeadCode.reportTransitivelyDeadMethodAsSeparateError%
trackMixedAccess: %shipmonkDeadCode.trackMixedAccess%

-
class: ShipMonk\PHPStan\DeadCode\Compatibility\BackwardCompatibilityChecker
Expand Down Expand Up @@ -137,6 +139,8 @@ parameters:
tests:
enabled: false
devPaths: null
debug:
usagesOf: []

parametersSchema:
shipmonkDeadCode: structure([
Expand Down Expand Up @@ -172,4 +176,7 @@ parametersSchema:
devPaths: schema(listOf(string()), nullable())
])
])
debug: structure([
usagesOf: listOf(string())
])
])
10 changes: 3 additions & 7 deletions src/Collector/ConstantFetchCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use ShipMonk\PHPStan\DeadCode\Graph\ClassConstantRef;
use ShipMonk\PHPStan\DeadCode\Graph\ClassConstantUsage;
use ShipMonk\PHPStan\DeadCode\Graph\CollectedUsage;
use ShipMonk\PHPStan\DeadCode\Graph\UsageOriginDetector;
use ShipMonk\PHPStan\DeadCode\Graph\UsageOrigin;
use function array_map;
use function count;
use function current;
Expand All @@ -33,8 +33,6 @@ class ConstantFetchCollector implements Collector

use BufferedUsageCollector;

private UsageOriginDetector $usageOriginDetector;

private ReflectionProvider $reflectionProvider;

private bool $trackMixedAccess;
Expand All @@ -48,15 +46,13 @@ class ConstantFetchCollector implements Collector
* @param list<MemberUsageExcluder> $memberUsageExcluders
*/
public function __construct(
UsageOriginDetector $usageOriginDetector,
ReflectionProvider $reflectionProvider,
bool $trackMixedAccess,
array $memberUsageExcluders
)
{
$this->reflectionProvider = $reflectionProvider;
$this->trackMixedAccess = $trackMixedAccess;
$this->usageOriginDetector = $usageOriginDetector;
$this->memberUsageExcluders = $memberUsageExcluders;
}

Expand Down Expand Up @@ -125,7 +121,7 @@ private function registerFunctionCall(FuncCall $node, Scope $scope): void

$this->registerUsage(
new ClassConstantUsage(
$this->usageOriginDetector->detectOrigin($scope),
UsageOrigin::createRegular($node, $scope),
new ClassConstantRef($className, $constantName, true),
),
$node,
Expand Down Expand Up @@ -157,7 +153,7 @@ private function registerFetch(ClassConstFetch $node, Scope $scope): void
foreach ($this->getDeclaringTypesWithConstant($ownerType, $constantName) as $className) {
$this->registerUsage(
new ClassConstantUsage(
$this->usageOriginDetector->detectOrigin($scope),
UsageOrigin::createRegular($node, $scope),
new ClassConstantRef($className, $constantName, $possibleDescendantFetch),
),
$node,
Expand Down
16 changes: 6 additions & 10 deletions src/Collector/MethodCallCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodRef;
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodUsage;
use ShipMonk\PHPStan\DeadCode\Graph\CollectedUsage;
use ShipMonk\PHPStan\DeadCode\Graph\UsageOriginDetector;
use ShipMonk\PHPStan\DeadCode\Graph\UsageOrigin;

/**
* @implements Collector<Node, list<string>>
Expand All @@ -35,8 +35,6 @@ class MethodCallCollector implements Collector

use BufferedUsageCollector;

private UsageOriginDetector $usageOriginDetector;

private bool $trackMixedAccess;

/**
Expand All @@ -48,12 +46,10 @@ class MethodCallCollector implements Collector
* @param list<MemberUsageExcluder> $memberUsageExcluders
*/
public function __construct(
UsageOriginDetector $usageOriginDetector,
bool $trackMixedAccess,
array $memberUsageExcluders
)
{
$this->usageOriginDetector = $usageOriginDetector;
$this->trackMixedAccess = $trackMixedAccess;
$this->memberUsageExcluders = $memberUsageExcluders;
}
Expand Down Expand Up @@ -133,7 +129,7 @@ private function registerMethodCall(
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo(), $possibleDescendantCall) as $methodRef) {
$this->registerUsage(
new ClassMethodUsage(
$this->usageOriginDetector->detectOrigin($scope),
UsageOrigin::createRegular($methodCall, $scope),
$methodRef,
),
$methodCall,
Expand Down Expand Up @@ -163,7 +159,7 @@ private function registerStaticCall(
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createYes(), $possibleDescendantCall) as $methodRef) {
$this->registerUsage(
new ClassMethodUsage(
$this->usageOriginDetector->detectOrigin($scope),
UsageOrigin::createRegular($staticCall, $scope),
$methodRef,
),
$staticCall,
Expand All @@ -189,7 +185,7 @@ private function registerArrayCallable(
foreach ($this->getDeclaringTypesWithMethod($methodName, $caller, TrinaryLogic::createMaybe()) as $methodRef) {
$this->registerUsage(
new ClassMethodUsage(
$this->usageOriginDetector->detectOrigin($scope),
UsageOrigin::createRegular($array, $scope),
$methodRef,
),
$array,
Expand All @@ -205,7 +201,7 @@ private function registerAttribute(Attribute $node, Scope $scope): void
{
$this->registerUsage(
new ClassMethodUsage(
null,
UsageOrigin::createRegular($node, $scope),
new ClassMethodRef($scope->resolveName($node->name), '__construct', false),
),
$node,
Expand All @@ -221,7 +217,7 @@ private function registerClone(Clone_ $node, Scope $scope): void
foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo()) as $methodRef) {
$this->registerUsage(
new ClassMethodUsage(
$this->usageOriginDetector->detectOrigin($scope),
UsageOrigin::createRegular($node, $scope),
$methodRef,
),
$node,
Expand Down
22 changes: 10 additions & 12 deletions src/Collector/ProvidedUsagesCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,13 @@ private function validateUsage(
$memberRef = $usage->getMemberRef();
$memberRefClass = $memberRef->getClassName();

$originRef = $usage->getOrigin();
$originRefClass = $originRef === null ? null : $originRef->getClassName();
$origin = $usage->getOrigin();
$originClass = $origin->getClassName();
$originMethod = $origin->getMethodName();

$context = sprintf(
"It was emitted as %s by %s for node '%s' in '%s' on line %s",
$usage->toHumanString(),
"It emitted usage of %s by %s for node '%s' in '%s' on line %s",
$usage->getMemberRef()->toHumanString(),
get_class($provider),
get_class($node),
$scope->getFile(),
Expand All @@ -102,16 +103,13 @@ private function validateUsage(
throw new LogicException("Class '$memberRefClass' does not exist. $context");
}

if (
$originRef !== null
&& $originRefClass !== null
) {
if (!$this->reflectionProvider->hasClass($originRefClass)) {
throw new LogicException("Class '{$originRefClass}' does not exist. $context");
if ($originClass !== null) {
if (!$this->reflectionProvider->hasClass($originClass)) {
throw new LogicException("Class '{$originClass}' does not exist. $context");
}

if (!$this->reflectionProvider->getClass($originRefClass)->hasMethod($originRef->getMemberName())) {
throw new LogicException("Method '{$originRef->getMemberName()}' does not exist in class '$originRefClass'. $context");
if ($originMethod !== null && !$this->reflectionProvider->getClass($originClass)->hasMethod($originMethod)) {
throw new LogicException("Method '{$originMethod}' does not exist in class '$originClass'. $context");
}
}
}
Expand Down
Loading