diff --git a/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/named_arg_add_new.php.inc b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/named_arg_add_new.php.inc new file mode 100644 index 00000000000..17b82f698d5 --- /dev/null +++ b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/named_arg_add_new.php.inc @@ -0,0 +1,35 @@ +run(b: 5); + } +} + +?> +----- +run(b: 5, c: 4); + } +} + +?> diff --git a/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/named_arg_mixed_positional_named.php.inc b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/named_arg_mixed_positional_named.php.inc new file mode 100644 index 00000000000..7d1f11d8254 --- /dev/null +++ b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/named_arg_mixed_positional_named.php.inc @@ -0,0 +1,37 @@ +run(0, c: 6); + $obj->run(0, b: 6); + } +} + +?> +----- +run(0, c: 6); + $obj->run(0, b: 6, c: 4); + } +} + +?> diff --git a/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/skip_named_arg_already_present.php.inc b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/skip_named_arg_already_present.php.inc new file mode 100644 index 00000000000..c59e14e3d4b --- /dev/null +++ b/rules-tests/Arguments/Rector/ClassMethod/ArgumentAdderRector/Fixture/skip_named_arg_already_present.php.inc @@ -0,0 +1,16 @@ +run(b: 5, c: 999); + $obj->run(c: 999, b: 5); + } +} diff --git a/rules-tests/Arguments/Rector/ClassMethod/ReplaceArgumentDefaultValueRector/Fixture/skip_named_arguments.php.inc b/rules-tests/Arguments/Rector/ClassMethod/ReplaceArgumentDefaultValueRector/Fixture/skip_named_arguments.php.inc new file mode 100644 index 00000000000..89276731361 --- /dev/null +++ b/rules-tests/Arguments/Rector/ClassMethod/ReplaceArgumentDefaultValueRector/Fixture/skip_named_arguments.php.inc @@ -0,0 +1,14 @@ +setSomeMethod(someValue: 'some value'); + } +} diff --git a/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/skip_named_arguments.php.inc b/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/skip_named_arguments.php.inc new file mode 100644 index 00000000000..f2a5b1563ac --- /dev/null +++ b/rules-tests/Arguments/Rector/FuncCall/FunctionArgumentDefaultValueReplacerRector/Fixture/skip_named_arguments.php.inc @@ -0,0 +1,12 @@ +process(first: 1, second: 2); + $caller->process(1, second: 2); + } +} diff --git a/rules/Arguments/ArgumentDefaultValueReplacer.php b/rules/Arguments/ArgumentDefaultValueReplacer.php index 36794967ded..7ca67bca3f1 100644 --- a/rules/Arguments/ArgumentDefaultValueReplacer.php +++ b/rules/Arguments/ArgumentDefaultValueReplacer.php @@ -15,6 +15,7 @@ use PhpParser\Node\Stmt\ClassMethod; use Rector\Arguments\Contract\ReplaceArgumentDefaultValueInterface; use Rector\Arguments\ValueObject\ReplaceArgumentDefaultValue; +use Rector\NodeAnalyzer\ArgsAnalyzer; use Rector\PhpParser\Node\NodeFactory; use Rector\PhpParser\Node\Value\ValueResolver; @@ -22,7 +23,8 @@ { public function __construct( private NodeFactory $nodeFactory, - private ValueResolver $valueResolver + private ValueResolver $valueResolver, + private ArgsAnalyzer $argsAnalyzer ) { } @@ -101,6 +103,10 @@ private function processArgs( return null; } + if ($this->argsAnalyzer->hasNamedArg($expr->getArgs())) { + return null; + } + $position = $replaceArgumentDefaultValue->getPosition(); $particularArg = $expr->getArgs()[$position] ?? null; if (! $particularArg instanceof Arg) { diff --git a/rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php b/rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php index d39b107f705..3981d5110b1 100644 --- a/rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php +++ b/rules/Arguments/Rector/ClassMethod/ArgumentAdderRector.php @@ -11,6 +11,7 @@ use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Expr\Variable; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Param; use PhpParser\Node\Stmt\Class_; @@ -24,6 +25,7 @@ use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Enum\ObjectReference; use Rector\Exception\ShouldNotHappenException; +use Rector\NodeAnalyzer\ArgsAnalyzer; use Rector\PhpParser\AstResolver; use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; use Rector\Rector\AbstractRector; @@ -48,7 +50,8 @@ public function __construct( private readonly ArgumentAddingScope $argumentAddingScope, private readonly ChangedArgumentsDetector $changedArgumentsDetector, private readonly AstResolver $astResolver, - private readonly StaticTypeMapper $staticTypeMapper + private readonly StaticTypeMapper $staticTypeMapper, + private readonly ArgsAnalyzer $argsAnalyzer ) { } @@ -181,13 +184,18 @@ private function processMethodCall( $defaultValue = $argumentAdder->getArgumentDefaultValue(); $arg = new Arg(BuilderHelpers::normalizeValue($defaultValue)); - if (isset($methodCall->args[$position])) { - return; - } - $this->fillGapBetweenWithDefaultValue($methodCall, $position); + if ($this->argsAnalyzer->hasNamedArg($methodCall->getArgs())) { + $argumentName = $argumentAdder->getArgumentName(); + if ($argumentName === null) { + return; + } + $arg->name = new Identifier($argumentName); + } else { + $this->fillGapBetweenWithDefaultValue($methodCall, $position); + } - $methodCall->args[$position] = $arg; + $methodCall->args[] = $arg; $this->hasChanged = true; } @@ -249,8 +257,18 @@ private function shouldSkipParameter( return $this->changedArgumentsDetector->isTypeChanged($param, $argumentAdder->getArgumentType()); } - if (isset($node->args[$position])) { - return true; + // If named arguments exist + if ($this->argsAnalyzer->hasNamedArg($node->getArgs())) { + // Check if the parameter we're trying to add is already present as a named argument + foreach ($node->getArgs() as $arg) { + if ($arg->name instanceof Identifier && $this->isName($arg->name, $argumentName)) { + return true; // Skip - parameter already provided with name + } + } + } else { + if (isset($node->args[$position])) { + return true; + } } // Check if default value is the same @@ -333,9 +351,15 @@ private function processStaticCall( return; } - $this->fillGapBetweenWithDefaultValue($staticCall, $position); + // Handle named arguments case - add as named argument at the end + $arg = new Arg(new Variable($argumentName)); + if ($this->argsAnalyzer->hasNamedArg($staticCall->getArgs())) { + $arg->name = new Identifier($argumentName); + } else { + $this->fillGapBetweenWithDefaultValue($staticCall, $position); + } - $staticCall->args[$position] = new Arg(new Variable($argumentName)); + $staticCall->args[] = $arg; $this->hasChanged = true; } diff --git a/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php b/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php index 7427216eb84..1e174e56735 100644 --- a/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php +++ b/rules/Arguments/Rector/MethodCall/RemoveMethodCallParamRector.php @@ -9,6 +9,7 @@ use PhpParser\Node\Expr\StaticCall; use Rector\Arguments\ValueObject\RemoveMethodCallParam; use Rector\Contract\Rector\ConfigurableRectorInterface; +use Rector\NodeAnalyzer\ArgsAnalyzer; use Rector\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -25,6 +26,11 @@ final class RemoveMethodCallParamRector extends AbstractRector implements Config */ private array $removeMethodCallParams = []; + public function __construct( + private readonly ArgsAnalyzer $argsAnalyzer, + ) { + } + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Remove parameter of method call', [ @@ -73,6 +79,10 @@ public function refactor(Node $node): ?Node return null; } + if ($this->argsAnalyzer->hasNamedArg($node->getArgs())) { + return null; + } + foreach ($this->removeMethodCallParams as $removeMethodCallParam) { if (! $this->isName($node->name, $removeMethodCallParam->getMethodName())) { continue;