#2824: Automatically add to DocBlock comments the thrown \Throwables.

This commit is contained in:
Aerendir 2020-02-10 18:10:16 +01:00
parent a8a9886c26
commit 12670ed9df
14 changed files with 604 additions and 1 deletions

View File

@ -1,4 +1,4 @@
# All 447 Rectors Overview
# All 448 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -1631,6 +1631,30 @@ Adds array default value to property to prevent foreach over null error
<br>
### `AnnotateThrowablesRector`
- class: `Rector\CodingStyle\Rector\ClassMethod\AnnotateThrowablesRector`
Adds @throws DocBlock comments to methods that thrwo \Throwables.
```diff
class RootExceptionInMethodWithDocblock
{
/**
* This is a comment.
*
* @param int $code
+ * @throws \RuntimeException
*/
public function throwException(int $code)
{
throw new \RuntimeException('', $code);
}
}
```
<br>
### `BinarySwitchToIfElseRector`
- class: `Rector\CodingStyle\Rector\Switch_\BinarySwitchToIfElseRector`

View File

@ -0,0 +1,174 @@
<?php
declare(strict_types=1);
namespace Rector\CodingStyle\Rector\ClassMethod;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Throw_;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwareGenericTagValueNode;
use Rector\AttributeAwarePhpDoc\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
use RuntimeException;
/**
* Adds "throws" DocBlock to methods.
*/
final class AnnotateThrowablesRector extends AbstractRector
{
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Throw_::class];
}
/**
* From this method documentation is generated.
*/
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Adds @throws DocBlock comments to methods that thrwo \Throwables.', [
new CodeSample(
// code before
<<<'PHP'
class RootExceptionInMethodWithDocblock
{
/**
* This is a comment.
*
* @param int $code
*/
public function throwException(int $code)
{
throw new \RuntimeException('', $code);
}
}
PHP
,
// code after
<<<'PHP'
class RootExceptionInMethodWithDocblock
{
/**
* This is a comment.
*
* @param int $code
* @throws \RuntimeException
*/
public function throwException(int $code)
{
throw new \RuntimeException('', $code);
}
}
PHP
),
]
);
}
/**
* @param Throw_ $node
*/
public function refactor(Node $node): ?Node
{
if ($this->isThrowableAnnotated($node)) {
return null;
}
$this->annotateMethod($node);
return $node;
}
private function isThrowableAnnotated(Throw_ $node): bool
{
$method = $node->getAttribute(AttributeKey::METHOD_NODE);
if ($method === null) {
throw new RuntimeException('This should not happen and is probably a bug. Please report it.');
}
/** @var PhpDocInfo $phpDocInfo */
$phpDocInfo = $method->getAttribute(AttributeKey::PHP_DOC_INFO);
$throwTags = $phpDocInfo->getTagsByName('throws');
$FQN = $this->buildFQN($node);
if (empty($throwTags)) {
return false;
}
/** @var AttributeAwarePhpDocTagNode $throwTag */
foreach ($throwTags as $throwTag) {
$throwClassName = $throwTag->value->type->name;
if ($throwClassName === $FQN) {
return true;
}
if (
! Strings::contains($throwClassName, '\\') &&
Strings::contains($FQN, $throwClassName) &&
$this->isThrowableImported($node)
) {
return true;
}
}
return false;
}
private function isThrowableImported(Throw_ $node): bool
{
$throwClassName = $this->getName($node->expr->class);
$useNodes = $node->getAttribute(AttributeKey::USE_NODES);
if ($useNodes === null) {
return false;
}
/** @var Use_ $useNode */
foreach ($useNodes as $useNode) {
/** @var UseUse $useStmt */
foreach ($useNode->uses as $useStmt) {
if ($this->getName($useStmt) === $throwClassName) {
return true;
}
}
}
return false;
}
private function annotateMethod(Throw_ $node): void
{
/** @var ClassMethod $method */
$method = $node->getAttribute(AttributeKey::METHOD_NODE);
$FQN = $this->buildFQN($node);
$docComment = $this->buildThrowsDocComment($FQN);
/** @var PhpDocInfo $methodPhpDocInfo */
$methodPhpDocInfo = $method->getAttribute(AttributeKey::PHP_DOC_INFO);
$methodPhpDocInfo->addPhpDocTagNode($docComment);
}
private function buildThrowsDocComment(string $FQNOrThrowableName): AttributeAwarePhpDocTagNode
{
$value = new AttributeAwareGenericTagValueNode($FQNOrThrowableName);
return new AttributeAwarePhpDocTagNode('@throws', $value);
}
private function buildFQN(Throw_ $node): string
{
return '\\' . $this->getName($node->expr->class);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowablesRector;
use Iterator;
use Rector\CodingStyle\Rector\ClassMethod\AnnotateThrowablesRector;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
final class AnnotateThrowablesRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideDataForTest(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return AnnotateThrowablesRector::class;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionAlreadyAnnotatedInMethod
{
/**
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function throwException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionAlreadyAnnotatedInMethod
{
/**
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function throwException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
}
?>

View File

@ -0,0 +1,38 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionInMethodWithDocblock
{
/**
* This is a comment.
*
* @param int $code
*/
public function throwException(int $code)
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException('', $code);
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionInMethodWithDocblock
{
/**
* This is a comment.
*
* @param int $code
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function throwException(int $code)
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException('', $code);
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionInMethodWithoutDocblock
{
public function throwException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionInMethodWithoutDocblock
{
/**
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function throwException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
}
?>

View File

@ -0,0 +1,46 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionThrownInTwoMethods
{
/**
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function throwException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
public function anotherThrowException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class CustomExceptionThrownInTwoMethods
{
/**
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function throwException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
/**
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function anotherThrowException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
use Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException;
class CustomImportedExceptionAlreadyAnnotatedInMethod
{
/**
* @throws TheException
*/
public function throwException()
{
throw new TheException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
use Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException;
class CustomImportedExceptionAlreadyAnnotatedInMethod
{
/**
* @throws TheException
*/
public function throwException()
{
throw new TheException();
}
}
?>

View File

@ -0,0 +1,33 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionAlreadyAnnotatedInMethod
{
/**
* @throws \RuntimeException
*/
public function throwException()
{
throw new \RuntimeException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionAlreadyAnnotatedInMethod
{
/**
* @throws \RuntimeException
*/
public function throwException()
{
throw new \RuntimeException();
}
}
?>

View File

@ -0,0 +1,38 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionInMethodWithDocblock
{
/**
* This is a comment.
*
* @param int $code
*/
public function throwException(int $code)
{
throw new \RuntimeException('', $code);
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionInMethodWithDocblock
{
/**
* This is a comment.
*
* @param int $code
* @throws \RuntimeException
*/
public function throwException(int $code)
{
throw new \RuntimeException('', $code);
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionInMethodWithoutDocblock
{
public function throwException()
{
throw new \RuntimeException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionInMethodWithoutDocblock
{
/**
* @throws \RuntimeException
*/
public function throwException()
{
throw new \RuntimeException();
}
}
?>

View File

@ -0,0 +1,46 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionThrownInTwoMethods
{
/**
* @throws \RuntimeException
*/
public function throwException()
{
throw new \RuntimeException();
}
public function anotherThrowException()
{
throw new \RuntimeException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
class RootExceptionThrownInTwoMethods
{
/**
* @throws \RuntimeException
*/
public function throwException()
{
throw new \RuntimeException();
}
/**
* @throws \RuntimeException
*/
public function anotherThrowException()
{
throw new \RuntimeException();
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
use \RuntimeException;
class RootImportedExceptionAlreadyAnnotatedInMethod
{
/**
* @throws RuntimeException
*/
public function throwException()
{
throw new RuntimeException();
}
}
?>
-----
<?php
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;
use \RuntimeException;
class RootImportedExceptionAlreadyAnnotatedInMethod
{
/**
* @throws RuntimeException
*/
public function throwException()
{
throw new RuntimeException();
}
}
?>

View File

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source;
class TheException extends \RuntimeException {}