diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php index 62e7a3da1622b..c341056409115 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/AbstractVisitor.php @@ -68,6 +68,19 @@ protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute|Node\ return false; } + protected function nodeFirstNamedArgumentIndex(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): int + { + $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args; + + foreach ($args as $i => $arg) { + if ($arg instanceof Node\Arg && null !== $arg->name) { + return $i; + } + } + + return \PHP_INT_MAX; + } + private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, string $argumentName = null, bool $isArgumentNamePattern = false): array { $args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args; diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php index 2c61659427b14..0b537baa24c13 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/TransMethodVisitor.php @@ -37,12 +37,13 @@ public function enterNode(Node $node): ?Node $name = (string) $node->name; if ('trans' === $name || 't' === $name) { - $nodeHasNamedArguments = $this->hasNodeNamedArguments($node); - if (!$messages = $this->getStringArguments($node, $nodeHasNamedArguments ? 'message' : 0)) { + $firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node); + + if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) { return null; } - $domain = $this->getStringArguments($node, $nodeHasNamedArguments ? 'domain' : 2)[0] ?? null; + $domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null; foreach ($messages as $message) { $this->addMessageToCatalogue($message, $domain, $node->getStartLine()); diff --git a/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php b/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php index 423982c82ce66..c1505a135437d 100644 --- a/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php +++ b/src/Symfony/Component/Translation/Extractor/Visitor/TranslatableMessageVisitor.php @@ -38,13 +38,13 @@ public function enterNode(Node $node): ?Node return null; } - $nodeHasNamedArguments = $this->hasNodeNamedArguments($node); + $firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node); - if (!$messages = $this->getStringArguments($node, $nodeHasNamedArguments ? 'message' : 0)) { + if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) { return null; } - $domain = $this->getStringArguments($node, $nodeHasNamedArguments ? 'domain' : 2)[0] ?? null; + $domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null; foreach ($messages as $message) { $this->addMessageToCatalogue($message, $domain, $node->getStartLine()); diff --git a/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php b/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php index 18f38f93fbec1..2c5b119eba0f9 100644 --- a/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php +++ b/src/Symfony/Component/Translation/Tests/Extractor/PhpAstExtractorTest.php @@ -91,6 +91,9 @@ public function testExtraction(iterable|string $resource) $expectedNowdoc => 'prefix'.$expectedNowdoc, 'concatenated message with heredoc and nowdoc' => 'prefixconcatenated message with heredoc and nowdoc', 'default domain' => 'prefixdefault domain', + 'mix-named-arguments' => 'prefixmix-named-arguments', + 'mix-named-arguments-locale' => 'prefixmix-named-arguments-locale', + 'mix-named-arguments-without-domain' => 'prefixmix-named-arguments-without-domain', ], 'not_messages' => [ 'translatable other-domain-test-no-params-short-array' => 'prefixtranslatable other-domain-test-no-params-short-array', @@ -119,6 +122,8 @@ public function testExtraction(iterable|string $resource) 'variable-assignation-inlined-in-trans-method-call2' => 'prefixvariable-assignation-inlined-in-trans-method-call2', 'variable-assignation-inlined-in-trans-method-call3' => 'prefixvariable-assignation-inlined-in-trans-method-call3', 'variable-assignation-inlined-with-named-arguments-in-trans-method' => 'prefixvariable-assignation-inlined-with-named-arguments-in-trans-method', + 'mix-named-arguments-without-parameters' => 'prefixmix-named-arguments-without-parameters', + 'mix-named-arguments-disordered' => 'prefixmix-named-arguments-disordered', ], 'validators' => [ 'message-in-constraint-attribute' => 'prefixmessage-in-constraint-attribute', diff --git a/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php b/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php index e34863f3dc660..db27b303c5945 100644 --- a/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php +++ b/src/Symfony/Component/Translation/Tests/fixtures/extractor-ast/translation.html.php @@ -55,4 +55,10 @@ trans(domain: $domain = 'not_messages', message: $key = 'variable-assignation-inlined-with-named-arguments-in-trans-method', parameters: $parameters = []); ?> +trans('mix-named-arguments', parameters: ['foo' => 'bar']); ?> +trans('mix-named-arguments-locale', parameters: ['foo' => 'bar'], locale: 'de'); ?> +trans('mix-named-arguments-without-domain', parameters: ['foo' => 'bar']); ?> +trans('mix-named-arguments-without-parameters', domain: 'not_messages'); ?> +trans('mix-named-arguments-disordered', domain: 'not_messages', parameters: []); ?> + trans(...); // should not fail ?>