Browse code

added Twig_Source to hold information about the original template

Fabien Potencier authored on 15/10/2016 17:26:16
Showing 36 changed files
... ...
@@ -1,5 +1,6 @@
1 1
 * 1.27.0 (2016-XX-XX)
2 2
 
3
+ * added Twig_Source to hold information about the original template
3 4
  * deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName()
4 5
  * deprecated Parser::getFilename()
5 6
  * fixed template paths when a template name contains a protocol like vfs://
... ...
@@ -21,7 +21,7 @@ Token Parsers
21 21
   * ``Twig_TokenParserBroker``
22 22
 
23 23
 * As of Twig 1.27, ``Twig_Parser::getFilename()`` is deprecated. From a token
24
-  parser, use ``$this->parser->getStream()->getFilename()`` instead.
24
+  parser, use ``$this->parser->getStream()->getSourceContext()->getPath()`` instead.
25 25
 
26 26
 Extensions
27 27
 ----------
... ...
@@ -119,6 +119,9 @@ Nodes
119 119
   instances, storing a ``null`` value is deprecated and won't be possible in
120 120
   Twig 2.x.
121 121
 
122
+* As of Twig 1.27, the ``filename`` attribute on ``Twig_Node_Module`` is
123
+  deprecated. Use ``name`` instead.
124
+
122 125
 Interfaces
123 126
 ----------
124 127
 
... ...
@@ -44,7 +44,11 @@ an instance of ``Twig_Token``, and the stream is an instance of
44 44
 You can manually convert a source code into a token stream by calling the
45 45
 ``tokenize()`` method of an environment::
46 46
 
47
-    $stream = $twig->tokenize($source, $identifier);
47
+    $stream = $twig->tokenize(new Twig_Source($source, $identifier));
48
+
49
+.. versionadded:: 1.27
50
+    ``Twig_Source`` was introduced in version 1.27, pass the source and the
51
+    identifier directly on previous versions.
48 52
 
49 53
 As the stream has a ``__toString()`` method, you can have a textual
50 54
 representation of it by echoing the object::
... ...
@@ -306,7 +306,7 @@ saving it. If the template code is stored in a `$template` variable, here is
306 306
 how you can do it::
307 307
 
308 308
     try {
309
-        $twig->parse($twig->tokenize($template));
309
+        $twig->parse($twig->tokenize(new Twig_Source($template)));
310 310
 
311 311
         // the $template is valid
312 312
     } catch (Twig_Error_Syntax $e) {
... ...
@@ -318,7 +318,7 @@ If you iterate over a set of files, you can pass the filename to the
318 318
 
319 319
     foreach ($files as $file) {
320 320
         try {
321
-            $twig->parse($twig->tokenize($template, $file));
321
+            $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));
322 322
 
323 323
             // the $template is valid
324 324
         } catch (Twig_Error_Syntax $e) {
... ...
@@ -326,6 +326,10 @@ If you iterate over a set of files, you can pass the filename to the
326 326
         }
327 327
     }
328 328
 
329
+.. versionadded:: 1.27
330
+    ``Twig_Source`` was introduced in version 1.27, pass the source and the
331
+    identifier directly on previous versions.
332
+
329 333
 .. note::
330 334
 
331 335
     This method won't catch any sandbox policy violations because the policy
... ...
@@ -403,7 +403,14 @@ class Twig_Environment
403 403
             }
404 404
 
405 405
             if (!class_exists($cls, false)) {
406
-                $content = $this->compileSource($this->getLoader()->getSource($name), $name);
406
+                $loader = $this->getLoader();
407
+                if ($loader instanceof Twig_SourceContextLoaderInterface) {
408
+                    $source = $loader->getSourceContext($name);
409
+                } else {
410
+                    $source = new Twig_Source($loader->getSource($name), $name);
411
+                }
412
+                $content = $this->compileSource($source);
413
+
407 414
                 if ($this->bcWriteCacheFile) {
408 415
                     $this->writeCacheFile($key, $content);
409 416
                 } else {
... ...
@@ -583,8 +590,8 @@ class Twig_Environment
583 590
     /**
584 591
      * Tokenizes a source code.
585 592
      *
586
-     * @param string $source The template source code
587
-     * @param string $name   The template name
593
+     * @param string|Twig_Source $source The template source code
594
+     * @param string             $name   The template name (deprecated)
588 595
      *
589 596
      * @return Twig_TokenStream A Twig_TokenStream instance
590 597
      *
... ...
@@ -592,11 +599,16 @@ class Twig_Environment
592 599
      */
593 600
     public function tokenize($source, $name = null)
594 601
     {
602
+        if (!$source instanceof Twig_Source) {
603
+            @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
604
+            $source = new Twig_Source($source, $name);
605
+        }
606
+
595 607
         if (null === $this->lexer) {
596 608
             $this->lexer = new Twig_Lexer($this);
597 609
         }
598 610
 
599
-        return $this->lexer->tokenize($source, $name);
611
+        return $this->lexer->tokenize($source);
600 612
     }
601 613
 
602 614
     /**
... ...
@@ -692,8 +704,8 @@ class Twig_Environment
692 704
     /**
693 705
      * Compiles a template source code.
694 706
      *
695
-     * @param string $source The template source code
696
-     * @param string $name   The template name
707
+     * @param string|Twig_Source $source The template source code
708
+     * @param string             $name   The template name (deprecated)
697 709
      *
698 710
      * @return string The compiled PHP source code
699 711
      *
... ...
@@ -701,13 +713,18 @@ class Twig_Environment
701 713
      */
702 714
     public function compileSource($source, $name = null)
703 715
     {
716
+        if (!$source instanceof Twig_Source) {
717
+            @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
718
+            $source = new Twig_Source($source, $name);
719
+        }
720
+
704 721
         try {
705
-            return $this->compile($this->parse($this->tokenize($source, $name)));
722
+            return $this->compile($this->parse($this->tokenize($source)));
706 723
         } catch (Twig_Error $e) {
707
-            $e->setTemplateName($name);
724
+            $e->setTemplateName($source->getName());
708 725
             throw $e;
709 726
         } catch (Exception $e) {
710
-            throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
727
+            throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source->getName(), $e);
711 728
         }
712 729
     }
713 730
 
... ...
@@ -171,7 +171,7 @@ class Twig_ExpressionParser
171 171
                     $negClass = 'Twig_Node_Expression_Unary_Neg';
172 172
                     $posClass = 'Twig_Node_Expression_Unary_Pos';
173 173
                     if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) {
174
-                        throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getFilename());
174
+                        throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName());
175 175
                     }
176 176
 
177 177
                     $this->parser->getStream()->next();
... ...
@@ -187,7 +187,7 @@ class Twig_ExpressionParser
187 187
                 } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
188 188
                     $node = $this->parseHashExpression();
189 189
                 } else {
190
-                    throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getFilename());
190
+                    throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName());
191 191
                 }
192 192
         }
193 193
 
... ...
@@ -278,7 +278,7 @@ class Twig_ExpressionParser
278 278
             } else {
279 279
                 $current = $stream->getCurrent();
280 280
 
281
-                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->getValue()), $current->getLine(), $stream->getFilename());
281
+                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->getValue()), $current->getLine(), $stream->getSourceContext()->getName());
282 282
             }
