[PHP] Add MultiExceptionCatchRector

This commit is contained in:
Tomas Votruba 2018-10-08 12:39:37 +08:00
parent 947f0f3e7d
commit 658cef5fc2
7 changed files with 214 additions and 0 deletions

View File

@ -2,3 +2,5 @@ services:
Rector\Php\Rector\BinaryOp\IsIterableRector: ~
Rector\Php\Rector\Name\ReservedObjectRector:
Object: 'BaseObject'
Rector\Php\Rector\TryCatch\MultiExceptionCatchRector: ~

View File

@ -0,0 +1,125 @@
<?php declare(strict_types=1);
namespace Rector\Php\Rector\TryCatch;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\TryCatch;
use Rector\Printer\BetterStandardPrinter;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see https://wiki.php.net/rfc/multiple-catch
*/
final class MultiExceptionCatchRector extends AbstractRector
{
/**
* @var BetterStandardPrinter
*/
private $betterStandardPrinter;
public function __construct(BetterStandardPrinter $betterStandardPrinter)
{
$this->betterStandardPrinter = $betterStandardPrinter;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Changes multi catch of same exception to single one | separated.',
[
new CodeSample(
<<<'CODE_SAMPLE'
try {
// Some code...
} catch (ExceptionType1 $exception) {
$sameCode;
} catch (ExceptionType2 $exception) {
$sameCode;
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
try {
// Some code...
} catch (ExceptionType1 | ExceptionType2 $exception) {
$sameCode;
}
CODE_SAMPLE
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [TryCatch::class];
}
/**
* @param TryCatch $tryCatchNode
*/
public function refactor(Node $tryCatchNode): ?Node
{
if (count($tryCatchNode->catches) < 2) {
return $tryCatchNode;
}
$catchKeysByContent = $this->collectCatchKeysByContent($tryCatchNode);
foreach ($catchKeysByContent as $keys) {
// no duplicates
if (count($keys) < 2) {
continue;
}
$collectedTypes = $this->collectTypesFromCatchedByIds($tryCatchNode, $keys);
$firstTryKey = array_shift($keys);
$tryCatchNode->catches[$firstTryKey]->types = $collectedTypes;
foreach ($keys as $key) {
unset($tryCatchNode->catches[$key]);
}
}
// reindex from 0 for printer
$tryCatchNode->catches = array_values($tryCatchNode->catches);
return $tryCatchNode;
}
/**
* @return int[][]
*/
private function collectCatchKeysByContent(TryCatch $tryCatchNode): array
{
$catchKeysByContent = [];
foreach ($tryCatchNode->catches as $key => $catch) {
$catchContent = $this->betterStandardPrinter->prettyPrint($catch->stmts);
/** @var int $key */
$catchKeysByContent[$catchContent][] = $key;
}
return $catchKeysByContent;
}
/**
* @param int[] $keys
* @return Name[]
*/
private function collectTypesFromCatchedByIds(TryCatch $tryCatchNode, array $keys): array
{
$collectedTypes = [];
foreach ($keys as $key) {
$collectedTypes = array_merge($collectedTypes, $tryCatchNode->catches[$key]->types);
}
return $collectedTypes;
}
}

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
try {
// Some code...
} catch (ExceptionType1|ExceptionType2 $e) {
// Code to handle the exception
} catch (Exception $e) {
// ...
}
try {
// Some code...
} catch (ExceptionType1 $e) {
// Code to handle the exception
} catch (ExceptionType2 $e) {
$differentContent = 'hey';
}
try {
// Some code...
} catch (ExceptionType1|ExceptionType2 $e) {
$e = 1;
}

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Rector\Php\Tests\Rector\TryCatch\MultiExceptionCatchRector;
use Iterator;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
/**
* @covers \Rector\Php\Rector\TryCatch\MultiExceptionCatchRector
*/
final class MultiExceptionCatchRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideWrongToFixedFiles()
*/
public function test(string $wrong, string $fixed): void
{
$this->doTestFileMatchesExpectedContent($wrong, $fixed);
}
public function provideWrongToFixedFiles(): Iterator
{
yield [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc'];
}
protected function provideConfig(): string
{
return __DIR__ . '/config.yml';
}
}

View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
try {
// Some code...
} catch (ExceptionType1 $e) {
// Code to handle the exception
} catch (ExceptionType2 $e) {
// Code to handle the exception
} catch (Exception $e) {
// ...
}
try {
// Some code...
} catch (ExceptionType1 $e) {
// Code to handle the exception
} catch (ExceptionType2 $e) {
$differentContent = 'hey';
}
try {
// Some code...
} catch (ExceptionType1 $e) {
$e = 1;
} catch (ExceptionType2 $e2) {
$e = 1;
}

View File

@ -0,0 +1,2 @@
services:
Rector\Php\Rector\TryCatch\MultiExceptionCatchRector: ~

View File

@ -41,6 +41,7 @@ parameters:
# false postive - type is set by annotatoin above
- '#Access to an undefined property PhpParser\\Node::\$(args|var)#'
- '#Call to function is_string\(\) with PhpParser\\Node\\Expr\|PhpParser\\Node\\Identifier will always evaluate to false#'
- '#Method Rector\\Php\\Rector\\TryCatch\\MultiExceptionCatchRector\:\:collectCatchKeysByContent\(\) should return array<array<int\>\> but returns array<string, array<int, int\|string\>\>#'
# irelevant
- '#Call to function in_array\(\) with arguments string, array<array<string\|false>> and true will always evaluate to false#'