Browse code

Merge branch '1.x'

* 1.x:
added Twig_Source to hold information about the original template

Fabien Potencier authored on 17/10/2016 20:51:13
Showing 32 changed files
... ...
@@ -19,6 +19,7 @@
19 19
 
20 20
 * 1.27.0 (2016-XX-XX)
21 21
 
22
+ * added Twig_Source to hold information about the original template
22 23
  * deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName()
23 24
  * deprecated Parser::getFilename()
24 25
  * fixed template paths when a template name contains a protocol like vfs://
... ...
@@ -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::
... ...
@@ -303,7 +303,7 @@ saving it. If the template code is stored in a `$template` variable, here is
303 303
 how you can do it::
304 304
 
305 305
     try {
306
-        $twig->parse($twig->tokenize($template));
306
+        $twig->parse($twig->tokenize(new Twig_Source($template)));
307 307
 
308 308
         // the $template is valid
309 309
     } catch (Twig_Error_Syntax $e) {
... ...
@@ -315,7 +315,7 @@ If you iterate over a set of files, you can pass the filename to the
315 315
 
316 316
     foreach ($files as $file) {
317 317
         try {
318
-            $twig->parse($twig->tokenize($template, $file));
318
+            $twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));
319 319
 
320 320
             // the $template is valid
321 321
         } catch (Twig_Error_Syntax $e) {
... ...
@@ -323,6 +323,10 @@ If you iterate over a set of files, you can pass the filename to the
323 323
         }
324 324
     }
325 325
 
326
+.. versionadded:: 1.27
327
+    ``Twig_Source`` was introduced in version 1.27, pass the source and the
328
+    identifier directly on previous versions.
329
+
326 330
 .. note::
327 331
 
328 332
     This method won't catch any sandbox policy violations because the policy
... ...
@@ -325,7 +325,14 @@ class Twig_Environment
325 325
             }
326 326
 
327 327
             if (!class_exists($cls, false)) {
328
-                $content = $this->compileSource($this->getLoader()->getSource($name), $name);
328
+                $loader = $this->getLoader();
329
+                if ($loader instanceof Twig_SourceContextLoaderInterface) {
330
+                    $source = $loader->getSourceContext($name);
331
+                } else {
332
+                    $source = new Twig_Source($loader->getSource($name), $name);
333
+                }
334
+                $content = $this->compileSource($source);
335
+
329 336
                 $this->cache->write($key, $content);
330 337
 
331 338
                 eval('?>'.$content);
... ...
@@ -436,8 +443,8 @@ class Twig_Environment
436 443
     /**
437 444
      * Tokenizes a source code.
438 445
      *
439
-     * @param string $source The template source code
440
-     * @param string $name   The template name
446
+     * @param string|Twig_Source $source The template source code
447
+     * @param string             $name   The template name (deprecated)
441 448
      *
442 449
      * @return Twig_TokenStream A Twig_TokenStream instance
443 450
      *
... ...
@@ -445,11 +452,16 @@ class Twig_Environment
445 452
      */
446 453
     public function tokenize($source, $name = null)
447 454
     {
455
+        if (!$source instanceof Twig_Source) {
456
+            @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);
457
+            $source = new Twig_Source($source, $name);
458
+        }
459
+
448 460
         if (null === $this->lexer) {
449 461
             $this->lexer = new Twig_Lexer($this);
450 462
         }
451 463
 
452
-        return $this->lexer->tokenize($source, $name);
464
+        return $this->lexer->tokenize($source);
453 465
     }
454 466
 
455 467
     /**
... ...
@@ -509,8 +521,8 @@ class Twig_Environment
509 521
     /**
510 522
      * Compiles a template source code.
511 523
      *
512
-     * @param string $source The template source code
513
-     * @param string $name   The template name
524
+     * @param string|Twig_Source $source The template source code
525
+     * @param string             $name   The template name (deprecated)
514 526
      *
515 527
      * @return string The compiled PHP source code
516 528
      *
... ...
@@ -518,13 +530,18 @@ class Twig_Environment
518 530
      */
519 531
     public function compileSource($source, $name = null)