283 283
 
284 284
             $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
... ...
@@ -317,11 +317,11 @@ class Twig_ExpressionParser
317 317
             case 'parent':
318 318
                 $this->parseArguments();
319 319
                 if (!count($this->parser->getBlockStack())) {
320
-                    throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getFilename());
320
+                    throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()->getName());
321 321
                 }
322 322
 
323 323
                 if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
324
-                    throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getFilename());
324
+                    throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()->getName());
325 325
                 }
326 326
 
327 327
                 return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
... ...
@@ -330,7 +330,7 @@ class Twig_ExpressionParser
330 330
             case 'attribute':
331 331
                 $args = $this->parseArguments();
332 332
                 if (count($args) < 2) {
333
-                    throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getFilename());
333
+                    throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()->getName());
334 334
                 }
335 335
 
336 336
                 return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line);
... ...
@@ -379,18 +379,18 @@ class Twig_ExpressionParser
379 379
                     }
380 380
                 }
381 381
             } else {
382
-                throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getFilename());
382
+                throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getSourceContext()->getName());
383 383
             }
384 384
 
385 385
             if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
386 386
                 if (!$arg instanceof Twig_Node_Expression_Constant) {
387
-                    throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getFilename());
387
+                    throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()->getName());
388 388
                 }
389 389
 
390 390
                 $name = $arg->getAttribute('value');
391 391
 
392 392
                 if ($this->parser->isReservedMacroName($name)) {
393
-                    throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getFilename());
393
+                    throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()->getName());
394 394
                 }
395 395
 
396 396
                 $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno);
... ...
@@ -500,7 +500,7 @@ class Twig_ExpressionParser
500 500
             $name = null;
501 501
             if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
502 502
                 if (!$value instanceof Twig_Node_Expression_Name) {
503
-                    throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getFilename());
503
+                    throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()->getName());
504 504
                 }
505 505
                 $name = $value->getAttribute('name');
506 506
 
... ...
@@ -508,7 +508,7 @@ class Twig_ExpressionParser
508 508
                     $value = $this->parsePrimaryExpression();
509 509
 
510 510
                     if (!$this->checkConstantExpression($value)) {
511
-                        throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getFilename());
511
+                        throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()->getName());
512 512
                     }
513 513
                 } else {
514 514
                     $value = $this->parseExpression();
... ...
@@ -542,7 +542,7 @@ class Twig_ExpressionParser
542 542
             $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
543 543
             $value = $token->getValue();
544 544
             if (in_array(strtolower($value), array('true', 'false', 'none', 'null'))) {
545
-                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getFilename());
545
+                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()->getName());
546 546
             }
547 547
             $targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine());
548 548
 
... ...
@@ -572,7 +572,7 @@ class Twig_ExpressionParser
572 572
         $env = $this->parser->getEnvironment();
573 573
 
574 574
         if (false === $function = $env->getFunction($name)) {
575
-            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getFilename());
575
+            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()->getName());
576 576
             $e->addSuggestions($name, array_keys($env->getFunctions()));
577 577
 
578 578
             throw $e;
... ...
@@ -586,7 +586,7 @@ class Twig_ExpressionParser
586 586
             if ($function->getAlternative()) {
587 587
                 $message .= sprintf('. Use "%s" instead', $function->getAlternative());
588 588
             }
589
-            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getFilename(), $line);
589
+            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line);
590 590
 
591 591
             @trigger_error($message, E_USER_DEPRECATED);
592 592
         }
... ...
@@ -603,7 +603,7 @@ class Twig_ExpressionParser
603 603
         $env = $this->parser->getEnvironment();
604 604
 
605 605
         if (false === $filter = $env->getFilter($name)) {
606
-            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getFilename());
606
+            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()->getName());
607 607
             $e->addSuggestions($name, array_keys($env->getFilters()));
608 608
 
609 609
             throw $e;
... ...
@@ -617,7 +617,7 @@ class Twig_ExpressionParser
617 617
             if ($filter->getAlternative()) {
618 618
                 $message .= sprintf('. Use "%s" instead', $filter->getAlternative());
619 619
             }
620
-            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getFilename(), $line);
620
+            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line);
621 621
 
622 622
             @trigger_error($message, E_USER_DEPRECATED);
623 623
         }
... ...
@@ -284,7 +284,7 @@ class Twig_Extension_Core extends Twig_Extension
284 284
             if ($test->getAlternative()) {
285 285
                 $message .= sprintf('. Use "%s" instead', $test->getAlternative());
286 286
             }
287
-            $message .= sprintf(' in %s at line %d.', $stream->getFilename(), $stream->getCurrent()->getLine());
287
+            $message .= sprintf(' in %s at line %d.', $stream->getSourceContext()->getName(), $stream->getCurrent()->getLine());
288 288
 
289 289
             @trigger_error($message, E_USER_DEPRECATED);
290 290
         }
... ...
@@ -319,7 +319,7 @@ class Twig_Extension_Core extends Twig_Extension
319 319
             }
320 320
         }
321 321
 
322
-        $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getFilename());
322
+        $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()->getName());
323 323
         $e->addSuggestions($name, array_keys($env->getTests()));
324 324
 
325 325
         throw $e;
... ...
@@ -26,6 +26,7 @@ class Twig_Lexer implements Twig_LexerInterface
26 26
     protected $states;
27 27
     protected $brackets;
28 28
     protected $env;
29
+    // to be renamed to $name in 2.0 (where it is private)
29 30
     protected $filename;
30 31
     protected $options;
31 32
     protected $regexes;
... ...
@@ -75,8 +76,15 @@ class Twig_Lexer implements Twig_LexerInterface
75 76
     /**
76 77
      * {@inheritdoc}
77 78
      */
78
-    public function tokenize($code, $filename = null)
79
+    public function tokenize($code, $name = null)
79 80
     {
81
+        if (!$code instanceof Twig_Source) {
82
+            @trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
83
+            $source = new Twig_Source($code, $name);
84
+        } else {
85
+            $source = $code;
86
+        }
87
+
80 88
         if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
81 89
             $mbEncoding = mb_internal_encoding();
82 90
             mb_internal_encoding('ASCII');
... ...
@@ -84,8 +92,8 @@ class Twig_Lexer implements Twig_LexerInterface
84 92
             $mbEncoding = null;
85 93
         }
86 94
 
87
-        $this->code = str_replace(array("\r\n", "\r"), "\n", $code);
88
-        $this->filename = $filename;
95
+        $this->code = str_replace(array("\r\n", "\r"), "\n", $source->getCode());
96
+        $this->filename = $source->getName();
89 97
         $this->cursor = 0;
90 98
         $this->lineno = 1;
91 99
         $this->end = strlen($this->code);
... ...
@@ -136,7 +144,7 @@ class Twig_Lexer implements Twig_LexerInterface
136 144
             mb_internal_encoding($mbEncoding);
137 145
         }
138 146
 
