diff --git a/doc/filters/markdown_to_html.rst b/doc/filters/markdown_to_html.rst index 8e3fede00..886cda4d4 100644 --- a/doc/filters/markdown_to_html.rst +++ b/doc/filters/markdown_to_html.rst @@ -7,7 +7,7 @@ The ``markdown_to_html`` filter converts a block of Markdown to HTML: {% apply markdown_to_html %} Title - ====== + ===== Hello! {% endapply %} @@ -19,7 +19,7 @@ removed consistently before conversion: {% apply markdown_to_html %} Title - ====== + ===== Hello! {% endapply %} diff --git a/doc/functions/attribute.rst b/doc/functions/attribute.rst index e9d9a842e..832859de1 100644 --- a/doc/functions/attribute.rst +++ b/doc/functions/attribute.rst @@ -21,3 +21,10 @@ attribute: The resolution algorithm is the same as the one used for the ``.`` notation, except that the item can be any valid expression. + +Arguments +--------- + +* ``variable``: The variable +* ``attribute``: The attribute name +* ``arguments``: An array of arguments to pass to the call diff --git a/doc/functions/block.rst b/doc/functions/block.rst index 117e160f5..efc516eb1 100644 --- a/doc/functions/block.rst +++ b/doc/functions/block.rst @@ -32,6 +32,12 @@ current template: ... {% endif %} +Arguments +--------- + +* ``name``: The block name +* ``template``: The template where to look for the block + .. seealso:: :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>` diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 99fd42382..7a04f5c9f 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -34,6 +34,7 @@ use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; use Twig\Node\Node; +use Twig\Util\CallableArgumentsExtractor; /** * Parses expressions. @@ -439,7 +440,6 @@ class ExpressionParser { switch ($name) { case 'parent': - $this->parseArguments(); if (!\count($this->parser->getBlockStack())) { throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()); } @@ -448,21 +448,23 @@ class ExpressionParser throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()); } + $this->parseArguments(true); + return new ParentExpression($this->parser->peekBlockStack(), $line); case 'block': - $args = $this->parseArguments(); - if (\count($args) < 1) { - throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()); - } + $fakeNode = new Node(lineno: $line); + $fakeNode->setSourceContext($this->parser->getStream()->getSourceContext()); + $fakeFunction = new TwigFunction('block', fn ($name, $template = null) => null); + $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($this->parseArguments(true)); - return new BlockReferenceExpression($args->getNode('0'), \count($args) > 1 ? $args->getNode('1') : null, $line); + return new BlockReferenceExpression($args[0], $args[1] ?? null, $line); case 'attribute': - $args = $this->parseArguments(); - if (\count($args) < 2) { - throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()); - } + $fakeNode = new Node(lineno: $line); + $fakeNode->setSourceContext($this->parser->getStream()->getSourceContext()); + $fakeFunction = new TwigFunction('attribute', fn ($variable, $attribute, $arguments = null) => null); + $args = (new CallableArgumentsExtractor($fakeNode, $fakeFunction))->extractArguments($this->parseArguments(true)); - return new GetAttrExpression($args->getNode('0'), $args->getNode('1'), \count($args) > 2 ? $args->getNode('2') : null, Template::ANY_CALL, $line); + return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line); case 'loop': $args = $this->parseArguments(); if (\count($args) < 1) { diff --git a/src/Util/CallableArgumentsExtractor.php b/src/Util/CallableArgumentsExtractor.php index 1c8244642..1eb6a4524 100644 --- a/src/Util/CallableArgumentsExtractor.php +++ b/src/Util/CallableArgumentsExtractor.php @@ -121,7 +121,7 @@ final class CallableArgumentsExtractor $optionalArguments = []; ++$pos; } elseif ($callableParameter->isDefaultValueAvailable()) { - $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1); + $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), $this->node->getTemplateLine()); } elseif ($callableParameter->isOptional()) { if (!$extractedArguments) { break; @@ -134,12 +134,12 @@ final class CallableArgumentsExtractor } if ($this->twigCallable->isVariadic()) { - $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1); + $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], $this->node->getTemplateLine()) : new ArrayExpression([], $this->node->getTemplateLine()); foreach ($extractedArguments as $key => $value) { if (\is_int($key)) { $arbitraryArguments->addElement($value); } else { - $arbitraryArguments->addElement($value, new ConstantExpression($key, -1)); + $arbitraryArguments->addElement($value, new ConstantExpression($key, $this->node->getTemplateLine())); } unset($extractedArguments[$key]); } diff --git a/tests/Fixtures/functions/attribute.test b/tests/Fixtures/functions/attribute.test index 4499ad4bd..2f70b08a4 100644 --- a/tests/Fixtures/functions/attribute.test +++ b/tests/Fixtures/functions/attribute.test @@ -2,17 +2,21 @@ "attribute" function --TEMPLATE-- {{ attribute(obj, method) }} +{{ attribute(variable=obj, attribute=method) }} {{ attribute(array, item) }} {{ attribute(obj, "bar", ["a", "b"]) }} {{ attribute(obj, "bar", arguments) }} +{{ attribute(variable=obj, attribute="bar", arguments=arguments) }} {{ attribute(obj, method) is defined ? 'ok' : 'ko' }} {{ attribute(obj, nonmethod) is defined ? 'ok' : 'ko' }} --DATA-- return ['obj' => new Twig\Tests\TwigTestFoo(), 'method' => 'foo', 'array' => ['foo' => 'bar'], 'item' => 'foo', 'nonmethod' => 'xxx', 'arguments' => ['a', 'b']] --EXPECT-- foo +foo bar bar_a-b bar_a-b +bar_a-b ok ko diff --git a/tests/Fixtures/functions/attribute_with_wrong_args.test b/tests/Fixtures/functions/attribute_with_wrong_args.test new file mode 100644 index 000000000..6e8a17cf8 --- /dev/null +++ b/tests/Fixtures/functions/attribute_with_wrong_args.test @@ -0,0 +1,8 @@ +--TEST-- +"attribute" function +--TEMPLATE-- +{{ attribute(var=var, template="tpl") }} +--DATA-- +return ['var' => null] +--EXCEPTION-- +Twig\Error\SyntaxError: Value for argument "variable" is required for function "attribute" in "index.twig" at line 2. diff --git a/tests/Fixtures/functions/block.test b/tests/Fixtures/functions/block.test index 1a4fd5492..bf8decd1e 100644 --- a/tests/Fixtures/functions/block.test +++ b/tests/Fixtures/functions/block.test @@ -5,8 +5,9 @@ {% block bar %}BAR{% endblock %} --TEMPLATE(base.twig)-- {% block foo %}{{ block('bar') }}{% endblock %} +{% block baz %}{{ block(name='bar') }}{% endblock %} {% block bar %}BAR_BASE{% endblock %} --DATA-- return [] --EXPECT-- -BARBAR +BARBARBAR diff --git a/tests/Fixtures/functions/block_with_template.test b/tests/Fixtures/functions/block_with_template.test index 37cb7a481..2e0d2916c 100644 --- a/tests/Fixtures/functions/block_with_template.test +++ b/tests/Fixtures/functions/block_with_template.test @@ -6,6 +6,10 @@ {{ block('foo', included_loaded_internal) }} {% set output = block('foo', 'included.twig') %} {{ output }} +{% set output = block(name='foo', template='included.twig') %} +{{ output }} +{% set output = block(template='included.twig', name='foo') %} +{{ output }} {% block foo %}NOT FOO{% endblock %} --TEMPLATE(included.twig)-- {% block foo %}FOO{% endblock %} @@ -19,4 +23,6 @@ FOO FOO FOO FOO +FOO +FOO NOT FOO diff --git a/tests/Fixtures/functions/block_without_name.test b/tests/Fixtures/functions/block_without_name.test index 236df9451..61896a8a2 100644 --- a/tests/Fixtures/functions/block_without_name.test +++ b/tests/Fixtures/functions/block_without_name.test @@ -9,4 +9,4 @@ --DATA-- return [] --EXCEPTION-- -Twig\Error\SyntaxError: The "block" function takes one argument (the block name) in "base.twig" at line 2. +Twig\Error\SyntaxError: Value for argument "name" is required for function "block" in "base.twig" at line 2.