Repository: symfony/ux
Pull request #2729 (2025-05-11 23:01:17)

[Twig] Improve Parser exceptions (minor)
Improves Parser exception

Throw a SyntaxError instead of a LogicError in ComponentParser

(following Twig #4601 : https://github.com/twigphp/Twig/pull/4601)


smnandre: (failures unrelated, PR already opened to handle them)

Patch:
diff --git a/src/TwigComponent/src/Twig/ComponentTokenParser.php b/src/TwigComponent/src/Twig/ComponentTokenParser.php
index ff072124..ec543ee7 100644
--- a/src/TwigComponent/src/Twig/ComponentTokenParser.php
+++ b/src/TwigComponent/src/Twig/ComponentTokenParser.php
@@ -12,6 +12,7 @@
 namespace Symfony\UX\TwigComponent\Twig;
 
 use Symfony\UX\TwigComponent\BlockStack;
+use Twig\Error\SyntaxError;
 use Twig\Node\Expression\AbstractExpression;
 use Twig\Node\Expression\ArrayExpression;
 use Twig\Node\Expression\ConstantExpression;
@@ -33,6 +34,7 @@ final class ComponentTokenParser extends AbstractTokenParser
     public function parse(Token $token): Node
     {
         $stream = $this->parser->getStream();
+
         if (method_exists($this->parser, 'parseExpression')) {
             // Since Twig 3.21
             $componentName = $this->componentName($this->parser->parseExpression());
@@ -40,6 +42,10 @@ final class ComponentTokenParser extends AbstractTokenParser
             $componentName = $this->componentName($this->parser->getExpressionParser()->parseExpression());
         }
 
+        if (null === $componentName) {
+            throw new SyntaxError('Could not parse component name.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
+        }
+
         [$propsExpression, $only] = $this->parseArguments();
 
         // Write a fake: "extends __parent__" into the "embedded" template.
@@ -80,7 +86,7 @@ final class ComponentTokenParser extends AbstractTokenParser
         return 'component';
     }
 
-    private function componentName(AbstractExpression $expression): string
+    private function componentName(AbstractExpression $expression): ?string
     {
         if ($expression instanceof ConstantExpression) { // using {% component 'name' %}
             return $expression->getAttribute('value');
@@ -90,7 +96,7 @@ final class ComponentTokenParser extends AbstractTokenParser
             return $expression->getAttribute('name');
         }
 
-        throw new \LogicException('Could not parse component name.');
+        return null;
     }
 
     /**


Test patch:
diff --git a/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php b/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php
index 476ee4f2..c5799440 100644
--- a/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php
+++ b/src/TwigComponent/tests/Integration/Twig/ComponentParserTest.php
@@ -13,6 +13,7 @@ namespace Symfony\UX\TwigComponent\Tests\Integration\Twig;
 
 use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
 use Twig\Environment;
+use Twig\Error\SyntaxError;
 use Twig\Loader\ArrayLoader;
 use Twig\TemplateWrapper;
 
@@ -28,12 +29,12 @@ final class ComponentParserTest extends KernelTestCase
      */
     public function testAcceptTwigComponentTagWithValidComponentName(string $name): void
     {
-        $environment = self::createEnvironment();
+        $environment = $this->createEnvironment();
         $source = str_replace('XXX', $name, "{% component 'XXX' %}{% endcomponent %}");
 
         $template = $environment->createTemplate($source);
 
-        self::assertInstanceOf(TemplateWrapper::class, $template);
+        $this->assertInstanceOf(TemplateWrapper::class, $template);
     }
 
     /**
@@ -41,12 +42,12 @@ final class ComponentParserTest extends KernelTestCase
      */
     public function testAcceptHtmlComponentTagWithValidComponentName(string $name): void
     {
-        $environment = self::createEnvironment();
+        $environment = $this->createEnvironment();
         $source = \sprintf('<twig:%s></twig:%s>', $name, $name);
 
         $template = $environment->createTemplate($source);
 
-        self::assertInstanceOf(TemplateWrapper::class, $template);
+        $this->assertInstanceOf(TemplateWrapper::class, $template);
     }
 
     /**
@@ -54,12 +55,24 @@ final class ComponentParserTest extends KernelTestCase
      */
     public function testAcceptHtmlSelfClosingComponentTagWithValidComponentName(string $name): void
     {
-        $environment = self::createEnvironment();
+        $environment = $this->createEnvironment();
         $source = \sprintf('<twig:%s />', $name);
 
         $template = $environment->createTemplate($source);
 
-        self::assertInstanceOf(TemplateWrapper::class, $template);
+        $this->assertInstanceOf(TemplateWrapper::class, $template);
+    }
+
+    public function testItThrowsWhenComponentNameCannotBeParsed(): void
+    {
+        $environment = $this->createEnvironment();
+        $source = '{% component [] %}{% endcomponent %}';
+
+        $this->expectException(SyntaxError::class);
+        $this->expectExceptionMessage('Could not parse component name in "foo.html.twig');
+        $this->expectExceptionMessage(')" at line 1.');
+
+        $environment->createTemplate($source, 'foo.html.twig');
     }
 
     public static function provideValidComponentNames(): iterable