520 532
     {
533
+        if (!$source instanceof Twig_Source) {
534
+            @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);
535
+            $source = new Twig_Source($source, $name);
536
+        }
537
+
521 538
         try {
522
-            return $this->compile($this->parse($this->tokenize($source, $name)));
539
+            return $this->compile($this->parse($this->tokenize($source)));
523 540
         } catch (Twig_Error $e) {
524
-            $e->setTemplateName($name);
541
+            $e->setTemplateName($source->getName());
525 542
             throw $e;
526 543
         } catch (Exception $e) {
527
-            throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
544
+            throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source->getName(), $e);
528 545
         }
529 546
     }
530 547
 
... ...
@@ -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,12 +379,12 @@ 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');
... ...
@@ -496,7 +496,7 @@ class Twig_ExpressionParser
496 496
             $name = null;
497 497
             if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
498 498
                 if (!$value instanceof Twig_Node_Expression_Name) {
499
-                    throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getFilename());
499
+                    throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()->getName());
500 500
                 }
501 501
                 $name = $value->getAttribute('name');
502 502
 
... ...
@@ -504,7 +504,7 @@ class Twig_ExpressionParser
504 504
                     $value = $this->parsePrimaryExpression();
505 505
 
506 506
                     if (!$this->checkConstantExpression($value)) {
507
-                        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());
507
+                        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());
508 508
                     }
509 509
                 } else {
510 510
                     $value = $this->parseExpression();
... ...
@@ -538,7 +538,7 @@ class Twig_ExpressionParser
538 538
             $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
539 539
             $value = $token->getValue();
540 540
             if (in_array(strtolower($value), array('true', 'false', 'none', 'null'))) {
541
-                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getFilename());
541
+                throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()->getName());
542 542
             }
543 543
             $targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine());
544 544
 
... ...
@@ -568,7 +568,7 @@ class Twig_ExpressionParser
568 568
         $env = $this->parser->getEnvironment();
569 569
 
570 570
         if (false === $function = $env->getFunction($name)) {
571
-            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getFilename());
571
+            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()->getName());
572 572
             $e->addSuggestions($name, array_keys($env->getFunctions()));
573 573
 
574 574
             throw $e;
... ...
@@ -582,7 +582,7 @@ class Twig_ExpressionParser
582 582
             if ($function->getAlternative()) {
583 583
                 $message .= sprintf('. Use "%s" instead', $function->getAlternative());
584 584
             }
585
-            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getFilename(), $line);
585
+            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line);
586 586
 
587 587
             @trigger_error($message, E_USER_DEPRECATED);
588 588
         }
... ...
@@ -595,7 +595,7 @@ class Twig_ExpressionParser
595 595
         $env = $this->parser->getEnvironment();
596 596
 
597 597
         if (false === $filter = $env->getFilter($name)) {
598
-            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getFilename());
598
+            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()->getName());
599 599
             $e->addSuggestions($name, array_keys($env->getFilters()));
600 600
 
601 601
             throw $e;
... ...
@@ -609,7 +609,7 @@ class Twig_ExpressionParser
609 609
             if ($filter->getAlternative()) {
610 610
                 $message .= sprintf('. Use "%s" instead', $filter->getAlternative());
611 611
             }
612
-            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getFilename(), $line);
612
+            $message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line);
613 613
 
614 614
             @trigger_error($message, E_USER_DEPRECATED);
615 615
         }
... ...
@@ -296,7 +296,7 @@ class Twig_Extension_Core extends Twig_Extension
296 296
             }
297 297
         }
298 298
 
299
-        $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getFilename());
299
+        $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()->getName());
300 300
         $e->addSuggestions($name, array_keys($env->getTests()));
301 301
 
302 302
         throw $e;
... ...
@@ -26,6 +26,7 @@ class Twig_Lexer
26 26
     private $states;
27 27
     private $brackets;
28 28
     private $env;
29
+    // to be renamed to $name in 2.0 (where it is private)
29 30
     private $filename;
30 31
     private $options;
31 32
     private $regexes;
... ...
@@ -75,8 +76,10 @@ class Twig_Lexer
75 76
     /**
76 77
      * {@inheritdoc}
77 78
      */