139
-        return new Twig_TokenStream($this->tokens, $this->filename, $this->env->isDebug() ? $code : '');
147
+        return new Twig_TokenStream($this->tokens, $source);
140 148
     }
141 149
 
142 150
     protected function lexData()
... ...
@@ -21,12 +21,12 @@ interface Twig_LexerInterface
21 21
     /**
22 22
      * Tokenizes a source code.
23 23
      *
24
-     * @param string $code     The source code
25
-     * @param string $filename A unique identifier for the source code
24
+     * @param string|Twig_Source $code The source code
25
+     * @param string             $name A unique identifier for the source code
26 26
      *
27 27
      * @return Twig_TokenStream A token stream instance
28 28
      *
29 29
      * @throws Twig_Error_Syntax When the code is syntactically wrong
30 30
      */
31
-    public function tokenize($code, $filename = null);
31
+    public function tokenize($code, $name = null);
32 32
 }
... ...
@@ -14,7 +14,7 @@
14 14
  *
15 15
  * @author Fabien Potencier <fabien@symfony.com>
16 16
  */
17
-class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
17
+class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface
18 18
 {
19 19
     private $hasSourceCache = array();
20 20
     protected $loaders = array();
... ...
@@ -66,6 +66,35 @@ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterf
66 66
     /**
67 67
      * {@inheritdoc}
68 68
      */
69
+    public function getSourceContext($name)
70
+    {
71
+        $exceptions = array();
72
+        foreach ($this->loaders as $loader) {
73
+            if (!$loader instanceof Twig_SourceContextLoaderInterface) {
74
+                continue;
75
+            }
76
+
77
+            if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) {
78
+                continue;
79
+            }
80
+
81
+            try {
82
+                return $loader->getSourceContext($name);
83
+            } catch (Twig_Error_Loader $e) {
84
+                $exceptions[] = $e->getMessage();
85
+            }
86
+        }
87
+
88
+        if ($exceptions) {
89
+            throw new Twig_Error_Loader(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
90
+        }
91
+
92
+        return new Twig_Source($this->getSource($name), $name);
93
+    }
94
+
95
+    /**
96
+     * {@inheritdoc}
97
+     */
69 98
     public function exists($name)
70 99
     {
71 100
         $name = (string) $name;
... ...
@@ -14,7 +14,7 @@
14 14
  *
15 15
  * @author Fabien Potencier <fabien@symfony.com>
16 16
  */
17
-class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
17
+class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface, Twig_SourceContextLoaderInterface
18 18
 {
19 19
     /** Identifier of the main namespace. */
20 20
     const MAIN_NAMESPACE = '__main__';
... ...
@@ -134,6 +134,16 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
134 134
     /**
135 135
      * {@inheritdoc}
136 136
      */
137
+    public function getSourceContext($name)
138
+    {
139
+        $path = $this->findTemplate($name);
140
+
141
+        return new Twig_Source(file_get_contents($path), $name, $path);
142
+    }
143
+
144
+    /**
145
+     * {@inheritdoc}
146
+     */
137 147
     public function getCacheKey($name)
138 148
     {
139 149
         return $this->findTemplate($name);
... ...
@@ -21,8 +21,15 @@
21 21
  */
22 22
 class Twig_Node_Module extends Twig_Node
23 23
 {
24
-    public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename, $source = '')
24
+    public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $name, $source = '')
25 25
     {
26
+        if (!$name instanceof Twig_Source) {
27
+            @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
28
+            $source = new Twig_Source($source, $name);
29
+        } else {
30
+            $source = $name;
31
+        }
32
+
26 33
         $nodes = array(
27 34
             'body' => $body,
28 35
             'blocks' => $blocks,
... ...
@@ -40,8 +47,11 @@ class Twig_Node_Module extends Twig_Node
40 47
 
41 48
         // embedded templates are set as attributes so that they are only visited once by the visitors
42 49
         parent::__construct($nodes, array(
43
-            'source' => $source,
44
-            'filename' => $filename,
50
+            'source' => $source->getCode(),
51
+            'name' => $source->getName(),
52
+            // filename to be remove in 2.0 (use name instead)
53
+            'filename' => $source->getName(),
54
+            'path' => $source->getPath(),
45 55
             'index' => null,
46 56
             'embedded_templates' => $embeddedTemplates,
47 57
         ), 1);
... ...
@@ -96,6 +106,8 @@ class Twig_Node_Module extends Twig_Node
96 106
 
97 107
         $this->compileGetSource($compiler);
98 108
 
109
+        $this->compileGetSourceContext($compiler);
110
+
99 111
         $this->compileClassFooter($compiler);
100 112
     }
101 113
 
... ...
@@ -120,7 +132,7 @@ class Twig_Node_Module extends Twig_Node
120 132
                 ->raw('$this->loadTemplate(')
121 133
                 ->subcompile($parent)
122 134
                 ->raw(', ')
123
-                ->repr($this->getAttribute('filename'))
135
+                ->repr($this->getAttribute('name'))
124 136
                 ->raw(', ')
125 137
                 ->repr($parent->getLine())
126 138
                 ->raw(')')
... ...
@@ -139,8 +151,8 @@ class Twig_Node_Module extends Twig_Node
139 151
         $compiler
140 152
             ->write("\n\n")
141 153
             // if the filename contains */, add a blank to avoid a PHP parse error
142
-            ->write('/* '.str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
143
-            ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index')))
154
+            ->write('/* '.str_replace('*/', '* /', $this->getAttribute('name'))." */\n")
155
+            ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('name'), $this->getAttribute('index')))
144 156
             ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
145 157
             ->write("{\n")
146 158
             ->indent()
... ...
@@ -165,7 +177,7 @@ class Twig_Node_Module extends Twig_Node
165 177
                 ->write('$this->parent = $this->loadTemplate(')
166 178
                 ->subcompile($parent)
167 179
                 ->raw(', ')
168
-                ->repr($this->getAttribute('filename'))
180
+                ->repr($this->getAttribute('name'))
169 181
                 ->raw(', ')
170 182
                 ->repr($parent->getLine())
171 183
                 ->raw(");\n")
... ...
@@ -323,7 +335,7 @@ class Twig_Node_Module extends Twig_Node
323 335
             ->write("public function getTemplateName()\n", "{\n")
324 336
             ->indent()
325 337
             ->write('return ')
326
-            ->repr($this->getAttribute('filename'))
338
+            ->repr($this->getAttribute('name'))
327 339
             ->raw(";\n")
328 340
             ->outdent()
329 341
             ->write("}\n\n")
... ...
@@ -399,9 +411,26 @@ class Twig_Node_Module extends Twig_Node
399 411
             ->write("public function getSource()\n", "{\n")
400 412
             ->indent()
401 413
             ->write('return ')
402
-            ->string($this->getAttribute('source'))
414
+            ->string($compiler->getEnvironment()->isDebug() ? $this->getAttribute('source') : '')
403 415
             ->raw(";\n")
404 416
             ->outdent()
417
+            ->write("}\n\n")
418
+        ;
419
+    }
420
+
421
+    protected function compileGetSourceContext(Twig_Compiler $compiler)
422
+    {
423
+        $compiler
424
+            ->write("public function getSourceContext()\n", "{\n")
425
+            ->indent()
426
+            ->write('return new Twig_Source(')
427
+            ->string($compiler->getEnvironment()->isDebug() ? $this->getAttribute('source') : '')
428
+            ->raw(', ')
429
+            ->string($this->getAttribute('name'))
430
+            ->raw(', ')
431
+            ->string($this->getAttribute('path'))
432
+            ->raw(");\n")
433
+            ->outdent()
405 434
             ->write("}\n")
406 435
         ;
407 436
     }
... ...
@@ -413,7 +442,7 @@ class Twig_Node_Module extends Twig_Node
413 442
                 ->write(sprintf('%s = $this->loadTemplate(', $var))
414 443
                 ->subcompile($node)
415 444
                 ->raw(', ')
416
-                ->repr($this->getAttribute('filename'))
445
+                ->repr($this->getAttribute('name'))
417 446
                 ->raw(', ')
418 447
                 ->repr($node->getLine())
419 448
                 ->raw(");\n")
... ...
@@ -53,13 +53,13 @@ class Twig_Parser implements Twig_ParserInterface
53 53
     }
54 54
 
55 55
     /**
56
-     * @deprecated since 1.20 (to be removed in 2.0). Use $parser->getStream()->getFilename() instead.
56
+     * @deprecated since 1.20 (to be removed in 2.0). Use $parser->getStream()->getSourceContext()->getPath() instead.
57 57
      */
58 58
     public function getFilename()
59 59
     {
60
-        @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getFilename() instead.', __METHOD__), E_USER_DEPRECATED);
60
+        @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.', __METHOD__), E_USER_DEPRECATED);
61 61
 
62
-        return $this->stream->getFilename();
62
+        return $this->stream->getSourceContext()->getName();
63 63
     }
64 64
 
65 65
     /**
... ...
@@ -109,7 +109,7 @@ class Twig_Parser implements Twig_ParserInterface
109 109
             }
110 110
         } catch (Twig_Error_Syntax $e) {
111 111
             if (!$e->getTemplateName()) {
112
-                $e->setTemplateName($this->stream->getFilename());
112
+                $e->setTemplateName($this->stream->getSourceContext()->getName());
113 113
             }
114 114
 
115 115
             if (!$e->getTemplateLine()) {
... ...
@@ -119,7 +119,7 @@ class Twig_Parser implements Twig_ParserInterface
119 119
             throw $e;
120 120
         }
121 121
 
122
-        $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(), $stream->getSource());
122
+        $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, $stream->getSourceContext());
123 123
 
124 124
         $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
125 125
 
... ...
@@ -156,7 +156,7 @@ class Twig_Parser implements Twig_ParserInterface
156 156
                     $token = $this->getCurrentToken();
157 157
 
158 158
                     if ($token->getType() !== Twig_Token::NAME_TYPE) {
159
-                        throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getFilename());
159
+                        throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()->getName());
160 160
                     }
161 161
 
162 162
                     if (null !== $test && call_user_func($test, $token)) {
... ...
@@ -174,13 +174,13 @@ class Twig_Parser implements Twig_ParserInterface
174 174
                     $subparser = $this->handlers->getTokenParser($token->getValue());
175 175
                     if (null === $subparser) {
176 176
                         if (null !== $test) {
177
-                            $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getFilename());
177
+                            $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName());
178 178
 
179 179
                             if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) {
180 180
                                 $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno));
181 181
                             }
182 182
                         } else {
183
-                            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getFilename());
183
+                            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName());
184 184
                             $e->addSuggestions($token->getValue(), array_keys($this->env->getTags()));
185 185
                         }
186 186
 
... ...
@@ -196,7 +196,7 @@ class Twig_Parser implements Twig_ParserInterface
196 196
                     break;
197 197
 
198 198
                 default:
199
-                    throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->stream->getFilename());
199
+                    throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->stream->getSourceContext()->getName());
200 200
             }
201 201
         }
202 202
 
... ...
@@ -260,7 +260,7 @@ class Twig_Parser implements Twig_ParserInterface
260 260
     public function setMacro($name, Twig_Node_Macro $node)
261 261
     {
262 262
         if ($this->isReservedMacroName($name)) {
263
-            throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getLine(), $this->stream->getFilename());
263
+            throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getLine(), $this->stream->getSourceContext()->getName());
264 264
         }
265 265
 
266 266
         $this->macros[$name] = $node;
... ...
@@ -378,10 +378,10 @@ class Twig_Parser implements Twig_ParserInterface
378 378
             (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
379 379
         ) {
380 380
             if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
381
-                throw new Twig_Error_Syntax('A template that extends another one cannot start with a byte order mark (BOM); it must be removed.', $node->getLine(), $this->stream->getFilename());
381
+                throw new Twig_Error_Syntax('A template that extends another one cannot start with a byte order mark (BOM); it must be removed.', $node->getLine(), $this->stream->getSourceContext()->getName());
382 382
             }
383 383
 
384
-            throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getLine(), $this->stream->getFilename());
384
+            throw new Twig_Error_Syntax('A template that extends another one cannot include contents outside Twig blocks. Did you forget to put the contents inside a {% block %} tag?', $node->getLine(), $this->stream->getSourceContext()->getName());
385 385
         }
386 386
 
387 387
         // bypass "set" nodes as they "capture" the output
388 388
new file mode 100644
... ...
@@ -0,0 +1,49 @@
1
+<?php
2
+
3
+/*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2016 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+/**
13
+ * Holds information about a non-compiled Twig template.
14
+ *
15
+ * @author Fabien Potencier <fabien@symfony.com>
16
+ */
17
+class Twig_Source
18
+{
19
+    private $code;
20
+    private $name;
21
+    private $path;
22
+
23
+    /**
24
+     * @param string $code The template source code
25
+     * @param string $name The template logical name
26
+     * @param string $path The filesystem path of the template if any
27
+     */
28
+    public function __construct($code, $name = null, $path = null)
29
+    {
30
+        $this->code = $code;
31
+        $this->name = $name;
32
+        $this->path = $path;
33
+    }
34
+
35
+    public function getCode()
36
+    {
37
+        return $this->code;
38
+    }
39
+
40
+    public function getName()
41
+    {
42
+        return $this->name;
43
+    }
44
+
45
+    public function getPath()
46
+    {
47
+        return $this->path;
48
+    }
49
+}
0 50
new file mode 100644
... ...
@@ -0,0 +1,22 @@
1
+<?php
2
+
3
+/*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2016 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+interface Twig_SourceContextLoaderInterface
13
+{
14
+    /**
15
+     * Returns the source context for a given template logical name.
16
+     *
17
+     * @param string $name The template logical name
18
+     *
19
+     * @return Twig_Source
20
+     */
21
+    public function getSourceContext($name);
22
+}
... ...
@@ -65,6 +65,16 @@ abstract class Twig_Template implements Twig_TemplateInterface
65 65
     }
