Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Too wide private method return type - level 4 (bleeding edge)
- Loading branch information
1 parent
78e04aa
commit 178953d
Showing
6 changed files
with
221 additions
and
1 deletion.
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
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,57 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Node; | ||
|
||
use PhpParser\Node\Stmt\ClassMethod; | ||
use PhpParser\NodeAbstract; | ||
use PHPStan\Analyser\StatementResult; | ||
|
||
class MethodReturnStatementsNode extends NodeAbstract implements VirtualNode | ||
{ | ||
|
||
/** @var \PHPStan\Node\ReturnStatement[] */ | ||
private $returnStatements; | ||
|
||
/** @var StatementResult */ | ||
private $statementResult; | ||
|
||
/** | ||
* @param \PhpParser\Node\Stmt\ClassMethod $method | ||
* @param \PHPStan\Node\ReturnStatement[] $returnStatements | ||
* @param \PHPStan\Analyser\StatementResult $statementResult | ||
*/ | ||
public function __construct( | ||
ClassMethod $method, | ||
array $returnStatements, | ||
StatementResult $statementResult | ||
) | ||
{ | ||
parent::__construct($method->getAttributes()); | ||
$this->returnStatements = $returnStatements; | ||
$this->statementResult = $statementResult; | ||
} | ||
|
||
/** | ||
* @return \PHPStan\Node\ReturnStatement[] | ||
*/ | ||
public function getReturnStatements(): array | ||
{ | ||
return $this->returnStatements; | ||
} | ||
|
||
public function getStatementResult(): StatementResult | ||
{ | ||
return $this->statementResult; | ||
} | ||
|
||
public function getType(): string | ||
{ | ||
return 'PHPStan_Node_FunctionReturnStatementsNode'; | ||
} | ||
|
||
public function getSubNodeNames(): array | ||
{ | ||
return []; | ||
} | ||
|
||
} |
80 changes: 80 additions & 0 deletions
80
src/Rules/TooWideTypehints/TooWidePrivateMethodReturnTypehintRule.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 declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\TooWideTypehints; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\MethodReturnStatementsNode; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\Reflection\ParametersAcceptorSelector; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Type\TypeCombinator; | ||
use PHPStan\Type\UnionType; | ||
use PHPStan\Type\VerbosityLevel; | ||
|
||
class TooWidePrivateMethodReturnTypehintRule implements Rule | ||
{ | ||
|
||
public function getNodeType(): string | ||
{ | ||
return MethodReturnStatementsNode::class; | ||
} | ||
|
||
/** | ||
* @param \PHPStan\Node\MethodReturnStatementsNode $node | ||
* @param \PHPStan\Analyser\Scope $scope | ||
* @return string[] | ||
*/ | ||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
$method = $scope->getFunction(); | ||
if (!$method instanceof MethodReflection) { | ||
throw new \PHPStan\ShouldNotHappenException(); | ||
} | ||
if (!$method->isPrivate()) { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
ondrejmirtes
Author
Member
|
||
return []; | ||
} | ||
|
||
$methodReturnType = ParametersAcceptorSelector::selectSingle($method->getVariants())->getReturnType(); | ||
if (!$methodReturnType instanceof UnionType) { | ||
return []; | ||
} | ||
$statementResult = $node->getStatementResult(); | ||
if ($statementResult->hasYield()) { | ||
return []; | ||
} | ||
|
||
$returnStatements = $node->getReturnStatements(); | ||
if (count($returnStatements) === 0) { | ||
return []; | ||
} | ||
|
||
$returnTypes = []; | ||
foreach ($returnStatements as $returnStatement) { | ||
$returnNode = $returnStatement->getReturnNode(); | ||
if ($returnNode->expr === null) { | ||
continue; | ||
} | ||
|
||
$returnTypes[] = $returnStatement->getScope()->getType($returnNode->expr); | ||
} | ||
|
||
if (count($returnTypes) === 0) { | ||
return []; | ||
} | ||
|
||
$returnType = TypeCombinator::union(...$returnTypes); | ||
|
||
$messages = []; | ||
foreach ($methodReturnType->getTypes() as $type) { | ||
if (!$type->isSuperTypeOf($returnType)->no()) { | ||
continue; | ||
} | ||
|
||
$messages[] = sprintf('Private method %s::%s() never returns %s so it can be removed from the return typehint.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), $type->describe(VerbosityLevel::typeOnly())); | ||
} | ||
|
||
return $messages; | ||
} | ||
|
||
} |
30 changes: 30 additions & 0 deletions
30
tests/PHPStan/Rules/TooWideTypehints/TooWidePrivateMethodReturnTypehintRuleTest.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,30 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\TooWideTypehints; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
class TooWidePrivateMethodReturnTypehintRuleTest extends RuleTestCase | ||
{ | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new TooWidePrivateMethodReturnTypehintRule(); | ||
} | ||
|
||
public function testRule(): void | ||
{ | ||
$this->analyse([__DIR__ . '/data/tooWidePrivateMethodReturnType.php'], [ | ||
[ | ||
'Private method TooWidePrivateMethodReturnType\Foo::bar() never returns string so it can be removed from the return typehint.', | ||
14, | ||
], | ||
[ | ||
'Private method TooWidePrivateMethodReturnType\Foo::baz() never returns null so it can be removed from the return typehint.', | ||
18, | ||
], | ||
]); | ||
} | ||
|
||
} |
34 changes: 34 additions & 0 deletions
34
tests/PHPStan/Rules/TooWideTypehints/data/tooWidePrivateMethodReturnType.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,34 @@ | ||
<?php | ||
|
||
namespace TooWidePrivateMethodReturnType; | ||
|
||
class Foo | ||
{ | ||
|
||
private function foo(): \Generator { | ||
yield 1; | ||
yield 2; | ||
return 3; | ||
} | ||
|
||
private function bar(): ?string { | ||
return null; | ||
} | ||
|
||
private function baz(): ?string { | ||
return 'foo'; | ||
} | ||
|
||
private function lorem(): ?string { | ||
if (rand(0, 1)) { | ||
return '1'; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public function ipsum(): ?string { | ||
return null; | ||
} | ||
|
||
} |
@ondrejmirtes
protected/public final
method can be checked too, right? Of course, you should check the parent class for potential conflict.