78
-    public function tokenize($code, $filename = null)
79
+    public function tokenize($code, $name = null)
79 80
     {
81
+        $source = $code;
82
+
80 83
         if (((int) ini_get('mbstring.func_overload')) & 2) {
81 84
             $mbEncoding = mb_internal_encoding();
82 85
             mb_internal_encoding('ASCII');
... ...
@@ -84,8 +87,8 @@ class Twig_Lexer
84 87
             $mbEncoding = null;
85 88
         }
86 89
 
87
-        $this->code = str_replace(array("\r\n", "\r"), "\n", $code);
88
-        $this->filename = $filename;
90
+        $this->code = str_replace(array("\r\n", "\r"), "\n", $source->getCode());
91
+        $this->filename = $source->getName();
89 92
         $this->cursor = 0;
90 93
         $this->lineno = 1;
91 94
         $this->end = strlen($this->code);
... ...
@@ -136,7 +139,7 @@ class Twig_Lexer
136 139
             mb_internal_encoding($mbEncoding);
137 140
         }
138 141
 
139
-        return new Twig_TokenStream($this->tokens, $this->filename, $this->env->isDebug() ? $code : '');
142
+        return new Twig_TokenStream($this->tokens, $source);
140 143
     }
141 144
 
142 145
     private function lexData()
... ...
@@ -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
     private $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
         if (isset($this->hasSourceCache[$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_Node $body, Twig_Node_Expression $parent = null, Twig_Node $blocks, Twig_Node $macros, Twig_Node $traits, $embeddedTemplates, $filename, $source = '')
24
+    public function __construct(Twig_Node $body, Twig_Node_Expression $parent = null, Twig_Node $blocks, Twig_Node $macros, Twig_Node $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")
... ...
@@ -333,7 +345,7 @@ class Twig_Node_Module extends Twig_Node
333 345
             ->write("public function getTemplateName()\n", "{\n")
334 346
             ->indent()
335 347
             ->write('return ')
336
-            ->repr($this->getAttribute('filename'))
348
+            ->repr($this->getAttribute('name'))
337 349
             ->raw(";\n")
338 350
             ->outdent()
339 351
             ->write("}\n\n")
... ...
@@ -409,9 +421,26 @@ class Twig_Node_Module extends Twig_Node
409 421
             ->write("public function getSource()\n", "{\n")
410 422
             ->indent()
411 423
             ->write('return ')
412
-            ->string($this->getAttribute('source'))
424
+            ->string($compiler->getEnvironment()->isDebug() ? $this->getAttribute('source') : '')
413 425
             ->raw(";\n")
414 426
             ->outdent()
427
+            ->write("}\n\n")
428
+        ;
429
+    }
430
+
431
+    protected function compileGetSourceContext(Twig_Compiler $compiler)
432
+    {
433
+        $compiler
434
+            ->write("public function getSourceContext()\n", "{\n")
435
+            ->indent()
436
+            ->write('return new Twig_Source(')
437
+            ->string($compiler->getEnvironment()->isDebug() ? $this->getAttribute('source') : '')
438
+            ->raw(', ')
439
+            ->string($this->getAttribute('name'))
440
+            ->raw(', ')
441
+            ->string($this->getAttribute('path'))
442
+            ->raw(");\n")
443
+            ->outdent()
415 444
             ->write("}\n")
416 445
         ;
417 446
     }
... ...
@@ -102,7 +102,7 @@ class Twig_Parser
102 102
             }
103 103
         } catch (Twig_Error_Syntax $e) {
104 104
             if (!$e->getTemplateName()) {
105
-                $e->setTemplateName($this->stream->getFilename());
105
+                $e->setTemplateName($this->stream->getSourceContext()->getName());
106 106
             }
107 107
 
108 108
             if (!$e->getTemplateLine()) {
... ...
@@ -112,7 +112,7 @@ class Twig_Parser
112 112
             throw $e;
113 113
         }
114 114
 
115
-        $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());
115
+        $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());
116 116
 
117 117
         $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
118 118
 
... ...
@@ -149,7 +149,7 @@ class Twig_Parser
149 149
                     $token = $this->getCurrentToken();
150 150
 
151 151
                     if ($token->getType() !== Twig_Token::NAME_TYPE) {
152
-                        throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getFilename());
152
+                        throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()->getName());
153 153
                     }
154 154
 
155 155
                     if (null !== $test && $test($token)) {
... ...
@@ -166,13 +166,13 @@ class Twig_Parser
166 166
 
167 167
                     if (!isset($this->handlers[$token->getValue()])) {
168 168
                         if (null !== $test) {
169
-                            $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getFilename());
169
+                            $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName());
170 170
 
171 171
                             if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) {
172 172
                                 $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno));
173 173
                             }
