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\Arguments\Rector\ClassMethod ;
2018-05-01 17:31:41 +00:00
2022-06-06 17:12:56 +00:00
use PhpParser\BuilderHelpers ;
use PhpParser\Node ;
use PhpParser\Node\Arg ;
use PhpParser\Node\Expr ;
use PhpParser\Node\Expr\MethodCall ;
use PhpParser\Node\Expr\StaticCall ;
use PhpParser\Node\Expr\Variable ;
use PhpParser\Node\Name ;
use PhpParser\Node\Param ;
use PhpParser\Node\Stmt\Class_ ;
use PhpParser\Node\Stmt\ClassMethod ;
use PHPStan\Type\ObjectType ;
use PHPStan\Type\Type ;
use Rector\Arguments\NodeAnalyzer\ArgumentAddingScope ;
use Rector\Arguments\NodeAnalyzer\ChangedArgumentsDetector ;
use Rector\Arguments\ValueObject\ArgumentAdder ;
2023-12-23 19:44:26 +00:00
use Rector\Arguments\ValueObject\ArgumentAdderWithoutDefaultValue ;
2024-01-02 02:40:38 +00:00
use Rector\Contract\Rector\ConfigurableRectorInterface ;
use Rector\Enum\ObjectReference ;
use Rector\Exception\ShouldNotHappenException ;
use Rector\PhpParser\AstResolver ;
2022-06-06 17:12:56 +00:00
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind ;
2024-01-02 02:40:38 +00:00
use Rector\Rector\AbstractRector ;
2023-09-29 12:48:57 +00:00
use Rector\StaticTypeMapper\StaticTypeMapper ;
2022-06-07 09:18:30 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2024-03-01 20:02:28 +00:00
use RectorPrefix202403\Webmozart\Assert\Assert ;
2019-09-03 09:11:45 +00:00
/**
2021-03-12 22:20:25 +00:00
* @ see \Rector\Tests\Arguments\Rector\ClassMethod\ArgumentAdderRector\ArgumentAdderRectorTest
2019-09-03 09:11:45 +00:00
*/
2022-06-07 08:22:29 +00:00
final class ArgumentAdderRector extends AbstractRector implements ConfigurableRectorInterface
2018-05-01 17:31:41 +00:00
{
/**
2023-06-11 23:01:39 +00:00
* @ readonly
2021-05-10 23:39:21 +00:00
* @ var \Rector\Arguments\NodeAnalyzer\ArgumentAddingScope
2020-08-25 21:25:00 +00:00
*/
2021-01-17 15:43:47 +00:00
private $argumentAddingScope ;
2021-09-19 09:13:32 +00:00
/**
2023-06-11 23:01:39 +00:00
* @ readonly
2021-09-19 09:13:32 +00:00
* @ var \Rector\Arguments\NodeAnalyzer\ChangedArgumentsDetector
*/
private $changedArgumentsDetector ;
2022-03-18 13:14:22 +00:00
/**
2023-06-11 23:01:39 +00:00
* @ readonly
2024-01-02 02:40:38 +00:00
* @ var \Rector\PhpParser\AstResolver
2022-03-18 13:14:22 +00:00
*/
private $astResolver ;
2023-09-29 12:48:57 +00:00
/**
* @ readonly
* @ var \Rector\StaticTypeMapper\StaticTypeMapper
*/
private $staticTypeMapper ;
2023-06-08 22:00:17 +00:00
/**
2023-12-23 19:44:26 +00:00
* @ var ArgumentAdder [] | ArgumentAdderWithoutDefaultValue []
2023-06-08 22:00:17 +00:00
*/
private $addedArguments = [];
/**
* @ var bool
*/
2023-06-09 17:42:49 +00:00
private $hasChanged = \false ;
2023-10-04 11:23:15 +00:00
public function __construct ( ArgumentAddingScope $argumentAddingScope , ChangedArgumentsDetector $changedArgumentsDetector , AstResolver $astResolver , StaticTypeMapper $staticTypeMapper )
2021-01-17 15:43:47 +00:00
{
$this -> argumentAddingScope = $argumentAddingScope ;
2021-09-19 09:13:32 +00:00
$this -> changedArgumentsDetector = $changedArgumentsDetector ;
2022-03-18 13:14:22 +00:00
$this -> astResolver = $astResolver ;
2023-09-29 12:48:57 +00:00
$this -> staticTypeMapper = $staticTypeMapper ;
2021-01-17 15:43:47 +00:00
}
2022-06-07 08:22:29 +00:00
public function getRuleDefinition () : RuleDefinition
2018-05-01 17:31:41 +00:00
{
2022-06-07 08:22:29 +00:00
return new RuleDefinition ( 'This Rector adds new default arguments in calls of defined methods and class types.' , [ new ConfiguredCodeSample ( <<< 'CODE_SAMPLE'
2018-10-21 19:43:47 +00:00
$someObject = new SomeExampleClass ;
2018-05-04 12:52:17 +00:00
$someObject -> someMethod ();
2021-08-27 12:14:08 +00:00
2018-10-21 19:43:47 +00:00
class MyCustomClass extends SomeExampleClass
2018-05-04 13:01:35 +00:00
{
public function someMethod ()
{
}
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2021-05-09 20:15:43 +00:00
, <<< 'CODE_SAMPLE'
2021-08-27 12:14:08 +00:00
$someObject = new SomeExampleClass ;
$someObject -> someMethod ( true );
2018-10-21 19:43:47 +00:00
class MyCustomClass extends SomeExampleClass
2018-05-04 13:01:35 +00:00
{
public function someMethod ( $value = true )
{
}
}
2020-09-15 08:23:13 +00:00
CODE_SAMPLE
2022-06-07 08:22:29 +00:00
, [ new ArgumentAdder ( 'SomeExampleClass' , 'someMethod' , 0 , 'someArgument' , \true , new ObjectType ( 'SomeType' ))])]);
2018-05-01 17:31:41 +00:00
}
2018-08-14 22:12:41 +00:00
/**
2021-02-27 00:06:15 +00:00
* @ return array < class - string < Node >>
2018-08-14 22:12:41 +00:00
*/
2021-05-09 20:15:43 +00:00
public function getNodeTypes () : array
2018-05-01 17:31:41 +00:00
{
2023-06-09 17:42:49 +00:00
return [ MethodCall :: class , StaticCall :: class , Class_ :: class ];
2018-05-01 17:31:41 +00:00
}
/**
2023-06-09 17:42:49 +00:00
* @ param MethodCall | StaticCall | Class_ $node
* @ return \PhpParser\Node\Expr\MethodCall | \PhpParser\Node\Expr\StaticCall | \PhpParser\Node\Stmt\Class_ | null
2018-05-01 17:31:41 +00:00
*/
2022-06-07 08:22:29 +00:00
public function refactor ( Node $node )
2018-05-01 17:31:41 +00:00
{
2023-06-09 17:42:49 +00:00
$this -> hasChanged = \false ;
if ( $node instanceof MethodCall || $node instanceof StaticCall ) {
$this -> refactorCall ( $node );
} else {
foreach ( $node -> getMethods () as $classMethod ) {
$this -> refactorClassMethod ( $node , $classMethod );
2018-05-01 17:31:41 +00:00
}
}
2023-06-09 17:42:49 +00:00
if ( $this -> hasChanged ) {
2021-08-27 12:14:08 +00:00
return $node ;
}
return null ;
2018-05-01 17:31:41 +00:00
}
2020-08-22 20:02:50 +00:00
/**
2021-11-28 17:01:20 +00:00
* @ param mixed [] $configuration
2020-08-22 20:02:50 +00:00
*/
2021-12-10 10:22:23 +00:00
public function configure ( array $configuration ) : void
2020-07-29 23:39:41 +00:00
{
2023-12-23 19:44:26 +00:00
Assert :: allIsAnyOf ( $configuration , [ ArgumentAdder :: class , ArgumentAdderWithoutDefaultValue :: class ]);
2022-02-18 01:46:17 +00:00
$this -> addedArguments = $configuration ;
2020-07-29 23:39:41 +00:00
}
2019-10-30 09:49:07 +00:00
/**
2023-06-09 17:42:49 +00:00
* @ param \PhpParser\Node\Expr\MethodCall | \PhpParser\Node\Expr\StaticCall $call
2019-10-30 09:49:07 +00:00
*/
2023-06-09 17:42:49 +00:00
private function isObjectTypeMatch ( $call , ObjectType $objectType ) : bool
2019-10-30 09:49:07 +00:00
{
2023-06-09 17:42:49 +00:00
if ( $call instanceof MethodCall ) {
return $this -> isObjectType ( $call -> var , $objectType );
2019-10-30 09:49:07 +00:00
}
2023-06-09 17:42:49 +00:00
return $this -> isObjectType ( $call -> class , $objectType );
2019-10-30 09:49:07 +00:00
}
2018-05-01 17:31:41 +00:00
/**
2022-04-26 08:13:18 +00:00
* @ param \PhpParser\Node\Stmt\ClassMethod | \PhpParser\Node\Expr\MethodCall | \PhpParser\Node\Expr\StaticCall $node
2023-12-23 19:44:26 +00:00
* @ param \Rector\Arguments\ValueObject\ArgumentAdder | \Rector\Arguments\ValueObject\ArgumentAdderWithoutDefaultValue $argumentAdder
2018-05-01 17:31:41 +00:00
*/
2023-12-23 19:44:26 +00:00
private function processPositionWithDefaultValues ( $node , $argumentAdder ) : void
2018-05-01 17:31:41 +00:00
{
2020-09-12 21:19:08 +00:00
if ( $this -> shouldSkipParameter ( $node , $argumentAdder )) {
2020-08-25 21:25:00 +00:00
return ;
}
2020-09-12 21:19:08 +00:00
$argumentType = $argumentAdder -> getArgumentType ();
$position = $argumentAdder -> getPosition ();
2022-06-07 08:22:29 +00:00
if ( $node instanceof ClassMethod ) {
2023-12-23 19:44:26 +00:00
$this -> addClassMethodParam ( $node , $argumentAdder , $argumentType , $position );
2022-03-18 13:14:22 +00:00
return ;
}
2022-06-07 08:22:29 +00:00
if ( $node instanceof StaticCall ) {
2020-10-09 20:00:45 +00:00
$this -> processStaticCall ( $node , $position , $argumentAdder );
2022-03-18 13:14:22 +00:00
return ;
}
2023-12-23 19:44:26 +00:00
$this -> processMethodCall ( $node , $argumentAdder , $position );
2022-03-18 13:14:22 +00:00
}
/**
2023-12-23 19:44:26 +00:00
* @ param \Rector\Arguments\ValueObject\ArgumentAdder | \Rector\Arguments\ValueObject\ArgumentAdderWithoutDefaultValue $argumentAdder
2022-03-18 13:14:22 +00:00
*/
2023-12-23 19:44:26 +00:00
private function processMethodCall ( MethodCall $methodCall , $argumentAdder , int $position ) : void
2022-03-18 13:14:22 +00:00
{
2023-12-23 19:44:26 +00:00
if ( $argumentAdder instanceof ArgumentAdderWithoutDefaultValue ) {
return ;
}
$defaultValue = $argumentAdder -> getArgumentDefaultValue ();
2022-06-07 08:22:29 +00:00
$arg = new Arg ( BuilderHelpers :: normalizeValue ( $defaultValue ));
2022-03-18 13:14:22 +00:00
if ( isset ( $methodCall -> args [ $position ])) {
return ;
}
$this -> fillGapBetweenWithDefaultValue ( $methodCall , $position );
$methodCall -> args [ $position ] = $arg ;
2023-06-09 17:42:49 +00:00
$this -> hasChanged = \true ;
2022-03-18 13:14:22 +00:00
}
/**
* @ param \PhpParser\Node\Expr\MethodCall | \PhpParser\Node\Expr\StaticCall $node
*/
private function fillGapBetweenWithDefaultValue ( $node , int $position ) : void
{
$lastPosition = \count ( $node -> getArgs ()) - 1 ;
if ( $position <= $lastPosition ) {
return ;
}
if ( $position - $lastPosition === 1 ) {
return ;
}
$classMethod = $this -> astResolver -> resolveClassMethodFromCall ( $node );
2022-06-07 08:22:29 +00:00
if ( ! $classMethod instanceof ClassMethod ) {
2022-03-18 13:14:22 +00:00
return ;
}
for ( $index = $lastPosition + 1 ; $index < $position ; ++ $index ) {
$param = $classMethod -> params [ $index ];
2022-06-07 08:22:29 +00:00
if ( ! $param -> default instanceof Expr ) {
2023-02-14 13:44:15 +00:00
throw new ShouldNotHappenException ( 'Previous position does not have default value' );
2020-10-09 20:00:45 +00:00
}
2023-10-04 11:38:51 +00:00
$node -> args [ $index ] = new Arg ( $this -> nodeFactory -> createReprintedExpr ( $param -> default ));
2018-05-01 17:31:41 +00:00
}
}
2019-03-08 23:40:33 +00:00
/**
2022-04-26 08:13:18 +00:00
* @ param \PhpParser\Node\Stmt\ClassMethod | \PhpParser\Node\Expr\MethodCall | \PhpParser\Node\Expr\StaticCall $node
2023-12-23 19:44:26 +00:00
* @ param \Rector\Arguments\ValueObject\ArgumentAdder | \Rector\Arguments\ValueObject\ArgumentAdderWithoutDefaultValue $argumentAdder
2019-03-08 23:40:33 +00:00
*/
2023-12-23 19:44:26 +00:00
private function shouldSkipParameter ( $node , $argumentAdder ) : bool
2019-03-08 23:40:33 +00:00
{
2020-09-12 21:19:08 +00:00
$position = $argumentAdder -> getPosition ();
$argumentName = $argumentAdder -> getArgumentName ();
2021-01-28 19:52:11 +00:00
if ( $argumentName === null ) {
2021-05-09 20:15:43 +00:00
return \true ;
2021-01-28 19:52:11 +00:00
}
2022-06-07 08:22:29 +00:00
if ( $node instanceof ClassMethod ) {
2019-03-08 23:40:33 +00:00
// already added?
2021-05-09 20:15:43 +00:00
if ( ! isset ( $node -> params [ $position ])) {
return \false ;
2020-12-19 15:24:53 +00:00
}
2021-09-19 09:13:32 +00:00
$param = $node -> params [ $position ];
// argument added and name has been changed
if ( ! $this -> isName ( $param , $argumentName )) {
return \true ;
}
// argument added and default has been changed
2023-12-23 19:44:26 +00:00
if ( $this -> isDefaultValueChanged ( $argumentAdder , $node , $position )) {
2021-09-19 09:13:32 +00:00
return \true ;
}
// argument added and type has been changed
return $this -> changedArgumentsDetector -> isTypeChanged ( $param , $argumentAdder -> getArgumentType ());
2019-03-08 23:40:33 +00:00
}
2021-08-27 12:14:08 +00:00
if ( isset ( $node -> args [ $position ])) {
return \true ;
2021-02-21 09:32:45 +00:00
}
2023-06-27 15:24:30 +00:00
// Check if default value is the same
$classMethod = $this -> astResolver -> resolveClassMethodFromCall ( $node );
2023-06-27 20:26:20 +00:00
if ( ! $classMethod instanceof ClassMethod ) {
// is correct scope?
return ! $this -> argumentAddingScope -> isInCorrectScope ( $node , $argumentAdder );
}
if ( ! isset ( $classMethod -> params [ $position ])) {
// is correct scope?
return ! $this -> argumentAddingScope -> isInCorrectScope ( $node , $argumentAdder );
}
2023-12-23 19:44:26 +00:00
if ( $this -> isDefaultValueChanged ( $argumentAdder , $classMethod , $position )) {
2023-06-27 20:26:20 +00:00
// is correct scope?
return ! $this -> argumentAddingScope -> isInCorrectScope ( $node , $argumentAdder );
2023-06-27 15:24:30 +00:00
}
2023-06-27 20:26:20 +00:00
return \true ;
2019-03-08 23:40:33 +00:00
}
2020-07-27 06:56:25 +00:00
/**
2023-12-23 19:44:26 +00:00
* @ param \Rector\Arguments\ValueObject\ArgumentAdder | \Rector\Arguments\ValueObject\ArgumentAdderWithoutDefaultValue $argumentAdder
*/
private function isDefaultValueChanged ( $argumentAdder , ClassMethod $classMethod , int $position ) : bool
{
return $argumentAdder instanceof ArgumentAdder && $this -> changedArgumentsDetector -> isDefaultValueChanged ( $classMethod -> params [ $position ], $argumentAdder -> getArgumentDefaultValue ());
}
/**
* @ param \Rector\Arguments\ValueObject\ArgumentAdder | \Rector\Arguments\ValueObject\ArgumentAdderWithoutDefaultValue $argumentAdder
2020-07-27 06:56:25 +00:00
*/
2023-12-23 19:44:26 +00:00
private function addClassMethodParam ( ClassMethod $classMethod , $argumentAdder , ? Type $type , int $position ) : void
2021-05-09 20:15:43 +00:00
{
2020-10-09 20:00:45 +00:00
$argumentName = $argumentAdder -> getArgumentName ();
if ( $argumentName === null ) {
2022-06-07 08:22:29 +00:00
throw new ShouldNotHappenException ();
2020-10-09 20:00:45 +00:00
}
2023-12-23 19:44:26 +00:00
if ( $argumentAdder instanceof ArgumentAdder ) {
$param = new Param ( new Variable ( $argumentName ), BuilderHelpers :: normalizeValue ( $argumentAdder -> getArgumentDefaultValue ()));
} else {
$param = new Param ( new Variable ( $argumentName ));
}
2023-03-23 23:21:34 +00:00
if ( $type instanceof Type ) {
2022-07-03 12:14:45 +00:00
$param -> type = $this -> staticTypeMapper -> mapPHPStanTypeToPhpParserNode ( $type , TypeKind :: PARAM );
2020-02-01 16:04:38 +00:00
}
$classMethod -> params [ $position ] = $param ;
2023-06-09 17:42:49 +00:00
$this -> hasChanged = \true ;
2020-02-01 16:04:38 +00:00
}
2023-12-23 19:44:26 +00:00
/**
* @ param \Rector\Arguments\ValueObject\ArgumentAdder | \Rector\Arguments\ValueObject\ArgumentAdderWithoutDefaultValue $argumentAdder
*/
private function processStaticCall ( StaticCall $staticCall , int $position , $argumentAdder ) : void
2020-02-01 16:04:38 +00:00
{
2023-12-23 19:44:26 +00:00
if ( $argumentAdder instanceof ArgumentAdderWithoutDefaultValue ) {
return ;
}
2020-10-09 20:00:45 +00:00
$argumentName = $argumentAdder -> getArgumentName ();
if ( $argumentName === null ) {
2022-06-07 08:22:29 +00:00
throw new ShouldNotHappenException ();
2020-10-09 20:00:45 +00:00
}
2022-06-07 08:22:29 +00:00
if ( ! $staticCall -> class instanceof Name ) {
2020-02-01 16:04:38 +00:00
return ;
}
2022-06-07 08:22:29 +00:00
if ( ! $this -> isName ( $staticCall -> class , ObjectReference :: PARENT )) {
2020-02-01 16:04:38 +00:00
return ;
}
2022-03-18 13:14:22 +00:00
$this -> fillGapBetweenWithDefaultValue ( $staticCall , $position );
2022-06-07 08:22:29 +00:00
$staticCall -> args [ $position ] = new Arg ( new Variable ( $argumentName ));
2023-06-09 17:42:49 +00:00
$this -> hasChanged = \true ;
}
/**
* @ param \PhpParser\Node\Expr\StaticCall | \PhpParser\Node\Expr\MethodCall $call
*/
private function refactorCall ( $call ) : void
{
2023-09-09 18:18:18 +00:00
$callName = $this -> getName ( $call -> name );
if ( $callName === null ) {
return ;
}
2023-06-09 17:42:49 +00:00
foreach ( $this -> addedArguments as $addedArgument ) {
2023-09-09 18:18:18 +00:00
if ( ! $this -> nodeNameResolver -> isStringName ( $callName , $addedArgument -> getMethod ())) {
2023-06-09 17:42:49 +00:00
continue ;
}
if ( ! $this -> isObjectTypeMatch ( $call , $addedArgument -> getObjectType ())) {
continue ;
}
$this -> processPositionWithDefaultValues ( $call , $addedArgument );
}
}
private function refactorClassMethod ( Class_ $class , ClassMethod $classMethod ) : void
{
foreach ( $this -> addedArguments as $addedArgument ) {
if ( ! $this -> isName ( $classMethod , $addedArgument -> getMethod ())) {
continue ;
}
if ( ! $this -> isObjectType ( $class , $addedArgument -> getObjectType ())) {
continue ;
}
$this -> processPositionWithDefaultValues ( $classMethod , $addedArgument );
}
2020-02-01 16:04:38 +00:00
}
2018-05-01 17:31:41 +00:00
}