1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) 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
12use Twig\Environment;
13use Twig\Lexer;
14use Twig\Source;
15use Twig\Token;
16
17class Twig_Tests_LexerTest extends \PHPUnit\Framework\TestCase
18{
19    /**
20     * @group legacy
21     */
22    public function testLegacyConstructorSignature()
23    {
24        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
25        $stream = $lexer->tokenize('{{ foo }}', 'foo');
26        $this->assertEquals('foo', $stream->getFilename());
27        $this->assertEquals('{{ foo }}', $stream->getSource());
28    }
29
30    public function testNameLabelForTag()
31    {
32        $template = '{% § %}';
33
34        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
35        $stream = $lexer->tokenize(new Source($template, 'index'));
36
37        $stream->expect(Token::BLOCK_START_TYPE);
38        $this->assertSame('§', $stream->expect(Token::NAME_TYPE)->getValue());
39    }
40
41    public function testNameLabelForFunction()
42    {
43        $template = '{{ §() }}';
44
45        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
46        $stream = $lexer->tokenize(new Source($template, 'index'));
47
48        $stream->expect(Token::VAR_START_TYPE);
49        $this->assertSame('§', $stream->expect(Token::NAME_TYPE)->getValue());
50    }
51
52    public function testBracketsNesting()
53    {
54        $template = '{{ {"a":{"b":"c"}} }}';
55
56        $this->assertEquals(2, $this->countToken($template, Token::PUNCTUATION_TYPE, '{'));
57        $this->assertEquals(2, $this->countToken($template, Token::PUNCTUATION_TYPE, '}'));
58    }
59
60    protected function countToken($template, $type, $value = null)
61    {
62        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
63        $stream = $lexer->tokenize(new Source($template, 'index'));
64
65        $count = 0;
66        while (!$stream->isEOF()) {
67            $token = $stream->next();
68            if ($type === $token->getType()) {
69                if (null === $value || $value === $token->getValue()) {
70                    ++$count;
71                }
72            }
73        }
74
75        return $count;
76    }
77
78    public function testLineDirective()
79    {
80        $template = "foo\n"
81            ."bar\n"
82            ."{% line 10 %}\n"
83            ."{{\n"
84            ."baz\n"
85            ."}}\n";
86
87        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
88        $stream = $lexer->tokenize(new Source($template, 'index'));
89
90        // foo\nbar\n
91        $this->assertSame(1, $stream->expect(Token::TEXT_TYPE)->getLine());
92        // \n (after {% line %})
93        $this->assertSame(10, $stream->expect(Token::TEXT_TYPE)->getLine());
94        // {{
95        $this->assertSame(11, $stream->expect(Token::VAR_START_TYPE)->getLine());
96        // baz
97        $this->assertSame(12, $stream->expect(Token::NAME_TYPE)->getLine());
98    }
99
100    public function testLineDirectiveInline()
101    {
102        $template = "foo\n"
103            ."bar{% line 10 %}{{\n"
104            ."baz\n"
105            ."}}\n";
106
107        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
108        $stream = $lexer->tokenize(new Source($template, 'index'));
109
110        // foo\nbar
111        $this->assertSame(1, $stream->expect(Token::TEXT_TYPE)->getLine());
112        // {{
113        $this->assertSame(10, $stream->expect(Token::VAR_START_TYPE)->getLine());
114        // baz
115        $this->assertSame(11, $stream->expect(Token::NAME_TYPE)->getLine());
116    }
117
118    public function testLongComments()
119    {
120        $template = '{# '.str_repeat('*', 100000).' #}';
121
122        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
123        $lexer->tokenize(new Source($template, 'index'));
124
125        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
126        // can be executed without throwing any exceptions
127        $this->addToAssertionCount(1);
128    }
129
130    public function testLongVerbatim()
131    {
132        $template = '{% verbatim %}'.str_repeat('*', 100000).'{% endverbatim %}';
133
134        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
135        $lexer->tokenize(new Source($template, 'index'));
136
137        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
138        // can be executed without throwing any exceptions
139        $this->addToAssertionCount(1);
140    }
141
142    public function testLongVar()
143    {
144        $template = '{{ '.str_repeat('x', 100000).' }}';
145
146        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
147        $lexer->tokenize(new Source($template, 'index'));
148
149        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
150        // can be executed without throwing any exceptions
151        $this->addToAssertionCount(1);
152    }
153
154    public function testLongBlock()
155    {
156        $template = '{% '.str_repeat('x', 100000).' %}';
157
158        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
159        $lexer->tokenize(new Source($template, 'index'));
160
161        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
162        // can be executed without throwing any exceptions
163        $this->addToAssertionCount(1);
164    }
165
166    public function testBigNumbers()
167    {
168        $template = '{{ 922337203685477580700 }}';
169
170        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
171        $stream = $lexer->tokenize(new Source($template, 'index'));
172        $stream->next();
173        $node = $stream->next();
174        $this->assertEquals('922337203685477580700', $node->getValue());
175    }
176
177    public function testStringWithEscapedDelimiter()
178    {
179        $tests = [
180            "{{ 'foo \' bar' }}" => 'foo \' bar',
181            '{{ "foo \" bar" }}' => 'foo " bar',
182        ];
183        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
184        foreach ($tests as $template => $expected) {
185            $stream = $lexer->tokenize(new Source($template, 'index'));
186            $stream->expect(Token::VAR_START_TYPE);
187            $stream->expect(Token::STRING_TYPE, $expected);
188
189            // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
190            // can be executed without throwing any exceptions
191            $this->addToAssertionCount(1);
192        }
193    }
194
195    public function testStringWithInterpolation()
196    {
197        $template = 'foo {{ "bar #{ baz + 1 }" }}';
198
199        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
200        $stream = $lexer->tokenize(new Source($template, 'index'));
201        $stream->expect(Token::TEXT_TYPE, 'foo ');
202        $stream->expect(Token::VAR_START_TYPE);
203        $stream->expect(Token::STRING_TYPE, 'bar ');
204        $stream->expect(Token::INTERPOLATION_START_TYPE);
205        $stream->expect(Token::NAME_TYPE, 'baz');
206        $stream->expect(Token::OPERATOR_TYPE, '+');
207        $stream->expect(Token::NUMBER_TYPE, '1');
208        $stream->expect(Token::INTERPOLATION_END_TYPE);
209        $stream->expect(Token::VAR_END_TYPE);
210
211        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
212        // can be executed without throwing any exceptions
213        $this->addToAssertionCount(1);
214    }
215
216    public function testStringWithEscapedInterpolation()
217    {
218        $template = '{{ "bar \#{baz+1}" }}';
219
220        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
221        $stream = $lexer->tokenize(new Source($template, 'index'));
222        $stream->expect(Token::VAR_START_TYPE);
223        $stream->expect(Token::STRING_TYPE, 'bar #{baz+1}');
224        $stream->expect(Token::VAR_END_TYPE);
225
226        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
227        // can be executed without throwing any exceptions
228        $this->addToAssertionCount(1);
229    }
230
231    public function testStringWithHash()
232    {
233        $template = '{{ "bar # baz" }}';
234
235        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
236        $stream = $lexer->tokenize(new Source($template, 'index'));
237        $stream->expect(Token::VAR_START_TYPE);
238        $stream->expect(Token::STRING_TYPE, 'bar # baz');
239        $stream->expect(Token::VAR_END_TYPE);
240
241        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
242        // can be executed without throwing any exceptions
243        $this->addToAssertionCount(1);
244    }
245
246    /**
247     * @expectedException \Twig\Error\SyntaxError
248     * @expectedExceptionMessage Unclosed """
249     */
250    public function testStringWithUnterminatedInterpolation()
251    {
252        $template = '{{ "bar #{x" }}';
253
254        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
255        $lexer->tokenize(new Source($template, 'index'));
256    }
257
258    public function testStringWithNestedInterpolations()
259    {
260        $template = '{{ "bar #{ "foo#{bar}" }" }}';
261
262        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
263        $stream = $lexer->tokenize(new Source($template, 'index'));
264        $stream->expect(Token::VAR_START_TYPE);
265        $stream->expect(Token::STRING_TYPE, 'bar ');
266        $stream->expect(Token::INTERPOLATION_START_TYPE);
267        $stream->expect(Token::STRING_TYPE, 'foo');
268        $stream->expect(Token::INTERPOLATION_START_TYPE);
269        $stream->expect(Token::NAME_TYPE, 'bar');
270        $stream->expect(Token::INTERPOLATION_END_TYPE);
271        $stream->expect(Token::INTERPOLATION_END_TYPE);
272        $stream->expect(Token::VAR_END_TYPE);
273
274        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
275        // can be executed without throwing any exceptions
276        $this->addToAssertionCount(1);
277    }
278
279    public function testStringWithNestedInterpolationsInBlock()
280    {
281        $template = '{% foo "bar #{ "foo#{bar}" }" %}';
282
283        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
284        $stream = $lexer->tokenize(new Source($template, 'index'));
285        $stream->expect(Token::BLOCK_START_TYPE);
286        $stream->expect(Token::NAME_TYPE, 'foo');
287        $stream->expect(Token::STRING_TYPE, 'bar ');
288        $stream->expect(Token::INTERPOLATION_START_TYPE);
289        $stream->expect(Token::STRING_TYPE, 'foo');
290        $stream->expect(Token::INTERPOLATION_START_TYPE);
291        $stream->expect(Token::NAME_TYPE, 'bar');
292        $stream->expect(Token::INTERPOLATION_END_TYPE);
293        $stream->expect(Token::INTERPOLATION_END_TYPE);
294        $stream->expect(Token::BLOCK_END_TYPE);
295
296        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
297        // can be executed without throwing any exceptions
298        $this->addToAssertionCount(1);
299    }
300
301    public function testOperatorEndingWithALetterAtTheEndOfALine()
302    {
303        $template = "{{ 1 and\n0}}";
304
305        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
306        $stream = $lexer->tokenize(new Source($template, 'index'));
307        $stream->expect(Token::VAR_START_TYPE);
308        $stream->expect(Token::NUMBER_TYPE, 1);
309        $stream->expect(Token::OPERATOR_TYPE, 'and');
310
311        // add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
312        // can be executed without throwing any exceptions
313        $this->addToAssertionCount(1);
314    }
315
316    /**
317     * @expectedException \Twig\Error\SyntaxError
318     * @expectedExceptionMessage Unclosed "variable" in "index" at line 3
319     */
320    public function testUnterminatedVariable()
321    {
322        $template = '
323
324{{
325
326bar
327
328
329';
330
331        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
332        $lexer->tokenize(new Source($template, 'index'));
333    }
334
335    /**
336     * @expectedException \Twig\Error\SyntaxError
337     * @expectedExceptionMessage Unclosed "block" in "index" at line 3
338     */
339    public function testUnterminatedBlock()
340    {
341        $template = '
342
343{%
344
345bar
346
347
348';
349
350        $lexer = new Lexer(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
351        $lexer->tokenize(new Source($template, 'index'));
352    }
353}
354