174 174
                         } else {
175
-                            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getFilename());
175
+                            $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()->getName());
176 176
                             $e->addSuggestions($token->getValue(), array_keys($this->env->getTags()));
177 177
                         }
178 178
 
... ...
@@ -189,7 +189,7 @@ class Twig_Parser
189 189
                     break;
190 190
 
191 191
                 default:
192
-                    throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->stream->getFilename());
192
+                    throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->stream->getSourceContext()->getName());
193 193
             }
194 194
         }
195 195
 
... ...
@@ -355,10 +355,10 @@ class Twig_Parser
355 355
             (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
356 356
         ) {
357 357
             if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
358
-                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());
358
+                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());
359 359
             }
360 360
 
361
-            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());
361
+            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());
362 362
         }
363 363
 
364 364
         // bypass "set" nodes as they "capture" the output
365 365
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
+}
... ...
@@ -24,7 +24,7 @@ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
24 24
         } else {
25 25
             $expr = $this->parser->getExpressionParser()->parseExpression();
26 26
             if (!$expr instanceof Twig_Node_Expression_Constant) {
27
-                throw new Twig_Error_Syntax('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getFilename());
27
+                throw new Twig_Error_Syntax('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName());
28 28
             }
29 29
             $value = $expr->getAttribute('value');
30 30
         }
... ...
@@ -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
     private function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_Node $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
 
... ...
@@ -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
 }
... ...
@@ -134,7 +134,7 @@ class Twig_Tests_EnvironmentTest extends PHPUnit_Framework_TestCase
134 134
         $twig = new Twig_Environment($loader = new Twig_Loader_Array(array('index' => '{{ foo }}')), $options);
135 135
 
136 136
         $key = $cache->generateKey('index', $twig->getTemplateClass('index'));
137
-        $cache->write($key, $twig->compileSource('{{ foo }}', 'index'));
137
+        $cache->write($key, $twig->compileSource(new Twig_Source('{{ foo }}', 'index')));
138 138
 
139 139
         // check that extensions won't be initialized when rendering a template that is already in the cache
140 140
         $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());
... ...
@@ -170,7 +169,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
170 169
 
171 170
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
172 171
         foreach ($tests as $template => $expected) {
173
-            $stream = $lexer->tokenize($template);
172
+            $stream = $lexer->tokenize(new Twig_Source($template));
174 173
             $stream->expect(Twig_Token::VAR_START_TYPE);
175 174
             $stream->expect(Twig_Token::STRING_TYPE, $expected);
176 175
         }
... ...
@@ -181,7 +180,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
181 180
         $template = 'foo {{ "bar #{ baz + 1 }" }}';
182 181
 
183 182
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
184
-        $stream = $lexer->tokenize($template);
183
+        $stream = $lexer->tokenize(new Twig_Source($template));
185 184
         $stream->expect(Twig_Token::TEXT_TYPE, 'foo ');
186 185
         $stream->expect(Twig_Token::VAR_START_TYPE);
187 186
         $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
... ...
@@ -198,7 +197,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
198 197
         $template = '{{ "bar \#{baz+1}" }}';
199 198
 
200 199
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
201
-        $stream = $lexer->tokenize($template);
200
+        $stream = $lexer->tokenize(new Twig_Source($template));
202 201
         $stream->expect(Twig_Token::VAR_START_TYPE);
203 202
         $stream->expect(Twig_Token::STRING_TYPE, 'bar #{baz+1}');
204 203
         $stream->expect(Twig_Token::VAR_END_TYPE);
... ...
@@ -209,7 +208,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
209 208
         $template = '{{ "bar # baz" }}';
210 209
 
211 210
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
212
-        $stream = $lexer->tokenize($template);
211
+        $stream = $lexer->tokenize(new Twig_Source($template));
213 212
         $stream->expect(Twig_Token::VAR_START_TYPE);
214 213
         $stream->expect(Twig_Token::STRING_TYPE, 'bar # baz');
215 214
         $stream->expect(Twig_Token::VAR_END_TYPE);
... ...
@@ -224,7 +223,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
224 223
         $template = '{{ "bar #{x" }}';
225 224
 
226 225
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
227
-        $lexer->tokenize($template);
226
+        $lexer->tokenize(new Twig_Source($template));
228 227
     }
229 228
 
230 229
     public function testStringWithNestedInterpolations()
... ...
@@ -232,7 +231,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
232 231
         $template = '{{ "bar #{ "foo#{bar}" }" }}';
