Skip to content

Commit 1a94628

Browse files
authored
Virtual usages always require note (#171)
1 parent 4d61d38 commit 1a94628

13 files changed

+179
-83
lines changed

README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,18 @@ services:
127127
```php
128128

129129
use ReflectionMethod;
130+
use ShipMonk\PHPStan\DeadCode\Provider\VirtualUsageData;
130131
use ShipMonk\PHPStan\DeadCode\Provider\ReflectionBasedMemberUsageProvider;
131132

132133
class FuzzyTwigUsageProvider extends ReflectionBasedMemberUsageProvider
133134
{
134135

135-
public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
136+
public function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData
136137
{
137-
return $method->getDeclaringClass()->implementsInterface(UsedInTwigMarkerInterface::class);
138+
if ($method->getDeclaringClass()->implementsInterface(UsedInTwigMarkerInterface::class)) {
139+
return VirtualUsageData::withNote('Probably used in twig');
140+
}
141+
return null;
138142
}
139143

140144
}
@@ -341,13 +345,18 @@ parameters:
341345
- The easiest way to ignore it is via custom `MemberUsageProvider`:
342346

343347
```php
348+
use ShipMonk\PHPStan\DeadCode\Provider\VirtualUsageData;
344349
use ShipMonk\PHPStan\DeadCode\Provider\ReflectionBasedMemberUsageProvider;
345350

346351
class IgnoreDeadInterfaceUsageProvider extends ReflectionBasedMemberUsageProvider
347352
{
348-
public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
353+
public function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData
349354
{
350-
return $method->getDeclaringClass()->isInterface();
355+
if ($method->getDeclaringClass()->isInterface()) {
356+
return VirtualUsageData::withNote('Interface method, kept for unification even when possibly unused');
357+
}
358+
359+
return null;
351360
}
352361
}
353362
```

src/Graph/UsageOrigin.php

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Reflection\MethodReflection;
99
use ShipMonk\PHPStan\DeadCode\Provider\MemberUsageProvider;
10+
use ShipMonk\PHPStan\DeadCode\Provider\VirtualUsageData;
1011
use function get_class;
1112

1213
/**
@@ -49,18 +50,16 @@ public function __construct(
4950

5051
/**
5152
* Creates virtual usage origin with no reference to any place in code
52-
*
53-
* @param ?string $note More detailed identification why provider emitted this virtual usage
5453
*/
55-
public static function createVirtual(MemberUsageProvider $provider, ?string $note = null): self
54+
public static function createVirtual(MemberUsageProvider $provider, VirtualUsageData $data): self
5655
{
5756
return new self(
5857
null,
5958
null,
6059
null,
6160
null,
6261
get_class($provider),
63-
$note,
62+
$data->getNote(),
6463
);
6564
}
6665

src/Provider/DoctrineUsageProvider.php

+24-9
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ private function getUsagesFromReflection(InClassNode $node): array
6565
continue;
6666
}
6767

68-
if ($this->shouldMarkMethodAsUsed($method)) {
69-
$usages[] = $this->createMethodUsage($classReflection->getNativeMethod($method->getName()));
68+
$usageNote = $this->shouldMarkMethodAsUsed($method);
69+
70+
if ($usageNote !== null) {
71+
$usages[] = $this->createMethodUsage($classReflection->getNativeMethod($method->getName()), $usageNote);
7072
}
7173
}
7274

@@ -121,15 +123,28 @@ private function getUsagesOfEventSubscriber(Return_ $node, Scope $scope): array
121123
return $usages;
122124
}
123125

124-
protected function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
126+
protected function shouldMarkMethodAsUsed(ReflectionMethod $method): ?string
125127
{
126128
$methodName = $method->getName();
127129
$class = $method->getDeclaringClass();
128130

129-
return $this->isLifecycleEventMethod($method)
130-
|| $this->isEntityRepositoryConstructor($class, $method)
131-
|| $this->isPartOfAsEntityListener($class, $methodName)
132-
|| $this->isProbablyDoctrineListener($methodName);
131+
if ($this->isLifecycleEventMethod($method)) {
132+
return 'Lifecycle event method via attribute';
133+
}
134+
135+
if ($this->isEntityRepositoryConstructor($class, $method)) {
136+
return 'Entity repository constructor (created by EntityRepositoryFactory)';
137+
}
138+
139+
if ($this->isPartOfAsEntityListener($class, $methodName)) {
140+
return 'Is part of AsEntityListener methods';
141+
}
142+
143+
if ($this->isProbablyDoctrineListener($methodName)) {
144+
return 'Is probable listener method';
145+
}
146+
147+
return null;
133148
}
134149

135150
protected function isLifecycleEventMethod(ReflectionMethod $method): bool
@@ -198,10 +213,10 @@ private function isDoctrineInstalled(): bool
198213
|| InstalledVersions::isInstalled('doctrine/doctrine-bundle');
199214
}
200215

201-
private function createMethodUsage(ExtendedMethodReflection $methodReflection): ClassMethodUsage
216+
private function createMethodUsage(ExtendedMethodReflection $methodReflection, string $note): ClassMethodUsage
202217
{
203218
return new ClassMethodUsage(
204-
UsageOrigin::createVirtual($this),
219+
UsageOrigin::createVirtual($this, VirtualUsageData::withNote($note)),
205220
new ClassMethodRef(
206221
$methodReflection->getDeclaringClass()->getName(),
207222
$methodReflection->getName(),

src/Provider/NetteUsageProvider.php

+16-14
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ public function __construct(
3939
$this->enabled = $enabled ?? $this->isNetteInstalled();
4040
}
4141

42-
public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
42+
public function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData
4343
{
4444
if (!$this->enabled) {
45-
return false;
45+
return null;
4646
}
4747

4848
$methodName = $method->getName();
@@ -53,37 +53,39 @@ public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
5353
return $this->isNetteMagic($reflection, $methodName);
5454
}
5555

56-
private function isNetteMagic(ClassReflection $reflection, string $methodName): bool
56+
private function isNetteMagic(ClassReflection $reflection, string $methodName): ?VirtualUsageData
5757
{
5858
if (
5959
$reflection->is(SignalReceiver::class)
6060
&& strpos($methodName, 'handle') === 0
6161
) {
62-
return true;
62+
return VirtualUsageData::withNote('Signal handler method');
6363
}
6464

6565
if (
6666
$reflection->is(Container::class)
6767
&& strpos($methodName, 'createComponent') === 0
6868
) {
69-
return true;
69+
return VirtualUsageData::withNote('Component factory method');
7070
}
7171

7272
if (
7373
$reflection->is(Control::class)
7474
&& strpos($methodName, 'render') === 0
7575
) {
76-
return true;
76+
return VirtualUsageData::withNote('Render method');
7777
}
7878

7979
if (
80-
$reflection->is(Presenter::class)
81-
&& (
82-
strpos($methodName, 'action') === 0
83-
|| strpos($methodName, 'inject') === 0
84-
)
80+
$reflection->is(Presenter::class) && strpos($methodName, 'action') === 0
8581
) {
86-
return true;
82+
return VirtualUsageData::withNote('Presenter action method');
83+
}
84+
85+
if (
86+
$reflection->is(Presenter::class) && strpos($methodName, 'inject') === 0
87+
) {
88+
return VirtualUsageData::withNote('Presenter inject method');
8789
}
8890

8991
if (
@@ -106,12 +108,12 @@ private function isNetteMagic(ClassReflection $reflection, string $methodName):
106108
$property = $this->getMagicProperties($reflection)[$name] ?? null;
107109

108110
if ($property !== null) {
109-
return true;
111+
return VirtualUsageData::withNote('Access method for magic property ' . $name);
110112
}
111113
}
112114
}
113115

114-
return false;
116+
return null;
115117
}
116118

117119
/**

src/Provider/PhpStanUsageProvider.php

+9-5
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,26 @@ public function __construct(bool $enabled, Container $container)
1818
$this->container = $container;
1919
}
2020

21-
public function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
21+
public function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData
2222
{
2323
if (!$this->enabled) {
24-
return false;
24+
return null;
2525
}
2626

2727
return $this->isConstructorCallInPhpStanDic($method);
2828
}
2929

30-
private function isConstructorCallInPhpStanDic(ReflectionMethod $method): bool
30+
private function isConstructorCallInPhpStanDic(ReflectionMethod $method): ?VirtualUsageData
3131
{
3232
if (!$method->isConstructor()) {
33-
return false;
33+
return null;
3434
}
3535

36-
return $this->container->findServiceNamesByType($method->getDeclaringClass()->getName()) !== [];
36+
if ($this->container->findServiceNamesByType($method->getDeclaringClass()->getName()) !== []) {
37+
return VirtualUsageData::withNote('Constructor call from PHPStan DI container');
38+
}
39+
40+
return null;
3741
}
3842

3943
}

src/Provider/PhpUnitUsageProvider.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ public function getUsages(Node $node, Scope $scope): array
5757

5858
foreach ($dataProviders as $dataProvider) {
5959
if ($classReflection->hasNativeMethod($dataProvider)) {
60-
$usages[] = $this->createUsage($classReflection->getNativeMethod($dataProvider), 'data provider method');
60+
$usages[] = $this->createUsage($classReflection->getNativeMethod($dataProvider), 'Data provider method');
6161
}
6262
}
6363

6464
if ($this->isTestCaseMethod($method)) {
65-
$usages[] = $this->createUsage($classReflection->getNativeMethod($method->getName()), 'test method');
65+
$usages[] = $this->createUsage($classReflection->getNativeMethod($method->getName()), 'Test method');
6666
}
6767
}
6868

@@ -145,7 +145,7 @@ private function hasAnnotation(ReflectionMethod $method, string $string): bool
145145
private function createUsage(ExtendedMethodReflection $getNativeMethod, string $reason): ClassMethodUsage
146146
{
147147
return new ClassMethodUsage(
148-
UsageOrigin::createVirtual($this, $reason),
148+
UsageOrigin::createVirtual($this, VirtualUsageData::withNote($reason)),
149149
new ClassMethodRef(
150150
$getNativeMethod->getDeclaringClass()->getName(),
151151
$getNativeMethod->getName(),

src/Provider/ReflectionBasedMemberUsageProvider.php

+16-12
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ public function getUsages(Node $node, Scope $scope): array
3636
return [];
3737
}
3838

39-
protected function shouldMarkMethodAsUsed(ReflectionMethod $method): bool
39+
protected function shouldMarkMethodAsUsed(ReflectionMethod $method): ?VirtualUsageData
4040
{
41-
return false; // Expected to be overridden by subclasses.
41+
return null; // Expected to be overridden by subclasses.
4242
}
4343

44-
protected function shouldMarkConstantAsUsed(ReflectionClassConstant $constant): bool
44+
protected function shouldMarkConstantAsUsed(ReflectionClassConstant $constant): ?VirtualUsageData
4545
{
46-
return false; // Expected to be overridden by subclasses.
46+
return null; // Expected to be overridden by subclasses.
4747
}
4848

4949
/**
@@ -60,8 +60,10 @@ private function getMethodUsages(ClassReflection $classReflection): array
6060
continue; // skip methods from ancestors
6161
}
6262

63-
if ($this->shouldMarkMethodAsUsed($nativeMethodReflection)) {
64-
$usages[] = $this->createMethodUsage($nativeMethodReflection);
63+
$usage = $this->shouldMarkMethodAsUsed($nativeMethodReflection);
64+
65+
if ($usage !== null) {
66+
$usages[] = $this->createMethodUsage($nativeMethodReflection, $usage);
6567
}
6668
}
6769

@@ -82,18 +84,20 @@ private function getConstantUsages(ClassReflection $classReflection): array
8284
continue; // skip constants from ancestors
8385
}
8486

85-
if ($this->shouldMarkConstantAsUsed($nativeConstantReflection)) {
86-
$usages[] = $this->createConstantUsage($nativeConstantReflection);
87+
$usage = $this->shouldMarkConstantAsUsed($nativeConstantReflection);
88+
89+
if ($usage !== null) {
90+
$usages[] = $this->createConstantUsage($nativeConstantReflection, $usage);
8791
}
8892
}
8993

9094
return $usages;
9195
}
9296

93-
private function createConstantUsage(ReflectionClassConstant $constantReflection): ClassConstantUsage
97+
private function createConstantUsage(ReflectionClassConstant $constantReflection, VirtualUsageData $data): ClassConstantUsage
9498
{
9599
return new ClassConstantUsage(
96-
UsageOrigin::createVirtual($this),
100+
UsageOrigin::createVirtual($this, $data),
97101
new ClassConstantRef(
98102
$constantReflection->getDeclaringClass()->getName(),
99103
$constantReflection->getName(),
@@ -102,10 +106,10 @@ private function createConstantUsage(ReflectionClassConstant $constantReflection
102106
);
103107
}
104108

105-
private function createMethodUsage(ReflectionMethod $methodReflection): ClassMethodUsage
109+
private function createMethodUsage(ReflectionMethod $methodReflection, VirtualUsageData $data): ClassMethodUsage
106110
{
107111
return new ClassMethodUsage(
108-
UsageOrigin::createVirtual($this),
112+
UsageOrigin::createVirtual($this, $data),
109113
new ClassMethodRef(
110114
$methodReflection->getDeclaringClass()->getName(),
111115
$methodReflection->getName(),

0 commit comments

Comments
 (0)