From c3854cc49408fb46302cc9c9ad84b099a1a0b4ed Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 30 Apr 2023 10:09:10 -0400 Subject: [PATCH] [TwigComponent] Html syntax bug test case + fix --- src/TwigComponent/src/Twig/TwigPreLexer.php | 37 +++++++++++++++---- .../tests/Unit/TwigPreLexerTest.php | 30 ++++++++++++++- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/TwigComponent/src/Twig/TwigPreLexer.php b/src/TwigComponent/src/Twig/TwigPreLexer.php index 3b5dd3aead3..6cbf3965e85 100644 --- a/src/TwigComponent/src/Twig/TwigPreLexer.php +++ b/src/TwigComponent/src/Twig/TwigPreLexer.php @@ -30,7 +30,10 @@ public function __construct(int $startingLine = 1) $this->line = $startingLine; } - public function preLexComponents(string $input): string + /** + * @param bool $insideOfBlock Are we sub-parsing the content inside a block? + */ + public function preLexComponents(string $input, bool $insideOfBlock = false): string { $this->input = $input; $this->length = \strlen($input); @@ -53,6 +56,15 @@ public function preLexComponents(string $input): string continue; } + // if we're already inside a component, and we're not inside a block, + // *and* we've just found a new component, then we should try to + // open the default block + if (!$insideOfBlock + && !empty($this->currentComponents) + && !$this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock']) { + $output .= $this->addDefaultBlock(); + } + $attributes = $this->consumeAttributes($componentName); $isSelfClosing = $this->consume('/>'); if (!$isSelfClosing) { @@ -100,13 +112,12 @@ public function preLexComponents(string $input): string ++$this->line; } - // handle adding a default block if needed + // handle adding a default block if we find non-whitespace outside of a block if (!empty($this->currentComponents) && !$this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] && preg_match('/\S/', $char) ) { - $this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] = true; - $output .= '{% block content %}'; + $output .= $this->addDefaultBlock(); } $output .= $char; @@ -202,10 +213,15 @@ private function consumeAttributes(string $componentName): string return implode(', ', $attributes); } + /** + * If the next character(s) exactly matches the given string, then + * consume it (move forward) and return true. + */ private function consume(string $string): bool { - if (substr($this->input, $this->position, \strlen($string)) === $string) { - $this->position += \strlen($string); + $stringLength = \strlen($string); + if (substr($this->input, $this->position, $stringLength) === $string) { + $this->position += $stringLength; return true; } @@ -317,7 +333,7 @@ private function consumeBlock(string $componentName): string $blockContents = $this->consumeUntilEndBlock(); $subLexer = new self($this->line); - $output .= $subLexer->preLexComponents($blockContents); + $output .= $subLexer->preLexComponents($blockContents, true); $this->consume($closingTag); $output .= '{% endblock %}'; @@ -395,4 +411,11 @@ private function doesStringEventuallyExist(string $needle): bool return str_contains($remainingString, $needle); } + + private function addDefaultBlock(): string + { + $this->currentComponents[\count($this->currentComponents) - 1]['hasDefaultBlock'] = true; + + return '{% block content %}'; + } } diff --git a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php index 23d45d3857a..ea1d8aca70b 100644 --- a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php +++ b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php @@ -96,9 +96,37 @@ public function getLexTests(): iterable 'Hello World!', '{% component \'foo\' %}{% block child %}{% component \'bar\' %}{% block message %}Hello World!{% endblock %}{% endcomponent %}{% endblock %}{% endcomponent %}', ]; - yield 'component_with_html_syntaxt_in_argument' => [ + yield 'component_with_mixture_of_string_and_twig_in_argument' => [ '', "{{ component('foo', { text: 'Hello '~(name)~'!' }) }}", ]; + yield 'component_with_mixture_of_string_and_twig_with_quote_in_argument' => [ + '', + "{{ component('foo', { text: 'Hello '~(name)~', I\'m Theo!' }) }}", + ]; + yield 'component_where_entire_default_block_is_embedded_component' => [ + << + bar content + + EOF, + << [ + << + + + EOF, + <<