233 232
 
234 233
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
235
-        $stream = $lexer->tokenize($template);
234
+        $stream = $lexer->tokenize(new Twig_Source($template));
236 235
         $stream->expect(Twig_Token::VAR_START_TYPE);
237 236
         $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
238 237
         $stream->expect(Twig_Token::INTERPOLATION_START_TYPE);
... ...
@@ -249,7 +248,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
249 248
         $template = '{% foo "bar #{ "foo#{bar}" }" %}';
250 249
 
251 250
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
252
-        $stream = $lexer->tokenize($template);
251
+        $stream = $lexer->tokenize(new Twig_Source($template));
253 252
         $stream->expect(Twig_Token::BLOCK_START_TYPE);
254 253
         $stream->expect(Twig_Token::NAME_TYPE, 'foo');
255 254
         $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
... ...
@@ -267,7 +266,7 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
267 266
         $template = "{{ 1 and\n0}}";
268 267
 
269 268
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
270
-        $stream = $lexer->tokenize($template);
269
+        $stream = $lexer->tokenize(new Twig_Source($template));
271 270
         $stream->expect(Twig_Token::VAR_START_TYPE);
272 271
         $stream->expect(Twig_Token::NUMBER_TYPE, 1);
273 272
         $stream->expect(Twig_Token::OPERATOR_TYPE, 'and');
... ...
@@ -289,7 +288,7 @@ bar
289 288
 ';
290 289
 
291 290
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
292
-        $lexer->tokenize($template);
291
+        $lexer->tokenize(new Twig_Source($template));
293 292
     }
294 293
 
