|
10 | 10 | use PhpParser\Node\Expr\MethodCall; |
11 | 11 | use PhpParser\Node\Expr\StaticCall; |
12 | 12 | use PhpParser\Node\Expr\Variable; |
| 13 | +use PhpParser\Node\Identifier; |
13 | 14 | use PhpParser\Node\Name; |
14 | 15 | use PhpParser\Node\Param; |
15 | 16 | use PhpParser\Node\Stmt\Class_; |
|
23 | 24 | use Rector\Contract\Rector\ConfigurableRectorInterface; |
24 | 25 | use Rector\Enum\ObjectReference; |
25 | 26 | use Rector\Exception\ShouldNotHappenException; |
| 27 | +use Rector\NodeAnalyzer\ArgsAnalyzer; |
26 | 28 | use Rector\PhpParser\AstResolver; |
27 | 29 | use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; |
28 | 30 | use Rector\Rector\AbstractRector; |
@@ -51,17 +53,22 @@ final class ArgumentAdderRector extends AbstractRector implements ConfigurableRe |
51 | 53 | * @readonly |
52 | 54 | */ |
53 | 55 | private StaticTypeMapper $staticTypeMapper; |
| 56 | + /** |
| 57 | + * @readonly |
| 58 | + */ |
| 59 | + private ArgsAnalyzer $argsAnalyzer; |
54 | 60 | /** |
55 | 61 | * @var ArgumentAdder[]|ArgumentAdderWithoutDefaultValue[] |
56 | 62 | */ |
57 | 63 | private array $addedArguments = []; |
58 | 64 | private bool $hasChanged = \false; |
59 | | - public function __construct(ArgumentAddingScope $argumentAddingScope, ChangedArgumentsDetector $changedArgumentsDetector, AstResolver $astResolver, StaticTypeMapper $staticTypeMapper) |
| 65 | + public function __construct(ArgumentAddingScope $argumentAddingScope, ChangedArgumentsDetector $changedArgumentsDetector, AstResolver $astResolver, StaticTypeMapper $staticTypeMapper, ArgsAnalyzer $argsAnalyzer) |
60 | 66 | { |
61 | 67 | $this->argumentAddingScope = $argumentAddingScope; |
62 | 68 | $this->changedArgumentsDetector = $changedArgumentsDetector; |
63 | 69 | $this->astResolver = $astResolver; |
64 | 70 | $this->staticTypeMapper = $staticTypeMapper; |
| 71 | + $this->argsAnalyzer = $argsAnalyzer; |
65 | 72 | } |
66 | 73 | public function getRuleDefinition(): RuleDefinition |
67 | 74 | { |
@@ -164,11 +171,17 @@ private function processMethodCall(MethodCall $methodCall, $argumentAdder, int $ |
164 | 171 | } |
165 | 172 | $defaultValue = $argumentAdder->getArgumentDefaultValue(); |
166 | 173 | $arg = new Arg(BuilderHelpers::normalizeValue($defaultValue)); |
167 | | - if (isset($methodCall->args[$position])) { |
168 | | - return; |
| 174 | + // if there are named argyments, we just add it at the end as a new named argument |
| 175 | + if ($this->argsAnalyzer->hasNamedArg($methodCall->getArgs())) { |
| 176 | + $argumentName = $argumentAdder->getArgumentName(); |
| 177 | + if ($argumentName === null) { |
| 178 | + throw new ShouldNotHappenException(); |
| 179 | + } |
| 180 | + $arg->name = new Identifier($argumentName); |
| 181 | + } else { |
| 182 | + $this->fillGapBetweenWithDefaultValue($methodCall, $position); |
169 | 183 | } |
170 | | - $this->fillGapBetweenWithDefaultValue($methodCall, $position); |
171 | | - $methodCall->args[$position] = $arg; |
| 184 | + $methodCall->args[] = $arg; |
172 | 185 | $this->hasChanged = \true; |
173 | 186 | } |
174 | 187 | /** |
@@ -223,7 +236,21 @@ private function shouldSkipParameter($node, $argumentAdder): bool |
223 | 236 | // argument added and type has been changed |
224 | 237 | return $this->changedArgumentsDetector->isTypeChanged($param, $argumentAdder->getArgumentType()); |
225 | 238 | } |
226 | | - if (isset($node->args[$position])) { |
| 239 | + $arguments = $node->getArgs(); |
| 240 | + $firstNamedArgumentPosition = $this->argsAnalyzer->resolveFirstNamedArgPosition($arguments); |
| 241 | + // If named arguments exist |
| 242 | + if ($firstNamedArgumentPosition !== null) { |
| 243 | + // Check if the parameter we're trying to add is before the first named argument |
| 244 | + if ($position < $firstNamedArgumentPosition) { |
| 245 | + return \true; |
| 246 | + //if that is the case, the parameter already exists, skip |
| 247 | + } |
| 248 | + // Check if the parameter we're trying to add is already present as a named argument |
| 249 | + if ($this->argsAnalyzer->resolveArgPosition($arguments, $argumentName, -1) !== -1) { |
| 250 | + return \true; |
| 251 | + // if it exists as a named argument, skip |
| 252 | + } |
| 253 | + } elseif (isset($node->args[$position])) { |
227 | 254 | return \true; |
228 | 255 | } |
229 | 256 | // Check if default value is the same |
@@ -287,8 +314,14 @@ private function processStaticCall(StaticCall $staticCall, int $position, $argum |
287 | 314 | if (!$this->isName($staticCall->class, ObjectReference::PARENT)) { |
288 | 315 | return; |
289 | 316 | } |
290 | | - $this->fillGapBetweenWithDefaultValue($staticCall, $position); |
291 | | - $staticCall->args[$position] = new Arg(new Variable($argumentName)); |
| 317 | + // if there are named arguments, we just add it at the end as a new named argument |
| 318 | + $arg = new Arg(new Variable($argumentName)); |
| 319 | + if ($this->argsAnalyzer->hasNamedArg($staticCall->getArgs())) { |
| 320 | + $arg->name = new Identifier($argumentName); |
| 321 | + } else { |
| 322 | + $this->fillGapBetweenWithDefaultValue($staticCall, $position); |
| 323 | + } |
| 324 | + $staticCall->args[] = $arg; |
292 | 325 | $this->hasChanged = \true; |
293 | 326 | } |
294 | 327 | /** |
|
0 commit comments