From 811dfad7c270b39b3d43aabbcfbc94b559eef845 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 10 Dec 2012 15:52:15 +0100 Subject: [PATCH] added a syntax error when using a loop variable that is not defined (closes #925) --- CHANGELOG | 1 + lib/Twig/TokenParser/For.php | 46 ++++++++++++++++++++ .../Tests/Fixtures/tags/for/loop_not_defined.test | 10 ++++ .../Fixtures/tags/for/loop_not_defined_cond.test | 9 ++++ 4 files changed, 66 insertions(+), 0 deletions(-) create mode 100644 test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test create mode 100644 test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test diff --git a/CHANGELOG b/CHANGELOG index fffef7e..8bd3e26 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ * 1.12.0 (2012-XX-XX) + * added a syntax error when using a loop variable that is not defined * added the ability to set default values for macro arguments * added support for named arguments for filters, tests, and functions * moved filters/functions/tests syntax errors to the parser diff --git a/lib/Twig/TokenParser/For.php b/lib/Twig/TokenParser/For.php index 8673de3..aef4f17 100644 --- a/lib/Twig/TokenParser/For.php +++ b/lib/Twig/TokenParser/For.php @@ -65,6 +65,11 @@ class Twig_TokenParser_For extends Twig_TokenParser $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine()); } + if ($ifexpr) { + $this->checkLoopUsageCondition($stream, $ifexpr); + $this->checkLoopUsageBody($stream, $body); + } + return new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag()); } @@ -78,6 +83,47 @@ class Twig_TokenParser_For extends Twig_TokenParser return $token->test('endfor'); } + // the loop variable cannot be used in the condition + protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node) + { + if ($node instanceof Twig_Node_Expression_GetAttr && 'loop' == $node->getNode('node')->getAttribute('name')) { + throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition', $node->getLine(), $stream->getFilename()); + } + + foreach ($node as $n) { + if (!$n) { + continue; + } + + $this->checkLoopUsageCondition($stream, $n); + } + } + + // check usage of non-defined loop-items + // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include) + protected function checkLoopUsageBody(Twig_TokenStream $stream, Twig_NodeInterface $node) + { + if ($node instanceof Twig_Node_Expression_GetAttr && 'loop' == $node->getNode('node')->getAttribute('name')) { + $attribute = $node->getNode('attribute'); + if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) { + throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition', $attribute->getAttribute('value')), $node->getLine(), $stream->getFilename()); + } + } + + // should check for parent.loop.XXX usage + if ($node instanceof Twig_Node_For) { + return; + } + + foreach ($node as $n) { + if (!$n) { + continue; + } + + $this->checkLoopUsageBody($stream, $n); + } + } + /** * Gets the tag name associated with this token parser. * diff --git a/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test b/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test new file mode 100644 index 0000000..4301ef2 --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test @@ -0,0 +1,10 @@ +--TEST-- +"for" tag +--TEMPLATE-- +{% for i, item in items if i > 0 %} + {{ loop.last }} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXCEPTION-- +Twig_Error_Syntax: The "loop.last" variable is not defined when looping with a condition in "index.twig" at line 3 diff --git a/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test b/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test new file mode 100644 index 0000000..c7e723a --- /dev/null +++ b/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test @@ -0,0 +1,9 @@ +--TEST-- +"for" tag +--TEMPLATE-- +{% for i, item in items if loop.last > 0 %} +{% endfor %} +--DATA-- +return array('items' => array('a', 'b')) +--EXCEPTION-- +Twig_Error_Syntax: The "loop" variable cannot be used in a looping condition in "index.twig" at line 2 -- 1.7.2.5