Move the extends-in-block and extends-in-macro errors into the CorrectnessNodeVisitor

This commit is contained in:
Fabien Potencier
2026-06-03 23:03:06 +02:00
parent c12100525e
commit de7bbc7be9
3 changed files with 38 additions and 10 deletions
@@ -13,8 +13,10 @@ namespace Twig\NodeVisitor;
use Twig\Environment; use Twig\Environment;
use Twig\Error\SyntaxError; use Twig\Error\SyntaxError;
use Twig\Node\BlockNode;
use Twig\Node\BlockReferenceNode; use Twig\Node\BlockReferenceNode;
use Twig\Node\ConfigNode; use Twig\Node\ConfigNode;
use Twig\Node\MacroNode;
use Twig\Node\ModuleNode; use Twig\Node\ModuleNode;
use Twig\Node\Node; use Twig\Node\Node;
use Twig\Node\NodeCaptureInterface; use Twig\Node\NodeCaptureInterface;
@@ -33,12 +35,17 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
private ?Node $currentTagNode = null; private ?Node $currentTagNode = null;
private bool $hasParent = false; private bool $hasParent = false;
private ?\WeakMap $blockNodes = null; private ?\WeakMap $blockNodes = null;
private int $blockDepth = 0;
private int $macroDepth = 0;
public function enterNode(Node $node, Environment $env): Node public function enterNode(Node $node, Environment $env): Node
{ {
if ($node instanceof ModuleNode) { if ($node instanceof ModuleNode) {
$this->rootNodes = new \WeakMap(); $this->rootNodes = new \WeakMap();
$this->hasParent = $node->hasNode('parent'); $this->hasParent = $node->hasNode('parent');
// reset in case a previous traversal threw before balancing the counters
$this->blockDepth = 0;
$this->macroDepth = 0;
// allows to identify when we enter/leave the block nodes // allows to identify when we enter/leave the block nodes
$this->blockNodes = new \WeakMap(); $this->blockNodes = new \WeakMap();
@@ -59,11 +66,25 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
return $node; return $node;
} }
if ($node instanceof BlockNode) {
++$this->blockDepth;
} elseif ($node instanceof MacroNode) {
++$this->macroDepth;
}
if ($this->hasParent && $node->getNodeTag() && !$node instanceof BlockReferenceNode) { if ($this->hasParent && $node->getNodeTag() && !$node instanceof BlockReferenceNode) {
$this->currentTagNode = $node; $this->currentTagNode = $node;
} }
if ($node instanceof ConfigNode && !isset($this->rootNodes[$node])) { if ($node instanceof ConfigNode && !isset($this->rootNodes[$node])) {
// "extends" inside a "block" or a "macro" has always been a hard error; keep it
if ('extends' === $node->getNodeTag() && $this->blockDepth) {
throw new SyntaxError('Cannot use "extend" in a block.', $node->getTemplateLine(), $node->getSourceContext());
}
if ('extends' === $node->getNodeTag() && $this->macroDepth) {
throw new SyntaxError('Cannot use "extend" in a macro.', $node->getTemplateLine(), $node->getSourceContext());
}
trigger_deprecation('twig/twig', '3.27', 'Using the "%s" tag outside the root of a template is deprecated in %s at line %d.', $node->getNodeTag(), $node->getSourceContext()->getName(), $node->getTemplateLine()); trigger_deprecation('twig/twig', '3.27', 'Using the "%s" tag outside the root of a template is deprecated in %s at line %d.', $node->getNodeTag(), $node->getSourceContext()->getName(), $node->getTemplateLine());
} }
@@ -85,6 +106,11 @@ final class CorrectnessNodeVisitor implements NodeVisitorInterface
$this->hasParent = false; $this->hasParent = false;
$this->blockNodes = null; $this->blockNodes = null;
} }
if ($node instanceof BlockNode) {
--$this->blockDepth;
} elseif ($node instanceof MacroNode) {
--$this->macroDepth;
}
if ($this->hasParent && $node->getNodeTag() && !$node instanceof BlockReferenceNode) { if ($this->hasParent && $node->getNodeTag() && !$node instanceof BlockReferenceNode) {
$this->currentTagNode = null; $this->currentTagNode = null;
} }
+1 -10
View File
@@ -12,7 +12,6 @@
namespace Twig\TokenParser; namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\ConfigNode; use Twig\Node\ConfigNode;
use Twig\Node\Node; use Twig\Node\Node;
use Twig\Token; use Twig\Token;
@@ -28,16 +27,8 @@ final class ExtendsTokenParser extends AbstractTokenParser
{ {
public function parse(Token $token): Node 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->setParent($this->parser->parseExpression());
$stream->expect(Token::BLOCK_END_TYPE); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
return new ConfigNode($token->getLine()); return new ConfigNode($token->getLine());
} }
@@ -0,0 +1,11 @@
--TEST--
"extends" tag nested in a condition inside a block
--TEMPLATE--
{% block foo %}
{% if true %}{% extends "base.twig" %}{% endif %}
{% endblock %}
--TEMPLATE(base.twig)--
--DATA--
return []
--EXCEPTION--
Twig\Error\SyntaxError: Cannot use "extend" in a block in "index.twig" at line 3.