Skip to content

Commit

Permalink
Merge 15d2c69 into a0f303f
Browse files Browse the repository at this point in the history
  • Loading branch information
Aerendir committed Feb 10, 2020
2 parents a0f303f + 15d2c69 commit a395128
Show file tree
Hide file tree
Showing 12 changed files with 522 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php

declare(strict_types=1);

namespace Rector\CodingStyle\Rector\ClassMethod;

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;

/**
* 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);
/** @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 (
strpos($throwClassName, '\\') === false &&
strpos($FQN, $throwClassName) !== false &&
$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);

/** @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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;

class CustomExceptionAlreadyAnnotated
{
/**
* @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 CustomExceptionAlreadyAnnotated
{
/**
* @throws \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException
*/
public function throwException()
{
throw new \Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException();
}
}

?>
Original file line number Diff line number Diff line change
@@ -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);
}
}

?>
Original file line number Diff line number Diff line change
@@ -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();
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;

use Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Source\TheException;

class CustomImportedExceptionAlreadyAnnotated
{
/**
* @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 CustomImportedExceptionAlreadyAnnotated
{
/**
* @throws TheException
*/
public function throwException()
{
throw new TheException();
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;

class RootExceptionAlreadyAnnotated
{
/**
* @throws \RuntimeException
*/
public function throwException()
{
throw new \RuntimeException();
}
}

?>
-----
<?php

namespace Rector\CodingStyle\Tests\Rector\ClassMethod\AnnotateThrowables\Fixture;

class RootExceptionAlreadyAnnotated
{
/**
* @throws \RuntimeException
*/
public function throwException()
{
throw new \RuntimeException();
}
}

?>

0 comments on commit a395128

Please sign in to comment.