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\Node\Node; 14use Twig\Node\SetNode; 15use Twig\Node\TextNode; 16use Twig\Parser; 17use Twig\Source; 18use Twig\Token; 19use Twig\TokenParser\AbstractTokenParser; 20use Twig\TokenStream; 21 22class Twig_Tests_ParserTest extends \PHPUnit\Framework\TestCase 23{ 24 /** 25 * @expectedException \Twig\Error\SyntaxError 26 */ 27 public function testSetMacroThrowsExceptionOnReservedMethods() 28 { 29 $parser = $this->getParser(); 30 $parser->setMacro('parent', $this->getMockBuilder('\Twig\Node\MacroNode')->disableOriginalConstructor()->getMock()); 31 } 32 33 /** 34 * @expectedException \Twig\Error\SyntaxError 35 * @expectedExceptionMessage Unknown "foo" tag. Did you mean "for" at line 1? 36 */ 37 public function testUnknownTag() 38 { 39 $stream = new TokenStream([ 40 new Token(Token::BLOCK_START_TYPE, '', 1), 41 new Token(Token::NAME_TYPE, 'foo', 1), 42 new Token(Token::BLOCK_END_TYPE, '', 1), 43 new Token(Token::EOF_TYPE, '', 1), 44 ]); 45 $parser = new Parser(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 46 $parser->parse($stream); 47 } 48 49 /** 50 * @expectedException \Twig\Error\SyntaxError 51 * @expectedExceptionMessage Unknown "foobar" tag at line 1. 52 */ 53 public function testUnknownTagWithoutSuggestions() 54 { 55 $stream = new TokenStream([ 56 new Token(Token::BLOCK_START_TYPE, '', 1), 57 new Token(Token::NAME_TYPE, 'foobar', 1), 58 new Token(Token::BLOCK_END_TYPE, '', 1), 59 new Token(Token::EOF_TYPE, '', 1), 60 ]); 61 $parser = new Parser(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 62 $parser->parse($stream); 63 } 64 65 /** 66 * @dataProvider getFilterBodyNodesData 67 */ 68 public function testFilterBodyNodes($input, $expected) 69 { 70 $parser = $this->getParser(); 71 72 $this->assertEquals($expected, $parser->filterBodyNodes($input)); 73 } 74 75 public function getFilterBodyNodesData() 76 { 77 return [ 78 [ 79 new Node([new TextNode(' ', 1)]), 80 new Node([]), 81 ], 82 [ 83 $input = new Node([new SetNode(false, new Node(), new Node(), 1)]), 84 $input, 85 ], 86 [ 87 $input = new Node([new SetNode(true, new Node(), new Node([new Node([new TextNode('foo', 1)])]), 1)]), 88 $input, 89 ], 90 ]; 91 } 92 93 /** 94 * @dataProvider getFilterBodyNodesDataThrowsException 95 * @expectedException \Twig\Error\SyntaxError 96 */ 97 public function testFilterBodyNodesThrowsException($input) 98 { 99 $parser = $this->getParser(); 100 101 $parser->filterBodyNodes($input); 102 } 103 104 public function getFilterBodyNodesDataThrowsException() 105 { 106 return [ 107 [new TextNode('foo', 1)], 108 [new Node([new Node([new TextNode('foo', 1)])])], 109 ]; 110 } 111 112 /** 113 * @dataProvider getFilterBodyNodesWithBOMData 114 */ 115 public function testFilterBodyNodesWithBOM($emptyNode) 116 { 117 $this->assertNull($this->getParser()->filterBodyNodes(new TextNode(\chr(0xEF).\chr(0xBB).\chr(0xBF).$emptyNode, 1))); 118 } 119 120 public function getFilterBodyNodesWithBOMData() 121 { 122 return [ 123 [' '], 124 ["\t"], 125 ["\n"], 126 ["\n\t\n "], 127 ]; 128 } 129 130 public function testParseIsReentrant() 131 { 132 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), [ 133 'autoescape' => false, 134 'optimizations' => 0, 135 ]); 136 $twig->addTokenParser(new TestTokenParser()); 137 138 $parser = new Parser($twig); 139 140 $parser->parse(new TokenStream([ 141 new Token(Token::BLOCK_START_TYPE, '', 1), 142 new Token(Token::NAME_TYPE, 'test', 1), 143 new Token(Token::BLOCK_END_TYPE, '', 1), 144 new Token(Token::VAR_START_TYPE, '', 1), 145 new Token(Token::NAME_TYPE, 'foo', 1), 146 new Token(Token::VAR_END_TYPE, '', 1), 147 new Token(Token::EOF_TYPE, '', 1), 148 ])); 149 150 $this->assertNull($parser->getParent()); 151 } 152 153 public function testGetVarName() 154 { 155 $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), [ 156 'autoescape' => false, 157 'optimizations' => 0, 158 ]); 159 160 $twig->parse($twig->tokenize(new Source(<<<EOF 161{% from _self import foo %} 162 163{% macro foo() %} 164 {{ foo }} 165{% endmacro %} 166EOF 167 , 'index'))); 168 169 // The getVarName() must not depend on the template loaders, 170 // If this test does not throw any exception, that's good. 171 // see https://github.com/symfony/symfony/issues/4218 172 $this->addToAssertionCount(1); 173 } 174 175 protected function getParser() 176 { 177 $parser = new TestParser(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock())); 178 $parser->setParent(new Node()); 179 $parser->stream = new TokenStream([]); 180 181 return $parser; 182 } 183} 184 185class TestParser extends Parser 186{ 187 public $stream; 188 189 public function filterBodyNodes(Twig_NodeInterface $node) 190 { 191 return parent::filterBodyNodes($node); 192 } 193} 194 195class TestTokenParser extends AbstractTokenParser 196{ 197 public function parse(Token $token) 198 { 199 // simulate the parsing of another template right in the middle of the parsing of the current template 200 $this->parser->parse(new TokenStream([ 201 new Token(Token::BLOCK_START_TYPE, '', 1), 202 new Token(Token::NAME_TYPE, 'extends', 1), 203 new Token(Token::STRING_TYPE, 'base', 1), 204 new Token(Token::BLOCK_END_TYPE, '', 1), 205 new Token(Token::EOF_TYPE, '', 1), 206 ])); 207 208 $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); 209 210 return new Node([]); 211 } 212 213 public function getTag() 214 { 215 return 'test'; 216 } 217} 218