-
Notifications
You must be signed in to change notification settings - Fork 471
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bleeding edge - check that function with
@throws void
does not have…
… an explicit throw point
- Loading branch information
1 parent
3624e66
commit 8b3382a
Showing
9 changed files
with
361 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
81 changes: 81 additions & 0 deletions
81
src/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRule.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,81 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Exceptions; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\FunctionReturnStatementsNode; | ||
use PHPStan\Reflection\FunctionReflection; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\Type\TypeUtils; | ||
use PHPStan\Type\TypeWithClassName; | ||
use PHPStan\Type\VerbosityLevel; | ||
use PHPStan\Type\VoidType; | ||
|
||
/** | ||
* @implements Rule<FunctionReturnStatementsNode> | ||
*/ | ||
class ThrowsVoidFunctionWithExplicitThrowPointRule implements Rule | ||
{ | ||
|
||
private ExceptionTypeResolver $exceptionTypeResolver; | ||
|
||
private bool $missingCheckedExceptionInThrows; | ||
|
||
public function __construct( | ||
ExceptionTypeResolver $exceptionTypeResolver, | ||
bool $missingCheckedExceptionInThrows | ||
) | ||
{ | ||
$this->exceptionTypeResolver = $exceptionTypeResolver; | ||
$this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return FunctionReturnStatementsNode::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if ($this->missingCheckedExceptionInThrows) { | ||
return []; | ||
} | ||
|
||
$statementResult = $node->getStatementResult(); | ||
$functionReflection = $scope->getFunction(); | ||
if (!$functionReflection instanceof FunctionReflection) { | ||
throw new \PHPStan\ShouldNotHappenException(); | ||
} | ||
|
||
if (!$functionReflection->getThrowType() instanceof VoidType) { | ||
return []; | ||
} | ||
|
||
$errors = []; | ||
foreach ($statementResult->getThrowPoints() as $throwPoint) { | ||
if (!$throwPoint->isExplicit()) { | ||
continue; | ||
} | ||
|
||
foreach (TypeUtils::flattenTypes($throwPoint->getType()) as $throwPointType) { | ||
if ( | ||
$throwPointType instanceof TypeWithClassName | ||
&& $this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName(), $throwPoint->getScope()) | ||
) { | ||
continue; | ||
} | ||
|
||
$errors[] = RuleErrorBuilder::message(sprintf( | ||
'Function %s() throws exception %s but the PHPDoc contains @throws void.', | ||
$functionReflection->getName(), | ||
$throwPointType->describe(VerbosityLevel::typeOnly()) | ||
))->line($throwPoint->getNode()->getLine())->build(); | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
} |
82 changes: 82 additions & 0 deletions
82
src/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRule.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,82 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Exceptions; | ||
|
||
use PhpParser\Node; | ||
use PHPStan\Analyser\Scope; | ||
use PHPStan\Node\MethodReturnStatementsNode; | ||
use PHPStan\Reflection\MethodReflection; | ||
use PHPStan\Rules\Rule; | ||
use PHPStan\Rules\RuleErrorBuilder; | ||
use PHPStan\Type\TypeUtils; | ||
use PHPStan\Type\TypeWithClassName; | ||
use PHPStan\Type\VerbosityLevel; | ||
use PHPStan\Type\VoidType; | ||
|
||
/** | ||
* @implements Rule<MethodReturnStatementsNode> | ||
*/ | ||
class ThrowsVoidMethodWithExplicitThrowPointRule implements Rule | ||
{ | ||
|
||
private ExceptionTypeResolver $exceptionTypeResolver; | ||
|
||
private bool $missingCheckedExceptionInThrows; | ||
|
||
public function __construct( | ||
ExceptionTypeResolver $exceptionTypeResolver, | ||
bool $missingCheckedExceptionInThrows | ||
) | ||
{ | ||
$this->exceptionTypeResolver = $exceptionTypeResolver; | ||
$this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; | ||
} | ||
|
||
public function getNodeType(): string | ||
{ | ||
return MethodReturnStatementsNode::class; | ||
} | ||
|
||
public function processNode(Node $node, Scope $scope): array | ||
{ | ||
if ($this->missingCheckedExceptionInThrows) { | ||
return []; | ||
} | ||
|
||
$statementResult = $node->getStatementResult(); | ||
$methodReflection = $scope->getFunction(); | ||
if (!$methodReflection instanceof MethodReflection) { | ||
throw new \PHPStan\ShouldNotHappenException(); | ||
} | ||
|
||
if (!$methodReflection->getThrowType() instanceof VoidType) { | ||
return []; | ||
} | ||
|
||
$errors = []; | ||
foreach ($statementResult->getThrowPoints() as $throwPoint) { | ||
if (!$throwPoint->isExplicit()) { | ||
continue; | ||
} | ||
|
||
foreach (TypeUtils::flattenTypes($throwPoint->getType()) as $throwPointType) { | ||
if ( | ||
$throwPointType instanceof TypeWithClassName | ||
&& $this->exceptionTypeResolver->isCheckedException($throwPointType->getClassName(), $throwPoint->getScope()) | ||
) { | ||
continue; | ||
} | ||
|
||
$errors[] = RuleErrorBuilder::message(sprintf( | ||
'Method %s::%s() throws exception %s but the PHPDoc contains @throws void.', | ||
$methodReflection->getDeclaringClass()->getDisplayName(), | ||
$methodReflection->getName(), | ||
$throwPointType->describe(VerbosityLevel::typeOnly()) | ||
))->line($throwPoint->getNode()->getLine())->build(); | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
} |
70 changes: 70 additions & 0 deletions
70
tests/PHPStan/Rules/Exceptions/ThrowsVoidFunctionWithExplicitThrowPointRuleTest.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,70 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Exceptions; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<ThrowsVoidFunctionWithExplicitThrowPointRule> | ||
*/ | ||
class ThrowsVoidFunctionWithExplicitThrowPointRuleTest extends RuleTestCase | ||
{ | ||
|
||
/** @var bool */ | ||
private $missingCheckedExceptionInThrows; | ||
|
||
/** @var string[] */ | ||
private $checkedExceptionClasses; | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new ThrowsVoidFunctionWithExplicitThrowPointRule(new DefaultExceptionTypeResolver( | ||
$this->createReflectionProvider(), | ||
[], | ||
[], | ||
[], | ||
$this->checkedExceptionClasses | ||
), $this->missingCheckedExceptionInThrows); | ||
} | ||
|
||
public function dataRule(): array | ||
{ | ||
return [ | ||
[ | ||
true, | ||
[], | ||
[], | ||
], | ||
[ | ||
false, | ||
['DifferentException'], | ||
[ | ||
[ | ||
'Function ThrowsVoidFunction\foo() throws exception ThrowsVoidFunction\MyException but the PHPDoc contains @throws void.', | ||
15, | ||
], | ||
], | ||
], | ||
[ | ||
false, | ||
[\ThrowsVoidFunction\MyException::class], | ||
[], | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider dataRule | ||
* @param bool $missingCheckedExceptionInThrows | ||
* @param string[] $checkedExceptionClasses | ||
* @param mixed[] $errors | ||
*/ | ||
public function testRule(bool $missingCheckedExceptionInThrows, array $checkedExceptionClasses, array $errors): void | ||
{ | ||
$this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; | ||
$this->checkedExceptionClasses = $checkedExceptionClasses; | ||
$this->analyse([__DIR__ . '/data/throws-void-function.php'], $errors); | ||
} | ||
|
||
} |
70 changes: 70 additions & 0 deletions
70
tests/PHPStan/Rules/Exceptions/ThrowsVoidMethodWithExplicitThrowPointRuleTest.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,70 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\Rules\Exceptions; | ||
|
||
use PHPStan\Rules\Rule; | ||
use PHPStan\Testing\RuleTestCase; | ||
|
||
/** | ||
* @extends RuleTestCase<ThrowsVoidMethodWithExplicitThrowPointRule> | ||
*/ | ||
class ThrowsVoidMethodWithExplicitThrowPointRuleTest extends RuleTestCase | ||
{ | ||
|
||
/** @var bool */ | ||
private $missingCheckedExceptionInThrows; | ||
|
||
/** @var string[] */ | ||
private $checkedExceptionClasses; | ||
|
||
protected function getRule(): Rule | ||
{ | ||
return new ThrowsVoidMethodWithExplicitThrowPointRule(new DefaultExceptionTypeResolver( | ||
$this->createReflectionProvider(), | ||
[], | ||
[], | ||
[], | ||
$this->checkedExceptionClasses | ||
), $this->missingCheckedExceptionInThrows); | ||
} | ||
|
||
public function dataRule(): array | ||
{ | ||
return [ | ||
[ | ||
true, | ||
[], | ||
[], | ||
], | ||
[ | ||
false, | ||
['DifferentException'], | ||
[ | ||
[ | ||
'Method ThrowsVoidMethod\Foo::doFoo() throws exception ThrowsVoidMethod\MyException but the PHPDoc contains @throws void.', | ||
18, | ||
], | ||
], | ||
], | ||
[ | ||
false, | ||
[\ThrowsVoidMethod\MyException::class], | ||
[], | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider dataRule | ||
* @param bool $missingCheckedExceptionInThrows | ||
* @param string[] $checkedExceptionClasses | ||
* @param mixed[] $errors | ||
*/ | ||
public function testRule(bool $missingCheckedExceptionInThrows, array $checkedExceptionClasses, array $errors): void | ||
{ | ||
$this->missingCheckedExceptionInThrows = $missingCheckedExceptionInThrows; | ||
$this->checkedExceptionClasses = $checkedExceptionClasses; | ||
$this->analyse([__DIR__ . '/data/throws-void-method.php'], $errors); | ||
} | ||
|
||
} |
16 changes: 16 additions & 0 deletions
16
tests/PHPStan/Rules/Exceptions/data/throws-void-function.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,16 @@ | ||
<?php | ||
|
||
namespace ThrowsVoidFunction; | ||
|
||
class MyException extends \Exception | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* @throws void | ||
*/ | ||
function foo(): void | ||
{ | ||
throw new MyException(); | ||
} |
Oops, something went wrong.