tests/ParserTest.php
eedce661
 <?php
 
2119e60c
 namespace Twig\Tests;
 
eedce661
 /*
  * This file is part of Twig.
  *
  * (c) Fabien Potencier
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
  */
90d579e4
 
34bdab4d
 use PHPUnit\Framework\TestCase;
90d579e4
 use Twig\Environment;
5ebcecf2
 use Twig\Error\SyntaxError;
90d579e4
 use Twig\Loader\LoaderInterface;
 use Twig\Node\Node;
 use Twig\Node\SetNode;
 use Twig\Node\TextNode;
 use Twig\Parser;
 use Twig\Source;
 use Twig\Token;
 use Twig\TokenParser\AbstractTokenParser;
 use Twig\TokenStream;
 
34bdab4d
 class ParserTest extends TestCase
eedce661
 {
49488b8f
     public function testUnknownTag()
08bda723
     {
5ebcecf2
         $this->expectException(SyntaxError::class);
b776e41f
         $this->expectExceptionMessage('Unknown "foo" tag. Did you mean "for" at line 1?');
 
90d579e4
         $stream = new TokenStream([
             new Token(Token::BLOCK_START_TYPE, '', 1),
             new Token(Token::NAME_TYPE, 'foo', 1),
             new Token(Token::BLOCK_END_TYPE, '', 1),
             new Token(Token::EOF_TYPE, '', 1),
5c55243d
         ]);
656c295e
         $parser = new Parser(new Environment($this->createMock(LoaderInterface::class)));
08bda723
         $parser->parse($stream);
     }
 
d0a5ef8c
     public function testUnknownTagWithoutSuggestions()
     {
5ebcecf2
         $this->expectException(SyntaxError::class);
b776e41f
         $this->expectExceptionMessage('Unknown "foobar" tag at line 1.');
 
90d579e4
         $stream = new TokenStream([
             new Token(Token::BLOCK_START_TYPE, '', 1),
             new Token(Token::NAME_TYPE, 'foobar', 1),
             new Token(Token::BLOCK_END_TYPE, '', 1),
             new Token(Token::EOF_TYPE, '', 1),
5c55243d
         ]);
656c295e
         $parser = new Parser(new Environment($this->createMock(LoaderInterface::class)));
d0a5ef8c
         $parser->parse($stream);
     }
 
     /**
7f5390a6
      * @dataProvider getFilterBodyNodesData
      */
     public function testFilterBodyNodes($input, $expected)
     {
2506be17
         $parser = $this->getParser();
010cfaf4
         $m = new \ReflectionMethod($parser, 'filterBodyNodes');
ab88c38a
         $m->setAccessible(true);
7f5390a6
 
ab88c38a
         $this->assertEquals($expected, $m->invoke($parser, $input));
7f5390a6
     }
 
     public function getFilterBodyNodesData()
     {
5c55243d
         return [
             [
90d579e4
                 new Node([new TextNode('   ', 1)]),
                 new Node([]),
5c55243d
             ],
             [
90d579e4
                 $input = new Node([new SetNode(false, new Node(), new Node(), 1)]),
7f5390a6
                 $input,
5c55243d
             ],
             [
90d579e4
                 $input = new Node([new SetNode(true, new Node(), new Node([new Node([new TextNode('foo', 1)])]), 1)]),
f20450d1
                 $input,
5c55243d
             ],
         ];
7f5390a6
     }
 
     /**
      * @dataProvider getFilterBodyNodesDataThrowsException
      */
     public function testFilterBodyNodesThrowsException($input)
     {
5ebcecf2
         $this->expectException(SyntaxError::class);
b776e41f
 
2506be17
         $parser = $this->getParser();
7f5390a6
 
010cfaf4
         $m = new \ReflectionMethod($parser, 'filterBodyNodes');
ab88c38a
         $m->setAccessible(true);
 
         $m->invoke($parser, $input);
7f5390a6
     }
 
     public function getFilterBodyNodesDataThrowsException()
     {
5c55243d
         return [
90d579e4
             [new TextNode('foo', 1)],
             [new Node([new Node([new TextNode('foo', 1)])])],
5c55243d
         ];
7f5390a6
     }
 
083d8390
     /**
a573d464
      * @dataProvider getFilterBodyNodesWithBOMData
083d8390
      */
a573d464
     public function testFilterBodyNodesWithBOM($emptyNode)
083d8390
     {
2506be17
         $parser = $this->getParser();
ab88c38a
 
010cfaf4
         $m = new \ReflectionMethod($parser, 'filterBodyNodes');
ab88c38a
         $m->setAccessible(true);
90d579e4
         $this->assertNull($m->invoke($parser, new TextNode(\chr(0xEF).\chr(0xBB).\chr(0xBF).$emptyNode, 1)));
a573d464
     }
 
     public function getFilterBodyNodesWithBOMData()
     {
         return [
             [' '],
             ["\t"],
             ["\n"],
             ["\n\t\n   "],
         ];
083d8390
     }
 
f958d91e
     public function testParseIsReentrant()
     {
656c295e
         $twig = new Environment($this->createMock(LoaderInterface::class), [
f958d91e
             'autoescape' => false,
             'optimizations' => 0,
5c55243d
         ]);
f958d91e
         $twig->addTokenParser(new TestTokenParser());
 
90d579e4
         $parser = new Parser($twig);
f958d91e
 
90d579e4
         $parser->parse(new TokenStream([
             new Token(Token::BLOCK_START_TYPE, '', 1),
             new Token(Token::NAME_TYPE, 'test', 1),
             new Token(Token::BLOCK_END_TYPE, '', 1),
             new Token(Token::VAR_START_TYPE, '', 1),
             new Token(Token::NAME_TYPE, 'foo', 1),
             new Token(Token::VAR_END_TYPE, '', 1),
             new Token(Token::EOF_TYPE, '', 1),
5c55243d
         ]));
f958d91e
 
e5e2a010
         $this->assertNull($parser->getParent());
f958d91e
     }
 
611f4451
     public function testGetVarName()
     {
656c295e
         $twig = new Environment($this->createMock(LoaderInterface::class), [
611f4451
             'autoescape' => false,
             'optimizations' => 0,
5c55243d
         ]);
611f4451
 
90d579e4
         $twig->parse($twig->tokenize(new Source(<<<EOF
611f4451
 {% from _self import foo %}
 
 {% macro foo() %}
     {{ foo }}
 {% endmacro %}
 EOF
8c337082
         , 'index')));
7259e52f
 
         // The getVarName() must not depend on the template loaders,
         // If this test does not throw any exception, that's good.
         // see https://github.com/symfony/symfony/issues/4218
         $this->addToAssertionCount(1);
611f4451
     }
 
2506be17
     protected function getParser()
7f5390a6
     {
656c295e
         $parser = new Parser(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $parser->setParent(new Node());
7f5390a6
 
010cfaf4
         $p = new \ReflectionProperty($parser, 'stream');
ab88c38a
         $p->setAccessible(true);
90d579e4
         $p->setValue($parser, new TokenStream([]));
7f5390a6
 
bac4974e
         return $parser;
     }
 }
7f5390a6
 
90d579e4
 class TestTokenParser extends AbstractTokenParser
f958d91e
 {
54cec4e3
     public function parse(Token $token): Node
f958d91e
     {
         // simulate the parsing of another template right in the middle of the parsing of the current template
90d579e4
         $this->parser->parse(new TokenStream([
             new Token(Token::BLOCK_START_TYPE, '', 1),
             new Token(Token::NAME_TYPE, 'extends', 1),
             new Token(Token::STRING_TYPE, 'base', 1),
             new Token(Token::BLOCK_END_TYPE, '', 1),
             new Token(Token::EOF_TYPE, '', 1),
5c55243d
         ]));
f958d91e
 
90d579e4
         $this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
f958d91e
 
90d579e4
         return new Node([]);
f958d91e
     }
 
54cec4e3
     public function getTag(): string
f958d91e
     {
         return 'test';
     }
 }