diff --git a/CHANGELOG b/CHANGELOG index 0365a0865..92e60a233 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ * Track the source offset of each token and expose it via `Token::getOffset()` * Fix nested `block()` calls to resolve against the overriding template when a block rendered through `block(name, template)` calls `parent()` * Deprecate the possibility to use a `block` tag within a capture node (like `set`) + * Deprecate the possibility to use a `block` tag within a capture node (like `set`) in child templates * Deprecate using a `macro`, `extends`, or `use` tag outside the root of a template * Stop reporting a skipped test in `IntegrationTestCase` when there is no legacy test to run * Fix `markdown_to_html` to strip the indentation shared by all lines instead of mangling content that starts with a blank line diff --git a/doc/deprecated.rst b/doc/deprecated.rst index 25bc8d5b8..30267cb5b 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -302,9 +302,10 @@ Templates instances of ``Twig\TemplateWrapper`` instead. * Having a "block" definition nested in another node that captures the output - (like "set") is deprecated in Twig 3.14 and will throw in Twig 4.0. Such use - cases should be avoided as the "block" tag is used to both define the block - AND display it in place. Here is how you can decouple both easily: + (like "set") in a child template is deprecated in Twig 3.14 and will throw + in Twig 4.0. Such use cases should be avoided as the "block" tag is used to + both define the block AND display it in place. Here is how you can decouple + both: Before:: diff --git a/src/NodeVisitor/CorrectnessNodeVisitor.php b/src/NodeVisitor/CorrectnessNodeVisitor.php index 0d4400e35..e0afd4d60 100644 --- a/src/NodeVisitor/CorrectnessNodeVisitor.php +++ b/src/NodeVisitor/CorrectnessNodeVisitor.php @@ -20,6 +20,7 @@ use Twig\Node\MacroNode; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\Node\NodeCaptureInterface; +use Twig\Node\NodeOutputInterface; use Twig\Node\Nodes; use Twig\Node\TextNode; @@ -145,6 +146,10 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface return $node->isBlank(); } + if (!$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) { + return false; + } + foreach ($node as $n) { if (!$this->isEmptyOutputNode($n)) { return false; diff --git a/src/TokenParser/ExtendsTokenParser.php b/src/TokenParser/ExtendsTokenParser.php index c1f0d7581..f6d9d0289 100644 --- a/src/TokenParser/ExtendsTokenParser.php +++ b/src/TokenParser/ExtendsTokenParser.php @@ -12,6 +12,7 @@ namespace Twig\TokenParser; +use Twig\Error\SyntaxError; use Twig\Node\ConfigNode; use Twig\Node\Node; use Twig\Token; @@ -27,8 +28,16 @@ final class ExtendsTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { + $stream = $this->parser->getStream(); + + if ($this->parser->peekBlockStack()) { + throw new SyntaxError('Cannot use "extend" in a block.', $token->getLine(), $stream->getSourceContext()); + } elseif (!$this->parser->isMainScope()) { + throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext()); + } + $this->parser->setParent($this->parser->parseExpression()); - $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $stream->expect(Token::BLOCK_END_TYPE); return new ConfigNode($token->getLine()); } diff --git a/tests/Fixtures/exceptions/child_print_outside_blocks.test b/tests/Fixtures/exceptions/child_print_outside_blocks.test new file mode 100644 index 000000000..343a92e3e --- /dev/null +++ b/tests/Fixtures/exceptions/child_print_outside_blocks.test @@ -0,0 +1,17 @@ +--TEST-- +Exception for child templates printing content outside blocks defined by parent +--TEMPLATE-- +{% extends 'base.twig' %} + +{{ content }} + +{% block sidebar %} + Content inside a block. +{% endblock %} +--TEMPLATE(base.twig)-- +{% block sidebar %} +{% endblock %} +--DATA-- +return ['content' => 'Content outside a block.']; +--EXCEPTION-- +Twig\Error\SyntaxError: A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag in "index.twig" at line 4? diff --git a/tests/Fixtures/tags/inheritance/extends_in_block_after_parent.test b/tests/Fixtures/tags/inheritance/extends_in_block_after_parent.test new file mode 100644 index 000000000..4c31b092a --- /dev/null +++ b/tests/Fixtures/tags/inheritance/extends_in_block_after_parent.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag in a block after a parent has been defined +--TEMPLATE-- +{% extends "base.twig" %} + +{% block foo %} + {% extends "foo.twig" %} +{% endblock %} +--TEMPLATE(base.twig)-- +--TEMPLATE(foo.twig)-- +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Cannot use "extend" in a block in "index.twig" at line 5. diff --git a/tests/Fixtures/tags/inheritance/extends_in_macro_after_parent.test b/tests/Fixtures/tags/inheritance/extends_in_macro_after_parent.test new file mode 100644 index 000000000..82ac30c6a --- /dev/null +++ b/tests/Fixtures/tags/inheritance/extends_in_macro_after_parent.test @@ -0,0 +1,14 @@ +--TEST-- +"extends" tag in a macro after a parent has been defined +--TEMPLATE-- +{% extends "base.twig" %} + +{% macro foo() %} + {% extends "foo.twig" %} +{% endmacro %} +--TEMPLATE(base.twig)-- +--TEMPLATE(foo.twig)-- +--DATA-- +return [] +--EXCEPTION-- +Twig\Error\SyntaxError: Cannot use "extend" in a macro in "index.twig" at line 5. diff --git a/tests/Node/TextTest.php b/tests/Node/TextTest.php index 7402da18a..748ded6e4 100644 --- a/tests/Node/TextTest.php +++ b/tests/Node/TextTest.php @@ -20,6 +20,7 @@ namespace Twig\Tests\Node; * file that was distributed with this source code. */ +use PHPUnit\Framework\Attributes\DataProvider; use Twig\Node\TextNode; use Twig\Test\NodeTestCase; @@ -43,6 +44,7 @@ class TextTest extends NodeTestCase /** * @dataProvider getIsBlankData */ + #[DataProvider('getIsBlankData')] public function testIsBlank($blank) { $this->assertTrue((new TextNode($blank, 1))->isBlank()); diff --git a/tests/NodeVisitor/CorrectnessTest.php b/tests/NodeVisitor/CorrectnessTest.php index 4b23d6758..75b1b7f3c 100644 --- a/tests/NodeVisitor/CorrectnessTest.php +++ b/tests/NodeVisitor/CorrectnessTest.php @@ -11,6 +11,7 @@ namespace Twig\Tests\NodeVisitor; * file that was distributed with this source code. */ +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Twig\Environment; use Twig\Error\SyntaxError; @@ -18,9 +19,11 @@ use Twig\Loader\ArrayLoader; use Twig\Node\BodyNode; use Twig\Node\EmptyNode; use Twig\Node\Expression\ConstantExpression; +use Twig\Node\IncludeNode; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\Node\Nodes; +use Twig\Node\PrintNode; use Twig\Node\SetNode; use Twig\Node\TextNode; use Twig\NodeTraverser; @@ -32,6 +35,7 @@ class CorrectnessTest extends TestCase /** * @dataProvider getFilterBodyNodesData */ + #[DataProvider('getFilterBodyNodesData')] public function testFilterBodyNodes($input, $expected) { $this->assertEquals($expected, $this->traverse($input, $expected)); @@ -54,6 +58,7 @@ class CorrectnessTest extends TestCase /** * @dataProvider getFilterBodyNodesDataThrowsException */ + #[DataProvider('getFilterBodyNodesDataThrowsException')] public function testFilterBodyNodesThrowsException($input) { $this->expectException(SyntaxError::class); @@ -64,6 +69,8 @@ class CorrectnessTest extends TestCase { return [ [new TextNode('foo', 1)], + [new PrintNode(new ConstantExpression('foo', 1), 1)], + [new IncludeNode(new ConstantExpression('foo', 1), null, false, false, 1)], [new Nodes([new Nodes([new TextNode('foo', 1)])])], ]; } @@ -71,6 +78,7 @@ class CorrectnessTest extends TestCase /** * @dataProvider getFilterBodyNodesWithBOMData */ + #[DataProvider('getFilterBodyNodesWithBOMData')] public function testFilterBodyNodesWithBOM($emptyText) { $input = new TextNode(\chr(0xEF).\chr(0xBB).\chr(0xBF).$emptyText, 1);