From dff6fdd675a672a4b0335fe4a88b6f244a2e71e7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 6 Jan 2025 13:51:14 +0100 Subject: [PATCH] Make {} optional for the types tag --- CHANGELOG | 1 + doc/tags/types.rst | 31 +++++++++++++++------- src/TokenParser/TypesTokenParser.php | 15 +++++------ tests/TokenParser/TypesTokenParserTest.php | 9 +++++++ 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 71eb10819..f1d757d20 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.19.0 (2025-XX-XX) + * Make `{}` optional for the `types` tag * Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes # 3.18.0 (2024-12-29) diff --git a/doc/tags/types.rst b/doc/tags/types.rst index a4e4a6c10..c3a175392 100644 --- a/doc/tags/types.rst +++ b/doc/tags/types.rst @@ -6,17 +6,23 @@ The ``types`` tag was added in Twig 3.13. This tag is **experimental** and can change based on usage and feedback. -The ``types`` tag declares the types of template variables. +Use the ``types`` tag to declare the type of a variable: -.. note:: +.. code-block:: twig - The types declared in a template are local to that template and must not be - propagated to included templates. This is because a template can be - included from multiple different places, each potentially having different - variable types. + {% types is_correct: 'boolean' %} + {% types score: 'number' %} -Here is how to declare that ``is_correct`` is a boolean, while ``score`` is a -number (see note below): +Or multiple variables: + +.. code-block:: twig + + {% types + is_correct: 'boolean', + score: 'number', + %} + +You can also enclose types with ``{}``: .. code-block:: twig @@ -25,7 +31,7 @@ number (see note below): score: 'number', } %} -You can declare variables as optional by adding the ``?`` suffix: +Declare optional variables by adding a ``?`` suffix: .. code-block:: twig @@ -43,6 +49,13 @@ validate variables or their types, this tag enables extensions to do this. Additionally, :ref:`Twig extensions ` can analyze these tags to perform compile-time and runtime analysis of templates. +.. note:: + + The types declared in a template are local to that template and must not be + propagated to included templates. This is because a template can be + included from multiple different places, each potentially having different + variable types. + .. note:: The syntax for and contents of type strings are intentionally left out of diff --git a/src/TokenParser/TypesTokenParser.php b/src/TokenParser/TypesTokenParser.php index 02aef81cc..a7da0f5ec 100644 --- a/src/TokenParser/TypesTokenParser.php +++ b/src/TokenParser/TypesTokenParser.php @@ -31,9 +31,7 @@ final class TypesTokenParser extends AbstractTokenParser public function parse(Token $token): Node { $stream = $this->parser->getStream(); - $types = $this->parseSimpleMappingExpression($stream); - $stream->expect(Token::BLOCK_END_TYPE); return new TypesNode($types, $token->getLine()); @@ -46,17 +44,15 @@ final class TypesTokenParser extends AbstractTokenParser */ private function parseSimpleMappingExpression(TokenStream $stream): array { - $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); - + $enclosed = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '{'); $types = []; - $first = true; - while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + while (!($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE))) { if (!$first) { $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma'); // trailing ,? - if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + if ($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE)) { break; } } @@ -78,7 +74,10 @@ final class TypesTokenParser extends AbstractTokenParser 'optional' => $isOptional, ]; } - $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); + + if ($enclosed) { + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); + } return $types; } diff --git a/tests/TokenParser/TypesTokenParserTest.php b/tests/TokenParser/TypesTokenParserTest.php index 400820fca..0acbb4dd3 100644 --- a/tests/TokenParser/TypesTokenParserTest.php +++ b/tests/TokenParser/TypesTokenParserTest.php @@ -64,6 +64,15 @@ class TypesTokenParserTest extends TestCase 'baz' => ['type' => 'baz', 'optional' => false], ], ], + + // without {} enclosing + [ + '{% types foo: "foo", bar: "bar" %}', + [ + 'foo' => ['type' => 'foo', 'optional' => false], + 'bar' => ['type' => 'bar', 'optional' => false], + ], + ], ]; } }