-
Notifications
You must be signed in to change notification settings - Fork 481
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Check static methods in first-class callables
- Loading branch information
1 parent
480c516
commit b42ceb6
Showing
5 changed files
with
251 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Methods; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Internal\SprintfHelper; | ||
use PHPStan\Node\StaticMethodCallableNode; | ||
use PHPStan\Php\PhpVersion; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
|
||
/** | ||
* @implements Rule<StaticMethodCallableNode> | ||
*/ | ||
class StaticMethodCallableRule implements Rule | ||
{ | ||
|
||
private StaticMethodCallCheck $methodCallCheck; | ||
|
||
private PhpVersion $phpVersion; | ||
|
||
public function __construct(StaticMethodCallCheck $methodCallCheck, PhpVersion $phpVersion) | ||
{ | ||
$this->methodCallCheck = $methodCallCheck; | ||
$this->phpVersion = $phpVersion; | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return StaticMethodCallableNode::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if (!$this->phpVersion->supportsFirstClassCallables()) { | ||
return [ | ||
RuleErrorBuilder::message('First-class callables are supported only on PHP 8.1 and later.') | ||
->nonIgnorable() | ||
->build(), | ||
]; | ||
} | ||
|
||
$methodName = $node->getName(); | ||
if (!$methodName instanceof Node\Identifier) { | ||
return []; | ||
} | ||
|
||
$methodNameName = $methodName->toString(); | ||
|
||
[$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass()); | ||
if ($methodReflection === null) { | ||
return $errors; | ||
} | ||
|
||
$declaringClass = $methodReflection->getDeclaringClass(); | ||
if ($declaringClass->hasNativeMethod($methodNameName)) { | ||
return $errors; | ||
} | ||
|
||
$messagesMethodName = SprintfHelper::escapeFormatString($declaringClass->getDisplayName() . '::' . $methodReflection->getName() . '()'); | ||
|
||
$errors[] = RuleErrorBuilder::message(sprintf('Creating callable from a non-native static method %s.', $messagesMethodName))->build(); | ||
|
||
return $errors; | ||
} | ||
|
||
} |
100 changes: 100 additions & 0 deletions
100
tests/PHPStan/Rules/Methods/StaticMethodCallableRuleTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Methods; | ||
|
||
use PHPStan\Php\PhpVersion; | ||
use PHPStan\Rules\ClassCaseSensitivityCheck; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleLevelHelper; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<StaticMethodCallableRule> | ||
*/ | ||
class StaticMethodCallableRuleTest extends RuleTestCase | ||
{ | ||
|
||
/** @var int */ | ||
private $phpVersion = PHP_VERSION_ID; | ||
|
||
protected function getRule(): Rule | ||
{ | ||
$reflectionProvider = $this->createReflectionProvider(); | ||
$ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, false); | ||
|
||
return new StaticMethodCallableRule( | ||
new StaticMethodCallCheck($reflectionProvider, $ruleLevelHelper, new ClassCaseSensitivityCheck($reflectionProvider, true), true, true), | ||
new PhpVersion($this->phpVersion) | ||
); | ||
} | ||
|
||
public function testNotSupportedOnOlderVersions(): void | ||
{ | ||
if (PHP_VERSION_ID >= 80100) { | ||
self::markTestSkipped('Test runs on PHP < 8.1.'); | ||
} | ||
if (!self::$useStaticReflectionProvider) { | ||
self::markTestSkipped('Test requires static reflection.'); | ||
} | ||
|
||
$this->analyse([__DIR__ . '/data/static-method-callable-not-supported.php'], [ | ||
[ | ||
'First-class callables are supported only on PHP 8.1 and later.', | ||
10, | ||
], | ||
]); | ||
} | ||
|
||
public function testRule(): void | ||
{ | ||
if (PHP_VERSION_ID < 80100) { | ||
self::markTestSkipped('Test requires PHP 8.1.'); | ||
} | ||
|
||
$this->analyse([__DIR__ . '/data/static-method-callable.php'], [ | ||
[ | ||
'Call to static method StaticMethodCallable\Foo::doFoo() with incorrect case: dofoo', | ||
11, | ||
], | ||
[ | ||
'Call to static method doFoo() on an unknown class StaticMethodCallable\Nonexistent.', | ||
12, | ||
'Learn more at https://phpstan.org/user-guide/discovering-symbols', | ||
], | ||
[ | ||
'Call to an undefined static method StaticMethodCallable\Foo::nonexistent().', | ||
13, | ||
], | ||
[ | ||
'Static call to instance method StaticMethodCallable\Foo::doBar().', | ||
14, | ||
], | ||
[ | ||
'Call to private static method doBar() of class StaticMethodCallable\Bar.', | ||
15, | ||
], | ||
[ | ||
'Cannot call abstract static method StaticMethodCallable\Bar::doBaz().', | ||
16, | ||
], | ||
[ | ||
'Call to static method doFoo() on an unknown class StaticMethodCallable\Nonexistent.', | ||
21, | ||
'Learn more at https://phpstan.org/user-guide/discovering-symbols', | ||
], | ||
[ | ||
'Cannot call static method doFoo() on int.', | ||
22, | ||
], | ||
[ | ||
'Creating callable from a non-native static method StaticMethodCallable\Lorem::doBar().', | ||
47, | ||
], | ||
[ | ||
'Creating callable from a non-native static method StaticMethodCallable\Ipsum::doBar().', | ||
66, | ||
], | ||
]); | ||
} | ||
|
||
} |
13 changes: 13 additions & 0 deletions
13
tests/PHPStan/Rules/Methods/data/static-method-callable-not-supported.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php // lint >= 8.1 | ||
|
||
namespace StaticMethodCallableNotSupported; | ||
|
||
class Foo | ||
{ | ||
|
||
public static function doFoo(): void | ||
{ | ||
self::doFoo(...); | ||
} | ||
|
||
} |
69 changes: 69 additions & 0 deletions
69
tests/PHPStan/Rules/Methods/data/static-method-callable.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?php // lint >= 8.1 | ||
|
||
namespace StaticMethodCallable; | ||
|
||
class Foo | ||
{ | ||
|
||
public static function doFoo() | ||
{ | ||
self::doFoo(...); | ||
self::dofoo(...); | ||
Nonexistent::doFoo(...); | ||
self::nonexistent(...); | ||
self::doBar(...); | ||
Bar::doBar(...); | ||
Bar::doBaz(...); | ||
} | ||
|
||
public function doBar(Nonexistent $n, int $i) | ||
{ | ||
$n::doFoo(...); | ||
$i::doFoo(...); | ||
} | ||
|
||
} | ||
|
||
abstract class Bar | ||
{ | ||
|
||
private static function doBar() | ||
{ | ||
|
||
} | ||
|
||
abstract public static function doBaz(); | ||
|
||
} | ||
|
||
/** | ||
* @method static void doBar() | ||
*/ | ||
class Lorem | ||
{ | ||
|
||
public function doFoo() | ||
{ | ||
self::doBar(...); | ||
} | ||
|
||
public function __call($name, $arguments) | ||
{ | ||
|
||
} | ||
|
||
|
||
} | ||
|
||
/** | ||
* @method static void doBar() | ||
*/ | ||
class Ipsum | ||
{ | ||
|
||
public function doFoo() | ||
{ | ||
self::doBar(...); | ||
} | ||
|
||
} |