Last active
January 24, 2025 09:37
-
-
Save janedbal/08ec3d5f45dad8627d2cf6499b082512 to your computer and use it in GitHub Desktop.
Detect doctrine query type widening in direct returns
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
<?php declare(strict_types = 1); | |
namespace ShipMonk\Rules\PHPStan\Rule; | |
use Doctrine\ORM\Query; | |
use PhpParser\Node; | |
use PhpParser\Node\Expr\MethodCall; | |
use PhpParser\Node\Stmt\Return_; | |
use PHPStan\Analyser\Scope; | |
use PHPStan\Node\MethodReturnStatementsNode; | |
use PHPStan\Reflection\ParametersAcceptorSelector; | |
use PHPStan\Rules\IdentifierRuleError; | |
use PHPStan\Rules\Rule; | |
use PHPStan\Rules\RuleErrorBuilder; | |
use PHPStan\Type\MixedType; | |
use PHPStan\Type\VerbosityLevel; | |
/** | |
* @implements Rule<MethodReturnStatementsNode> | |
*/ | |
class DoctrineTypeWideningRule implements Rule | |
{ | |
public function getNodeType(): string | |
{ | |
return MethodReturnStatementsNode::class; | |
} | |
/** | |
* @param MethodReturnStatementsNode $node | |
* | |
* @return list<IdentifierRuleError> | |
*/ | |
public function processNode( | |
Node $node, | |
Scope $scope, | |
): array | |
{ | |
$methodReflection = $node->getMethodReflection(); | |
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); | |
foreach ($node->getReturnStatements() as $returnStatement) { | |
$scope = $returnStatement->getScope(); | |
$returnNode = $returnStatement->getReturnNode(); | |
if ($returnNode->expr === null) { | |
continue; | |
} | |
$realReturnedType = $scope->getType($returnNode->expr); | |
if ($realReturnedType instanceof MixedType) { | |
continue; | |
} | |
if (!$this->isQueryReturn($returnNode, $scope)) { | |
continue; | |
} | |
if ($returnType->isSuperTypeOf($realReturnedType)->yes()) { | |
$error = "Method {$methodReflection->getName()} returns {$realReturnedType->describe(VerbosityLevel::precise())}, but is annotated as {$returnType->describe(VerbosityLevel::precise())}"; | |
return [ | |
RuleErrorBuilder::message($error) | |
->line($returnNode->getLine()) | |
->identifier('shipmonk.doctrineTypeWidening') | |
->build(), | |
]; | |
} | |
} | |
return []; | |
} | |
private function isQueryReturn(Return_ $returnNode, Scope $scope): bool | |
{ | |
return $returnNode->expr instanceof MethodCall | |
&& $scope->getType($returnNode->expr->var)->getObjectClassNames() === [Query::class]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment