2019-10-13 05:59:52 +00:00
< ? php
2021-05-09 20:15:43 +00:00
declare ( strict_types = 1 );
2022-06-06 17:12:56 +00:00
namespace Rector\CodeQuality\Rector\Catch_ ;
2019-07-23 09:07:32 +00:00
2022-06-06 17:12:56 +00:00
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\Identifier ;
use PhpParser\Node\Name ;
use PhpParser\Node\Stmt\Catch_ ;
use PhpParser\Node\Stmt\Throw_ ;
use PhpParser\NodeTraverser ;
use PHPStan\Reflection\ParametersAcceptorSelector ;
use PHPStan\Reflection\ReflectionProvider ;
use PHPStan\Type\ObjectType ;
use PHPStan\Type\TypeWithClassName ;
use Rector\NodeTypeResolver\Node\AttributeKey ;
2024-01-02 02:40:38 +00:00
use Rector\Rector\AbstractRector ;
use Rector\ValueObject\MethodName ;
2022-06-07 09:18:30 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2019-07-23 09:07:32 +00:00
/**
2021-04-10 18:47:17 +00:00
* @ changelog https :// github . com / thecodingmachine / phpstan - strict - rules / blob / e3d746a61d38993ca2bc2e2fcda7012150de120c / src / Rules / Exceptions / ThrowMustBundlePreviousExceptionRule . php #L83
2021-03-12 22:20:25 +00:00
* @ see \Rector\Tests\CodeQuality\Rector\Catch_\ThrowWithPreviousExceptionRector\ThrowWithPreviousExceptionRectorTest
2019-07-23 09:07:32 +00:00
*/
2022-06-07 08:22:29 +00:00
final class ThrowWithPreviousExceptionRector extends AbstractRector
2019-07-23 09:07:32 +00:00
{
2021-02-18 22:34:51 +00:00
/**
2023-06-11 23:01:39 +00:00
* @ readonly
2021-08-23 00:20:32 +00:00
* @ var \PHPStan\Reflection\ReflectionProvider
2021-02-18 22:34:51 +00:00
*/
private $reflectionProvider ;
2023-06-08 22:00:17 +00:00
/**
* @ var int
*/
private const DEFAULT_EXCEPTION_ARGUMENT_POSITION = 2 ;
2022-06-07 08:22:29 +00:00
public function __construct ( ReflectionProvider $reflectionProvider )
2021-02-18 22:34:51 +00:00
{
$this -> reflectionProvider = $reflectionProvider ;
}
2022-06-07 08:22:29 +00:00
public function getRuleDefinition () : RuleDefinition
2019-07-23 09:07:32 +00:00
{
2022-06-07 08:22:29 +00:00
return new RuleDefinition ( 'When throwing into a catch block, checks that the previous exception is passed to the new throw clause' , [ new CodeSample ( <<< 'CODE_SAMPLE'
2019-07-23 09:07:32 +00:00
class SomeClass
{
public function run ()
{
try {
$someCode = 1 ;
} catch ( Throwable $throwable ) {
throw new AnotherException ( 'ups' );
}
}
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2021-05-09 20:15:43 +00:00
, <<< 'CODE_SAMPLE'
2019-07-23 09:07:32 +00:00
class SomeClass
{
public function run ()
{
try {
$someCode = 1 ;
} catch ( Throwable $throwable ) {
throw new AnotherException ( 'ups' , $throwable -> getCode (), $throwable );
}
}
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2021-05-09 20:15:43 +00:00
)]);
2019-07-23 09:07:32 +00:00
}
/**
2021-02-27 00:06:15 +00:00
* @ return array < class - string < Node >>
2019-07-23 09:07:32 +00:00
*/
2021-05-09 20:15:43 +00:00
public function getNodeTypes () : array
2019-07-23 09:07:32 +00:00
{
2022-06-07 08:22:29 +00:00
return [ Catch_ :: class ];
2019-07-23 09:07:32 +00:00
}
/**
2021-12-10 10:22:23 +00:00
* @ param Catch_ $node
2019-07-23 09:07:32 +00:00
*/
2022-06-07 08:22:29 +00:00
public function refactor ( Node $node ) : ? Node
2019-07-23 09:07:32 +00:00
{
2020-06-03 09:39:18 +00:00
$caughtThrowableVariable = $node -> var ;
2022-06-07 08:22:29 +00:00
if ( ! $caughtThrowableVariable instanceof Variable ) {
2020-06-03 09:39:18 +00:00
return null ;
}
2022-02-25 08:28:29 +00:00
$isChanged = \false ;
2022-06-07 08:22:29 +00:00
$this -> traverseNodesWithCallable ( $node -> stmts , function ( Node $node ) use ( $caughtThrowableVariable , & $isChanged ) : ? int {
if ( ! $node instanceof Throw_ ) {
2019-07-23 09:07:32 +00:00
return null ;
}
2022-02-25 08:28:29 +00:00
$isChanged = $this -> refactorThrow ( $node , $caughtThrowableVariable );
return $isChanged ;
2019-11-11 13:06:40 +00:00
});
2022-02-25 08:28:29 +00:00
if ( ! ( bool ) $isChanged ) {
return null ;
}
2019-11-11 13:06:40 +00:00
return $node ;
}
2022-06-07 08:22:29 +00:00
private function refactorThrow ( Throw_ $throw , Variable $catchedThrowableVariable ) : ? int
2019-11-11 13:06:40 +00:00
{
2022-06-07 08:22:29 +00:00
if ( ! $throw -> expr instanceof New_ ) {
2019-11-11 13:06:40 +00:00
return null ;
}
2021-02-18 22:34:51 +00:00
$new = $throw -> expr ;
2022-06-07 08:22:29 +00:00
if ( ! $new -> class instanceof Name ) {
2019-11-11 13:06:40 +00:00
return null ;
}
2021-02-18 22:34:51 +00:00
$exceptionArgumentPosition = $this -> resolveExceptionArgumentPosition ( $new -> class );
2019-11-11 13:06:40 +00:00
if ( $exceptionArgumentPosition === null ) {
return null ;
}
2023-06-02 12:32:12 +00:00
if ( $new -> isFirstClassCallable ()) {
return null ;
}
2019-11-11 13:06:40 +00:00
// exception is bundled
2023-05-08 07:49:33 +00:00
if ( isset ( $new -> getArgs ()[ $exceptionArgumentPosition ])) {
2019-11-11 13:06:40 +00:00
return null ;
}
2023-05-08 07:49:33 +00:00
if ( ! isset ( $new -> getArgs ()[ 0 ])) {
2021-02-18 22:34:51 +00:00
// get previous message
2023-05-08 07:49:33 +00:00
$getMessageMethodCall = new MethodCall ( $catchedThrowableVariable , 'getMessage' );
$new -> args [ 0 ] = new Arg ( $getMessageMethodCall );
2021-02-18 22:34:51 +00:00
}
2023-05-08 07:49:33 +00:00
if ( ! isset ( $new -> getArgs ()[ 1 ])) {
2019-11-11 13:06:40 +00:00
// get previous code
2022-06-07 08:22:29 +00:00
$new -> args [ 1 ] = new Arg ( new MethodCall ( $catchedThrowableVariable , 'getCode' ));
2019-11-11 13:06:40 +00:00
}
2021-09-27 15:43:15 +00:00
/** @var Arg $arg1 */
2021-02-28 09:35:09 +00:00
$arg1 = $new -> args [ 1 ];
2022-06-07 08:22:29 +00:00
if ( $arg1 -> name instanceof Identifier && $arg1 -> name -> toString () === 'previous' ) {
$new -> args [ 1 ] = new Arg ( new MethodCall ( $catchedThrowableVariable , 'getCode' ));
2021-02-28 09:35:09 +00:00
$new -> args [ $exceptionArgumentPosition ] = $arg1 ;
} else {
2022-06-07 08:22:29 +00:00
$new -> args [ $exceptionArgumentPosition ] = new Arg ( $catchedThrowableVariable );
2021-02-28 09:35:09 +00:00
}
2021-02-18 22:34:51 +00:00
// null the node, to fix broken format preserving printers, see https://github.com/rectorphp/rector/issues/5576
2022-06-07 08:22:29 +00:00
$new -> setAttribute ( AttributeKey :: ORIGINAL_NODE , null );
2019-11-11 13:06:40 +00:00
// nothing more to add
2022-12-21 13:01:53 +00:00
return NodeTraverser :: DONT_TRAVERSE_CURRENT_AND_CHILDREN ;
2019-11-11 13:06:40 +00:00
}
2022-06-07 08:22:29 +00:00
private function resolveExceptionArgumentPosition ( Name $exceptionName ) : ? int
2019-11-11 13:06:40 +00:00
{
2021-02-18 22:34:51 +00:00
$className = $this -> getName ( $exceptionName );
2019-11-11 13:06:40 +00:00
// is native exception?
2021-05-29 22:10:59 +00:00
if ( \strpos ( $className , '\\' ) === \false ) {
2019-11-11 13:06:40 +00:00
return self :: DEFAULT_EXCEPTION_ARGUMENT_POSITION ;
}
2021-05-09 20:15:43 +00:00
if ( ! $this -> reflectionProvider -> hasClass ( $className )) {
2019-11-11 13:06:40 +00:00
return self :: DEFAULT_EXCEPTION_ARGUMENT_POSITION ;
}
2021-02-18 22:34:51 +00:00
$classReflection = $this -> reflectionProvider -> getClass ( $className );
2022-06-07 08:22:29 +00:00
$construct = $classReflection -> hasMethod ( MethodName :: CONSTRUCT );
2021-05-09 20:15:43 +00:00
if ( ! $construct ) {
2019-11-11 13:06:40 +00:00
return self :: DEFAULT_EXCEPTION_ARGUMENT_POSITION ;
}
2022-07-18 15:25:27 +00:00
$extendedMethodReflection = $classReflection -> getConstructor ();
2023-02-21 17:09:39 +00:00
$parametersAcceptorWithPhpDocs = ParametersAcceptorSelector :: selectSingle ( $extendedMethodReflection -> getVariants ());
foreach ( $parametersAcceptorWithPhpDocs -> getParameters () as $position => $parameterReflectionWithPhpDoc ) {
$parameterType = $parameterReflectionWithPhpDoc -> getType ();
2022-06-07 08:22:29 +00:00
if ( ! $parameterType instanceof TypeWithClassName ) {
2019-11-11 13:06:40 +00:00
continue ;
2019-07-23 09:07:32 +00:00
}
2022-06-07 08:22:29 +00:00
$objectType = new ObjectType ( 'Throwable' );
2021-03-21 12:12:35 +00:00
if ( $objectType -> isSuperTypeOf ( $parameterType ) -> no ()) {
2019-11-11 13:06:40 +00:00
continue ;
}
return $position ;
}
return null ;
2019-07-23 09:07:32 +00:00
}
}