Make {} optional for the types tag

This commit is contained in:
Fabien Potencier
2025-01-06 13:51:14 +01:00
parent fb715039c3
commit dff6fdd675
4 changed files with 39 additions and 17 deletions
+1
View File
@@ -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)
+22 -9
View File
@@ -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 <creating_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
+7 -8
View File
@@ -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;
}
@@ -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],
],
],
];
}
}