66 66
 
67 67
     /**
68
+     * Returns information about the original template source code.
69
+     *
70
+     * @return Twig_Source
71
+     */
72
+    public function getSourceContext()
73
+    {
74
+        return new Twig_Source('', $this->getTemplateName());
75
+    }
76
+
77
+    /**
68 78
      * @deprecated since 1.20 (to be removed in 2.0)
69 79
      */
70 80
     public function getEnvironment()
... ...
@@ -39,7 +39,7 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
39 39
         } else {
40 40
             $expr = $this->parser->getExpressionParser()->parseExpression();
41 41
             if (!$expr instanceof Twig_Node_Expression_Constant) {
42
-                throw new Twig_Error_Syntax('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getFilename());
42
+                throw new Twig_Error_Syntax('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
43 43
             }
44 44
             $value = $expr->getAttribute('value');
45 45
 
... ...
@@ -53,7 +53,7 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
53 53
                 @trigger_error('Using the autoescape tag with "true" or "false" before the strategy name is deprecated since version 1.21.', E_USER_DEPRECATED);
54 54
 
55 55
                 if (false === $value) {
56
-                    throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getFilename());
56
+                    throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
57 57
                 }
58 58
 
59 59
                 $value = $stream->next()->getValue();
... ...
@@ -28,7 +28,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
28 28
         $stream = $this->parser->getStream();
29 29
         $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
30 30
         if ($this->parser->hasBlock($name)) {
31
-            throw new Twig_Error_Syntax(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getLine()), $stream->getCurrent()->getLine(), $stream->getFilename());
31
+            throw new Twig_Error_Syntax(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
32 32
         }
33 33
         $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno));
