tests/LexerTest.php
d2df756d
 <?php
 
2119e60c
 namespace Twig\Tests;
 
d2df756d
 /*
  * 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\Lexer;
 use Twig\Loader\LoaderInterface;
 use Twig\Source;
 use Twig\Token;
 
34bdab4d
 class LexerTest extends TestCase
d2df756d
 {
112a16bf
     public function testNameLabelForTag()
     {
c8fe2538
         $template = '{% § %}';
112a16bf
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
112a16bf
 
90d579e4
         $stream->expect(Token::BLOCK_START_TYPE);
         $this->assertSame('§', $stream->expect(Token::NAME_TYPE)->getValue());
112a16bf
     }
 
     public function testNameLabelForFunction()
     {
c8fe2538
         $template = '{{ §() }}';
112a16bf
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
112a16bf
 
90d579e4
         $stream->expect(Token::VAR_START_TYPE);
         $this->assertSame('§', $stream->expect(Token::NAME_TYPE)->getValue());
112a16bf
     }
 
d2df756d
     public function testBracketsNesting()
     {
         $template = '{{ {"a":{"b":"c"}} }}';
 
90d579e4
         $this->assertEquals(2, $this->countToken($template, Token::PUNCTUATION_TYPE, '{'));
         $this->assertEquals(2, $this->countToken($template, Token::PUNCTUATION_TYPE, '}'));
d2df756d
     }
 
     protected function countToken($template, $type, $value = null)
     {
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
d2df756d
 
         $count = 0;
         while (!$stream->isEOF()) {
             $token = $stream->next();
             if ($type === $token->getType()) {
                 if (null === $value || $value === $token->getValue()) {
                     ++$count;
                 }
             }
         }
 
         return $count;
     }
5ec19550
 
     public function testLineDirective()
     {
         $template = "foo\n"
00ecabf1
             ."bar\n"
             ."{% line 10 %}\n"
             ."{{\n"
             ."baz\n"
             ."}}\n";
5ec19550
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
5ec19550
 
         // foo\nbar\n
90d579e4
         $this->assertSame(1, $stream->expect(Token::TEXT_TYPE)->getLine());
5ec19550
         // \n (after {% line %})
90d579e4
         $this->assertSame(10, $stream->expect(Token::TEXT_TYPE)->getLine());
5ec19550
         // {{
90d579e4
         $this->assertSame(11, $stream->expect(Token::VAR_START_TYPE)->getLine());
5ec19550
         // baz
90d579e4
         $this->assertSame(12, $stream->expect(Token::NAME_TYPE)->getLine());
5ec19550
     }
 
     public function testLineDirectiveInline()
     {
         $template = "foo\n"
00ecabf1
             ."bar{% line 10 %}{{\n"
             ."baz\n"
             ."}}\n";
5ec19550
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
5ec19550
 
         // foo\nbar
90d579e4
         $this->assertSame(1, $stream->expect(Token::TEXT_TYPE)->getLine());
5ec19550
         // {{
90d579e4
         $this->assertSame(10, $stream->expect(Token::VAR_START_TYPE)->getLine());
5ec19550
         // baz
90d579e4
         $this->assertSame(11, $stream->expect(Token::NAME_TYPE)->getLine());
5ec19550
     }
6783d581
 
     public function testLongComments()
     {
         $template = '{# '.str_repeat('*', 100000).' #}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $lexer->tokenize(new Source($template, 'index'));
6783d581
 
7259e52f
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
6783d581
     }
 
8c4291ac
     public function testLongVerbatim()
6783d581
     {
39885c91
         $template = '{% verbatim %}'.str_repeat('*', 100000).'{% endverbatim %}';
6783d581
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $lexer->tokenize(new Source($template, 'index'));
6783d581
 
7259e52f
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
6783d581
     }
 
7da9f2f9
     public function testLongVar()
6783d581
     {
7da9f2f9
         $template = '{{ '.str_repeat('x', 100000).' }}';
6783d581
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $lexer->tokenize(new Source($template, 'index'));
6783d581
 
7259e52f
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
6783d581
     }
 
7da9f2f9
     public function testLongBlock()
6783d581
     {
7da9f2f9
         $template = '{% '.str_repeat('x', 100000).' %}';
6783d581
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $lexer->tokenize(new Source($template, 'index'));
6783d581
 
7259e52f
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
6783d581
     }
a6f9f9b5
 
     public function testBigNumbers()
     {
         $template = '{{ 922337203685477580700 }}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
2030482e
         $stream->next();
a6f9f9b5
         $node = $stream->next();
b0c41d42
         $this->assertEquals('922337203685477580700', $node->getValue());
a6f9f9b5
     }
1036f90e
 
8b569e49
     public function testStringWithEscapedDelimiter()
     {
5c55243d
         $tests = [
8b569e49
             "{{ 'foo \' bar' }}" => 'foo \' bar',
b0c41d42
             '{{ "foo \" bar" }}' => 'foo " bar',
5c55243d
         ];
a089f1e8
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
8b569e49
         foreach ($tests as $template => $expected) {
90d579e4
             $stream = $lexer->tokenize(new Source($template, 'index'));
             $stream->expect(Token::VAR_START_TYPE);
             $stream->expect(Token::STRING_TYPE, $expected);
7259e52f
 
             // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
             // can be executed without throwing any exceptions
             $this->addToAssertionCount(1);
8b569e49
         }
     }
 
     public function testStringWithInterpolation()
c8bba24d
     {
         $template = 'foo {{ "bar #{ baz + 1 }" }}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
         $stream->expect(Token::TEXT_TYPE, 'foo ');
         $stream->expect(Token::VAR_START_TYPE);
         $stream->expect(Token::STRING_TYPE, 'bar ');
         $stream->expect(Token::INTERPOLATION_START_TYPE);
         $stream->expect(Token::NAME_TYPE, 'baz');
         $stream->expect(Token::OPERATOR_TYPE, '+');
         $stream->expect(Token::NUMBER_TYPE, '1');
         $stream->expect(Token::INTERPOLATION_END_TYPE);
         $stream->expect(Token::VAR_END_TYPE);
7259e52f
 
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
c8bba24d
     }
 
     public function testStringWithEscapedInterpolation()
     {
         $template = '{{ "bar \#{baz+1}" }}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
         $stream->expect(Token::VAR_START_TYPE);
         $stream->expect(Token::STRING_TYPE, 'bar #{baz+1}');
         $stream->expect(Token::VAR_END_TYPE);
7259e52f
 
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
c8bba24d
     }
 
     public function testStringWithHash()
     {
         $template = '{{ "bar # baz" }}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
         $stream->expect(Token::VAR_START_TYPE);
         $stream->expect(Token::STRING_TYPE, 'bar # baz');
         $stream->expect(Token::VAR_END_TYPE);
7259e52f
 
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
c8bba24d
     }
 
     public function testStringWithUnterminatedInterpolation()
     {
5ebcecf2
         $this->expectException(SyntaxError::class);
b776e41f
         $this->expectExceptionMessage('Unclosed """');
 
c8bba24d
         $template = '{{ "bar #{x" }}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $lexer->tokenize(new Source($template, 'index'));
c8bba24d
     }
 
     public function testStringWithNestedInterpolations()
     {
         $template = '{{ "bar #{ "foo#{bar}" }" }}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
         $stream->expect(Token::VAR_START_TYPE);
         $stream->expect(Token::STRING_TYPE, 'bar ');
         $stream->expect(Token::INTERPOLATION_START_TYPE);
         $stream->expect(Token::STRING_TYPE, 'foo');
         $stream->expect(Token::INTERPOLATION_START_TYPE);
         $stream->expect(Token::NAME_TYPE, 'bar');
         $stream->expect(Token::INTERPOLATION_END_TYPE);
         $stream->expect(Token::INTERPOLATION_END_TYPE);
         $stream->expect(Token::VAR_END_TYPE);
7259e52f
 
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
c8bba24d
     }
 
     public function testStringWithNestedInterpolationsInBlock()
     {
         $template = '{% foo "bar #{ "foo#{bar}" }" %}';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
         $stream->expect(Token::BLOCK_START_TYPE);
         $stream->expect(Token::NAME_TYPE, 'foo');
         $stream->expect(Token::STRING_TYPE, 'bar ');
         $stream->expect(Token::INTERPOLATION_START_TYPE);
         $stream->expect(Token::STRING_TYPE, 'foo');
         $stream->expect(Token::INTERPOLATION_START_TYPE);
         $stream->expect(Token::NAME_TYPE, 'bar');
         $stream->expect(Token::INTERPOLATION_END_TYPE);
         $stream->expect(Token::INTERPOLATION_END_TYPE);
         $stream->expect(Token::BLOCK_END_TYPE);
7259e52f
 
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
c8bba24d
     }
ae250f6b
 
     public function testOperatorEndingWithALetterAtTheEndOfALine()
     {
         $template = "{{ 1 and\n0}}";
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $stream = $lexer->tokenize(new Source($template, 'index'));
         $stream->expect(Token::VAR_START_TYPE);
         $stream->expect(Token::NUMBER_TYPE, 1);
         $stream->expect(Token::OPERATOR_TYPE, 'and');
7259e52f
 
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
ae250f6b
     }
ac86c636
 
     public function testUnterminatedVariable()
     {
5ebcecf2
         $this->expectException(SyntaxError::class);
b776e41f
         $this->expectExceptionMessage('Unclosed "variable" in "index" at line 3');
 
ac86c636
         $template = '
 
 {{
 
 bar
 
 
 ';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $lexer->tokenize(new Source($template, 'index'));
ac86c636
     }
 
     public function testUnterminatedBlock()
     {
5ebcecf2
         $this->expectException(SyntaxError::class);
b776e41f
         $this->expectExceptionMessage('Unclosed "block" in "index" at line 3');
 
ac86c636
         $template = '
 
 {%
 
 bar
 
 
 ';
 
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)));
90d579e4
         $lexer->tokenize(new Source($template, 'index'));
ac86c636
     }
7b5851fe
 
     public function testOverridingSyntax()
     {
         $template = '[# comment #]{# variable #}/# if true #/true/# endif #/';
656c295e
         $lexer = new Lexer(new Environment($this->createMock(LoaderInterface::class)), [
7b5851fe
             'tag_comment' => ['[#', '#]'],
             'tag_block' => ['/#', '#/'],
             'tag_variable' => ['{#', '#}'],
         ]);
         $stream = $lexer->tokenize(new Source($template, 'index'));
         $stream->expect(Token::VAR_START_TYPE);
         $stream->expect(Token::NAME_TYPE, 'variable');
         $stream->expect(Token::VAR_END_TYPE);
         $stream->expect(Token::BLOCK_START_TYPE);
         $stream->expect(Token::NAME_TYPE, 'if');
         $stream->expect(Token::NAME_TYPE, 'true');
         $stream->expect(Token::BLOCK_END_TYPE);
         $stream->expect(Token::TEXT_TYPE, 'true');
         $stream->expect(Token::BLOCK_START_TYPE);
         $stream->expect(Token::NAME_TYPE, 'endif');
         $stream->expect(Token::BLOCK_END_TYPE);
 
         // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
         // can be executed without throwing any exceptions
         $this->addToAssertionCount(1);
     }
d2df756d
 }