diff --git a/packages/coding-standard/src/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer.php b/packages/coding-standard/src/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer.php index 69b05f0c5b..a84fc35c7e 100644 --- a/packages/coding-standard/src/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer.php +++ b/packages/coding-standard/src/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer.php @@ -8,7 +8,6 @@ use PhpCsFixer\FixerDefinition\CodeSample; use PhpCsFixer\FixerDefinition\FixerDefinition; use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; -use PhpCsFixer\Tokenizer\Analyzer\NamespaceUsesAnalyzer; use PhpCsFixer\Tokenizer\Token; use PhpCsFixer\Tokenizer\Tokens; use SplFileInfo; @@ -21,8 +20,7 @@ * Every argument of a Symfony attribute must be on a standalone line, to ease git diffs when arguments change. * * Only a fixed allowlist of Symfony attributes is handled, so third-party attributes keep their original layout. - * Both fully-qualified names (#[\Symfony\...\AsCommand]) and short names imported via a use statement (#[AsCommand]) - * are recognized. See self::SYMFONY_ATTRIBUTE_CLASSES. + * Attributes are matched by their short name (#[AsCommand] or #[\Symfony\...\AsCommand]). See self::SYMFONY_ATTRIBUTE_SHORT_NAMES. * * @see \Symplify\CodingStandard\Tests\Fixer\Spacing\StandaloneLineSymfonyAttributeParamFixer\StandaloneLineSymfonyAttributeParamFixerTest */ @@ -33,29 +31,28 @@ final class StandaloneLineSymfonyAttributeParamFixer extends AbstractSymplifyFix /** * @var string[] */ - private const array SYMFONY_ATTRIBUTE_CLASSES = [ - 'Symfony\Component\Console\Attribute\AsCommand', - 'Symfony\Component\Routing\Attribute\Route', - 'Symfony\Component\DependencyInjection\Attribute\Autowire', - 'Symfony\Component\DependencyInjection\Attribute\AutowireIterator', - 'Symfony\Component\DependencyInjection\Attribute\AutowireLocator', - 'Symfony\Component\DependencyInjection\Attribute\AsAlias', - 'Symfony\Component\DependencyInjection\Attribute\AsDecorator', - 'Symfony\Component\DependencyInjection\Attribute\AsTaggedItem', - 'Symfony\Component\DependencyInjection\Attribute\When', - 'Symfony\Component\EventDispatcher\Attribute\AsEventListener', - 'Symfony\Component\Messenger\Attribute\AsMessageHandler', - 'Symfony\Component\HttpKernel\Attribute\AsController', - 'Symfony\Component\HttpKernel\Attribute\MapRequestPayload', - 'Symfony\Component\HttpKernel\Attribute\MapQueryParameter', - 'Symfony\Component\HttpKernel\Attribute\MapQueryString', - 'Symfony\Component\HttpKernel\Attribute\MapEntity', - 'Symfony\Component\Security\Http\Attribute\IsGranted', + private const array SYMFONY_ATTRIBUTE_SHORT_NAMES = [ + 'AsCommand', + 'Route', + 'Autowire', + 'AutowireIterator', + 'AutowireLocator', + 'AsAlias', + 'AsDecorator', + 'AsTaggedItem', + 'When', + 'AsEventListener', + 'AsMessageHandler', + 'AsController', + 'MapRequestPayload', + 'MapQueryParameter', + 'MapQueryString', + 'MapEntity', + 'IsGranted', ]; public function __construct( - private readonly TokensNewliner $tokensNewliner, - private readonly NamespaceUsesAnalyzer $namespaceUsesAnalyzer + private readonly TokensNewliner $tokensNewliner ) { } @@ -95,8 +92,6 @@ public function isCandidate(Tokens $tokens): bool */ public function fix(SplFileInfo $fileInfo, Tokens $tokens): void { - $shortNameToFullName = $this->resolveShortNameToFullName($tokens); - // from the bottom up, as adding tokens shifts every position after them for ($position = count($tokens) - 1; $position >= 0; --$position) { /** @var Token $token */ @@ -118,7 +113,7 @@ public function fix(SplFileInfo $fileInfo, Tokens $tokens): void continue; } - if (! $this->isSymfonyAttribute($tokens, $openBracketPosition, $shortNameToFullName)) { + if (! $this->isSymfonyAttribute($tokens, $openBracketPosition)) { continue; } @@ -135,59 +130,22 @@ public function fix(SplFileInfo $fileInfo, Tokens $tokens): void /** * @param Tokens $tokens - * @param array $shortNameToFullName */ - private function isSymfonyAttribute(Tokens $tokens, int $openBracketPosition, array $shortNameToFullName): bool + private function isSymfonyAttribute(Tokens $tokens, int $openBracketPosition): bool { - $attributeName = $this->resolveAttributeName($tokens, $openBracketPosition); - - // fully-qualified name, e.g. #[\Symfony\...\AsCommand] - if (str_starts_with($attributeName, '\\')) { - return in_array(ltrim($attributeName, '\\'), self::SYMFONY_ATTRIBUTE_CLASSES, true); + // the attribute short name is the T_STRING right before its "(", e.g. "AsCommand" + $previousPosition = $tokens->getPrevMeaningfulToken($openBracketPosition); + if ($previousPosition === null) { + return false; } - // short name imported via a use statement, e.g. #[AsCommand] with "use Symfony\...\AsCommand;" - $firstNamePart = explode('\\', $attributeName)[0]; - $fullName = $shortNameToFullName[$firstNamePart] ?? null; - - return $fullName !== null && in_array($fullName, self::SYMFONY_ATTRIBUTE_CLASSES, true); - } - - /** - * Reads the attribute name written right before its "(", e.g. "\Symfony\...\AsCommand" or "AsCommand". - * - * @param Tokens $tokens - */ - private function resolveAttributeName(Tokens $tokens, int $openBracketPosition): string - { - $attributeName = ''; - - for ($index = $openBracketPosition - 1; $index >= 0; --$index) { - /** @var Token $token */ - $token = $tokens[$index]; - - if (! $token->isGivenKind([T_STRING, T_NS_SEPARATOR])) { - break; - } - - $attributeName = $token->getContent() . $attributeName; - } - - return $attributeName; - } - - /** - * @param Tokens $tokens - * @return array - */ - private function resolveShortNameToFullName(Tokens $tokens): array - { - $shortNameToFullName = []; + /** @var Token $previousToken */ + $previousToken = $tokens[$previousPosition]; - foreach ($this->namespaceUsesAnalyzer->getDeclarationsFromTokens($tokens) as $namespaceUseAnalysis) { - $shortNameToFullName[$namespaceUseAnalysis->getShortName()] = $namespaceUseAnalysis->getFullName(); + if (! $previousToken->isGivenKind(T_STRING)) { + return false; } - return $shortNameToFullName; + return in_array($previousToken->getContent(), self::SYMFONY_ATTRIBUTE_SHORT_NAMES, true); } } diff --git a/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/fqn_autowire_attribute.php.inc b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/fqn_autowire_attribute.php.inc new file mode 100644 index 0000000000..7a4915c372 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/fqn_autowire_attribute.php.inc @@ -0,0 +1,32 @@ + +----- + diff --git a/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/fqn_route_attribute.php.inc b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/fqn_route_attribute.php.inc new file mode 100644 index 0000000000..f8ee3358c6 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/fqn_route_attribute.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/imported_autowire_attribute.php.inc b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/imported_autowire_attribute.php.inc new file mode 100644 index 0000000000..c367d11b13 --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/imported_autowire_attribute.php.inc @@ -0,0 +1,36 @@ + +----- + diff --git a/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/imported_route_attribute.php.inc b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/imported_route_attribute.php.inc new file mode 100644 index 0000000000..8390868bcf --- /dev/null +++ b/packages/coding-standard/tests/Fixer/Spacing/StandaloneLineSymfonyAttributeParamFixer/Fixture/imported_route_attribute.php.inc @@ -0,0 +1,34 @@ + +----- +