Skip to content

Commit 030bd55

Browse files
axlonondrejmirtes
authored andcommitted
Add always used method extension
1 parent e0d63b0 commit 030bd55

File tree

8 files changed

+131
-1
lines changed

8 files changed

+131
-1
lines changed

conf/config.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,9 @@ services:
984984
-
985985
class: PHPStan\Rules\Constants\LazyAlwaysUsedClassConstantsExtensionProvider
986986

987+
-
988+
class: PHPStan\Rules\Methods\LazyAlwaysUsedMethodExtensionProvider
989+
987990
-
988991
class: PHPStan\Rules\PhpDoc\ConditionalReturnTypeRuleHelper
989992

src/Rules/DeadCode/UnusedPrivateMethodRule.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Node\ClassMethodsNode;
99
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Rules\Methods\AlwaysUsedMethodExtensionProvider;
1011
use PHPStan\Rules\Rule;
1112
use PHPStan\Rules\RuleErrorBuilder;
1213
use PHPStan\Type\Constant\ConstantStringType;
@@ -22,6 +23,10 @@
2223
class UnusedPrivateMethodRule implements Rule
2324
{
2425

26+
public function __construct(private AlwaysUsedMethodExtensionProvider $extensionProvider)
27+
{
28+
}
29+
2530
public function getNodeType(): string
2631
{
2732
return ClassMethodsNode::class;
@@ -54,6 +59,14 @@ public function processNode(Node $node, Scope $scope): array
5459
if (strtolower($methodName) === '__clone') {
5560
continue;
5661
}
62+
63+
$methodReflection = $classType->getMethod($methodName, $scope);
64+
foreach ($this->extensionProvider->getExtensions() as $extension) {
65+
if ($extension->isAlwaysUsed($methodReflection)) {
66+
continue 2;
67+
}
68+
}
69+
5770
$methods[strtolower($methodName)] = $method;
5871
}
5972

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PHPStan\Reflection\MethodReflection;
6+
7+
/**
8+
* This is the extension interface to implement if you want to describe an always-used class method.
9+
*
10+
* To register it in the configuration file use the `phpstan.methods.alwaysUsedMethodExtension` service tag:
11+
*
12+
* ```
13+
* services:
14+
* -
15+
* class: App\PHPStan\MyExtension
16+
* tags:
17+
* - phpstan.methods.alwaysUsedMethodExtension
18+
* ```
19+
*
20+
* @api
21+
*/
22+
interface AlwaysUsedMethodExtension
23+
{
24+
25+
public function isAlwaysUsed(MethodReflection $methodReflection): bool;
26+
27+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
interface AlwaysUsedMethodExtensionProvider
6+
{
7+
8+
public const EXTENSION_TAG = 'phpstan.methods.alwaysUsedMethodExtension';
9+
10+
/**
11+
* @return AlwaysUsedMethodExtension[]
12+
*/
13+
public function getExtensions(): array;
14+
15+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
class DirectAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider
6+
{
7+
8+
/**
9+
* @param AlwaysUsedMethodExtension[] $extensions
10+
*/
11+
public function __construct(private array $extensions)
12+
{
13+
}
14+
15+
public function getExtensions(): array
16+
{
17+
return $this->extensions;
18+
}
19+
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Methods;
4+
5+
use PHPStan\DependencyInjection\Container;
6+
7+
class LazyAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider
8+
{
9+
10+
/** @var AlwaysUsedMethodExtension[]|null */
11+
private ?array $extensions = null;
12+
13+
public function __construct(private Container $container)
14+
{
15+
}
16+
17+
public function getExtensions(): array
18+
{
19+
return $this->extensions ??= $this->container->getServicesByTag(static::EXTENSION_TAG);
20+
}
21+
22+
}

tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace PHPStan\Rules\DeadCode;
44

5+
use PHPStan\Reflection\MethodReflection;
6+
use PHPStan\Rules\Methods\AlwaysUsedMethodExtension;
7+
use PHPStan\Rules\Methods\DirectAlwaysUsedMethodExtensionProvider;
58
use PHPStan\Rules\Rule;
69
use PHPStan\Testing\RuleTestCase;
710
use const PHP_VERSION_ID;
@@ -14,7 +17,19 @@ class UnusedPrivateMethodRuleTest extends RuleTestCase
1417

1518
protected function getRule(): Rule
1619
{
17-
return new UnusedPrivateMethodRule();
20+
return new UnusedPrivateMethodRule(
21+
new DirectAlwaysUsedMethodExtensionProvider([
22+
new class() implements AlwaysUsedMethodExtension {
23+
24+
public function isAlwaysUsed(MethodReflection $methodReflection): bool
25+
{
26+
return $methodReflection->getDeclaringClass()->is('UnusedPrivateMethod\IgnoredByExtension')
27+
&& $methodReflection->getName() === 'foo';
28+
}
29+
30+
},
31+
]),
32+
);
1833
}
1934

2035
public function testRule(): void
@@ -40,6 +55,10 @@ public function testRule(): void
4055
'Method UnusedPrivateMethod\Lorem::doBaz() is unused.',
4156
99,
4257
],
58+
[
59+
'Method UnusedPrivateMethod\IgnoredByExtension::bar() is unused.',
60+
181,
61+
],
4362
]);
4463
}
4564

tests/PHPStan/Rules/DeadCode/data/unused-private-method.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,14 @@ public function doTest(): void
171171
}
172172

173173
}
174+
175+
class IgnoredByExtension
176+
{
177+
private function foo(): void
178+
{
179+
}
180+
181+
private function bar(): void
182+
{
183+
}
184+
}

0 commit comments

Comments
 (0)