-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #4235 Add types tag (drjayvee)
This PR was squashed before being merged into the 3.x branch. Discussion ---------- Add types tag The is a draft implementation for #4165. Commits ------- 67dabd8 Add types tag
- Loading branch information
Showing
8 changed files
with
271 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# 3.13.0 (2024-XX-XX) | ||
|
||
* n/a | ||
* Add the `types` tag (experimental) | ||
|
||
# 3.12.0 (2024-08-29) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ Tags | |
macro | ||
sandbox | ||
set | ||
types | ||
use | ||
verbatim | ||
with |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
``types`` | ||
========= | ||
|
||
.. versionadded:: 3.13 | ||
|
||
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. | ||
|
||
To do this, specify a :ref:`mapping <twig-expressions>` of names to their types as strings. | ||
|
||
Here is how to declare that ``foo`` is a boolean, while ``bar`` is an integer (see note below): | ||
|
||
.. code-block:: twig | ||
{% types { | ||
foo: 'bool', | ||
bar: 'int', | ||
} %} | ||
You can declare variables as optional by adding the ``?`` suffix: | ||
|
||
.. code-block:: twig | ||
{% types { | ||
foo: 'bool', | ||
bar?: 'int', | ||
} %} | ||
By default, this tag does not affect the template compilation or runtime behavior. | ||
|
||
Its purpose is to enable designers and developers to document and specify the context's available | ||
and/or required variables. While Twig itself does not 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 syntax for and contents of type strings are intentionally left out of scope. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
namespace Twig\Node; | ||
|
||
use Twig\Attribute\YieldReady; | ||
use Twig\Compiler; | ||
|
||
/** | ||
* Represents a types node. | ||
* | ||
* @author Jeroen Versteeg <[email protected]> | ||
*/ | ||
#[YieldReady] | ||
class TypesNode extends Node implements NodeCaptureInterface | ||
{ | ||
/** | ||
* @param array<string, array{type: string, optional: bool}> $types | ||
*/ | ||
public function __construct(array $types, int $lineno) | ||
{ | ||
parent::__construct([], ['mapping' => $types], $lineno); | ||
} | ||
|
||
public function compile(Compiler $compiler) | ||
{ | ||
// Don't compile anything. | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of Twig. | ||
* | ||
* (c) Fabien Potencier | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Twig\TokenParser; | ||
|
||
use Twig\Error\SyntaxError; | ||
use Twig\Node\Node; | ||
use Twig\Node\TypesNode; | ||
use Twig\Token; | ||
use Twig\TokenStream; | ||
|
||
/** | ||
* Declare variable types. | ||
* | ||
* {% types {foo: 'int', bar?: 'string'} %} | ||
* | ||
* @author Jeroen Versteeg <[email protected]> | ||
* @internal | ||
*/ | ||
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()); | ||
} | ||
|
||
/** | ||
* @return array<string, array{type: string, optional: bool}> | ||
* | ||
* @throws SyntaxError | ||
*/ | ||
private function parseSimpleMappingExpression(TokenStream $stream): array | ||
{ | ||
$stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); | ||
|
||
$types = []; | ||
|
||
$first = true; | ||
while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { | ||
if (!$first) { | ||
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma'); | ||
|
||
// trailing ,? | ||
if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { | ||
break; | ||
} | ||
} | ||
$first = false; | ||
|
||
$nameToken = $stream->expect(Token::NAME_TYPE); | ||
$isOptional = $stream->nextIf(Token::PUNCTUATION_TYPE, '?') !== null; | ||
|
||
$stream->expect(Token::PUNCTUATION_TYPE, ':', 'A type name must be followed by a colon (:)'); | ||
|
||
$valueToken = $stream->expect(Token::STRING_TYPE); | ||
|
||
$types[$nameToken->getValue()] = [ | ||
'type' => $valueToken->getValue(), | ||
'optional' => $isOptional, | ||
]; | ||
} | ||
$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed'); | ||
|
||
return $types; | ||
} | ||
|
||
public function getTag(): string | ||
{ | ||
return 'types'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
|
||
namespace Twig\Tests\Node; | ||
|
||
use Twig\Node\TypesNode; | ||
use Twig\Test\NodeTestCase; | ||
|
||
class TypesTest extends NodeTestCase | ||
{ | ||
private function getValidMapping(): array | ||
{ | ||
// {foo: 'string', bar?: 'int'} | ||
return [ | ||
'foo' => [ | ||
'type' => 'string', | ||
'optional' => false, | ||
], | ||
'bar' => [ | ||
'type' => 'int', | ||
'optional' => true, | ||
] | ||
]; | ||
} | ||
|
||
public function testConstructor() | ||
{ | ||
$types = $this->getValidMapping(); | ||
$node = new TypesNode($types, 1); | ||
|
||
$this->assertEquals($types, $node->getAttribute('mapping')); | ||
} | ||
|
||
public function getTests() | ||
{ | ||
return [ | ||
// 1st test: Node shouldn't compile at all | ||
[ | ||
new TypesNode($this->getValidMapping(), 1), | ||
'' | ||
] | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?php | ||
|
||
namespace Twig\Tests\TokenParser; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Twig\Environment; | ||
use Twig\Loader\ArrayLoader; | ||
use Twig\Parser; | ||
use Twig\Source; | ||
|
||
class TypesTokenParserTest extends TestCase | ||
{ | ||
/** @dataProvider getMappingTests */ | ||
public function testMappingParsing(string $template, array $expected): void | ||
{ | ||
$env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]); | ||
$stream = $env->tokenize($source = new Source($template, '')); | ||
$parser = new Parser($env); | ||
|
||
$typesNode = $parser->parse($stream)->getNode('body')->getNode('0'); | ||
|
||
self::assertEquals($expected, $typesNode->getAttribute('mapping')); | ||
} | ||
|
||
public function getMappingTests(): array | ||
{ | ||
return [ | ||
// empty mapping | ||
[ | ||
'{% types {} %}', | ||
[], | ||
], | ||
|
||
// simple | ||
[ | ||
'{% types {foo: "bar"} %}', | ||
[ | ||
'foo' => ['type' => 'bar', 'optional' => false] | ||
], | ||
], | ||
|
||
// trailing comma | ||
[ | ||
'{% types {foo: "bar",} %}', | ||
[ | ||
'foo' => ['type' => 'bar', 'optional' => false] | ||
], | ||
], | ||
|
||
// optional name | ||
[ | ||
'{% types {foo?: "bar"} %}', | ||
[ | ||
'foo' => ['type' => 'bar', 'optional' => true] | ||
], | ||
], | ||
|
||
// multiple pairs, duplicate values | ||
[ | ||
'{% types {foo: "foo", bar?: "foo", baz: "baz"} %}', | ||
[ | ||
'foo' => ['type' => 'foo', 'optional' => false], | ||
'bar' => ['type' => 'foo', 'optional' => true], | ||
'baz' => ['type' => 'baz', 'optional' => false] | ||
], | ||
], | ||
]; | ||
} | ||
} |