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);
16 public function testParserThrowsSyntaxError() {
17 $this->expectException(Error::class);
18 $this->expectExceptionMessage('Syntax error, unexpected EOF on line 1');
19 $parser = $this->getParser(new Lexer());
20 $parser->parse('<?php foo');
23 public function testParserThrowsSpecialError() {
24 $this->expectException(Error::class);
25 $this->expectExceptionMessage('Cannot use foo as self because \'self\' is a special class name on line 1');
26 $parser = $this->getParser(new Lexer());
27 $parser->parse('<?php use foo as self;');
30 public function testParserThrowsLexerError() {
31 $this->expectException(Error::class);
32 $this->expectExceptionMessage('Unterminated comment on line 1');
33 $parser = $this->getParser(new Lexer());
34 $parser->parse('<?php /*');
37 public function testAttributeAssignment() {
40 'comments', 'startLine', 'endLine',
41 'startTokenPos', 'endTokenPos',
54 $code = canonicalize($code);
56 $parser = $this->getParser($lexer);
57 $stmts = $parser->parse($code);
59 /** @var Stmt\Function_ $fn */
61 $this->assertInstanceOf(Stmt\Function_::class, $fn);
64 new Comment\Doc('/** Doc comment */', 2, 6, 1),
70 ], $fn->getAttributes());
72 $param = $fn->params[0];
73 $this->assertInstanceOf(Node\Param::class, $param);
79 ], $param->getAttributes());
81 /** @var Stmt\Echo_ $echo */
82 $echo = $fn->stmts[0];
83 $this->assertInstanceOf(Stmt\Echo_::class, $echo);
86 new Comment("// Line\n", 4, 49, 12),
87 new Comment("// Comments\n", 5, 61, 14),
91 'startTokenPos' => 16,
93 ], $echo->getAttributes());
95 /** @var \PhpParser\Node\Expr\Variable $var */
96 $var = $echo->exprs[0];
97 $this->assertInstanceOf(Expr\Variable::class, $var);
101 'startTokenPos' => 18,
103 ], $var->getAttributes());
106 public function testInvalidToken() {
107 $this->expectException(\RangeException::class);
108 $this->expectExceptionMessage('The lexer returned an invalid token (id=999, value=foobar)');
109 $lexer = new InvalidTokenLexer;
110 $parser = $this->getParser($lexer);
111 $parser->parse('dummy');
115 * @dataProvider provideTestExtraAttributes
117 public function testExtraAttributes($code, $expectedAttributes) {
118 $parser = $this->getParser(new Lexer\Emulative);
119 $stmts = $parser->parse("<?php $code;");
120 $node = $stmts[0] instanceof Stmt\Expression ? $stmts[0]->expr : $stmts[0];
121 $attributes = $node->getAttributes();
122 foreach ($expectedAttributes as $name => $value) {
123 $this->assertSame($value, $attributes[$name]);
127 public function provideTestExtraAttributes() {
129 ['0', ['kind' => Scalar\LNumber::KIND_DEC]],
130 ['9', ['kind' => Scalar\LNumber::KIND_DEC]],
131 ['07', ['kind' => Scalar\LNumber::KIND_OCT]],
132 ['0xf', ['kind' => Scalar\LNumber::KIND_HEX]],
133 ['0XF', ['kind' => Scalar\LNumber::KIND_HEX]],
134 ['0b1', ['kind' => Scalar\LNumber::KIND_BIN]],
135 ['0B1', ['kind' => Scalar\LNumber::KIND_BIN]],
136 ['[]', ['kind' => Expr\Array_::KIND_SHORT]],
137 ['array()', ['kind' => Expr\Array_::KIND_LONG]],
138 ["'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
139 ["b'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
140 ["B'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
141 ['"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
142 ['b"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
143 ['B"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
144 ['"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
145 ['b"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
146 ['B"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
147 ["<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
148 ["<<<STR\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
149 ["<<<\"STR\"\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
150 ["b<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
151 ["B<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
152 ["<<< \t 'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
153 ["<<<'\xff'\n\xff\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => "\xff", 'docIndentation' => '']],
154 ["<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
155 ["b<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
156 ["B<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
157 ["<<< \t \"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
158 ["<<<STR\n STR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => ' ']],
159 ["<<<STR\n\tSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => "\t"]],
160 ["<<<'STR'\n Foo\n STR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => ' ']],
161 ["die", ['kind' => Expr\Exit_::KIND_DIE]],
162 ["die('done')", ['kind' => Expr\Exit_::KIND_DIE]],
163 ["exit", ['kind' => Expr\Exit_::KIND_EXIT]],
164 ["exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]],
165 ["?>Foo", ['hasLeadingNewline' => false]],
166 ["?>\nFoo", ['hasLeadingNewline' => true]],
167 ["namespace Foo;", ['kind' => Stmt\Namespace_::KIND_SEMICOLON]],
168 ["namespace Foo {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
169 ["namespace {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
174 class InvalidTokenLexer extends Lexer
176 public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {