optimized the way Twig exceptions are managed
authorFabien Potencier <fabien.potencier@gmail.com>
Wed, 17 Oct 2012 17:29:45 +0000 (19:29 +0200)
committerFabien Potencier <fabien.potencier@gmail.com>
Fri, 19 Oct 2012 12:45:58 +0000 (14:45 +0200)
As guessing the template name and the line number of where an error
occurred is quite expensive, this is now avoided as much as possible.

28 files changed:
CHANGELOG
lib/Twig/Compiler.php
lib/Twig/Environment.php
lib/Twig/Error.php
lib/Twig/Error/Loader.php
lib/Twig/ExpressionParser.php
lib/Twig/Node.php
lib/Twig/Node/Expression/Filter.php
lib/Twig/Node/Expression/Function.php
lib/Twig/Node/Expression/Test.php
lib/Twig/Node/Expression/Test/Defined.php
lib/Twig/Node/Sandbox.php
lib/Twig/Parser.php
lib/Twig/Template.php
lib/Twig/Token.php
lib/Twig/TokenParser/AutoEscape.php
lib/Twig/TokenParser/Block.php
lib/Twig/TokenParser/Extends.php
lib/Twig/TokenParser/If.php
lib/Twig/TokenParser/Macro.php
lib/Twig/TokenParser/Sandbox.php
lib/Twig/TokenParser/Set.php
lib/Twig/TokenParser/Use.php
lib/Twig/TokenParserBroker.php
lib/Twig/TokenStream.php
test/Twig/Tests/Fixtures/tags/include/missing.test [new file with mode: 0644]
test/Twig/Tests/Fixtures/tags/include/missing_nested.test [new file with mode: 0644]
test/Twig/Tests/ParserTest.php

index db781f8..614b362 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,6 @@
 * 1.11.0 (2012-XX-XX)
 
+ * optimized the way Twig exceptions are managed (to make them faster)
  * added Twig_ExistsLoaderInterface (implementing this interface in your loader make the chain loader much faster)
 
 * 1.10.3 (2012-10-19)
index d03dfa0..786a75f 100644 (file)
@@ -25,6 +25,7 @@ class Twig_Compiler implements Twig_CompilerInterface
     protected $debugInfo;
     protected $sourceOffset;
     protected $sourceLine;
+    protected $filename;
 
     /**
      * Constructor.
@@ -37,6 +38,11 @@ class Twig_Compiler implements Twig_CompilerInterface
         $this->debugInfo = array();
     }
 
+    public function getFilename()
+    {
+        return $this->filename;
+    }
+
     /**
      * Returns the environment instance related to this compiler.
      *
@@ -73,6 +79,10 @@ class Twig_Compiler implements Twig_CompilerInterface
         $this->sourceLine = 0;
         $this->indentation = $indentation;
 
+        if ($node instanceof Twig_Node_Module) {
+            $this->filename = $node->getAttribute('filename');
+        }
+
         $node->compile($this);
 
         return $this;
@@ -246,7 +256,7 @@ class Twig_Compiler implements Twig_CompilerInterface
     {
         // can't outdent by more steps that the current indentation level
         if ($this->indentation < $step) {
-            throw new Twig_Error('Unable to call outdent() as the indentation would become negative');
+            throw new LogicException('Unable to call outdent() as the indentation would become negative');
         }
 
         $this->indentation -= $step;
index 9763db8..64095e8 100644 (file)
@@ -711,7 +711,7 @@ class Twig_Environment
                     } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
                         $this->parsers->addTokenParserBroker($parser);
                     } else {
-                        throw new Twig_Error_Runtime('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
+                        throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
                     }
                 }
             }
@@ -1100,6 +1100,6 @@ class Twig_Environment
             }
         }
 
-        throw new Twig_Error_Runtime(sprintf('Failed to write cache file "%s".', $file));
+        throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file));
     }
 }
index 28de6a7..138151b 100644 (file)
 /**
  * Twig base exception.
  *
+ * This exception class and its children must only be used when
+ * an error occurs during the loading of a template, when a syntax error
+ * is detected in a template, or when rendering a template. Other
+ * errors must use regular PHP exception classes (like when the template
+ * cache directory is not writable for instance).
+ *
+ * To help debugging template issues, this class tracks the original template
+ * name and line where the error occurred.
+ *
+ * Whenever possible, you must set these information (original template name
+ * and line number) yourself by passing them to the constructor. If some or all
+ * these information are not available from where you throw the exception, then
+ * this class will guess them automatically (when the line number is set to -1
+ * and/or the filename is set to null). As this is a costly operation, this
+ * can be disabled by passing false for both the filename and the line number
+ * when creating a new instance of this class.
+ *
  * @package    twig
  * @author     Fabien Potencier <fabien@symfony.com>
  */
