Repository: symfony/ux
Pull request #2464 (2024-12-22 04:54:17)

[TwigComponent] Minimal support of comment lines
Twig introduced the inline comments in https://github.com/twigphp/Twig/pull/4349

This PR add minimal support for it the PreLexer / HTML syntax

```twig
<twig:Button
    # comment
    bar="bar"
/>
```

I'd like some IRL feedbacks on this one :)


smnandre: Not at all, they already are compatible. I'm just following Twig syntax here, not adding anything new. But that means adapting the PreLexer we use to convert HTML Syntax into regular Twig code :) 
Kocal: I mean, for tools that does not use our `PreLexer`?

I've tested with Twig-CS-Fixer and no issues so far, but I think it does not support Twig Component at all.
I don't know about TwigStan (which I believe indirectly use our `PreLexer`).

But for syntax coloration it does not work, and I don't know who/when this new syntax will be added to IDE and other syntax coloration libraries: 
<img width="220" alt="image" src="https://github.com/user-attachments/assets/9a4e8cc2-af0d-4e3c-a5c6-89ef7780a219" />

smnandre: > I've tested with Twig-CS-Fixer and no issues so far, but I think it does not support Twig Component at all.

They do, and they integrated inline comments right after Twig release :) 


> But, for syntax coloration it does not work, and I don't know who/when this new syntax will be added

Me neither but... how is this related to this PR ? :) I'm genuinely not sure to get the questions here.

Twig syntax changes are generally pretty quickly integrated into PhpStorm Twig integration (or in the Symfony plugin).
smnandre: cc @Kocal @kbond @WebMamba ?

Patch:
diff --git a/src/TwigComponent/src/Twig/TwigPreLexer.php b/src/TwigComponent/src/Twig/TwigPreLexer.php
index 5d6d41f3..3cdee464 100644
--- a/src/TwigComponent/src/Twig/TwigPreLexer.php
+++ b/src/TwigComponent/src/Twig/TwigPreLexer.php
@@ -233,6 +233,7 @@ class TwigPreLexer
             $isAttributeDynamic = false;
 
             // :someProp="dynamicVar"
+            $this->consumeWhitespace();
             if ($this->check(':')) {
                 $this->consume(':');
                 $isAttributeDynamic = true;
@@ -244,6 +245,7 @@ class TwigPreLexer
 
             // <twig:component someProp> -> someProp: true
             if (!$this->check('=')) {
+                $this->consumeWhitespace();
                 // don't allow "<twig:component :someProp>"
                 if ($isAttributeDynamic) {
                     throw new SyntaxError(\sprintf('Expected "=" after ":%s" when parsing the "<twig:%s" syntax.', $key, $componentName), $this->line);
@@ -332,6 +334,12 @@ class TwigPreLexer
         $whitespace = substr($this->input, $this->position, strspn($this->input, " \t\n\r\0\x0B", $this->position));
         $this->line += substr_count($whitespace, "\n");
         $this->position += \strlen($whitespace);
+
+        if ($this->check('#')) {
+            $this->consume('#');
+            $this->consumeUntil("\n");
+            $this->consumeWhitespace();
+        }
     }
 
     /**


Test patch:
diff --git a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php
index f4489867..2878fb3b 100644
--- a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php
+++ b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php
@@ -376,5 +376,65 @@ final class TwigPreLexerTest extends TestCase
             '<twig:foobar bar="baz" {{ ...attr }}>content</twig:foobar>',
             '{% component \'foobar\' with { bar: \'baz\', ...attr } %}{% block content %}content{% endblock %}{% endcomponent %}',
         ];
+        yield 'component_with_comment_line' => [
+            "<twig:foo \n   # bar  \n />",
+            '{{ component(\'foo\') }}',
+        ];
+        yield 'component_with_comment_line_between_args' => [
+            <<<TWIG
+            <twig:foo
+                # bar
+                bar="baz"
+            />
+            TWIG,
+            '{{ component(\'foo\', { bar: \'baz\' }) }}',
+        ];
+        yield 'component_with_comment_lines_between_args' => [
+            <<<TWIG
+            <twig:foo
+                # comment
+                foo="foo"
+                # comment
+                bar="bar"
+            />
+            TWIG,
+            '{{ component(\'foo\', { foo: \'foo\', bar: \'bar\' }) }}',
+        ];
+        yield 'component_with_comment_line_containing_ending_tag' => [
+            <<<TWIG
+            <twig:foo
+                # comment /></twig:foo>
+                bar="bar"
+            />
+            TWIG,
+            '{{ component(\'foo\', { bar: \'bar\' }) }}',
+        ];
+        yield 'component_with_comment_line_in_argument_value' => [
+            <<<TWIG
+            <twig:foo
+                bar="# bar"
+            />
+            TWIG,
+            '{{ component(\'foo\', { bar: \'# bar\' }) }}',
+        ];
+        yield 'component_with_comment_line_in_argument_array_value_is_kept' => [
+            <<<TWIG
+            <twig:foo
+                bar="{{ {
+                    a: 'b',
+                    # comment
+                    c: 'd'
+                } }}"
+            />
+            TWIG,
+            // Twig will remove the comment, we don't need to remove it
+            <<<TWIG
+            {{ component('foo', { bar: ({
+                    a: 'b',
+                    # comment
+                    c: 'd'
+                }) }) }}
+            TWIG,
+        ];
     }
 }