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
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,9 @@ services:
-
class: PHPStan\Rules\Constants\LazyAlwaysUsedClassConstantsExtensionProvider

-
class: PHPStan\Rules\Methods\LazyAlwaysUsedMethodExtensionProvider

-
class: PHPStan\Rules\PhpDoc\ConditionalReturnTypeRuleHelper

Expand Down
13 changes: 13 additions & 0 deletions src/Rules/DeadCode/UnusedPrivateMethodRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Analyser\Scope;
use PHPStan\Node\ClassMethodsNode;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Rules\Methods\AlwaysUsedMethodExtensionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Constant\ConstantStringType;
Expand All @@ -22,6 +23,10 @@
class UnusedPrivateMethodRule implements Rule
{

public function __construct(private AlwaysUsedMethodExtensionProvider $extensionProvider)
{
}

public function getNodeType(): string
{
return ClassMethodsNode::class;
Expand Down Expand Up @@ -54,6 +59,14 @@ public function processNode(Node $node, Scope $scope): array
if (strtolower($methodName) === '__clone') {
continue;
}

$methodReflection = $classType->getMethod($methodName, $scope);
foreach ($this->extensionProvider->getExtensions() as $extension) {
if ($extension->isAlwaysUsed($methodReflection)) {
continue 2;
}
}

$methods[strtolower($methodName)] = $method;
}

Expand Down
27 changes: 27 additions & 0 deletions src/Rules/Methods/AlwaysUsedMethodExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PHPStan\Reflection\MethodReflection;

/**
* This is the extension interface to implement if you want to describe an always-used class method.
*
* To register it in the configuration file use the `phpstan.methods.alwaysUsedMethodExtension` service tag:
*
* ```
* services:
* -
* class: App\PHPStan\MyExtension
* tags:
* - phpstan.methods.alwaysUsedMethodExtension
* ```
*
* @api
*/
interface AlwaysUsedMethodExtension
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{

public function isAlwaysUsed(MethodReflection $methodReflection): bool;

}
15 changes: 15 additions & 0 deletions src/Rules/Methods/AlwaysUsedMethodExtensionProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

interface AlwaysUsedMethodExtensionProvider
{

public const EXTENSION_TAG = 'phpstan.methods.alwaysUsedMethodExtension';

/**
* @return AlwaysUsedMethodExtension[]
*/
public function getExtensions(): array;

}
20 changes: 20 additions & 0 deletions src/Rules/Methods/DirectAlwaysUsedMethodExtensionProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

class DirectAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider
{

/**
* @param AlwaysUsedMethodExtension[] $extensions
*/
public function __construct(private array $extensions)
{
}

public function getExtensions(): array
{
return $this->extensions;
}

}
22 changes: 22 additions & 0 deletions src/Rules/Methods/LazyAlwaysUsedMethodExtensionProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Methods;

use PHPStan\DependencyInjection\Container;

class LazyAlwaysUsedMethodExtensionProvider implements AlwaysUsedMethodExtensionProvider
{

/** @var AlwaysUsedMethodExtension[]|null */
private ?array $extensions = null;

public function __construct(private Container $container)
{
}

public function getExtensions(): array
{
return $this->extensions ??= $this->container->getServicesByTag(static::EXTENSION_TAG);
}

}
21 changes: 20 additions & 1 deletion tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace PHPStan\Rules\DeadCode;

use PHPStan\Reflection\MethodReflection;
use PHPStan\Rules\Methods\AlwaysUsedMethodExtension;
use PHPStan\Rules\Methods\DirectAlwaysUsedMethodExtensionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use const PHP_VERSION_ID;
Expand All @@ -14,7 +17,19 @@ class UnusedPrivateMethodRuleTest extends RuleTestCase

protected function getRule(): Rule
{
return new UnusedPrivateMethodRule();
return new UnusedPrivateMethodRule(
new DirectAlwaysUsedMethodExtensionProvider([
new class() implements AlwaysUsedMethodExtension {

public function isAlwaysUsed(MethodReflection $methodReflection): bool
{
return $methodReflection->getDeclaringClass()->is('UnusedPrivateMethod\IgnoredByExtension')
&& $methodReflection->getName() === 'foo';
}

},
]),
);
}

public function testRule(): void
Expand All @@ -40,6 +55,10 @@ public function testRule(): void
'Method UnusedPrivateMethod\Lorem::doBaz() is unused.',
99,
],
[
'Method UnusedPrivateMethod\IgnoredByExtension::bar() is unused.',
181,
],
]);
}

Expand Down
11 changes: 11 additions & 0 deletions tests/PHPStan/Rules/DeadCode/data/unused-private-method.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,14 @@ public function doTest(): void
}

}

class IgnoredByExtension
{
private function foo(): void
{
}

private function bar(): void
{
}
}