@@ -25,6 +42,15 @@ class Twig_Error extends Exception
     /**
      * Constructor.
      *
+     * Set both the line number and the filename to false to
+     * disable automatic guessing of the original template name
+     * and line number.
+     *
+     * Set the line number to -1 to enable its automatic guessing.
+     * Set the filename to null to enable its automatic guessing.
+     *
+     * By default, automatic guessing is enabled.
+     *
      * @param string    $message  The error message
      * @param integer   $lineno   The template line where the error occurred
      * @param string    $filename The template file name where the error occurred
@@ -105,6 +131,12 @@ class Twig_Error extends Exception
         $this->updateRepr();
     }
 
+    public function guess()
+    {
+        $this->guessTemplateInfo();
+        $this->updateRepr();
+    }
+
     /**
      * For PHP < 5.3.0, provides access to the getPrevious() method.
      *
@@ -134,7 +166,7 @@ class Twig_Error extends Exception
             $dot = true;
         }
 
-        if (null !== $this->filename) {
+        if ($this->filename) {
             if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) {
                 $filename = sprintf('"%s"', $this->filename);
             } else {
@@ -143,7 +175,7 @@ class Twig_Error extends Exception
             $this->message .= sprintf(' in %s', $filename);
         }
 
-        if ($this->lineno >= 0) {
+        if ($this->lineno && $this->lineno >= 0) {
             $this->message .= sprintf(' at line %d', $this->lineno);
         }
 
index 418a776..7124974 100644 (file)
 /**
  * Exception thrown when an error occurs during template loading.
  *
+ * Automatic template information guessing is always turned off as
+ * if a template cannot be loaded, there is nothing to guess.
+ * However, when a template is loaded from another one, then, we need
+ * to find the current context and this is automatically done by
+ * Twig_Template::displayWithErrorHandling().
+ *
+ * This strategy makes Twig_Environment::resolveTemplate() much faster.
+ *
  * @package    twig
  * @author     Fabien Potencier <fabien@symfony.com>
  */
 class Twig_Error_Loader extends Twig_Error
 {
+    public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
+    {
+        parent::__construct($message, false, false, $previous);
+    }
 }
index 7c1ce24..3309bb5 100644 (file)
@@ -158,7 +158,7 @@ class Twig_ExpressionParser
                 } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
                     $node = $this->parseHashExpression();
                 } else {
-                    throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine());
+                    throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine(), $this->parser->getFilename());
                 }
         }
 
@@ -252,7 +252,7 @@ class Twig_ExpressionParser
             } else {
                 $current = $stream->getCurrent();
 
-                throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine());
+                throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', Twig_Token::typeToEnglish($current->getType(), $current->getLine()), $current->getValue()), $current->getLine(), $this->parser->getFilename());
             }
 
             $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
@@ -291,11 +291,11 @@ class Twig_ExpressionParser
         switch ($name) {
             case 'parent':
                 if (!count($this->parser->getBlockStack())) {
-                    throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line);
+                    throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
                 }
 
                 if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
-                    throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line);
+                    throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename());
                 }
 
                 return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