34 34
         $this->parser->pushLocalScope();
... ...
@@ -40,7 +40,7 @@ class Twig_TokenParser_Block extends Twig_TokenParser
40 40
                 $value = $token->getValue();
41 41
 
42 42
                 if ($value != $name) {
43
-                    throw new Twig_Error_Syntax(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getFilename());
43
+                    throw new Twig_Error_Syntax(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
44 44
                 }
45 45
             }
46 46
         } else {
... ...
@@ -24,11 +24,11 @@ class Twig_TokenParser_Extends extends Twig_TokenParser
24 24
         $stream = $this->parser->getStream();
25 25
 
26 26
         if (!$this->parser->isMainScope()) {
27
-            throw new Twig_Error_Syntax('Cannot extend from a block.', $token->getLine(), $stream->getFilename());
27
+            throw new Twig_Error_Syntax('Cannot extend from a block.', $token->getLine(), $stream->getSourceContext()->getName());
28 28
         }
29 29
 
30 30
         if (null !== $this->parser->getParent()) {
31
-            throw new Twig_Error_Syntax('Multiple extends tags are forbidden.', $token->getLine(), $stream->getFilename());
31
+            throw new Twig_Error_Syntax('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()->getName());
32 32
         }
33 33
         $this->parser->setParent($this->parser->getExpressionParser()->parseExpression());
34 34
 
... ...
@@ -79,7 +79,7 @@ class Twig_TokenParser_For extends Twig_TokenParser
79 79
     protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node)
80 80
     {
81 81
         if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) {
82
-            throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition.', $node->getLine(), $stream->getFilename());
82
+            throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition.', $node->getLine(), $stream->getSourceContext()->getName());
83 83
         }
84 84
 
85 85
         foreach ($node as $n) {
... ...
@@ -98,7 +98,7 @@ class Twig_TokenParser_For extends Twig_TokenParser
98 98
         if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) {
99 99
             $attribute = $node->getNode('attribute');
100 100
             if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) {
101
-                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());
101
+                throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getLine(), $stream->getSourceContext()->getName());
102 102
             }
103 103
         }
104 104
 
... ...
@@ -46,7 +46,7 @@ class Twig_TokenParser_From extends Twig_TokenParser
46 46
 
47 47
         foreach ($targets as $name => $alias) {
48 48
             if ($this->parser->isReservedMacroName($name)) {
49
-                throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getFilename());
49
+                throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()->getName());
50 50
             }
51 51
 
52 52
             $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var'));
... ...
@@ -56,7 +56,7 @@ class Twig_TokenParser_If extends Twig_TokenParser
56 56
                     break;
57 57
 
58 58
                 default:
59
-                    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());
59
+                    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->getSourceContext()->getName());
60 60
             }
61 61
         }
62 62
 
... ...
@@ -35,7 +35,7 @@ class Twig_TokenParser_Macro extends Twig_TokenParser
35 35
             $value = $token->getValue();
36 36
 
37 37
             if ($value != $name) {
38
-                throw new Twig_Error_Syntax(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getFilename());
38
+                throw new Twig_Error_Syntax(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
39 39
             }
40 40
         }
41 41
         $this->parser->popLocalScope();
... ...
@@ -37,7 +37,7 @@ class Twig_TokenParser_Sandbox extends Twig_TokenParser
37 37
                 }
38 38
 
39 39
                 if (!$node instanceof Twig_Node_Include) {
40
-                    throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section.', $node->getLine(), $stream->getFilename());
40
+                    throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section.', $node->getLine(), $stream->getSourceContext()->getName());
41 41
                 }
42 42
             }
43 43
         }
... ...
@@ -41,13 +41,13 @@ class Twig_TokenParser_Set extends Twig_TokenParser
41 41
             $stream->expect(Twig_Token::BLOCK_END_TYPE);
42 42
 
