From 3cfbc8540c74b6ee89abe691ffb4110e991b775a Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 23 Feb 2026 23:32:17 +0700 Subject: [PATCH 1/7] [CodeQuality] Skip no code parameter on custom Throwable instance on ThrowWithPreviousExceptionRector --- .../Fixture/empty_brackets.php.inc | 8 +++--- .../Fixture/fixture.php.inc | 8 +++--- .../Fixture/named_argument.php.inc | 4 +-- .../named_argument_for_message.php.inc | 4 +-- ...kip_custom_exception_no_code_param.php.inc | 27 +++++++++++++++++++ ...n_param_order_flipped_no_namespace.php.inc | 2 +- ..._exception_with_different_location.php.inc | 4 +-- .../Fixture/skip_missing_location.php.inc | 2 +- 8 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc diff --git a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/empty_brackets.php.inc b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/empty_brackets.php.inc index a9d94007688..2a51e74803e 100644 --- a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/empty_brackets.php.inc +++ b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/empty_brackets.php.inc @@ -8,8 +8,8 @@ class EmptyBrackets { try { $someCode = 1; - } catch (Throwable $throwable) { - throw new AnotherException; + } catch (\Throwable $throwable) { + throw new \RuntimeException; } } } @@ -26,8 +26,8 @@ class EmptyBrackets { try { $someCode = 1; - } catch (Throwable $throwable) { - throw new AnotherException($throwable->getMessage(), $throwable->getCode(), $throwable); + } catch (\Throwable $throwable) { + throw new \RuntimeException($throwable->getMessage(), $throwable->getCode(), $throwable); } } } diff --git a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/fixture.php.inc b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/fixture.php.inc index fcac2a63b66..8e1085cee31 100644 --- a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/fixture.php.inc +++ b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/fixture.php.inc @@ -8,8 +8,8 @@ class Fixture { try { $someCode = 1; - } catch (Throwable $throwable) { - throw new AnotherException('ups'); + } catch (\Throwable $throwable) { + throw new \RuntimeException('ups'); } } } @@ -26,8 +26,8 @@ class Fixture { try { $someCode = 1; - } catch (Throwable $throwable) { - throw new AnotherException('ups', $throwable->getCode(), $throwable); + } catch (\Throwable $throwable) { + throw new \RuntimeException('ups', $throwable->getCode(), $throwable); } } } diff --git a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument.php.inc b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument.php.inc index 0e9b6859aa1..792b460802d 100644 --- a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument.php.inc +++ b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument.php.inc @@ -9,7 +9,7 @@ class NamedArgument try { $this->run(); }catch(\Throwable $throwable) { - throw new LogicException('Some exception', previous: $throwable); + throw new \LogicException('Some exception', previous: $throwable); } } } @@ -27,7 +27,7 @@ class NamedArgument try { $this->run(); }catch(\Throwable $throwable) { - throw new LogicException('Some exception', $throwable->getCode(), previous: $throwable); + throw new \LogicException('Some exception', $throwable->getCode(), previous: $throwable); } } } diff --git a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument_for_message.php.inc b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument_for_message.php.inc index bd3ca8a1a25..03ba153ebc7 100644 --- a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument_for_message.php.inc +++ b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/named_argument_for_message.php.inc @@ -9,7 +9,7 @@ class NamedArgumentForMessage try { $this->run(); }catch(\Throwable $throwable) { - throw new LogicException(message: 'Some exception'); + throw new \LogicException(message: 'Some exception'); } } } @@ -27,7 +27,7 @@ class NamedArgumentForMessage try { $this->run(); }catch(\Throwable $throwable) { - throw new LogicException(message: 'Some exception', code: $throwable->getCode(), previous: $throwable); + throw new \LogicException(message: 'Some exception', code: $throwable->getCode(), previous: $throwable); } } } diff --git a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc new file mode 100644 index 00000000000..de837649dc5 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc @@ -0,0 +1,27 @@ + Date: Mon, 23 Feb 2026 23:32:42 +0700 Subject: [PATCH 2/7] Fix --- .../ThrowWithPreviousExceptionRector.php | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php index fd14c099856..0e56681914a 100644 --- a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php +++ b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php @@ -51,7 +51,7 @@ public function run() try { $someCode = 1; } catch (Throwable $throwable) { - throw new AnotherException('ups'); + throw new \RuntimeException('ups'); } } } @@ -65,7 +65,7 @@ public function run() try { $someCode = 1; } catch (Throwable $throwable) { - throw new AnotherException('ups', $throwable->getCode(), $throwable); + throw new \RuntimeException('ups', $throwable->getCode(), $throwable); } } } @@ -155,21 +155,29 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) } if (! isset($new->getArgs()[1])) { - // get previous code - $new->args[1] = new Arg( - new MethodCall($caughtThrowableVariable, 'getCode'), - name: $shouldUseNamedArguments ? new Identifier('code') : null - ); + if ($this->hasCodeParameter($new->class)) { + // get previous code + $new->args[1] = new Arg( + new MethodCall($caughtThrowableVariable, 'getCode'), + name: $shouldUseNamedArguments ? new Identifier('code') : null + ); + } + + return null; } /** @var Arg $arg1 */ $arg1 = $new->args[1]; if ($arg1->name instanceof Identifier && $arg1->name->toString() === 'previous') { - $new->args[1] = new Arg( - new MethodCall($caughtThrowableVariable, 'getCode'), - name: $shouldUseNamedArguments ? new Identifier('code') : null - ); - $new->args[$exceptionArgumentPosition] = $arg1; + if ($this->hasCodeParameter($new->class)) { + $new->args[1] = new Arg( + new MethodCall($caughtThrowableVariable, 'getCode'), + name: $shouldUseNamedArguments ? new Identifier('code') : null + ); + $new->args[$exceptionArgumentPosition] = $arg1; + } else { + return null; + } } else { $new->args[$exceptionArgumentPosition] = new Arg( $caughtThrowableVariable, @@ -184,6 +192,34 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN; } + private function hasCodeParameter(Name $exceptionName): bool + { + $className = $this->getName($exceptionName); + if (! $this->reflectionProvider->hasClass($className)) { + return false; + } + + $classReflection = $this->reflectionProvider->getClass($className); + $construct = $classReflection->hasMethod(MethodName::CONSTRUCT); + + if (! $construct) { + return false; + } + + $extendedMethodReflection = $classReflection->getConstructor(); + $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors( + $extendedMethodReflection->getVariants() + ); + + foreach ($extendedParametersAcceptor->getParameters() as $extendedParameterReflection) { + if ($extendedParameterReflection->getName() === 'code') { + return true; + } + } + + return false; + } + private function resolveExceptionArgumentPosition(Name $exceptionName): ?int { $className = $this->getName($exceptionName); From efbb894c8ee53d353fa3379cebef8050f07d64fd Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 23 Feb 2026 23:33:58 +0700 Subject: [PATCH 3/7] Fix --- .../ThrowWithPreviousExceptionRector.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php index 0e56681914a..837b5e412d6 100644 --- a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php +++ b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php @@ -142,18 +142,6 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) $messageArgument = $new->args[0] ?? null; $shouldUseNamedArguments = $messageArgument instanceof Arg && $messageArgument->name instanceof Identifier; - if (! isset($new->args[0])) { - // get previous message - $getMessageMethodCall = new MethodCall($caughtThrowableVariable, 'getMessage'); - $new->args[0] = new Arg($getMessageMethodCall); - } elseif ($new->args[0] instanceof Arg && $new->args[0]->name instanceof Identifier && $new->args[0]->name->toString() === 'previous' && $this->nodeComparator->areNodesEqual( - $new->args[0]->value, - $caughtThrowableVariable - )) { - $new->args[0]->name->name = 'message'; - $new->args[0]->value = new MethodCall($caughtThrowableVariable, 'getMessage'); - } - if (! isset($new->getArgs()[1])) { if ($this->hasCodeParameter($new->class)) { // get previous code @@ -166,6 +154,18 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) return null; } + if (! isset($new->args[0])) { + // get previous message + $getMessageMethodCall = new MethodCall($caughtThrowableVariable, 'getMessage'); + $new->args[0] = new Arg($getMessageMethodCall); + } elseif ($new->args[0] instanceof Arg && $new->args[0]->name instanceof Identifier && $new->args[0]->name->toString() === 'previous' && $this->nodeComparator->areNodesEqual( + $new->args[0]->value, + $caughtThrowableVariable + )) { + $new->args[0]->name->name = 'message'; + $new->args[0]->value = new MethodCall($caughtThrowableVariable, 'getMessage'); + } + /** @var Arg $arg1 */ $arg1 = $new->args[1]; if ($arg1->name instanceof Identifier && $arg1->name->toString() === 'previous') { From 79d5c063514735a7d083244573652aa74d48c745 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 23 Feb 2026 23:35:02 +0700 Subject: [PATCH 4/7] fix --- .../ThrowWithPreviousExceptionRector.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php index 837b5e412d6..0e56681914a 100644 --- a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php +++ b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php @@ -142,18 +142,6 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) $messageArgument = $new->args[0] ?? null; $shouldUseNamedArguments = $messageArgument instanceof Arg && $messageArgument->name instanceof Identifier; - if (! isset($new->getArgs()[1])) { - if ($this->hasCodeParameter($new->class)) { - // get previous code - $new->args[1] = new Arg( - new MethodCall($caughtThrowableVariable, 'getCode'), - name: $shouldUseNamedArguments ? new Identifier('code') : null - ); - } - - return null; - } - if (! isset($new->args[0])) { // get previous message $getMessageMethodCall = new MethodCall($caughtThrowableVariable, 'getMessage'); @@ -166,6 +154,18 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) $new->args[0]->value = new MethodCall($caughtThrowableVariable, 'getMessage'); } + if (! isset($new->getArgs()[1])) { + if ($this->hasCodeParameter($new->class)) { + // get previous code + $new->args[1] = new Arg( + new MethodCall($caughtThrowableVariable, 'getCode'), + name: $shouldUseNamedArguments ? new Identifier('code') : null + ); + } + + return null; + } + /** @var Arg $arg1 */ $arg1 = $new->args[1]; if ($arg1->name instanceof Identifier && $arg1->name->toString() === 'previous') { From 5e49b21432074898a3bf64cf82fb0400225a81ee Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 23 Feb 2026 22:26:03 +0700 Subject: [PATCH 5/7] fix phpstan --- .../FuncCall/JsonThrowOnErrorRector.php | 20 +++++++++---------- .../Output/GitlabOutputFormatter.php | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php index 38342c24806..32eafaacdc7 100644 --- a/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php +++ b/rules/Php73/Rector/FuncCall/JsonThrowOnErrorRector.php @@ -29,8 +29,6 @@ final class JsonThrowOnErrorRector extends AbstractRector implements MinPhpVersi { private const array FLAGS = ['JSON_THROW_ON_ERROR']; - private bool $hasChanged = false; - public function __construct( private readonly ValueResolver $valueResolver, private readonly BetterNodeFinder $betterNodeFinder @@ -77,9 +75,9 @@ public function refactor(Node $node): ?Node return null; } - $this->hasChanged = false; + $hasChanged = false; - $this->traverseNodesWithCallable($node, function (Node $currentNode): ?FuncCall { + $this->traverseNodesWithCallable($node, function (Node $currentNode) use (&$hasChanged): ?FuncCall { if (! $currentNode instanceof FuncCall) { return null; } @@ -89,17 +87,17 @@ public function refactor(Node $node): ?Node } if ($this->isName($currentNode, 'json_encode')) { - return $this->processJsonEncode($currentNode); + return $this->processJsonEncode($currentNode, $hasChanged); } if ($this->isName($currentNode, 'json_decode')) { - return $this->processJsonDecode($currentNode); + return $this->processJsonDecode($currentNode, $hasChanged); } return null; }); - if ($this->hasChanged) { + if ($hasChanged) { return $node; } @@ -134,7 +132,7 @@ private function shouldSkipFuncCall(FuncCall $funcCall): bool return $this->isFirstValueStringOrArray($funcCall); } - private function processJsonEncode(FuncCall $funcCall): FuncCall + private function processJsonEncode(FuncCall $funcCall, bool &$hasChanged): FuncCall { $flags = []; if (isset($funcCall->args[1])) { @@ -145,14 +143,14 @@ private function processJsonEncode(FuncCall $funcCall): FuncCall $newArg = $this->getArgWithFlags($flags); if ($newArg instanceof Arg) { - $this->hasChanged = true; + $hasChanged = true; $funcCall->args[1] = $newArg; } return $funcCall; } - private function processJsonDecode(FuncCall $funcCall): FuncCall + private function processJsonDecode(FuncCall $funcCall, bool &$hasChanged): FuncCall { $flags = []; if (isset($funcCall->args[3])) { @@ -172,7 +170,7 @@ private function processJsonDecode(FuncCall $funcCall): FuncCall $newArg = $this->getArgWithFlags($flags); if ($newArg instanceof Arg) { - $this->hasChanged = true; + $hasChanged = true; $funcCall->args[3] = $newArg; } diff --git a/src/ChangesReporting/Output/GitlabOutputFormatter.php b/src/ChangesReporting/Output/GitlabOutputFormatter.php index b0e8530153b..d43ac4ba2a2 100644 --- a/src/ChangesReporting/Output/GitlabOutputFormatter.php +++ b/src/ChangesReporting/Output/GitlabOutputFormatter.php @@ -30,7 +30,7 @@ private const string ERROR_SEVERITY_MINOR = 'minor'; public function __construct( - private Filehasher $filehasher, + private FileHasher $filehasher, ) { } From f5852099541cf46fa13faa092a5297c75fa38dbf Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 23 Feb 2026 22:26:03 +0700 Subject: [PATCH 6/7] fix --- .../Fixture/skip_custom_exception_no_code_param.php.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc index de837649dc5..7c8ffbe7677 100644 --- a/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc +++ b/rules-tests/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector/Fixture/skip_custom_exception_no_code_param.php.inc @@ -4,7 +4,7 @@ namespace Rector\Tests\CodeQuality\Rector\Catch_\ThrowWithPreviousExceptionRecto use stdClass; -class Error extends \Exception +class CustomError extends \Exception { public function __construct( string $message = '', @@ -23,5 +23,5 @@ class Error extends \Exception try { throw new \InvalidArgumentException('foo'); } catch (\InvalidArgumentException $e) { - throw new Error(message: 'Some error message', previous: $e); + throw new CustomError(message: 'Some error message', previous: $e); } \ No newline at end of file From 4c506f69040963753e3a97808e4189249ca71deb Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Mon, 23 Feb 2026 23:44:25 +0700 Subject: [PATCH 7/7] clean flag --- .../Catch_/ThrowWithPreviousExceptionRector.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php index 0e56681914a..c53fce5269a 100644 --- a/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php +++ b/rules/CodeQuality/Rector/Catch_/ThrowWithPreviousExceptionRector.php @@ -142,16 +142,19 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) $messageArgument = $new->args[0] ?? null; $shouldUseNamedArguments = $messageArgument instanceof Arg && $messageArgument->name instanceof Identifier; + $hasChanged = false; if (! isset($new->args[0])) { // get previous message $getMessageMethodCall = new MethodCall($caughtThrowableVariable, 'getMessage'); $new->args[0] = new Arg($getMessageMethodCall); + $hasChanged = true; } elseif ($new->args[0] instanceof Arg && $new->args[0]->name instanceof Identifier && $new->args[0]->name->toString() === 'previous' && $this->nodeComparator->areNodesEqual( $new->args[0]->value, $caughtThrowableVariable )) { $new->args[0]->name->name = 'message'; $new->args[0]->value = new MethodCall($caughtThrowableVariable, 'getMessage'); + $hasChanged = true; } if (! isset($new->getArgs()[1])) { @@ -161,9 +164,10 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) new MethodCall($caughtThrowableVariable, 'getCode'), name: $shouldUseNamedArguments ? new Identifier('code') : null ); + $hasChanged = true; + } else { + return null; } - - return null; } /** @var Arg $arg1 */ @@ -175,7 +179,8 @@ private function refactorThrow(Throw_ $throw, Variable $caughtThrowableVariable) name: $shouldUseNamedArguments ? new Identifier('code') : null ); $new->args[$exceptionArgumentPosition] = $arg1; - } else { + $hasChanged = true; + } elseif (! $hasChanged) { return null; } } else {