1 <?php declare(strict_types=1);
5 use PhpParser\Node\Expr;
6 use PhpParser\Node\Scalar;
7 use PhpParser\Node\Scalar\String_;
8 use PhpParser\Node\Stmt;
9 use PHPUnit\Framework\TestCase;
11 abstract class ParserTest extends TestCase
13 /** @returns Parser */
14 abstract protected function getParser(Lexer $lexer);
17 * @expectedException \PhpParser\Error
18 * @expectedExceptionMessage Syntax error, unexpected EOF on line 1
20 public function testParserThrowsSyntaxError() {
21 $parser = $this->getParser(new Lexer());
22 $parser->parse('<?php foo');
26 * @expectedException \PhpParser\Error
27 * @expectedExceptionMessage Cannot use foo as self because 'self' is a special class name on line 1
29 public function testParserThrowsSpecialError() {
30 $parser = $this->getParser(new Lexer());
31 $parser->parse('<?php use foo as self;');
35 * @expectedException \PhpParser\Error
36 * @expectedExceptionMessage Unterminated comment on line 1
38 public function testParserThrowsLexerError() {
39 $parser = $this->getParser(new Lexer());
40 $parser->parse('<?php /*');
43 public function testAttributeAssignment() {
46 'comments', 'startLine', 'endLine',
47 'startTokenPos', 'endTokenPos',
60 $code = canonicalize($code);
62 $parser = $this->getParser($lexer);
63 $stmts = $parser->parse($code);
65 /** @var Stmt\Function_ $fn */
67 $this->assertInstanceOf(Stmt\Function_::class, $fn);
70 new Comment\Doc('/** Doc comment */', 2, 6, 1),
76 ], $fn->getAttributes());
78 $param = $fn->params[0];
79 $this->assertInstanceOf(Node\Param::class, $param);
85 ], $param->getAttributes());
87 /** @var Stmt\Echo_ $echo */
88 $echo = $fn->stmts[0];
89 $this->assertInstanceOf(Stmt\Echo_::class, $echo);
92 new Comment("// Line\n", 4, 49, 12),
93 new Comment("// Comments\n", 5, 61, 14),
97 'startTokenPos' => 16,
99 ], $echo->getAttributes());
101 /** @var \PhpParser\Node\Expr\Variable $var */
102 $var = $echo->exprs[0];
103 $this->assertInstanceOf(Expr\Variable::class, $var);
104 $this->assertEquals([
107 'startTokenPos' => 18,
109 ], $var->getAttributes());
113 * @expectedException \RangeException
114 * @expectedExceptionMessage The lexer returned an invalid token (id=999, value=foobar)
116 public function testInvalidToken() {
117 $lexer = new InvalidTokenLexer;
118 $parser = $this->getParser($lexer);
119 $parser->parse('dummy');
123 * @dataProvider provideTestExtraAttributes
125 public function testExtraAttributes($code, $expectedAttributes) {
126 $parser = $this->getParser(new Lexer);
127 $stmts = $parser->parse("<?php $code;");
128 $node = $stmts[0] instanceof Stmt\Expression ? $stmts[0]->expr : $stmts[0];
129 $attributes = $node->getAttributes();
130 foreach ($expectedAttributes as $name => $value) {
131 $this->assertSame($value, $attributes[$name]);
135 public function provideTestExtraAttributes() {
137 ['0', ['kind' => Scalar\LNumber::KIND_DEC]],
138 ['9', ['kind' => Scalar\LNumber::KIND_DEC]],
139 ['07', ['kind' => Scalar\LNumber::KIND_OCT]],
140 ['0xf', ['kind' => Scalar\LNumber::KIND_HEX]],
141 ['0XF', ['kind' => Scalar\LNumber::KIND_HEX]],
142 ['0b1', ['kind' => Scalar\LNumber::KIND_BIN]],
143 ['0B1', ['kind' => Scalar\LNumber::KIND_BIN]],
144 ['[]', ['kind' => Expr\Array_::KIND_SHORT]],
145 ['array()', ['kind' => Expr\Array_::KIND_LONG]],
146 ["'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
147 ["b'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
148 ["B'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
149 ['"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
150 ['b"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
151 ['B"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
152 ['"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
153 ['b"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
154 ['B"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
155 ["<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']],
156 ["<<<STR\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']],
157 ["<<<\"STR\"\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']],
158 ["b<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']],
159 ["B<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']],
160 ["<<< \t 'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']],
161 ["<<<'\xff'\n\xff\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => "\xff"]],
162 ["<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']],
163 ["b<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']],
164 ["B<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']],
165 ["<<< \t \"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']],
166 ["die", ['kind' => Expr\Exit_::KIND_DIE]],
167 ["die('done')", ['kind' => Expr\Exit_::KIND_DIE]],
168 ["exit", ['kind' => Expr\Exit_::KIND_EXIT]],
169 ["exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]],
170 ["?>Foo", ['hasLeadingNewline' => false]],
171 ["?>\nFoo", ['hasLeadingNewline' => true]],
172 ["namespace Foo;", ['kind' => Stmt\Namespace_::KIND_SEMICOLON]],
173 ["namespace Foo {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
174 ["namespace {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
179 class InvalidTokenLexer extends Lexer
181 public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {