From ae700e74996e223268bbbf1e7fded9510624485f Mon Sep 17 00:00:00 2001 From: Dave Liddament Date: Mon, 22 Jun 2026 23:30:13 +0100 Subject: [PATCH 1/2] [code-quality] Add AttributeNamedArgsRector Convert positional arguments on configured attributes into named arguments, taking the names from the attribute constructor signature. Configurable per attribute class via the AttributeNamedArgs value object, with an optional firstNamedPosition threshold to leave leading arguments positional. Skips already-named arguments and bails out when an argument maps to a variadic or missing parameter to avoid producing invalid PHP. --- ...eNamedArgsRectorFirstNamedPositionTest.php | 28 +++ .../AttributeNamedArgsRectorTest.php | 28 +++ .../Fixture/positional_except.php.inc | 27 +++ .../Fixture/positional_middleware.php.inc | 27 +++ .../Fixture/positional_only.php.inc | 27 +++ .../Fixture/skip_already_named.php.inc | 11 ++ .../Fixture/skip_other_attribute.php.inc | 10 ++ .../Fixture/skip_variadic.php.inc | 11 ++ .../Fixture/variadic_named_prefix.php.inc | 27 +++ .../positional_except.php.inc | 27 +++ .../positional_only.php.inc | 27 +++ .../skip_middleware_left_positional.php.inc | 11 ++ .../Source/Middleware.php | 11 ++ .../Source/MiddlewareWithVariadic.php | 11 ++ .../Source/OtherAttribute.php | 11 ++ .../Source/RequireToken.php | 7 + .../config/configured_rule.php | 16 ++ .../configured_rule_first_named_position.php | 14 ++ .../Attribute/AttributeNamedArgsRector.php | 169 ++++++++++++++++++ .../ValueObject/AttributeNamedArgs.php | 31 ++++ 20 files changed, 531 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_other_attribute.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_variadic.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/variadic_named_prefix.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/Middleware.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/MiddlewareWithVariadic.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/OtherAttribute.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/RequireToken.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php create mode 100644 rules/CodeQuality/Rector/Attribute/AttributeNamedArgsRector.php create mode 100644 rules/CodeQuality/ValueObject/AttributeNamedArgs.php diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php new file mode 100644 index 00000000000..b6a986a395e --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/FixtureFirstNamedPosition'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule_first_named_position.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php new file mode 100644 index 00000000000..0293e82123b --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc new file mode 100644 index 00000000000..a1a5519f2be --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc new file mode 100644 index 00000000000..d5e1188a94b --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc new file mode 100644 index 00000000000..3c4bf778795 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc new file mode 100644 index 00000000000..7076df4b111 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc @@ -0,0 +1,11 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc new file mode 100644 index 00000000000..5d29d159a01 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc new file mode 100644 index 00000000000..1f048f0173b --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc new file mode 100644 index 00000000000..5f1b08412de --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc @@ -0,0 +1,11 @@ +ruleWithConfiguration(AttributeNamedArgsRector::class, [ + new AttributeNamedArgs(Middleware::class), + new AttributeNamedArgs(MiddlewareWithVariadic::class), + ]); +}; diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php new file mode 100644 index 00000000000..f7e976081cd --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php @@ -0,0 +1,14 @@ +ruleWithConfiguration(AttributeNamedArgsRector::class, [ + new AttributeNamedArgs(Middleware::class, 1), + ]); +}; diff --git a/rules/CodeQuality/Rector/Attribute/AttributeNamedArgsRector.php b/rules/CodeQuality/Rector/Attribute/AttributeNamedArgsRector.php new file mode 100644 index 00000000000..cd3541b28cf --- /dev/null +++ b/rules/CodeQuality/Rector/Attribute/AttributeNamedArgsRector.php @@ -0,0 +1,169 @@ +> + */ + public function getNodeTypes(): array + { + return [Attribute::class]; + } + + /** + * @param Attribute $node + */ + public function refactor(Node $node): ?Node + { + foreach ($this->attributeNamedArgs as $attributeNamedArg) { + if ($this->isName($node->name, $attributeNamedArg->getAttributeClass())) { + return $this->nameArguments($node, $attributeNamedArg); + } + } + + return null; + } + + /** + * @param mixed[] $configuration + */ + public function configure(array $configuration): void + { + Assert::allIsAOf($configuration, AttributeNamedArgs::class); + + $this->attributeNamedArgs = $configuration; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::NAMED_ARGUMENTS; + } + + private function nameArguments(Attribute $attribute, AttributeNamedArgs $attributeNamedArgs): ?Attribute + { + $methodReflection = $this->reflectionResolver->resolveConstructorReflectionFromAttribute($attribute); + if (! $methodReflection instanceof MethodReflection) { + return null; + } + + $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants()); + $parameters = $extendedParametersAcceptor->getParameters(); + + $namesToApply = $this->resolveArgNamesToApply( + $attribute->args, + $parameters, + $attributeNamedArgs->getFirstNamedPosition() + ); + if ($namesToApply === []) { + return null; + } + + foreach ($namesToApply as $position => $name) { + $attribute->args[$position]->name = new Identifier($name); + } + + return $attribute; + } + + /** + * Resolve the positional arguments to name, as a position => parameter-name map, or [] when + * nothing should change. Naming an argument forces every later positional argument to be named + * too (PHP forbids a positional argument after a named one). So if any argument in the named + * range maps to a variadic parameter, or to no parameter at all (overflow past a variadic), + * the whole attribute is left untouched rather than producing invalid PHP. + * + * @param Arg[] $args + * @param ParameterReflection[] $parameters + * @return array + */ + private function resolveArgNamesToApply(array $args, array $parameters, int $firstNamedPosition): array + { + $namesToApply = []; + + $count = count($args); + for ($position = $firstNamedPosition; $position < $count; ++$position) { + $arg = $args[$position]; + + // already named + if ($arg->name instanceof Identifier) { + continue; + } + + $parameter = $parameters[$position] ?? null; + + // no matching parameter, e.g. overflow past a variadic + if ($parameter === null) { + return []; + } + + // naming a variadic would rebind it or strand later positional arguments + if ($parameter->isVariadic()) { + return []; + } + + $namesToApply[$position] = $parameter->getName(); + } + + return $namesToApply; + } +} diff --git a/rules/CodeQuality/ValueObject/AttributeNamedArgs.php b/rules/CodeQuality/ValueObject/AttributeNamedArgs.php new file mode 100644 index 00000000000..226aeaaccec --- /dev/null +++ b/rules/CodeQuality/ValueObject/AttributeNamedArgs.php @@ -0,0 +1,31 @@ +attributeClass; + } + + public function getFirstNamedPosition(): int + { + return $this->firstNamedPosition; + } +} From de45000f5165192e9f6cb546318f8351cef5dc73 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 27 Jun 2026 14:51:52 +0200 Subject: [PATCH 2/2] Address review: rename to ExplicitAttributeNamedArgsRector, make non-configurable (name all attributes), move to named-args set --- config/set/named-args.php | 2 + ...eNamedArgsRectorFirstNamedPositionTest.php | 28 ------- .../Fixture/positional_except.php.inc | 27 ------- .../Fixture/positional_middleware.php.inc | 27 ------- .../Fixture/positional_only.php.inc | 27 ------- .../Fixture/skip_already_named.php.inc | 11 --- .../Fixture/skip_other_attribute.php.inc | 10 --- .../Fixture/skip_variadic.php.inc | 11 --- .../Fixture/variadic_named_prefix.php.inc | 27 ------- .../positional_except.php.inc | 27 ------- .../positional_only.php.inc | 27 ------- .../skip_middleware_left_positional.php.inc | 11 --- .../Source/RequireToken.php | 7 -- .../config/configured_rule.php | 16 ---- .../configured_rule_first_named_position.php | 14 ---- .../ExplicitAttributeNamedArgsRectorTest.php} | 4 +- .../Fixture/other_attribute.php.inc | 25 ++++++ .../Fixture/positional_except.php.inc | 27 +++++++ .../Fixture/positional_middleware.php.inc | 27 +++++++ .../Fixture/positional_only.php.inc | 27 +++++++ .../Fixture/skip_already_named.php.inc | 11 +++ .../Fixture/skip_variadic.php.inc | 11 +++ .../Fixture/variadic_named_prefix.php.inc | 27 +++++++ .../Source/Middleware.php | 2 +- .../Source/MiddlewareWithVariadic.php | 2 +- .../Source/OtherAttribute.php | 2 +- .../Source/RequireToken.php | 7 ++ .../config/configured_rule.php | 10 +++ ...p => ExplicitAttributeNamedArgsRector.php} | 76 +++++-------------- .../ValueObject/AttributeNamedArgs.php | 31 -------- 30 files changed, 198 insertions(+), 363 deletions(-) delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_other_attribute.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_variadic.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/variadic_named_prefix.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/RequireToken.php delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule.php delete mode 100644 rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php rename rules-tests/CodeQuality/Rector/Attribute/{AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php => ExplicitAttributeNamedArgsRector/ExplicitAttributeNamedArgsRectorTest.php} (76%) create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/other_attribute.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_except.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_middleware.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_only.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/skip_already_named.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/skip_variadic.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/variadic_named_prefix.php.inc rename rules-tests/CodeQuality/Rector/Attribute/{AttributeNamedArgsRector => ExplicitAttributeNamedArgsRector}/Source/Middleware.php (59%) rename rules-tests/CodeQuality/Rector/Attribute/{AttributeNamedArgsRector => ExplicitAttributeNamedArgsRector}/Source/MiddlewareWithVariadic.php (58%) rename rules-tests/CodeQuality/Rector/Attribute/{AttributeNamedArgsRector => ExplicitAttributeNamedArgsRector}/Source/OtherAttribute.php (54%) create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Source/RequireToken.php create mode 100644 rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/config/configured_rule.php rename rules/CodeQuality/Rector/Attribute/{AttributeNamedArgsRector.php => ExplicitAttributeNamedArgsRector.php} (58%) delete mode 100644 rules/CodeQuality/ValueObject/AttributeNamedArgs.php diff --git a/config/set/named-args.php b/config/set/named-args.php index 34f4859a561..cf4480a4786 100644 --- a/config/set/named-args.php +++ b/config/set/named-args.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Rector\CodeQuality\Rector\Attribute\ExplicitAttributeNamedArgsRector; use Rector\CodeQuality\Rector\Attribute\SortAttributeNamedArgsRector; use Rector\CodeQuality\Rector\CallLike\AddNameToBooleanArgumentRector; use Rector\CodeQuality\Rector\CallLike\AddNameToNullArgumentRector; @@ -17,6 +18,7 @@ RemoveNullNamedArgOnNullDefaultParamRector::class, SortCallLikeNamedArgsRector::class, SortAttributeNamedArgsRector::class, + ExplicitAttributeNamedArgsRector::class, UtilsJsonStaticCallNamedArgRector::class, ]); }; diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php deleted file mode 100644 index b6a986a395e..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorFirstNamedPositionTest.php +++ /dev/null @@ -1,28 +0,0 @@ -doTestFile($filePath); - } - - public static function provideData(): Iterator - { - return self::yieldFilesFromDirectory(__DIR__ . '/FixtureFirstNamedPosition'); - } - - public function provideConfigFilePath(): string - { - return __DIR__ . '/config/configured_rule_first_named_position.php'; - } -} diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc deleted file mode 100644 index a1a5519f2be..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_except.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc deleted file mode 100644 index d5e1188a94b..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_middleware.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc deleted file mode 100644 index 3c4bf778795..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/positional_only.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc deleted file mode 100644 index 7076df4b111..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Fixture/skip_already_named.php.inc +++ /dev/null @@ -1,11 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc deleted file mode 100644 index 5d29d159a01..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_except.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc deleted file mode 100644 index 1f048f0173b..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/positional_only.php.inc +++ /dev/null @@ -1,27 +0,0 @@ - ------ - diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc deleted file mode 100644 index 5f1b08412de..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/FixtureFirstNamedPosition/skip_middleware_left_positional.php.inc +++ /dev/null @@ -1,11 +0,0 @@ -ruleWithConfiguration(AttributeNamedArgsRector::class, [ - new AttributeNamedArgs(Middleware::class), - new AttributeNamedArgs(MiddlewareWithVariadic::class), - ]); -}; diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php b/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php deleted file mode 100644 index f7e976081cd..00000000000 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/config/configured_rule_first_named_position.php +++ /dev/null @@ -1,14 +0,0 @@ -ruleWithConfiguration(AttributeNamedArgsRector::class, [ - new AttributeNamedArgs(Middleware::class, 1), - ]); -}; diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/ExplicitAttributeNamedArgsRectorTest.php similarity index 76% rename from rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php rename to rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/ExplicitAttributeNamedArgsRectorTest.php index 0293e82123b..cb9c17c8a37 100644 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/AttributeNamedArgsRectorTest.php +++ b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/ExplicitAttributeNamedArgsRectorTest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Rector\Tests\CodeQuality\Rector\Attribute\AttributeNamedArgsRector; +namespace Rector\Tests\CodeQuality\Rector\Attribute\ExplicitAttributeNamedArgsRector; use Iterator; use PHPUnit\Framework\Attributes\DataProvider; use Rector\Testing\PHPUnit\AbstractRectorTestCase; -final class AttributeNamedArgsRectorTest extends AbstractRectorTestCase +final class ExplicitAttributeNamedArgsRectorTest extends AbstractRectorTestCase { #[DataProvider('provideData')] public function test(string $filePath): void diff --git a/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/other_attribute.php.inc b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/other_attribute.php.inc new file mode 100644 index 00000000000..1a891cb27e1 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/other_attribute.php.inc @@ -0,0 +1,25 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_except.php.inc b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_except.php.inc new file mode 100644 index 00000000000..dbdeb04caf3 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_except.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_middleware.php.inc b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_middleware.php.inc new file mode 100644 index 00000000000..8186c5f9aa1 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_middleware.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_only.php.inc b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_only.php.inc new file mode 100644 index 00000000000..dd9dd8ec645 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/positional_only.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/skip_already_named.php.inc b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/skip_already_named.php.inc new file mode 100644 index 00000000000..ca9badf0b8a --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Fixture/skip_already_named.php.inc @@ -0,0 +1,11 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/Middleware.php b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Source/Middleware.php similarity index 59% rename from rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/Middleware.php rename to rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Source/Middleware.php index cd6c7ddab07..87ff9b96a20 100644 --- a/rules-tests/CodeQuality/Rector/Attribute/AttributeNamedArgsRector/Source/Middleware.php +++ b/rules-tests/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector/Source/Middleware.php @@ -1,6 +1,6 @@ rule(ExplicitAttributeNamedArgsRector::class); +}; diff --git a/rules/CodeQuality/Rector/Attribute/AttributeNamedArgsRector.php b/rules/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector.php similarity index 58% rename from rules/CodeQuality/Rector/Attribute/AttributeNamedArgsRector.php rename to rules/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector.php index cd3541b28cf..5ac76052bd8 100644 --- a/rules/CodeQuality/Rector/Attribute/AttributeNamedArgsRector.php +++ b/rules/CodeQuality/Rector/Attribute/ExplicitAttributeNamedArgsRector.php @@ -11,26 +11,18 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParameterReflection; use PHPStan\Reflection\ParametersAcceptorSelector; -use Rector\CodeQuality\ValueObject\AttributeNamedArgs; -use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Rector\AbstractRector; use Rector\Reflection\ReflectionResolver; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; -use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; -use Webmozart\Assert\Assert; /** - * @see \Rector\Tests\CodeQuality\Rector\Attribute\AttributeNamedArgsRector\AttributeNamedArgsRectorTest + * @see \Rector\Tests\CodeQuality\Rector\Attribute\ExplicitAttributeNamedArgsRector\ExplicitAttributeNamedArgsRectorTest */ -final class AttributeNamedArgsRector extends AbstractRector implements ConfigurableRectorInterface, MinPhpVersionInterface +final class ExplicitAttributeNamedArgsRector extends AbstractRector implements MinPhpVersionInterface { - /** - * @var AttributeNamedArgs[] - */ - private array $attributeNamedArgs = []; - public function __construct( private readonly ReflectionResolver $reflectionResolver ) { @@ -39,9 +31,9 @@ public function __construct( public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Convert positional arguments on configured attributes into named arguments, using the attribute constructor parameter names', + 'Convert positional arguments on attributes into named arguments, using the attribute constructor parameter names', [ - new ConfiguredCodeSample( + new CodeSample( <<<'CODE_SAMPLE' #[SomeAttribute(SomeClass::class, null, ['home'])] class SomeClass @@ -55,8 +47,6 @@ class SomeClass { } CODE_SAMPLE - , - [new AttributeNamedArgs('Some\Attribute\SomeAttribute')] ), ] ); @@ -75,33 +65,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - foreach ($this->attributeNamedArgs as $attributeNamedArg) { - if ($this->isName($node->name, $attributeNamedArg->getAttributeClass())) { - return $this->nameArguments($node, $attributeNamedArg); - } - } - - return null; - } - - /** - * @param mixed[] $configuration - */ - public function configure(array $configuration): void - { - Assert::allIsAOf($configuration, AttributeNamedArgs::class); - - $this->attributeNamedArgs = $configuration; - } - - public function provideMinPhpVersion(): int - { - return PhpVersionFeature::NAMED_ARGUMENTS; - } - - private function nameArguments(Attribute $attribute, AttributeNamedArgs $attributeNamedArgs): ?Attribute - { - $methodReflection = $this->reflectionResolver->resolveConstructorReflectionFromAttribute($attribute); + $methodReflection = $this->reflectionResolver->resolveConstructorReflectionFromAttribute($node); if (! $methodReflection instanceof MethodReflection) { return null; } @@ -109,41 +73,39 @@ private function nameArguments(Attribute $attribute, AttributeNamedArgs $attribu $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants()); $parameters = $extendedParametersAcceptor->getParameters(); - $namesToApply = $this->resolveArgNamesToApply( - $attribute->args, - $parameters, - $attributeNamedArgs->getFirstNamedPosition() - ); + $namesToApply = $this->resolveArgNamesToApply($node->args, $parameters); if ($namesToApply === []) { return null; } foreach ($namesToApply as $position => $name) { - $attribute->args[$position]->name = new Identifier($name); + $node->args[$position]->name = new Identifier($name); } - return $attribute; + return $node; + } + + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::NAMED_ARGUMENTS; } /** * Resolve the positional arguments to name, as a position => parameter-name map, or [] when * nothing should change. Naming an argument forces every later positional argument to be named - * too (PHP forbids a positional argument after a named one). So if any argument in the named - * range maps to a variadic parameter, or to no parameter at all (overflow past a variadic), - * the whole attribute is left untouched rather than producing invalid PHP. + * too (PHP forbids a positional argument after a named one). So if any argument maps to a + * variadic parameter, or to no parameter at all (overflow past a variadic), the whole attribute + * is left untouched rather than producing invalid PHP. * * @param Arg[] $args * @param ParameterReflection[] $parameters * @return array */ - private function resolveArgNamesToApply(array $args, array $parameters, int $firstNamedPosition): array + private function resolveArgNamesToApply(array $args, array $parameters): array { $namesToApply = []; - $count = count($args); - for ($position = $firstNamedPosition; $position < $count; ++$position) { - $arg = $args[$position]; - + foreach ($args as $position => $arg) { // already named if ($arg->name instanceof Identifier) { continue; diff --git a/rules/CodeQuality/ValueObject/AttributeNamedArgs.php b/rules/CodeQuality/ValueObject/AttributeNamedArgs.php deleted file mode 100644 index 226aeaaccec..00000000000 --- a/rules/CodeQuality/ValueObject/AttributeNamedArgs.php +++ /dev/null @@ -1,31 +0,0 @@ -attributeClass; - } - - public function getFirstNamedPosition(): int - { - return $this->firstNamedPosition; - } -}