Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,35 @@

namespace Rector\CodeQuality\Rector\Catch_;

use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\Catch_;
use PhpParser\Node\Stmt\Throw_;
use PhpParser\NodeTraverser;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
use ReflectionClass;
use ReflectionMethod;
use ReflectionNamedType;
use Throwable;

/**
* @see https://github.com/thecodingmachine/phpstan-strict-rules/blob/e3d746a61d38993ca2bc2e2fcda7012150de120c/src/Rules/Exceptions/ThrowMustBundlePreviousExceptionRule.php#L83
* @see \Rector\CodeQuality\Tests\Rector\Catch_\ThrowWithPreviousExceptionRector\ThrowWithPreviousExceptionRectorTest
*/
final class ThrowWithPreviousExceptionRector extends AbstractRector
{
/**
* @var int
*/
private const DEFAULT_EXCEPTION_ARGUMENT_POSITION = 2;

public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
Expand Down Expand Up @@ -80,30 +91,78 @@ public function refactor(Node $node): ?Node
return null;
}

if (! $node->expr instanceof New_) {
return null;
}
return $this->refactorThrow($node, $catchedThrowableVariable);
});

if (! $node->expr->class instanceof Name) {
return null;
}
return $node;
}

// exception is bundled
if (isset($node->expr->args[2])) {
return null;
}
private function refactorThrow(Throw_ $throw, Variable $catchedThrowableVariable): ?int
{
if (! $throw->expr instanceof New_) {
return null;
}

if (! isset($node->expr->args[1])) {
// get previous code
$node->expr->args[1] = new Arg(new MethodCall($catchedThrowableVariable, 'getCode'));
if (! $throw->expr->class instanceof Name) {
return null;
}

$exceptionArgumentPosition = $this->resolveExceptionArgumentPosition($throw->expr->class);
if ($exceptionArgumentPosition === null) {
return null;
}

// exception is bundled
if (isset($throw->expr->args[$exceptionArgumentPosition])) {
return null;
}

if (! isset($throw->expr->args[1])) {
// get previous code
$throw->expr->args[1] = new Arg(new MethodCall($catchedThrowableVariable, 'getCode'));
}

$throw->expr->args[$exceptionArgumentPosition] = new Arg($catchedThrowableVariable);

// nothing more to add
return NodeTraverser::STOP_TRAVERSAL;
}

private function resolveExceptionArgumentPosition(Name $exceptionName): ?int
{
$fullyQualifiedName = $this->getName($exceptionName);

// is native exception?
if (! Strings::contains($fullyQualifiedName, '\\')) {
return self::DEFAULT_EXCEPTION_ARGUMENT_POSITION;
}

// is class missing?
if (! class_exists($fullyQualifiedName)) {
return self::DEFAULT_EXCEPTION_ARGUMENT_POSITION;
}

$reflectionClass = new ReflectionClass($fullyQualifiedName);
if (! $reflectionClass->hasMethod('__construct')) {
return self::DEFAULT_EXCEPTION_ARGUMENT_POSITION;
}

/** @var ReflectionMethod $constructorReflectionMethod */
$constructorReflectionMethod = $reflectionClass->getConstructor();
foreach ($constructorReflectionMethod->getParameters() as $position => $reflectionParameter) {
if (! $reflectionParameter->hasType()) {
continue;
}

$node->expr->args[2] = new Arg($catchedThrowableVariable);
/** @var ReflectionNamedType $reflectionNamedType */
$reflectionNamedType = $reflectionParameter->getType();
if (! is_a($reflectionNamedType->getName(), Throwable::class, true)) {
continue;
}

// nothing more to add
return NodeTraverser::STOP_TRAVERSAL;
});
return $position;
}

return $node;
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private function shortenElseIf(If_ $node): ?Node
}

$else = $node->else;
if (count($else->stmts) > 1) {
if (count($else->stmts) !== 1) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Rector\CodeQuality\Tests\Rector\Catch_\ThrowWithPreviousExceptionRector\Fixture;

use Exception;
use Throwable;

class SkipFilledExceptionWithDifferentLocation
{
public function run()
{
try {
} catch (Throwable $throwable) {
throw new BadRequestHttpException('message some', $throwable);
}
}
}

class BadRequestHttpException extends Exception
{
public function __construct(string $message = null, \Throwable $previous = null, int $code = 0, array $headers = [])
{
parent::__construct('message', 400, $previous);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Rector\CodeQuality\Tests\Rector\Catch_\ThrowWithPreviousExceptionRector\Fixture;

use Exception;
use Throwable;

class SkipMissingLocation
{
public function run()
{
try {
} catch (Throwable $throwable) {
throw new MissingPreviousException('message some');
}
}
}

class MissingPreviousException extends Exception
{
public function __construct(string $message = null, int $code = 0, array $headers = [])
{
parent::__construct('message', 400);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Rector\Php\Tests\Rector\If_\ShortenElseIfRector\Fixture;

class SkipEmptyElse
{
/**
* @var bool
*/
private $cond1;

public function run()
{
if ($this->cond1) {
return 1;
} else {
// some comment
[];
}
}
}