@@ -303,7 +303,7 @@ class Twig_ExpressionParser
                 return new Twig_Node_Expression_BlockReference($args->getNode(0), false, $line);
             case 'attribute':
                 if (count($args) < 2) {
-                    throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line);
+                    throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
                 }
 
                 return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : new Twig_Node_Expression_Array(array(), $line), Twig_TemplateInterface::ANY_CALL, $line);
@@ -351,7 +351,7 @@ class Twig_ExpressionParser
                     }
                 }
             } else {
-                throw new Twig_Error_Syntax('Expected name or number', $lineno);
+                throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
             }
         } else {
             $type = Twig_TemplateInterface::ARRAY_CALL;
@@ -446,7 +446,7 @@ class Twig_ExpressionParser
         while (true) {
             $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
             if (in_array($token->getValue(), array('true', 'false', 'none'))) {
-                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine());
+                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
             }
             $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
 
index 651ffc4..04f735c 100644 (file)
@@ -139,7 +139,7 @@ class Twig_Node implements Twig_NodeInterface
     public function getAttribute($name)
     {
         if (!array_key_exists($name, $this->attributes)) {
-            throw new Twig_Error_Runtime(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
+            throw new LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
         }
 
         return $this->attributes[$name];
@@ -188,7 +188,7 @@ class Twig_Node implements Twig_NodeInterface
     public function getNode($name)
     {
         if (!array_key_exists($name, $this->nodes)) {
-            throw new Twig_Error_Runtime(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
+            throw new LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
         }
 
         return $this->nodes[$name];
index 8a0903a..eb9cd31 100644 (file)
@@ -26,7 +26,7 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression
                 $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
             }
 
-            throw new Twig_Error_Syntax($message, $this->getLine());
+            throw new Twig_Error_Syntax($message, $this->getLine(), $compiler->getFilename());
         }
 
         $this->compileFilter($compiler, $filter);
index 9342bb1..5640377 100644 (file)
@@ -25,7 +25,7 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression
                 $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
             }
 
-            throw new Twig_Error_Syntax($message, $this->getLine());
+            throw new Twig_Error_Syntax($message, $this->getLine(), $compiler->getFilename());
         }
 
         $compiler->raw($function->compile().'(');
index 4e0b25e..076e39d 100644 (file)
@@ -25,7 +25,7 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression
                 $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
             }
 
-            throw new Twig_Error_Syntax($message, $this->getLine());
+            throw new Twig_Error_Syntax($message, $this->getLine(), $compiler->getFilename());
         }
 
         $name = $this->getAttribute('name');
index e7c6828..915e60a 100644 (file)
@@ -35,7 +35,7 @@ class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test
 
             $this->changeIgnoreStrictCheck($node);
         } else {
-            throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine());
+            throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine(), $compiler->getFilename());
         }
     }
 