43 43
             if (count($names) !== count($values)) {
44
-                throw new Twig_Error_Syntax('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getFilename());
44
+                throw new Twig_Error_Syntax('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
45 45
             }
46 46
         } else {
47 47
             $capture = true;
48 48
 
49 49
             if (count($names) > 1) {
50
-                throw new Twig_Error_Syntax('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getFilename());
50
+                throw new Twig_Error_Syntax('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
51 51
             }
52 52
 
53 53
             $stream->expect(Twig_Token::BLOCK_END_TYPE);
... ...
@@ -31,7 +31,7 @@ class Twig_TokenParser_Use extends Twig_TokenParser
31 31
         $stream = $this->parser->getStream();
32 32
 
33 33
         if (!$template instanceof Twig_Node_Expression_Constant) {
34
-            throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getFilename());
34
+            throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
35 35
         }
36 36
 
37 37
         $targets = array();
... ...
@@ -26,15 +26,25 @@ class Twig_TokenStream
26 26
     /**
27 27
      * Constructor.
28 28
      *
29
-     * @param array       $tokens   An array of tokens
30
-     * @param string|null $filename The name of the filename which tokens are associated with
31
-     * @param string|null $source   The source code associated with the tokens
29
+     * @param array       $tokens An array of tokens
30
+     * @param string|null $name   The name of the template which tokens are associated with
31
+     * @param string|null $source The source code associated with the tokens
32 32
      */
33
-    public function __construct(array $tokens, $filename = null, $source = null)
33
+    public function __construct(array $tokens, $name = null, $source = null)
34 34
     {
35
+        if (!$name instanceof Twig_Source) {
36
+            if (null !== $name || null !== $source) {
37
+                @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
38
+            }
39
+            $this->source = new Twig_Source($source, $name);
40
+        } else {
41
+            $this->source = $name;
42
+        }
43
+
35 44
         $this->tokens = $tokens;
36
-        $this->filename = $filename;
37
-        $this->source = $source ? $source : '';
45
+
46
+        // deprecated, not used anymore, to be removed in 2.0
47
+        $this->filename = $this->source->getName();
38 48
     }
39 49
 
40 50
     /**
... ...
@@ -60,7 +70,7 @@ class Twig_TokenStream
60 70
     public function next()
61 71
     {
62 72
         if (!isset($this->tokens[++$this->current])) {
63
-            throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->filename);
73
+            throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source->getName());
64 74
         }
65 75
 
66 76
         return $this->tokens[$this->current - 1];
... ...
@@ -93,7 +103,7 @@ class Twig_TokenStream
93 103
                 Twig_Token::typeToEnglish($token->getType()), $token->getValue(),
94 104
                 Twig_Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''),
95 105
                 $line,
96
-                $this->filename
106
+                $this->source->getName()
97 107
             );
98 108
         }
99 109
         $this->next();
... ...
@@ -111,7 +121,7 @@ class Twig_TokenStream
111 121
     public function look($number = 1)
112 122
     {
113 123
         if (!isset($this->tokens[$this->current + $number])) {
114
-            throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->filename);
124
+            throw new Twig_Error_Syntax('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source->getName());
115 125
         }
116 126
 
117 127
         return $this->tokens[$this->current + $number];
... ...
@@ -148,13 +158,17 @@ class Twig_TokenStream
148 158
     }
149 159
 
150 160
     /**
151
-     * Gets the filename associated with this stream (null if not defined).
161
+     * Gets the name associated with this stream (null if not defined).
152 162
      *
153 163
      * @return string|null
164
+     *
165
+     * @deprecated since 1.27 (to be removed in 2.0)
154 166
      */
155 167
     public function getFilename()
156 168
     {
157
-        return $this->filename;
169
+        @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
170
+
171
+        return $this->source->getName();
158 172
     }
159 173
 
160 174
     /**
... ...
@@ -163,9 +177,25 @@ class Twig_TokenStream
163 177
      * @return string
164 178
      *
165 179
      * @internal Don't use this as it might be empty depending on the environment configuration
180
+     *
181
+     * @deprecated since 1.27 (to be removed in 2.0)
166 182
      */
167 183
     public function getSource()
168 184
     {
185
+        @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
186
+
187
+        return $this->source->getCode();
188
+    }
189
+
190
+    /**
191
+     * Gets the source associated with this stream.
192
+     *
193
+     * @return Twig_Source
194
+     *
195
+     * @internal
196
+     */
197
+    public function getSourceContext()
198
+    {
169 199
         return $this->source;
170 200
     }
171 201
 }
... ...
@@ -16,6 +16,27 @@ class Twig_Tests_EnvironmentTest extends PHPUnit_Framework_TestCase
16 16
     private $deprecations = array();
17 17
 
18 18
     /**
19
+     * @group legacy
20
+     */
21
+    public function testLegacyTokenizeSignature()
22
+    {
23
+        $env = new Twig_Environment();
24
+        $stream = $env->tokenize('{{ foo }}', 'foo');
25
+        $this->assertEquals('{{ foo }}', $stream->getSource());
26
+        $this->assertEquals('foo', $stream->getFilename());
27
+    }
28
+
29
+    /**
30
+     * @group legacy
31
+     */
32
+    public function testLegacyCompileSourceSignature()
33
+    {
34
+        $loader = new Twig_Loader_Array(array('foo' => '{{ foo }}'));
35
+        $env = new Twig_Environment($loader);
36
+        $this->assertContains('getTemplateName', $env->compileSource('{{ foo }}', 'foo'));
37
+    }
38
+
39
+    /**
19 40
      * @expectedException        LogicException
20 41
      * @expectedExceptionMessage You must set a loader first.
21 42
      * @group legacy
... ...
@@ -152,7 +173,7 @@ class Twig_Tests_EnvironmentTest extends PHPUnit_Framework_TestCase
152 173
         $twig = new Twig_Environment($loader = new Twig_Loader_Array(array('index' => '{{ foo }}')), $options);
153 174
 
154 175
         $key = $cache->generateKey('index', $twig->getTemplateClass('index'));
155
-        $cache->write($key, $twig->compileSource('{{ foo }}', 'index'));
176
+        $cache->write($key, $twig->compileSource(new Twig_Source('{{ foo }}', 'index')));
156 177
 
157 178
         // check that extensions won't be initialized when rendering a template that is already in the cache
158 179
         $twig = $this
... ...
@@ -20,7 +20,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
20 20
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
21 21
         $parser = new Twig_Parser($env);
22 22
 
23
-        $parser->parse($env->tokenize($template, 'index'));
23
+        $parser->parse($env->tokenize(new Twig_Source($template, 'index')));
24 24
     }
25 25
 
26 26
     public function getFailingTestsForAssignment()
... ...
@@ -47,7 +47,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
47 47
     public function testArrayExpression($template, $expected)
48 48
     {
49 49
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
50
-        $stream = $env->tokenize($template, 'index');
50
+        $stream = $env->tokenize(new Twig_Source($template, 'index'));
51 51
         $parser = new Twig_Parser($env);
52 52
 
53 53
         $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
... ...
@@ -62,7 +62,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
62 62
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
63 63
         $parser = new Twig_Parser($env);
64 64
 
65
-        $parser->parse($env->tokenize($template, 'index'));
65
+        $parser->parse($env->tokenize(new Twig_Source($template, 'index')));
66 66
     }
67 67
 
68 68
     public function getFailingTestsForArray()
... ...
@@ -155,7 +155,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
155 155
     public function testStringExpressionDoesNotConcatenateTwoConsecutiveStrings()
156 156
     {
157 157
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0));
158
-        $stream = $env->tokenize('{{ "a" "b" }}', 'index');
158
+        $stream = $env->tokenize(new Twig_Source('{{ "a" "b" }}', 'index'));
159 159
         $parser = new Twig_Parser($env);
160 160
 
161 161
         $parser->parse($stream);
... ...
@@ -167,7 +167,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
167 167
     public function testStringExpression($template, $expected)
168 168
     {
169 169
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0));
170
-        $stream = $env->tokenize($template, 'index');
170
+        $stream = $env->tokenize(new Twig_Source($template, 'index'));
171 171
         $parser = new Twig_Parser($env);
172 172
 
173 173
         $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
... ...
@@ -228,7 +228,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
228 228
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
229 229
         $parser = new Twig_Parser($env);
230 230
 
231
-        $parser->parse($env->tokenize('{{ foo.bar(name="Foo") }}', 'index'));
231
+        $parser->parse($env->tokenize(new Twig_Source('{{ foo.bar(name="Foo") }}', 'index')));
232 232
     }
233 233
 
234 234
     /**
... ...
@@ -239,7 +239,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
239 239
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
240 240
         $parser = new Twig_Parser($env);
241 241
 
242
-        $parser->parse($env->tokenize('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index'));
242
+        $parser->parse($env->tokenize(new Twig_Source('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index')));
243 243
     }
244 244
 
245 245
     /**
... ...
@@ -251,7 +251,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
251 251
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
252 252
         $parser = new Twig_Parser($env);
253 253
 
254
-        $parser->parse($env->tokenize('{% macro foo("a") %}{% endmacro %}', 'index'));
254
+        $parser->parse($env->tokenize(new Twig_Source('{% macro foo("a") %}{% endmacro %}', 'index')));
255 255
     }
256 256
 
257 257
     /**
... ...
@@ -264,7 +264,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
264 264
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
265 265
         $parser = new Twig_Parser($env);
266 266
 
267
-        $parser->parse($env->tokenize($template, 'index'));
267
+        $parser->parse($env->tokenize(new Twig_Source($template, 'index')));
268 268
     }
269 269
 
270 270
     public function getMacroDefinitionDoesNotSupportNonConstantDefaultValues()
... ...
@@ -283,7 +283,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
283 283
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
284 284
         $parser = new Twig_Parser($env);
285 285
 
286
-        $parser->parse($env->tokenize($template, 'index'));
286
+        $parser->parse($env->tokenize(new Twig_Source($template, 'index')));
287 287
     }
288 288
 
289 289
     public function getMacroDefinitionSupportsConstantDefaultValues()
... ...
@@ -308,7 +308,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
308 308
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
309 309
         $parser = new Twig_Parser($env);
310 310
 
311
-        $parser->parse($env->tokenize('{{ cycl() }}', 'index'));
311
+        $parser->parse($env->tokenize(new Twig_Source('{{ cycl() }}', 'index')));
312 312
     }
313 313
 
314 314
     /**
... ...
@@ -320,7 +320,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
320 320
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
321 321
         $parser = new Twig_Parser($env);
322 322
 
323
-        $parser->parse($env->tokenize('{{ foobar() }}', 'index'));
323
+        $parser->parse($env->tokenize(new Twig_Source('{{ foobar() }}', 'index')));
324 324
     }
325 325
 
326 326
     /**
... ...
@@ -332,7 +332,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
332 332
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
333 333
         $parser = new Twig_Parser($env);
334 334
 
335
-        $parser->parse($env->tokenize('{{ 1|lowe }}', 'index'));
335
+        $parser->parse($env->tokenize(new Twig_Source('{{ 1|lowe }}', 'index')));
336 336
     }
337 337
 
338 338
     /**
... ...
@@ -344,7 +344,7 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
344 344
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
345 345
         $parser = new Twig_Parser($env);
346 346
 
347
-        $parser->parse($env->tokenize('{{ 1|foobar }}', 'index'));
347
+        $parser->parse($env->tokenize(new Twig_Source('{{ 1|foobar }}', 'index')));
348 348
     }
349 349
 
350 350
     /**
... ...
@@ -355,8 +355,8 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
355 355
     {
356 356
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
357 357
         $parser = new Twig_Parser($env);
358
-
359
-        $parser->parse($env->tokenize('{{ 1 is nul }}', 'index'));
358
+        $stream = $env->tokenize(new Twig_Source('{{ 1 is nul }}', 'index'));
359
+        $parser->parse($stream);
360 360
     }
361 361
 
362 362
     /**
... ...
@@ -368,6 +368,6 @@ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
368 368
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
369 369
         $parser = new Twig_Parser($env);
370 370
 
371
-        $parser->parse($env->tokenize('{{ 1 is foobar }}', 'index'));
371
+        $parser->parse($env->tokenize(new Twig_Source('{{ 1 is foobar }}', 'index')));
372 372
     }
373 373
 }
... ...
@@ -10,16 +10,15 @@
10 10
  */
11 11
 class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
12 12
 {
13
-    public function testStreamSource()
13
+    /**
14
+     * @group legacy
15
+     */
16
+    public function testLegacyConstructorSignature()
14 17
     {
15
-        $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock());
16
-        $lexer = new Twig_Lexer($env);
17
-
18
-        $env->disableDebug();
19
-        $this->assertSame('', $lexer->tokenize('foo')->getSource());
20
-
21
-        $env->enableDebug();
22
-        $this->assertSame('foo', $lexer->tokenize('foo')->getSource());
18
+        $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
19
+        $stream = $lexer->tokenize('{{ foo }}', 'foo');
20
+        $this->assertEquals('foo', $stream->getFilename());
21
+        $this->assertEquals('{{ foo }}', $stream->getSource());
23 22
     }
24 23
 
25 24
     public function testNameLabelForTag()
... ...
@@ -27,7 +26,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
27 26
         $template = '{% § %}';
28 27
 
29 28
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
30
-        $stream = $lexer->tokenize($template);
29
+        $stream = $lexer->tokenize(new Twig_Source($template));
31 30
 
32 31
         $stream->expect(Twig_Token::BLOCK_START_TYPE);
33 32
         $this->assertSame('§', $stream->expect(Twig_Token::NAME_TYPE)->getValue());
... ...
@@ -38,7 +37,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
38 37
         $template = '{{ §() }}';
39 38
 
40 39
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
41
-        $stream = $lexer->tokenize($template);
40
+        $stream = $lexer->tokenize(new Twig_Source($template));
42 41
 
43 42
         $stream->expect(Twig_Token::VAR_START_TYPE);
44 43
         $this->assertSame('§', $stream->expect(Twig_Token::NAME_TYPE)->getValue());
... ...
@@ -55,7 +54,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
55 54
     protected function countToken($template, $type, $value = null)
56 55
     {
57 56
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
58
-        $stream = $lexer->tokenize($template);
57
+        $stream = $lexer->tokenize(new Twig_Source($template));
59 58
 
60 59
         $count = 0;
61 60
         while (!$stream->isEOF()) {
... ...
@@ -80,7 +79,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
80 79
             ."}}\n";
81 80
 
82 81
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
83
-        $stream = $lexer->tokenize($template);
82
+        $stream = $lexer->tokenize(new Twig_Source($template));
84 83
 
85 84
         // foo\nbar\n
86 85
         $this->assertSame(1, $stream->expect(Twig_Token::TEXT_TYPE)->getLine());
... ...
@@ -100,7 +99,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
100 99
             ."}}\n";
101 100
 
102 101
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
103
-        $stream = $lexer->tokenize($template);
102
+        $stream = $lexer->tokenize(new Twig_Source($template));
104 103
 
105 104
         // foo\nbar
106 105
         $this->assertSame(1, $stream->expect(Twig_Token::TEXT_TYPE)->getLine());
... ...
@@ -115,7 +114,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
115 114
         $template = '{# '.str_repeat('*', 100000).' #}';
116 115
 
117 116
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
118
-        $lexer->tokenize($template);
117
+        $lexer->tokenize(new Twig_Source($template));
119 118
 
120 119
         // should not throw an exception
121 120
     }
... ...
@@ -125,7 +124,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
125 124
         $template = '{% verbatim %}'.str_repeat('*', 100000).'{% endverbatim %}';
126 125
 
127 126
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
128
-        $lexer->tokenize($template);
127
+        $lexer->tokenize(new Twig_Source($template));
129 128
 
130 129
         // should not throw an exception
131 130
     }
... ...
@@ -135,7 +134,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
135 134
         $template = '{{ '.str_repeat('x', 100000).' }}';
136 135
 
137 136
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
138
-        $lexer->tokenize($template);
137
+        $lexer->tokenize(new Twig_Source($template));
139 138
 
140 139
         // should not throw an exception
141 140
     }
... ...
@@ -145,7 +144,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
145 144
         $template = '{% '.str_repeat('x', 100000).' %}';
146 145
 
147 146
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
148
-        $lexer->tokenize($template);
147
+        $lexer->tokenize(new Twig_Source($template));
149 148
 
150 149
         // should not throw an exception
151 150
     }
