Skip to content

Commit c3854cc

Browse files
committed
[TwigComponent] Html syntax bug test case + fix
1 parent e7fc5b8 commit c3854cc

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

src/TwigComponent/src/Twig/TwigPreLexer.php

+30-7
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ public function __construct(int $startingLine = 1)
3030
$this->line = $startingLine;
3131
}
3232

33-
public function preLexComponents(string $input): string
33+
/**
34+
* @param bool $insideOfBlock Are we sub-parsing the content inside a block?
35+
*/
36+
public function preLexComponents(string $input, bool $insideOfBlock = false): string
3437
{
3538
$this->input = $input;
3639
$this->length = \strlen($input);
@@ -53,6 +56,15 @@ public function preLexComponents(string $input): string
5356
continue;
5457
}
5558

59+
// if we're already inside a component, and we're not inside a block,
60+
// *and* we've just found a new component, then we should try to
61+
// open the default block
62+
if (!$insideOfBlock
63+
&& !empty($this->currentComponents)
64+
&& !$this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock']) {
65+
$output .= $this->addDefaultBlock();
66+
}
67+
5668
$attributes = $this->consumeAttributes($componentName);
5769
$isSelfClosing = $this->consume('/>');
5870
if (!$isSelfClosing) {
@@ -100,13 +112,12 @@ public function preLexComponents(string $input): string
100112
++$this->line;
101113
}
102114

103-
// handle adding a default block if needed
115+
// handle adding a default block if we find non-whitespace outside of a block
104116
if (!empty($this->currentComponents)
105117
&& !$this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock']
106118
&& preg_match('/\S/', $char)
107119
) {
108-
$this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] = true;
109-
$output .= '{% block content %}';
120+
$output .= $this->addDefaultBlock();
110121
}
111122

112123
$output .= $char;
@@ -202,10 +213,15 @@ private function consumeAttributes(string $componentName): string
202213
return implode(', ', $attributes);
203214
}
204215

216+
/**
217+
* If the next character(s) exactly matches the given string, then
218+
* consume it (move forward) and return true.
219+
*/
205220
private function consume(string $string): bool
206221
{
207-
if (substr($this->input, $this->position, \strlen($string)) === $string) {
208-
$this->position += \strlen($string);
222+
$stringLength = \strlen($string);
223+
if (substr($this->input, $this->position, $stringLength) === $string) {
224+
$this->position += $stringLength;
209225

210226
return true;
211227
}
@@ -317,7 +333,7 @@ private function consumeBlock(string $componentName): string
317333
$blockContents = $this->consumeUntilEndBlock();
318334

319335
$subLexer = new self($this->line);
320-
$output .= $subLexer->preLexComponents($blockContents);
336+
$output .= $subLexer->preLexComponents($blockContents, true);
321337

322338
$this->consume($closingTag);
323339
$output .= '{% endblock %}';
@@ -395,4 +411,11 @@ private function doesStringEventuallyExist(string $needle): bool
395411

396412
return str_contains($remainingString, $needle);
397413
}
414+
415+
private function addDefaultBlock(): string
416+
{
417+
$this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] = true;
418+
419+
return '{% block content %}';
420+
}
398421
}

src/TwigComponent/tests/Unit/TwigPreLexerTest.php

+29-1
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,37 @@ public function getLexTests(): iterable
9696
'<twig:foo><twig:block name="child"><twig:bar><twig:block name="message">Hello World!</twig:block></twig:bar></twig:block></twig:foo>',
9797
'{% component \'foo\' %}{% block child %}{% component \'bar\' %}{% block message %}Hello World!{% endblock %}{% endcomponent %}{% endblock %}{% endcomponent %}',
9898
];
99-
yield 'component_with_html_syntaxt_in_argument' => [
99+
yield 'component_with_mixture_of_string_and_twig_in_argument' => [
100100
'<twig:foo text="Hello {{ name }}!"/>',
101101
"{{ component('foo', { text: 'Hello '~(name)~'!' }) }}",
102102
];
103+
yield 'component_with_mixture_of_string_and_twig_with_quote_in_argument' => [
104+
'<twig:foo text="Hello {{ name }}, I\'m Theo!"/>',
105+
"{{ component('foo', { text: 'Hello '~(name)~', I\'m Theo!' }) }}",
106+
];
107+
yield 'component_where_entire_default_block_is_embedded_component' => [
108+
<<<EOF
109+
<twig:foo>
110+
<twig:bar>bar content</twig:bar>
111+
</twig:foo>
112+
EOF,
113+
<<<EOF
114+
{% component 'foo' %}
115+
{% block content %}{% component 'bar' %}{% block content %}bar content{% endblock %}{% endcomponent %}
116+
{% endblock %}{% endcomponent %}
117+
EOF
118+
];
119+
yield 'component_where_entire_default_block_is_embedded_component_self_closing' => [
120+
<<<EOF
121+
<twig:foo>
122+
<twig:bar />
123+
</twig:foo>
124+
EOF,
125+
<<<EOF
126+
{% component 'foo' %}
127+
{% block content %}{{ component('bar') }}
128+
{% endblock %}{% endcomponent %}
129+
EOF
130+
];
103131
}
104132
}

0 commit comments

Comments
 (0)