Skip to content

Commit

Permalink
Add always used method extension
Browse files Browse the repository at this point in the history
  • Loading branch information
axlon authored and ondrejmirtes committed Mar 20, 2024
1 parent e0d63b0 commit 030bd55
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 1 deletion.
3 changes: 3 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,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
{

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
{
}
}

0 comments on commit 030bd55

Please sign in to comment.