295 294
     /**
... ...
@@ -308,6 +307,6 @@ bar
308 307
 ';
309 308
 
310 309
         $lexer = new Twig_Lexer(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
311
-        $lexer->tokenize($template);
310
+        $lexer->tokenize(new Twig_Source($template));
312 311
     }
313 312
 }
... ...
@@ -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';
22
-        $node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
21
+        $source = new Twig_Source('{{ foo }}', 'foo.twig');
22
+        $node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, new Twig_Node(array()), $source);
23 23
 
24 24
         $this->assertEquals($body, $node->getNode('body'));
25 25
         $this->assertEquals($blocks, $node->getNode('blocks'));
26 26
         $this->assertEquals($macros, $node->getNode('macros'));
27 27
         $this->assertEquals($parent, $node->getNode('parent'));
28
-        $this->assertEquals($filename, $node->getAttribute('filename'));
28
+        $this->assertEquals($source->getName(), $node->getAttribute('name'));
29 29
     }
30 30
 
31 31
     public function getTests()
... ...
@@ -39,9 +39,9 @@ class Twig_Tests_Node_ModuleTest extends Twig_Test_NodeTestCase
39 39
         $blocks = new Twig_Node();
40 40
         $macros = new Twig_Node();
41 41
         $traits = new Twig_Node();
42
-        $filename = 'foo.twig';
42
+        $source = new Twig_Source('{{ foo }}', 'foo.twig');
43 43
 
44
-        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
44
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $source);
45 45
         $tests[] = array($node, <<<EOF
46 46
 <?php
47 47
 
... ...
@@ -78,6 +78,11 @@ class __TwigTemplate_%x extends Twig_Template
78 78
     {
79 79
         return "";
80 80
     }
81
+
82
+    public function getSourceContext()
83
+    {
84
+        return new Twig_Source("", "foo.twig", "");
85
+    }
81 86
 }
82 87
 EOF
83 88
         , $twig, true);
... ...
@@ -87,7 +92,7 @@ EOF
87 92
         $body = new Twig_Node(array($import));
88 93
         $extends = new Twig_Node_Expression_Constant('layout.twig', 1);
89 94
 
90
-        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
95
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $source);
91 96
         $tests[] = array($node, <<<EOF
92 97
 <?php
93 98
 
... ...
@@ -136,6 +141,11 @@ class __TwigTemplate_%x extends Twig_Template
136 141
     {
137 142
         return "";
138 143
     }
144
+
145
+    public function getSourceContext()
146
+    {
147
+        return new Twig_Source("", "foo.twig", "");
148
+    }
139 149
 }
140 150
 EOF
141 151
         , $twig, true);
... ...
@@ -149,7 +159,8 @@ EOF
149 159
                         2
150 160
                     );
151 161
 
152
-        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename, '{{ foo }}');
162
+        $twig = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('debug' => true));
163
+        $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $source);
153 164
         $tests[] = array($node, <<<EOF
154 165
 <?php
155 166
 
... ...
@@ -189,6 +200,11 @@ class __TwigTemplate_%x extends Twig_Template
189 200
     {
190 201
         return "{{ foo }}";
191 202
     }
203
+
204
+    public function getSourceContext()
205
+    {
206
+        return new Twig_Source("{{ foo }}", "foo.twig", "");
207
+    }
192 208
 }
193 209
 EOF
194 210
         , $twig, true);
... ...
@@ -14,7 +14,7 @@ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase
14 14
     {
15 15
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
16 16
 
17
-        $stream = $env->parse($env->tokenize('{{ block("foo") }}', 'index'));
17
+        $stream = $env->parse($env->tokenize(new Twig_Source('{{ block("foo") }}', 'index')));
18 18
 
19 19
         $node = $stream->getNode('body')->getNode(0);
20 20
 
... ...
@@ -26,7 +26,7 @@ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase
26 26
     {
27 27
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false, 'autoescape' => false));
28 28
 
29
-        $stream = $env->parse($env->tokenize('{% extends "foo" %}{% block content %}{{ parent() }}{% endblock %}', 'index'));
29
+        $stream = $env->parse($env->tokenize(new Twig_Source('{% extends "foo" %}{% block content %}{{ parent() }}{% endblock %}', 'index')));
30 30
 
31 31
         $node = $stream->getNode('blocks')->getNode('content')->getNode(0)->getNode('body');
32 32
 
... ...
@@ -41,7 +41,7 @@ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase
41 41
     {
42 42
         $env = new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock(), array('cache' => false));
43 43
 
44
-        $stream = $env->parse($env->tokenize($template, 'index'));
44
+        $stream = $env->parse($env->tokenize(new Twig_Source($template, 'index')));
45 45
 
46 46
         foreach ($expected as $target => $withLoop) {
47 47
             $this->assertTrue($this->checkForConfiguration($stream, $target, $withLoop), sprintf('variable %s is %soptimized', $target, $withLoop ? 'not ' : ''));
... ...
@@ -140,23 +140,27 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
140 140
             'optimizations' => 0,
141 141
         ));
142 142
 
143
-        $twig->parse($twig->tokenize(<<<EOF
143
+        $twig->parse($twig->tokenize(new Twig_Source(<<<EOF
144 144
 {% from _self import foo %}
145 145
 
146 146
 {% macro foo() %}
147 147
     {{ foo }}
148 148
 {% endmacro %}
149 149
 EOF
150
-        ));
150
+        )));
151 151
     }
152 152
 
153 153
     protected function getParser()
154 154
     {
155 155
         $parser = new Twig_Parser(new Twig_Environment($this->getMockBuilder('Twig_LoaderInterface')->getMock()));
156 156
         $parser->setParent(new Twig_Node());
157
+
158
+        $stream = $this->getMockBuilder('Twig_TokenStream')->disableOriginalConstructor()->getMock();
159
+        $stream->expects($this->any())->method('getSourceContext')->will($this->returnValue(new Twig_Source('')));
160
+
157 161
         $p = new ReflectionProperty($parser, 'stream');
158 162
         $p->setAccessible(true);
159
-        $p->setValue($parser, $this->getMockBuilder('Twig_TokenStream')->disableOriginalConstructor()->getMock());
163
+        $p->setValue($parser, $stream);
160 164
 
161 165
         return $parser;
162 166
     }
... ...
@@ -27,6 +27,18 @@ class Twig_Tests_TokenStreamTest extends PHPUnit_Framework_TestCase
27 27
         );
28 28
     }
29 29
 
30
+    /**
31
+     * @group legacy
32
+     */
33
+    public function testLegacyConstructorSignature()
34
+    {
35
+        $stream = new Twig_TokenStream(array(), 'foo', '{{ foo }}');
36
+        $this->assertEquals('foo', $stream->getFilename());
37
+        $this->assertEquals('{{ foo }}', $stream->getSource());
38
+        $this->assertEquals('foo', $stream->getSourceContext()->getName());
39
+        $this->assertEquals('{{ foo }}', $stream->getSourceContext()->getCode());
40
+    }
41
+
30 42
     public function testNext()
31 43
     {
32