added a syntax error when using a loop variable that is not defined (closes #925)
authorFabien Potencier <fabien.potencier@gmail.com>
Mon, 10 Dec 2012 14:52:15 +0000 (15:52 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Mon, 10 Dec 2012 20:06:39 +0000 (21:06 +0100)
CHANGELOG
lib/Twig/TokenParser/For.php
test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test [new file with mode: 0644]
test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test [new file with mode: 0644]

index fffef7e..8bd3e26 100644 (file)
--- 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
index 8673de3..aef4f17 100644 (file)
@@ -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 (file)
index 0000000..4301ef2
--- /dev/null
@@ -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 (file)
index 0000000..c7e723a
--- /dev/null
@@ -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