index fbafd99..cbfcb41 100644 (file)
@@ -20,19 +20,6 @@ class Twig_Node_Sandbox extends Twig_Node
     public function __construct(Twig_NodeInterface $body, $lineno, $tag = null)
     {
         parent::__construct(array('body' => $body), array(), $lineno, $tag);
-
-        // in a sandbox tag, only include tags are allowed
-        if (!$this->getNode('body') instanceof Twig_Node_Include) {
-            foreach ($this->getNode('body') as $node) {
-                if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
-                    continue;
-                }
-
-                if (!$node instanceof Twig_Node_Include) {
-                    throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section', $node->getLine());
-                }
-            }
-        }
     }
 
     /**
index b2959e5..1179913 100644 (file)
@@ -53,6 +53,11 @@ class Twig_Parser implements Twig_ParserInterface
         return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false));
     }
 
+    public function getFilename()
+    {
+        return $this->stream->getFilename();
+    }
+
     /**
      * Converts a token stream to a node tree.
      *
@@ -100,14 +105,18 @@ class Twig_Parser implements Twig_ParserInterface
                 }
             }
         } catch (Twig_Error_Syntax $e) {
-            if (null === $e->getTemplateFile()) {
-                $e->setTemplateFile($this->stream->getFilename());
+            if (!$e->getTemplateFile()) {
+                $e->setTemplateFile($this->getFilename());
+            }
+
+            if (!$e->getTemplateLine()) {
+                $e->setTemplateLine($this->stream->getCurrent()->getLine());
             }
 
             throw $e;
         }
 
-        $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->stream->getFilename());
+        $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->getFilename());
 
         $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
 
@@ -144,7 +153,7 @@ class Twig_Parser implements Twig_ParserInterface
                     $token = $this->getCurrentToken();
 
                     if ($token->getType() !== Twig_Token::NAME_TYPE) {
-                        throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->stream->getFilename());
+                        throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename());
                     }
 
                     if (null !== $test && call_user_func($test, $token)) {
@@ -167,7 +176,7 @@ class Twig_Parser implements Twig_ParserInterface
                                 $error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno);
                             }
 
-                            throw new Twig_Error_Syntax($error, $token->getLine(), $this->stream->getFilename());
+                            throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename());
                         }
 
                         $message = sprintf('Unknown tag name "%s"', $token->getValue());
@@ -175,7 +184,7 @@ class Twig_Parser implements Twig_ParserInterface
                             $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
                         }
 
-                        throw new Twig_Error_Syntax($message, $token->getLine(), $this->stream->getFilename());
+                        throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename());
                     }
 
                     $this->stream->next();
@@ -187,7 +196,7 @@ class Twig_Parser implements Twig_ParserInterface
                     break;
 
                 default:
-                    throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', -1, $this->stream->getFilename());
+                    throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename());
             }
         }
 
@@ -259,7 +268,7 @@ class Twig_Parser implements Twig_ParserInterface
         }
 
         if (in_array($name, $this->reservedMacroNames)) {
-            throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine());
+            throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename());
         }
 
         $this->macros[$name] = $node;
@@ -360,10 +369,10 @@ class Twig_Parser implements Twig_ParserInterface
             (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
         ) {
             if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
-                throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->stream->getFilename());
+                throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->getFilename());
             }
 
-            throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->stream->getFilename());
+            throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename());
         }
 
         // bypass "set" nodes as they "capture" the output
index 5a9d53c..039c8ca 100644 (file)
@@ -264,6 +264,17 @@ abstract class Twig_Template implements Twig_TemplateInterface
         try {
             $this->doDisplay($context, $blocks);
         } catch (Twig_Error $e) {
+            if (!$e->getTemplateFile()) {
+                $e->setTemplateFile($this->getTemplateName());
+            }
+
+            // this is mostly useful for Twig_Error_Loader exceptions
+            // see Twig_Error_Loader
+            if (false === $e->getTemplateLine()) {
+                $e->setTemplateLine(-1);
+                $e->guess();
+            }
+
             throw $e;
         } catch (Exception $e) {
             throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e);
@@ -350,11 +361,11 @@ abstract class Twig_Template implements Twig_TemplateInterface
                 }
 
                 if (is_object($object)) {
-                    throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)));
+                    throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
                 } elseif (is_array($object)) {
-                    throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))));
+                    throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName());
                 } else {
-                    throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)));
+                    throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName());
                 }
             }
         }
@@ -368,7 +379,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
                 return null;
             }
 
-            throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object));
+            throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object), -1, $this->getTemplateName());
         }
 
         $class = get_class($object);
@@ -411,7 +422,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
                 return null;
             }
 
-            throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
+            throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
         }
 
         if ($isDefinedTest) {
index 918bb91..1232696 100644 (file)
@@ -169,7 +169,7 @@ class Twig_Token
                 $name = 'INTERPOLATION_END_TYPE';
                 break;
             default:
-                throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line);
+                throw new LogicException(sprintf('Token of type "%s" does not exist.', $type));
         }
 
         return $short ? $name : 'Twig_Token::'.$name;
@@ -213,7 +213,7 @@ class Twig_Token
             case self::INTERPOLATION_END_TYPE:
                 return 'end of string interpolation';
             default:
-                throw new Twig_Error_Syntax(sprintf('Token of type "%s" does not exist.', $type), $line);
+                throw new LogicException(sprintf('Token of type "%s" does not exist.', $type));
         }
     }
 }
index 0040845..2756028 100644 (file)
@@ -39,13 +39,14 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
     public function parse(Twig_Token $token)
     {
         $lineno = $token->getLine();
+        $stream = $this->parser->getStream();
 
-        if ($this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE)) {
+        if ($stream->test(Twig_Token::BLOCK_END_TYPE)) {
             $value = 'html';
         } else {
             $expr = $this->parser->getExpressionParser()->parseExpression();
             if (!$expr instanceof Twig_Node_Expression_Constant) {
-                throw new Twig_Error_Syntax('An escaping strategy must be a string or a Boolean.', $lineno);
+                throw new Twig_Error_Syntax('An escaping strategy must be a string or a Boolean.', $stream->getCurrent()->getLine(), $stream->getFilename());
             }
             $value = $expr->getAttribute('value');
 
@@ -55,18 +56,18 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
                 $value = 'html';
             }
 
-            if ($compat && $this->parser->getStream()->test(Twig_Token::NAME_TYPE)) {
+            if ($compat && $stream->test(Twig_Token::NAME_TYPE)) {
                 if (false === $value) {
-                    throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $lineno);
+                    throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getFilename());
                 }
 
-                $value = $this->parser->getStream()->next()->getValue();
+                $value = $stream->next()->getValue();
             }
         }
 
-        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $stream->expect(Twig_Token::BLOCK_END_TYPE);
         $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
-        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $stream->expect(Twig_Token::BLOCK_END_TYPE);
 
         return new Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag());
     }
index 994078e..a2e017f 100644 (file)
@@ -35,7 +35,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
         $stream = $this->parser->getStream();
         $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
         if ($this->parser->hasBlock($name)) {
-            throw new Twig_Error_Syntax(sprintf("The block '$name' has already been defined line %d", $this->parser->getBlock($name)->getLine()), $lineno);
+            throw new Twig_Error_Syntax(sprintf("The block '$name' has already been defined line %d", $this->parser->getBlock($name)->getLine()), $stream->getCurrent()->getLine(), $stream->getFilename());
         }
         $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno));
         $this->parser->pushLocalScope();
@@ -49,7 +49,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
                 $value = $stream->next()->getValue();
 
                 if ($value != $name) {
-                    throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $lineno);
+                    throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename());
                 }
             }
         } else {
index 54f49ad..110bc8b 100644 (file)
@@ -29,11 +29,11 @@ class Twig_TokenParser_Extends extends Twig_TokenParser
     public function parse(Twig_Token $token)
     {
         if (!$this->parser->isMainScope()) {
-            throw new Twig_Error_Syntax('Cannot extend from a block', $token->getLine());
+            throw new Twig_Error_Syntax('Cannot extend from a block', $token->getLine(), $this->parser->getFilename());
         }
 
         if (null !== $this->parser->getParent()) {
-            throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine());
+            throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine(), $this->parser->getFilename());
         }
         $this->parser->setParent($this->parser->getExpressionParser()->parseExpression());
 
index 1a694af..3d7d1f5 100644 (file)
@@ -36,22 +36,23 @@ class Twig_TokenParser_If extends Twig_TokenParser
     {
         $lineno = $token->getLine();
         $expr = $this->parser->getExpressionParser()->parseExpression();
-        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $stream = $this->parser->getStream();
+        $stream->expect(Twig_Token::BLOCK_END_TYPE);
         $body = $this->parser->subparse(array($this, 'decideIfFork'));
         $tests = array($expr, $body);
         $else = null;
 
         $end = false;
         while (!$end) {
-            switch ($this->parser->getStream()->next()->getValue()) {
+            switch ($stream->next()->getValue()) {
                 case 'else':
-                    $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+                    $stream->expect(Twig_Token::BLOCK_END_TYPE);
                     $else = $this->parser->subparse(array($this, 'decideIfEnd'));
                     break;
 
                 case 'elseif':
                     $expr = $this->parser->getExpressionParser()->parseExpression();
-                    $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+                    $stream->expect(Twig_Token::BLOCK_END_TYPE);
                     $body = $this->parser->subparse(array($this, 'decideIfFork'));
                     $tests[] = $expr;
                     $tests[] = $body;
@@ -62,11 +63,11 @@ class Twig_TokenParser_If extends Twig_TokenParser
                     break;
 
                 default:
-                    throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), -1);
+                    throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), $stream->getCurrent()->getLine(), $stream->getFilename());
             }
         }
 
-        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $stream->expect(Twig_Token::BLOCK_END_TYPE);
 
         return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag());
     }
index ffd5848..de10059 100644 (file)
@@ -30,22 +30,23 @@ class Twig_TokenParser_Macro extends Twig_TokenParser
     public function parse(Twig_Token $token)
     {
         $lineno = $token->getLine();
-        $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+        $stream = $this->parser->getStream();
+        $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
 
         $arguments = $this->parser->getExpressionParser()->parseArguments();
 
-        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $stream->expect(Twig_Token::BLOCK_END_TYPE);
         $this->parser->pushLocalScope();
         $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
-        if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE)) {
-            $value = $this->parser->getStream()->next()->getValue();
+        if ($stream->test(Twig_Token::NAME_TYPE)) {
+            $value = $stream->next()->getValue();
 
             if ($value != $name) {
-                throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $lineno);
+                throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename());
             }
         }
         $this->parser->popLocalScope();
-        $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+        $stream->expect(Twig_Token::BLOCK_END_TYPE);
 
         $this->parser->setMacro($name, new Twig_Node_Macro($name, new Twig_Node_Body(array($body)), $arguments, $lineno, $this->getTag()));
 
index 0277c70..9457325 100644 (file)
@@ -35,6 +35,19 @@ class Twig_TokenParser_Sandbox extends Twig_TokenParser
         $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
         $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
 
+        // in a sandbox tag, only include tags are allowed
+        if (!$body instanceof Twig_Node_Include) {
+            foreach ($body as $node) {
+                if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
+                    continue;
+                }
+
+                if (!$node instanceof Twig_Node_Include) {
+                    throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section', $node->getLine(), $this->parser->getFilename());
+                }
+            }
+        }
+
         return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag());
     }
 
index 3b4479c..6dbd1db 100644 (file)
@@ -49,13 +49,13 @@ class Twig_TokenParser_Set extends Twig_TokenParser
             $stream->expect(Twig_Token::BLOCK_END_TYPE);
 
             if (count($names) !== count($values)) {
-                throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignements.", $lineno);
+                throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignements.", $stream->getCurrent()->getLine(), $stream->getFilename());
             }
         } else {
             $capture = true;
 
             if (count($names) > 1) {
-                throw new Twig_Error_Syntax("When using set with a block, you cannot have a multi-target.", $lineno);
+                throw new Twig_Error_Syntax("When using set with a block, you cannot have a multi-target.", $stream->getCurrent()->getLine(), $stream->getFilename());
             }
 
             $stream->expect(Twig_Token::BLOCK_END_TYPE);
index beafc80..85f084a 100644 (file)
@@ -35,13 +35,12 @@ class Twig_TokenParser_Use extends Twig_TokenParser
     public function parse(Twig_Token $token)
     {
         $template = $this->parser->getExpressionParser()->parseExpression();
+        $stream = $this->parser->getStream();
 
         if (!$template instanceof Twig_Node_Expression_Constant) {
-            throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $token->getLine());
+            throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getFilename());
         }
 
-        $stream = $this->parser->getStream();
-
         $targets = array();
         if ($stream->test('with')) {
             $stream->next();
index b214e99..f2c27ee 100644 (file)
@@ -32,13 +32,13 @@ class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface
     {
         foreach ($parsers as $parser) {
             if (!$parser instanceof Twig_TokenParserInterface) {
-                throw new Twig_Error('$parsers must a an array of Twig_TokenParserInterface');
+                throw new LogicException('$parsers must a an array of Twig_TokenParserInterface');
             }
             $this->parsers[$parser->getTag()] = $parser;
         }
         foreach ($brokers as $broker) {
             if (!$broker instanceof Twig_TokenParserBrokerInterface) {
-                throw new Twig_Error('$brokers must a an array of Twig_TokenParserBrokerInterface');
+                throw new LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface');
             }
             $this->brokers[] = $broker;
         }
index 5708091..292c11f 100644 (file)
@@ -58,7 +58,7 @@ class Twig_TokenStream
     public function next()
     {
         if (!isset($this->tokens[++$this->current])) {
-            throw new Twig_Error_Syntax('Unexpected end of template', -1, $this->filename);
+            throw new Twig_Error_Syntax('Unexpected end of template', $this->token[$this->current - 1]->getLine(), $this->filename);
         }
 
         return $this->tokens[$this->current - 1];
@@ -97,7 +97,7 @@ class Twig_TokenStream
     public function look($number = 1)
     {
         if (!isset($this->tokens[$this->current + $number])) {
-            throw new Twig_Error_Syntax('Unexpected end of template', -1, $this->filename);
+            throw new Twig_Error_Syntax('Unexpected end of template', $this->token[$this->current + $number - 1]->getLine(), $this->filename);
         }
 
         return $this->tokens[$this->current + $number];
diff --git a/test/Twig/Tests/Fixtures/tags/include/missing.test b/test/Twig/Tests/Fixtures/tags/include/missing.test
new file mode 100644 (file)
index 0000000..f25e871
--- /dev/null
@@ -0,0 +1,8 @@
+--TEST--
+"include" tag
+--TEMPLATE--
+{% include "foo.twig" %}
+--DATA--
+return array();
+--EXCEPTION--
+Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2.
diff --git a/test/Twig/Tests/Fixtures/tags/include/missing_nested.test b/test/Twig/Tests/Fixtures/tags/include/missing_nested.test
new file mode 100644 (file)
index 0000000..86c1864
--- /dev/null
@@ -0,0 +1,16 @@
+--TEST--
+"include" tag
+--TEMPLATE--
+{% extends "base.twig" %}
+
+{% block content %}
+    {{ parent() }}
+{% endblock %}
+--TEMPLATE(base.twig)--
+{% block content %}
+    {% include "foo.twig" %}
+{% endblock %}
+--DATA--
+return array();
+--EXCEPTION--
+Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3.
index b0facf7..9c757ee 100644 (file)
@@ -15,7 +15,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
      */
     public function testSetMacroThrowsExceptionOnReservedMethods()
     {
-        $parser = new Twig_Parser(new Twig_Environment());
+        $parser = $this->getParser();
         $parser->setMacro('display', $this->getMock('Twig_Node_Macro', array(), array(), '', null));
     }
 
@@ -40,7 +40,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
      */
     public function testFilterBodyNodes($input, $expected)
     {
-        $parser = $this->getParserForFilterBodyNodes();
+        $parser = $this->getParser();
 
         $this->assertEquals($expected, $parser->filterBodyNodes($input));
     }
@@ -69,7 +69,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
      */
     public function testFilterBodyNodesThrowsException($input)
     {
-        $parser = $this->getParserForFilterBodyNodes();
+        $parser = $this->getParser();
 
         $parser->filterBodyNodes($input);
     }
@@ -88,7 +88,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
      */
     public function testFilterBodyNodesWithBOM()
     {
-        $parser = $this->getParserForFilterBodyNodes();
+        $parser = $this->getParser();
         $parser->filterBodyNodes(new Twig_Node_Text(chr(0xEF).chr(0xBB).chr(0xBF), 1));
     }
 
@@ -135,7 +135,7 @@ EOF
         ));
     }
 
-    protected function getParserForFilterBodyNodes()
+    protected function getParser()
     {
         $parser = new TestParser(new Twig_Environment());
         $parser->setParent(new Twig_Node());