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\ConstFetch ;
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 ;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface ;
use Rector\Core\Enum\ObjectReference ;
use Rector\Core\Exception\ShouldNotHappenException ;
use Rector\Core\PhpParser\AstResolver ;
2023-06-08 14:23:03 +00:00
use Rector\Core\PhpParser\Printer\BetterStandardPrinter ;
2022-06-06 17:12:56 +00:00
use Rector\Core\Rector\AbstractRector ;
use Rector\PHPStanStaticTypeMapper\Enum\TypeKind ;
2022-06-07 09:18:30 +00:00
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample ;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition ;
2023-06-01 08:56:46 +00:00
use RectorPrefix202306\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
{
2020-07-29 23:39:41 +00:00
/**
2021-01-17 15:43:47 +00:00
* @ var ArgumentAdder []
2020-08-25 21:25:00 +00:00
*/
2021-01-17 15:43:47 +00:00
private $addedArguments = [];
2021-08-27 12:14:08 +00:00
/**
* @ var bool
*/
private $haveArgumentsChanged = \false ;
2018-05-01 17:31:41 +00:00
/**
2021-12-04 12:47:17 +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
/**
2021-12-04 12:47:17 +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
/**
* @ readonly
* @ var \Rector\Core\PhpParser\AstResolver
*/
private $astResolver ;
2022-04-09 22:42:24 +00:00
/**
* @ readonly
2023-06-08 14:23:03 +00:00
* @ var \Rector\Core\PhpParser\Printer\BetterStandardPrinter
2022-04-09 22:42:24 +00:00
*/
2023-06-08 14:23:03 +00:00
private $betterStandardPrinter ;
public function __construct ( ArgumentAddingScope $argumentAddingScope , ChangedArgumentsDetector $changedArgumentsDetector , AstResolver $astResolver , BetterStandardPrinter $betterStandardPrinter )
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-06-08 14:23:03 +00:00
$this -> betterStandardPrinter = $betterStandardPrinter ;
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
{
2022-06-07 08:22:29 +00:00
return [ MethodCall :: class , StaticCall :: class , ClassMethod :: class ];
2018-05-01 17:31:41 +00:00
}
/**
2021-12-10 10:22:23 +00:00
* @ param MethodCall | StaticCall | ClassMethod $node
2022-08-11 14:46:08 +00:00
* @ return \PhpParser\Node\Expr\MethodCall | \PhpParser\Node\Expr\StaticCall | \PhpParser\Node\Stmt\ClassMethod | 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
{
2021-08-27 12:14:08 +00:00
$this -> haveArgumentsChanged = \false ;
2020-08-25 21:25:00 +00:00
foreach ( $this -> addedArguments as $addedArgument ) {
2023-03-22 14:20:22 +00:00
if ( ! $this -> isName ( $node -> name , $addedArgument -> getMethod ())) {
2018-10-21 19:43:47 +00:00
continue ;
}
2023-03-22 14:20:22 +00:00
if ( ! $this -> isObjectTypeMatch ( $node , $addedArgument -> getObjectType ())) {
2020-08-25 21:25:00 +00:00
continue ;
2018-05-01 17:31:41 +00:00
}
2020-08-25 21:25:00 +00:00
$this -> processPositionWithDefaultValues ( $node , $addedArgument );
2018-05-01 17:31:41 +00:00
}
2021-08-27 12:14:08 +00:00
if ( $this -> haveArgumentsChanged ) {
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
{
2022-06-07 08:22:29 +00:00
Assert :: allIsAOf ( $configuration , ArgumentAdder :: 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
/**
2021-08-23 00:20:32 +00:00
* @ param \PhpParser\Node\Expr\MethodCall | \PhpParser\Node\Expr\StaticCall | \PhpParser\Node\Stmt\ClassMethod $node
2019-10-30 09:49:07 +00:00
*/
2022-06-07 08:22:29 +00:00
private function isObjectTypeMatch ( $node , ObjectType $objectType ) : bool
2019-10-30 09:49:07 +00:00
{
2022-06-07 08:22:29 +00:00
if ( $node instanceof MethodCall ) {
2021-02-27 02:13:22 +00:00
return $this -> isObjectType ( $node -> var , $objectType );
2019-10-30 09:49:07 +00:00
}
2022-06-07 08:22:29 +00:00
if ( $node instanceof StaticCall ) {
2021-02-27 02:13:22 +00:00
return $this -> isObjectType ( $node -> class , $objectType );
2019-10-30 09:49:07 +00:00
}
2022-06-07 08:22:29 +00:00
$classLike = $this -> betterNodeFinder -> findParentType ( $node , Class_ :: class );
if ( ! $classLike instanceof Class_ ) {
2021-05-09 20:15:43 +00:00
return \false ;
2019-10-30 09:49:07 +00:00
}
2021-02-27 02:13:22 +00:00
return $this -> isObjectType ( $classLike , $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
2018-05-01 17:31:41 +00:00
*/
2022-06-07 08:22:29 +00:00
private function processPositionWithDefaultValues ( $node , ArgumentAdder $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
$defaultValue = $argumentAdder -> getArgumentDefaultValue ();
$argumentType = $argumentAdder -> getArgumentType ();
$position = $argumentAdder -> getPosition ();
2022-06-07 08:22:29 +00:00
if ( $node instanceof ClassMethod ) {
2020-10-09 20:00:45 +00:00
$this -> addClassMethodParam ( $node , $argumentAdder , $defaultValue , $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 ;
}
$this -> processMethodCall ( $node , $defaultValue , $position );
}
/**
* @ param mixed $defaultValue
*/
2022-06-07 08:22:29 +00:00
private function processMethodCall ( MethodCall $methodCall , $defaultValue , int $position ) : void
2022-03-18 13:14:22 +00:00
{
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 ;
$this -> haveArgumentsChanged = \true ;
}
/**
* @ 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-06-08 14:23:03 +00:00
$default = $this -> betterStandardPrinter -> print ( $param -> default );
2022-06-07 08:22:29 +00:00
$node -> args [ $index ] = new Arg ( new ConstFetch ( new Name ( $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
2019-03-08 23:40:33 +00:00
*/
2022-06-07 08:22:29 +00:00
private function shouldSkipParameter ( $node , ArgumentAdder $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
if ( $this -> changedArgumentsDetector -> isDefaultValueChanged ( $param , $argumentAdder -> getArgumentDefaultValue ())) {
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
}
2021-08-27 12:14:08 +00:00
// is correct scope?
return ! $this -> argumentAddingScope -> isInCorrectScope ( $node , $argumentAdder );
2019-03-08 23:40:33 +00:00
}
2020-07-27 06:56:25 +00:00
/**
* @ param mixed $defaultValue
*/
2022-06-07 08:22:29 +00:00
private function addClassMethodParam ( ClassMethod $classMethod , ArgumentAdder $argumentAdder , $defaultValue , ? 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
}
2022-06-07 08:22:29 +00:00
$param = new Param ( new Variable ( $argumentName ), BuilderHelpers :: normalizeValue ( $defaultValue ));
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 ;
2021-08-27 12:14:08 +00:00
$this -> haveArgumentsChanged = \true ;
2020-02-01 16:04:38 +00:00
}
2022-06-07 08:22:29 +00:00
private function processStaticCall ( StaticCall $staticCall , int $position , ArgumentAdder $argumentAdder ) : void
2020-02-01 16:04:38 +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
}
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 ));
2021-08-27 12:14:08 +00:00
$this -> haveArgumentsChanged = \true ;
2020-02-01 16:04:38 +00:00
}
2018-05-01 17:31:41 +00:00
}