... ...
@@ -155,7 +154,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
155 154
         $template = '{{ 922337203685477580700 }}';
156 155
 
157 156
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
158
-        $stream = $lexer->tokenize($template);
157
+        $stream = $lexer->tokenize(new Twig_Source($template));
159 158
         $stream->next();
160 159
         $node = $stream->next();
161 160
         $this->assertEquals('922337203685477580700', $node->getValue());
... ...
@@ -169,7 +168,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
169 168
         );
170 169
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
171 170
         foreach ($tests as $template => $expected) {
172
-            $stream = $lexer->tokenize($template);
171
+            $stream = $lexer->tokenize(new Twig_Source($template));
173 172
             $stream->expect(Twig_Token::VAR_START_TYPE);
174 173
             $stream->expect(Twig_Token::STRING_TYPE, $expected);
175 174
         }
... ...
@@ -180,7 +179,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
180 179
         $template = 'foo {{ "bar #{ baz + 1 }" }}';
181 180
 
182 181
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
183
-        $stream = $lexer->tokenize($template);
182
+        $stream = $lexer->tokenize(new Twig_Source($template));
184 183
         $stream->expect(Twig_Token::TEXT_TYPE, 'foo ');
185 184
         $stream->expect(Twig_Token::VAR_START_TYPE);
