From 44e92e3490b2be8fcf7d23b11f6b2eb58275bde3 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 16 Dec 2025 03:42:51 +0700 Subject: [PATCH 1/5] [Symfony73] Fix named arg different position crash on InvokableCommandInputAttributeRector --- ..._named_arg_mode_different_position.php.inc | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_named_arg_mode_different_position.php.inc diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_named_arg_mode_different_position.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_named_arg_mode_different_position.php.inc new file mode 100644 index 000000000..9b61f6156 --- /dev/null +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_named_arg_mode_different_position.php.inc @@ -0,0 +1,60 @@ +addOption( + name: 'startDate', + mode: InputOption::VALUE_REQUIRED, + description: 'non empty description needed to reproduce the bug', + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + return Command::SUCCESS; + } +} + +?> +----- + \ No newline at end of file From d22beda1cffd88baf34e519e04652b2fec77619e Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 16 Dec 2025 03:43:42 +0700 Subject: [PATCH 2/5] fix --- .../NodeAnalyzer/CommandOptionsResolver.php | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php index 8219670ab..2e7aac576 100644 --- a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php +++ b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php @@ -6,6 +6,7 @@ use PhpParser\Node\Arg; use PhpParser\Node\Expr; +use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Type\Type; use Rector\NodeTypeResolver\NodeTypeResolver; @@ -32,34 +33,29 @@ public function resolve(ClassMethod $configureClassMethod): array $commandOptions = []; foreach ($addOptionMethodCalls as $addOptionMethodCall) { - $addOptionArgs = $addOptionMethodCall->getArgs(); - - $optionName = $this->valueResolver->getValue($addOptionArgs[0]->value); - - $isImplicitBoolean = $this->isImplicitBoolean($addOptionArgs); + $optionName = $this->valueResolver->getValue($addOptionMethodCall->getArg('name', 0)->value); + $isImplicitBoolean = $this->isImplicitBoolean($addOptionMethodCall); $commandOptions[] = new CommandOption( $optionName, - $addOptionArgs[0]->value, - $addOptionArgs[1]->value ?? null, - $addOptionArgs[2]->value ?? null, - $addOptionArgs[3]->value ?? null, - $addOptionArgs[4]->value ?? null, - $this->isArrayMode($addOptionArgs), + $addOptionMethodCall->getArg('name', 0) + ->value, + $addOptionMethodCall->getArg('shortcut', 1)?->value ?? null, + $addOptionMethodCall->getArg('mode', 2)?->value ?? null, + $addOptionMethodCall->getArg('description', 3)?->value ?? null, + $addOptionMethodCall->getArg('default', 4)?->value ?? null, + $this->isArrayMode($addOptionMethodCall), $isImplicitBoolean, - $this->resolveDefaultType($addOptionArgs) + $this->resolveDefaultType($addOptionMethodCall) ); } return $commandOptions; } - /** - * @param Arg[] $args - */ - private function resolveDefaultType(array $args): ?Type + private function resolveDefaultType(MethodCall $methodCall): ?Type { - $defaultArg = $args[4] ?? null; + $defaultArg = $methodCall->getArg('default', 4) ?? null; if (! $defaultArg instanceof Arg) { return null; } @@ -67,12 +63,9 @@ private function resolveDefaultType(array $args): ?Type return $this->nodeTypeResolver->getType($defaultArg->value); } - /** - * @param Arg[] $args - */ - private function isArrayMode(array $args): bool + private function isArrayMode(MethodCall $methodCall): bool { - $modeExpr = $args[2]->value ?? null; + $modeExpr = $methodCall->getArg('mode', 2)?->value ?? null; if (! $modeExpr instanceof Expr) { return false; } @@ -82,12 +75,9 @@ private function isArrayMode(array $args): bool return (bool) ($modeValue & 8); } - /** - * @param Arg[] $args - */ - private function isImplicitBoolean(array $args): bool + private function isImplicitBoolean(MethodCall $methodCall): bool { - $modeExpr = $args[2]->value ?? null; + $modeExpr = $methodCall->getArg('mode', 2)?->value ?? null; if (! $modeExpr instanceof Expr) { return false; } From bd99c61cee8125a339ab7d6d33cf717c97c455cb Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Tue, 16 Dec 2025 03:47:38 +0700 Subject: [PATCH 3/5] fix phpstan --- .../NodeAnalyzer/CommandOptionsResolver.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php index 2e7aac576..00af2ba3a 100644 --- a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php +++ b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php @@ -33,17 +33,16 @@ public function resolve(ClassMethod $configureClassMethod): array $commandOptions = []; foreach ($addOptionMethodCalls as $addOptionMethodCall) { - $optionName = $this->valueResolver->getValue($addOptionMethodCall->getArg('name', 0)->value); + $optionName = $this->valueResolver->getValue($addOptionMethodCall->getArg('name', 0)?->value); $isImplicitBoolean = $this->isImplicitBoolean($addOptionMethodCall); $commandOptions[] = new CommandOption( $optionName, - $addOptionMethodCall->getArg('name', 0) - ->value, - $addOptionMethodCall->getArg('shortcut', 1)?->value ?? null, - $addOptionMethodCall->getArg('mode', 2)?->value ?? null, - $addOptionMethodCall->getArg('description', 3)?->value ?? null, - $addOptionMethodCall->getArg('default', 4)?->value ?? null, + $addOptionMethodCall->getArg('name', 0)?->value, + $addOptionMethodCall->getArg('shortcut', 1)?->value, + $addOptionMethodCall->getArg('mode', 2)?->value, + $addOptionMethodCall->getArg('description', 3)?->value, + $addOptionMethodCall->getArg('default', 4)?->value, $this->isArrayMode($addOptionMethodCall), $isImplicitBoolean, $this->resolveDefaultType($addOptionMethodCall) @@ -65,7 +64,7 @@ private function resolveDefaultType(MethodCall $methodCall): ?Type private function isArrayMode(MethodCall $methodCall): bool { - $modeExpr = $methodCall->getArg('mode', 2)?->value ?? null; + $modeExpr = $methodCall->getArg('mode', 2)?->value; if (! $modeExpr instanceof Expr) { return false; } @@ -77,7 +76,7 @@ private function isArrayMode(MethodCall $methodCall): bool private function isImplicitBoolean(MethodCall $methodCall): bool { - $modeExpr = $methodCall->getArg('mode', 2)?->value ?? null; + $modeExpr = $methodCall->getArg('mode', 2)?->value; if (! $modeExpr instanceof Expr) { return false; } From 5ee1f282c4f76f7a0477943b50f570c2882373b3 Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 20 Dec 2025 20:25:07 +0700 Subject: [PATCH 4/5] final touch: ensure name arg always exists --- rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php index 00af2ba3a..33087a2aa 100644 --- a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php +++ b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php @@ -33,12 +33,17 @@ public function resolve(ClassMethod $configureClassMethod): array $commandOptions = []; foreach ($addOptionMethodCalls as $addOptionMethodCall) { - $optionName = $this->valueResolver->getValue($addOptionMethodCall->getArg('name', 0)?->value); + $nameArg = $addOptionMethodCall->getArg('name', 0); + if (! $nameArg instanceof Arg) { + continue; + } + + $optionName = $this->valueResolver->getValue($nameArg->value); $isImplicitBoolean = $this->isImplicitBoolean($addOptionMethodCall); $commandOptions[] = new CommandOption( $optionName, - $addOptionMethodCall->getArg('name', 0)?->value, + $nameArg->value, $addOptionMethodCall->getArg('shortcut', 1)?->value, $addOptionMethodCall->getArg('mode', 2)?->value, $addOptionMethodCall->getArg('description', 3)?->value, From 2715677185b416dd20819e0e8c06bfd5e80243eb Mon Sep 17 00:00:00 2001 From: Abdul Malik Ikhsan Date: Sat, 20 Dec 2025 20:29:29 +0700 Subject: [PATCH 5/5] really final touch: default expr --- rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php index 33087a2aa..7e7bf456f 100644 --- a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php +++ b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php @@ -59,12 +59,12 @@ public function resolve(ClassMethod $configureClassMethod): array private function resolveDefaultType(MethodCall $methodCall): ?Type { - $defaultArg = $methodCall->getArg('default', 4) ?? null; - if (! $defaultArg instanceof Arg) { + $defaultExpr = $methodCall->getArg('default', 4)?->value; + if (! $defaultExpr instanceof Expr) { return null; } - return $this->nodeTypeResolver->getType($defaultArg->value); + return $this->nodeTypeResolver->getType($defaultExpr); } private function isArrayMode(MethodCall $methodCall): bool