From 664dd13faf6ca288282d3e41d4ddfaa391847b44 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 10 Sep 2025 14:47:38 +0200 Subject: [PATCH 1/2] add fixture --- .../implicit_no_value_bool_option.php.inc | 52 +++++++++++++++++++ .../InvokableCommandInputAttributeRector.php | 29 ++++++----- 2 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/implicit_no_value_bool_option.php.inc diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/implicit_no_value_bool_option.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/implicit_no_value_bool_option.php.inc new file mode 100644 index 000000000..d911a55fd --- /dev/null +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/DefaultValue/implicit_no_value_bool_option.php.inc @@ -0,0 +1,52 @@ +addOption('third', null, InputOption::VALUE_NONE); + } + protected function execute(InputInterface $input, OutputInterface $output): int + { + } +} + +?> +----- + diff --git a/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php b/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php index e3e5976ae..876c7fedb 100644 --- a/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php +++ b/rules/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector.php @@ -156,7 +156,6 @@ public function refactor(Node $node): ?Class_ if ($configureClassMethod instanceof ClassMethod) { // 3. create arguments and options parameters $commandArguments = $this->commandArgumentsResolver->resolve($configureClassMethod); - $commandOptions = $this->commandOptionsResolver->resolve($configureClassMethod); // 4. remove configure() method @@ -173,17 +172,7 @@ public function refactor(Node $node): ?Class_ // 6. remove parent class $node->extends = null; - foreach ($executeClassMethod->attrGroups as $attrGroupKey => $attrGroup) { - foreach ($attrGroup->attrs as $attributeKey => $attr) { - if ($this->isName($attr->name, 'Override')) { - unset($attrGroup->attrs[$attributeKey]); - } - } - - if ($attrGroup->attrs === []) { - unset($executeClassMethod->attrGroups[$attrGroupKey]); - } - } + $this->removeOverrideAttributeAsDifferentMethod($executeClassMethod); if ($configureClassMethod instanceof ClassMethod) { // 7. replace input->getArgument() and input->getOption() calls with direct variable access @@ -266,4 +255,20 @@ private function isFluentArgumentOptionChain(MethodCall $methodCall): bool // the left-most var must be $this return $current instanceof Variable && $this->isName($current, 'this'); } + + private function removeOverrideAttributeAsDifferentMethod(ClassMethod $executeClassMethod): void + { + foreach ($executeClassMethod->attrGroups as $attrGroupKey => $attrGroup) { + foreach ($attrGroup->attrs as $attributeKey => $attr) { + if ($this->isName($attr->name, 'Override')) { + unset($attrGroup->attrs[$attributeKey]); + } + } + + // is attribute empty? remove whole group + if ($attrGroup->attrs === []) { + unset($executeClassMethod->attrGroups[$attrGroupKey]); + } + } + } } From 739bbff8b0d942a14d17eaeacabfc735fbb80c7a Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 10 Sep 2025 14:54:26 +0200 Subject: [PATCH 2/2] [console] [7.3] Add implicit boolean type --- .../Fixture/some_class.php | 28 +++++++++++++++++++ .../Fixture/some_command.php.inc | 2 +- .../some_command_with_method_chaining.php.inc | 2 +- .../some_command_with_set_help.php.inc | 2 +- ..._multiple_arguments_options_fluent.php.inc | 4 +-- ...ltiple_arguments_options_no_fluent.php.inc | 4 +-- .../Fixture/with_optional_argument.php.inc | 2 +- .../Fixture/with_override.php.inc | 2 +- .../NodeAnalyzer/CommandOptionsResolver.php | 20 ++++++++++++- .../CommandInvokeParamsFactory.php | 9 ++++++ rules/Symfony73/ValueObject/CommandOption.php | 6 ++++ ...replace_new_cookie_parameter_value.php.inc | 1 + ..._cookie_and_create_parameter_value.php.inc | 1 + ...kie_with_same_site_parameter_value.php.inc | 1 + .../Fixture/command_remove_config.php.inc | 2 +- 15 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 rules-tests/Symfony51/Rector/ClassMethod/RouteCollectionBuilderToRoutingConfiguratorRector/Fixture/some_class.php diff --git a/rules-tests/Symfony51/Rector/ClassMethod/RouteCollectionBuilderToRoutingConfiguratorRector/Fixture/some_class.php b/rules-tests/Symfony51/Rector/ClassMethod/RouteCollectionBuilderToRoutingConfiguratorRector/Fixture/some_class.php new file mode 100644 index 000000000..a178e7c60 --- /dev/null +++ b/rules-tests/Symfony51/Rector/ClassMethod/RouteCollectionBuilderToRoutingConfiguratorRector/Fixture/some_class.php @@ -0,0 +1,28 @@ +add('/admin', 'App\Controller\AdminController::dashboard', 'admin_dashboard'); + } + + public function registerBundles() + { + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} + +?> diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc index 6679395fe..3e48572db 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command.php.inc @@ -47,7 +47,7 @@ final class SomeCommand { public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - $option): int + bool $option = false): int { $someArgument = $argument; $someOption = $option; diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc index 6f1ab9b6a..249388936 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_method_chaining.php.inc @@ -48,7 +48,7 @@ final class SomeCommandWithMethodChaining { public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - $option): int + bool $option = false): int { $someArgument = $argument; $someOption = $option; diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc index d93b8d354..a78a933dc 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/some_command_with_set_help.php.inc @@ -53,7 +53,7 @@ final class SomeCommandWithSetHelp public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - $option): int + bool $option = false): int { $someArgument = $argument; $someOption = $option; diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_fluent.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_fluent.php.inc index bd4ea2901..38ad7f45e 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_fluent.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_fluent.php.inc @@ -57,9 +57,9 @@ final class WithMultipleArgumentsOptionsFluent #[\Symfony\Component\Console\Attribute\Argument(name: 'argument2', description: 'Argument2 description')] string $argument2, #[\Symfony\Component\Console\Attribute\Option(name: 'option1', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option1 description')] - $option1, + bool $option1 = false, #[\Symfony\Component\Console\Attribute\Option(name: 'option2', shortcut: 'p', mode: InputOption::VALUE_NONE, description: 'Option2 description')] - $option2 + bool $option2 = false ): int { $arg1 = $argument1; diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_no_fluent.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_no_fluent.php.inc index 5baf04887..545a39c82 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_no_fluent.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_multiple_arguments_options_no_fluent.php.inc @@ -62,9 +62,9 @@ final class WithMultipleArgumentsOptionsNotFluent #[\Symfony\Component\Console\Attribute\Argument(name: 'argument2', description: 'Argument2 description')] string $argument2, #[\Symfony\Component\Console\Attribute\Option(name: 'option1', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option1 description')] - $option1, + bool $option1 = false, #[\Symfony\Component\Console\Attribute\Option(name: 'option2', shortcut: 'p', mode: InputOption::VALUE_NONE, description: 'Option2 description')] - $option2 + bool $option2 = false ): int { $arg1 = $argument1; diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc index f55bae24b..b23d6a78a 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_optional_argument.php.inc @@ -47,7 +47,7 @@ final class WithOptionalArgument { public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] ?string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - $option): int + bool $option = false): int { $someArgument = $argument; $someOption = $option; diff --git a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_override.php.inc b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_override.php.inc index 9ed55b340..aa8617d02 100644 --- a/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_override.php.inc +++ b/rules-tests/Symfony73/Rector/Class_/InvokableCommandInputAttributeRector/Fixture/with_override.php.inc @@ -51,7 +51,7 @@ final class WithOverride #[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - $option + bool $option = false ): int { $someArgument = $argument; diff --git a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php index f68f89416..8219670ab 100644 --- a/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php +++ b/rules/Symfony73/NodeAnalyzer/CommandOptionsResolver.php @@ -36,6 +36,8 @@ public function resolve(ClassMethod $configureClassMethod): array $optionName = $this->valueResolver->getValue($addOptionArgs[0]->value); + $isImplicitBoolean = $this->isImplicitBoolean($addOptionArgs); + $commandOptions[] = new CommandOption( $optionName, $addOptionArgs[0]->value, @@ -44,6 +46,7 @@ public function resolve(ClassMethod $configureClassMethod): array $addOptionArgs[3]->value ?? null, $addOptionArgs[4]->value ?? null, $this->isArrayMode($addOptionArgs), + $isImplicitBoolean, $this->resolveDefaultType($addOptionArgs) ); } @@ -75,7 +78,22 @@ private function isArrayMode(array $args): bool } $modeValue = $this->valueResolver->getValue($modeExpr); - // binary check for InputOptions::VALUE_IS_ARRAY + // binary check for InputOption::VALUE_IS_ARRAY return (bool) ($modeValue & 8); } + + /** + * @param Arg[] $args + */ + private function isImplicitBoolean(array $args): bool + { + $modeExpr = $args[2]->value ?? null; + if (! $modeExpr instanceof Expr) { + return false; + } + + $modeValue = $this->valueResolver->getValue($modeExpr); + // binary check for InputOption::VALUE_NONE + return (bool) ($modeValue & 1); + } } diff --git a/rules/Symfony73/NodeFactory/CommandInvokeParamsFactory.php b/rules/Symfony73/NodeFactory/CommandInvokeParamsFactory.php index 30f7ef3d4..37ff6b5f5 100644 --- a/rules/Symfony73/NodeFactory/CommandInvokeParamsFactory.php +++ b/rules/Symfony73/NodeFactory/CommandInvokeParamsFactory.php @@ -4,6 +4,8 @@ namespace Rector\Symfony\Symfony73\NodeFactory; +use PhpParser\Node\Expr\ConstFetch; +use PhpParser\Node\Name; use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Attribute; @@ -99,6 +101,8 @@ private function createOptionParams(array $commandOptions): array if ($commandOption->getDefault() instanceof Expr) { $optionParam->default = $commandOption->getDefault(); + } elseif ($commandOption->isImplicitBoolean()) { + $optionParam->default = new ConstFetch(new Name('false')); } $this->decorateParamType($optionParam, $commandOption); @@ -165,6 +169,11 @@ private function decorateParamType( Param $argumentParam, CommandArgument|CommandOption $commandArgumentOrOption ): void { + if ($commandArgumentOrOption instanceof CommandOption && $commandArgumentOrOption->isImplicitBoolean()) { + $argumentParam->type = new Identifier('bool'); + return; + } + if ($commandArgumentOrOption->isArray()) { $argumentParam->type = new Identifier('array'); return; diff --git a/rules/Symfony73/ValueObject/CommandOption.php b/rules/Symfony73/ValueObject/CommandOption.php index 058cd38b6..7ade1e904 100644 --- a/rules/Symfony73/ValueObject/CommandOption.php +++ b/rules/Symfony73/ValueObject/CommandOption.php @@ -17,6 +17,7 @@ public function __construct( private ?Expr $description, private ?Expr $default, private bool $isArray, + private bool $isImplicitBoolean, private ?Type $defaultType ) { } @@ -60,4 +61,9 @@ public function isArray(): bool { return $this->isArray; } + + public function isImplicitBoolean(): bool + { + return $this->isImplicitBoolean; + } } diff --git a/tests/Set/Symfony32/Fixture/replace_new_cookie_parameter_value.php.inc b/tests/Set/Symfony32/Fixture/replace_new_cookie_parameter_value.php.inc index 713615ecb..ca3ed3e21 100644 --- a/tests/Set/Symfony32/Fixture/replace_new_cookie_parameter_value.php.inc +++ b/tests/Set/Symfony32/Fixture/replace_new_cookie_parameter_value.php.inc @@ -30,3 +30,4 @@ class ReplaceNewCookieParameterValue } ?> + diff --git a/tests/Set/Symfony42/Fixture/replace_new_cookie_and_create_parameter_value.php.inc b/tests/Set/Symfony42/Fixture/replace_new_cookie_and_create_parameter_value.php.inc index 58c1cc35a..84997316e 100644 --- a/tests/Set/Symfony42/Fixture/replace_new_cookie_and_create_parameter_value.php.inc +++ b/tests/Set/Symfony42/Fixture/replace_new_cookie_and_create_parameter_value.php.inc @@ -34,3 +34,4 @@ class ReplaceNewCookieAndCreateParameterValue } ?> + diff --git a/tests/Set/Symfony51/Fixture/replace_cookie_with_same_site_parameter_value.php.inc b/tests/Set/Symfony51/Fixture/replace_cookie_with_same_site_parameter_value.php.inc index 17ced89b3..ff3d9b3db 100644 --- a/tests/Set/Symfony51/Fixture/replace_cookie_with_same_site_parameter_value.php.inc +++ b/tests/Set/Symfony51/Fixture/replace_cookie_with_same_site_parameter_value.php.inc @@ -36,3 +36,4 @@ class ReplaceCookieWithSameSiteParameterValue } ?> + diff --git a/tests/Set/Symfony73/Fixture/command_remove_config.php.inc b/tests/Set/Symfony73/Fixture/command_remove_config.php.inc index 7986383e0..ea4efd5fe 100644 --- a/tests/Set/Symfony73/Fixture/command_remove_config.php.inc +++ b/tests/Set/Symfony73/Fixture/command_remove_config.php.inc @@ -58,7 +58,7 @@ final class SomeCommand { public function __invoke(#[\Symfony\Component\Console\Attribute\Argument(name: 'argument', description: 'Argument description')] string $argument, #[\Symfony\Component\Console\Attribute\Option(name: 'option', shortcut: 'o', mode: InputOption::VALUE_NONE, description: 'Option description')] - $option): int + bool $option = false): int { $someArgument = $argument; $someOption = $option;