-
Notifications
You must be signed in to change notification settings - Fork 462
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Check
@param-immediately-invoked-callable
and `@param-later-invoked…
…-callable`
- Loading branch information
1 parent
95c0a58
commit 580a6ad
Showing
5 changed files
with
237 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
94 changes: 94 additions & 0 deletions
94
src/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRule.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,94 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\PhpDoc; | ||
|
||
use PhpParser\Node; | ||
use PhpParser\Node\Expr\Variable; | ||
use PhpParser\Node\FunctionLike; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\ShouldNotHappenException; | ||
use PHPStan\Type\FileTypeMapper; | ||
use PHPStan\Type\VerbosityLevel; | ||
use function is_string; | ||
use function sprintf; | ||
use function trim; | ||
|
||
/** | ||
* @implements Rule<FunctionLike> | ||
*/ | ||
final class IncompatibleParamImmediatelyInvokedCallableRule implements Rule | ||
{ | ||
|
||
public function __construct( | ||
private FileTypeMapper $fileTypeMapper, | ||
) | ||
{ | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return FunctionLike::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if ($node instanceof Node\Stmt\ClassMethod) { | ||
$functionName = $node->name->name; | ||
} elseif ($node instanceof Node\Stmt\Function_) { | ||
$functionName = trim($scope->getNamespace() . '\\' . $node->name->name, '\\'); | ||
} else { | ||
return []; | ||
} | ||
|
||
$docComment = $node->getDocComment(); | ||
if ($docComment === null) { | ||
return []; | ||
} | ||
|
||
$resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( | ||
$scope->getFile(), | ||
$scope->isInClass() ? $scope->getClassReflection()->getName() : null, | ||
$scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, | ||
$functionName, | ||
$docComment->getText(), | ||
); | ||
$nativeParameterTypes = []; | ||
foreach ($node->getParams() as $parameter) { | ||
if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) { | ||
throw new ShouldNotHappenException(); | ||
} | ||
$nativeParameterTypes[$parameter->var->name] = $scope->getFunctionType( | ||
$parameter->type, | ||
$scope->isParameterValueNullable($parameter), | ||
false, | ||
); | ||
} | ||
|
||
$errors = []; | ||
foreach ($resolvedPhpDoc->getParamsImmediatelyInvokedCallable() as $parameterName => $immediately) { | ||
$tagName = $immediately ? '@param-immediately-invoked-callable' : '@param-later-invoked-callable'; | ||
if (!isset($nativeParameterTypes[$parameterName])) { | ||
$errors[] = RuleErrorBuilder::message(sprintf( | ||
'PHPDoc tag %s references unknown parameter: $%s', | ||
$tagName, | ||
$parameterName, | ||
))->identifier('parameter.notFound')->build(); | ||
} elseif ($nativeParameterTypes[$parameterName]->isCallable()->no()) { | ||
$errors[] = RuleErrorBuilder::message(sprintf( | ||
'PHPDoc tag %s is for parameter $%s with non-callable type %s.', | ||
$tagName, | ||
$parameterName, | ||
$nativeParameterTypes[$parameterName]->describe(VerbosityLevel::typeOnly()), | ||
))->identifier(sprintf( | ||
'%s.nonCallable', | ||
$immediately ? 'paramImmediatelyInvokedCallable' : 'paramLaterInvokedCallable', | ||
))->build(); | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
} |
60 changes: 60 additions & 0 deletions
60
tests/PHPStan/Rules/PhpDoc/IncompatibleParamImmediatelyInvokedCallableRuleTest.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,60 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\PhpDoc; | ||
|
||
use PHPStan\Rules\Rule as TRule; | ||
use PHPStan\Testing\RuleTestCase; | ||
use PHPStan\Type\FileTypeMapper; | ||
|
||
/** | ||
* @extends RuleTestCase<IncompatibleParamImmediatelyInvokedCallableRule> | ||
*/ | ||
class IncompatibleParamImmediatelyInvokedCallableRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): TRule | ||
{ | ||
return new IncompatibleParamImmediatelyInvokedCallableRule( | ||
self::getContainer()->getByType(FileTypeMapper::class), | ||
); | ||
} | ||
|
||
public function testRule(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/incompatible-param-immediately-invoked-callable.php'], [ | ||
[ | ||
'PHPDoc tag @param-immediately-invoked-callable references unknown parameter: $b', | ||
21, | ||
], | ||
[ | ||
'PHPDoc tag @param-later-invoked-callable references unknown parameter: $c', | ||
21, | ||
], | ||
[ | ||
'PHPDoc tag @param-immediately-invoked-callable is for parameter $b with non-callable type int.', | ||
30, | ||
], | ||
[ | ||
'PHPDoc tag @param-later-invoked-callable is for parameter $b with non-callable type int.', | ||
39, | ||
], | ||
[ | ||
'PHPDoc tag @param-immediately-invoked-callable references unknown parameter: $b', | ||
59, | ||
], | ||
[ | ||
'PHPDoc tag @param-later-invoked-callable references unknown parameter: $c', | ||
59, | ||
], | ||
[ | ||
'PHPDoc tag @param-immediately-invoked-callable is for parameter $b with non-callable type int.', | ||
68, | ||
], | ||
[ | ||
'PHPDoc tag @param-later-invoked-callable is for parameter $b with non-callable type int.', | ||
77, | ||
], | ||
]); | ||
} | ||
|
||
} |
80 changes: 80 additions & 0 deletions
80
tests/PHPStan/Rules/PhpDoc/data/incompatible-param-immediately-invoked-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,80 @@ | ||
<?php | ||
|
||
namespace IncompatibleParamImmediatelyInvokedCallable; | ||
|
||
class Foo | ||
{ | ||
|
||
/** | ||
* @param-immediately-invoked-callable $a | ||
* @param-later-invoked-callable $b | ||
*/ | ||
public function doFoo(callable $a, callable $b): void | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* @param-immediately-invoked-callable $b | ||
* @param-later-invoked-callable $c | ||
*/ | ||
public function doBar(callable $a): void | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* @param-immediately-invoked-callable $a | ||
* @param-immediately-invoked-callable $b | ||
*/ | ||
public function doBaz(string $a, int $b): void | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* @param-later-invoked-callable $a | ||
* @param-later-invoked-callable $b | ||
*/ | ||
public function doBaz2(string $a, int $b): void | ||
{ | ||
|
||
} | ||
|
||
} | ||
|
||
/** | ||
* @param-immediately-invoked-callable $a | ||
* @param-later-invoked-callable $b | ||
*/ | ||
function doFoo(callable $a, callable $b): void | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* @param-immediately-invoked-callable $b | ||
* @param-later-invoked-callable $c | ||
*/ | ||
function doBar(callable $a): void | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* @param-immediately-invoked-callable $a | ||
* @param-immediately-invoked-callable $b | ||
*/ | ||
function doBaz(string $a, int $b): void | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* @param-later-invoked-callable $a | ||
* @param-later-invoked-callable $b | ||
*/ | ||
function doBaz2(string $a, int $b): void | ||
{ | ||
|
||
} |