186 185
         $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
... ...
@@ -197,7 +196,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
197 196
         $template = '{{ "bar \#{baz+1}" }}';
198 197
 
199 198
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
200
-        $stream = $lexer->tokenize($template);
199
+        $stream = $lexer->tokenize(new Twig_Source($template));
201 200
         $stream->expect(Twig_Token::VAR_START_TYPE);
202 201
         $stream->expect(Twig_Token::STRING_TYPE, 'bar #{baz+1}');
203 202
         $stream->expect(Twig_Token::VAR_END_TYPE);
... ...
@@ -208,7 +207,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
208 207
         $template = '{{ "bar # baz" }}';
209 208
 
210 209
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
211
-        $stream = $lexer->tokenize($template);
210
+        $stream = $lexer->tokenize(new Twig_Source($template));
212 211
         $stream->expect(Twig_Token::VAR_START_TYPE);
213 212
         $stream->expect(Twig_Token::STRING_TYPE, 'bar # baz');
214 213
         $stream->expect(Twig_Token::VAR_END_TYPE);
... ...
@@ -223,7 +222,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
223 222
         $template = '{{ "bar #{x" }}';
224 223
 
225 224
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
226
-        $lexer->tokenize($template);
225
+        $lexer->tokenize(new Twig_Source($template));
227 226
     }
228 227
 
229 228
     public function testStringWithNestedInterpolations()
... ...
@@ -231,7 +230,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
231 230
         $template = '{{ "bar #{ "foo#{bar}" }" }}';
232 231
 
233 232
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
234
-        $stream = $lexer->tokenize($template);
233
+        $stream = $lexer->tokenize(new Twig_Source($template));
235 234
         $stream->expect(Twig_Token::VAR_START_TYPE);
236 235
         $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
237 236
         $stream->expect(Twig_Token::INTERPOLATION_START_TYPE);
... ...
@@ -248,7 +247,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
248 247
         $template = '{% foo "bar #{ "foo#{bar}" }" %}';
249 248
 
250 249
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
251
-        $stream = $lexer->tokenize($template);
250
+        $stream = $lexer->tokenize(new Twig_Source($template));
252 251
         $stream->expect(Twig_Token::BLOCK_START_TYPE);
253 252
         $stream->expect(Twig_Token::NAME_TYPE, 'foo');
254 253
         $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
... ...
@@ -266,7 +265,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
266 265
         $template = "{{ 1 and\n0}}";
267 266
 
268 267
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
269
-        $stream = $lexer->tokenize($template);
268
+        $stream = $lexer->tokenize(new Twig_Source($template));
270 269
         $stream->expect(Twig_Token::VAR_START_TYPE);
271 270
         $stream->expect(Twig_Token::NUMBER_TYPE, 1);
272 271
         $stream->expect(Twig_Token::OPERATOR_TYPE, 'and');
... ...
@@ -288,7 +287,7 @@ bar
288 287
 ';
289 288
 
290 289
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
291
-        $lexer->tokenize($template);
290
+        $lexer->tokenize(new Twig_Source($template));
292 291
     }
293 292
 
294 293
     /**
... ...
@@ -307,6 +306,6 @@ bar
307 306
 ';
308 307
 
309 308
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
310
-        $lexer->tokenize($template);
309
+        $lexer->tokenize(new Twig_Source($template));
311 310
     }
312 311
 }
... ...
@@ -22,6 +22,21 @@ class Twig_Tests_Loader_ChainTest extends PHPUnit_Framework_TestCase
22 22
         $this->assertEquals('foo', $loader->getSource('bar'));
23 23
     }
24 24
 
25
+    public function testGetSourceContext()
26
+    {
27
+        $path = dirname(__FILE__).'/../Fixtures';
28
+        $loader = new Twig_Loader_Chain(array(
29
+            new Twig_Loader_Array(array('foo' => 'bar')),
30
+            new Twig_Loader_Filesystem(array($path)),
31
+        ));
32
+
33
+        $this->assertEquals('foo', $loader->getSourceContext('foo')->getName());
34
+        $this->assertNull($loader->getSourceContext('foo')->getPath());
35
+
36
+        $this->assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName());
37
+        $this->assertEquals(realpath($path.'/errors/index.html'), realpath($loader->getSourceContext('errors/index.html')->getPath()));
38
+    }
39
+
25 40
     /**
26 41
      * @expectedException Twig_Error_Loader
27 42
      */
... ...
@@ -11,6 +11,14 @@
11 11
 
12 12
 class Twig_Tests_Loader_FilesystemTest extends PHPUnit_Framework_TestCase
13 13
 {
14
+    public function testGetSourceContext()
15
+    {
16
+        $path = dirname(__FILE__).'/../Fixtures';
17
+        $loader = new Twig_Loader_Filesystem(array($path));
18
+        $this->assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName());
19
+        $this->assertEquals(realpath($path.'/errors/index.html'), realpath($loader->getSourceContext('errors/index.html')->getPath()));
20
+    }
21
+
14 22
     /**
15 23
      * @dataProvider getSecurityTests
16 24
      */
... ...
@@ -18,14 +18,14 @@ class Twig_Tests_Node_ModuleTest extends Twig_Test_NodeTestCase
18 18
         $blocks = new Twig_Node();
19 19
         $macros = new Twig_Node();
20 20
         $traits = new Twig_Node();
21
-        $filename = 'foo.twig';