Chris@13
|
1 <?php declare(strict_types=1);
|
Chris@0
|
2
|
Chris@0
|
3 namespace PhpParser;
|
Chris@0
|
4
|
Chris@0
|
5 use PhpParser\Node\Expr;
|
Chris@0
|
6 use PhpParser\Node\Scalar;
|
Chris@0
|
7 use PhpParser\Node\Scalar\String_;
|
Chris@13
|
8 use PhpParser\Node\Stmt;
|
Chris@0
|
9
|
Chris@17
|
10 abstract class ParserTest extends \PHPUnit\Framework\TestCase
|
Chris@0
|
11 {
|
Chris@0
|
12 /** @returns Parser */
|
Chris@0
|
13 abstract protected function getParser(Lexer $lexer);
|
Chris@0
|
14
|
Chris@0
|
15 public function testParserThrowsSyntaxError() {
|
Chris@17
|
16 $this->expectException(Error::class);
|
Chris@17
|
17 $this->expectExceptionMessage('Syntax error, unexpected EOF on line 1');
|
Chris@0
|
18 $parser = $this->getParser(new Lexer());
|
Chris@0
|
19 $parser->parse('<?php foo');
|
Chris@0
|
20 }
|
Chris@0
|
21
|
Chris@0
|
22 public function testParserThrowsSpecialError() {
|
Chris@17
|
23 $this->expectException(Error::class);
|
Chris@17
|
24 $this->expectExceptionMessage('Cannot use foo as self because \'self\' is a special class name on line 1');
|
Chris@0
|
25 $parser = $this->getParser(new Lexer());
|
Chris@0
|
26 $parser->parse('<?php use foo as self;');
|
Chris@0
|
27 }
|
Chris@0
|
28
|
Chris@0
|
29 public function testParserThrowsLexerError() {
|
Chris@17
|
30 $this->expectException(Error::class);
|
Chris@17
|
31 $this->expectExceptionMessage('Unterminated comment on line 1');
|
Chris@0
|
32 $parser = $this->getParser(new Lexer());
|
Chris@0
|
33 $parser->parse('<?php /*');
|
Chris@0
|
34 }
|
Chris@0
|
35
|
Chris@0
|
36 public function testAttributeAssignment() {
|
Chris@13
|
37 $lexer = new Lexer([
|
Chris@13
|
38 'usedAttributes' => [
|
Chris@0
|
39 'comments', 'startLine', 'endLine',
|
Chris@0
|
40 'startTokenPos', 'endTokenPos',
|
Chris@13
|
41 ]
|
Chris@13
|
42 ]);
|
Chris@0
|
43
|
Chris@0
|
44 $code = <<<'EOC'
|
Chris@0
|
45 <?php
|
Chris@0
|
46 /** Doc comment */
|
Chris@0
|
47 function test($a) {
|
Chris@0
|
48 // Line
|
Chris@0
|
49 // Comments
|
Chris@0
|
50 echo $a;
|
Chris@0
|
51 }
|
Chris@0
|
52 EOC;
|
Chris@0
|
53 $code = canonicalize($code);
|
Chris@0
|
54
|
Chris@0
|
55 $parser = $this->getParser($lexer);
|
Chris@0
|
56 $stmts = $parser->parse($code);
|
Chris@0
|
57
|
Chris@13
|
58 /** @var Stmt\Function_ $fn */
|
Chris@0
|
59 $fn = $stmts[0];
|
Chris@13
|
60 $this->assertInstanceOf(Stmt\Function_::class, $fn);
|
Chris@13
|
61 $this->assertEquals([
|
Chris@13
|
62 'comments' => [
|
Chris@13
|
63 new Comment\Doc('/** Doc comment */', 2, 6, 1),
|
Chris@13
|
64 ],
|
Chris@0
|
65 'startLine' => 3,
|
Chris@0
|
66 'endLine' => 7,
|
Chris@0
|
67 'startTokenPos' => 3,
|
Chris@0
|
68 'endTokenPos' => 21,
|
Chris@13
|
69 ], $fn->getAttributes());
|
Chris@0
|
70
|
Chris@0
|
71 $param = $fn->params[0];
|
Chris@13
|
72 $this->assertInstanceOf(Node\Param::class, $param);
|
Chris@13
|
73 $this->assertEquals([
|
Chris@0
|
74 'startLine' => 3,
|
Chris@0
|
75 'endLine' => 3,
|
Chris@0
|
76 'startTokenPos' => 7,
|
Chris@0
|
77 'endTokenPos' => 7,
|
Chris@13
|
78 ], $param->getAttributes());
|
Chris@0
|
79
|
Chris@13
|
80 /** @var Stmt\Echo_ $echo */
|
Chris@0
|
81 $echo = $fn->stmts[0];
|
Chris@13
|
82 $this->assertInstanceOf(Stmt\Echo_::class, $echo);
|
Chris@13
|
83 $this->assertEquals([
|
Chris@13
|
84 'comments' => [
|
Chris@13
|
85 new Comment("// Line\n", 4, 49, 12),
|
Chris@13
|
86 new Comment("// Comments\n", 5, 61, 14),
|
Chris@13
|
87 ],
|
Chris@0
|
88 'startLine' => 6,
|
Chris@0
|
89 'endLine' => 6,
|
Chris@0
|
90 'startTokenPos' => 16,
|
Chris@0
|
91 'endTokenPos' => 19,
|
Chris@13
|
92 ], $echo->getAttributes());
|
Chris@0
|
93
|
Chris@0
|
94 /** @var \PhpParser\Node\Expr\Variable $var */
|
Chris@0
|
95 $var = $echo->exprs[0];
|
Chris@13
|
96 $this->assertInstanceOf(Expr\Variable::class, $var);
|
Chris@13
|
97 $this->assertEquals([
|
Chris@0
|
98 'startLine' => 6,
|
Chris@0
|
99 'endLine' => 6,
|
Chris@0
|
100 'startTokenPos' => 18,
|
Chris@0
|
101 'endTokenPos' => 18,
|
Chris@13
|
102 ], $var->getAttributes());
|
Chris@0
|
103 }
|
Chris@0
|
104
|
Chris@0
|
105 public function testInvalidToken() {
|
Chris@17
|
106 $this->expectException(\RangeException::class);
|
Chris@17
|
107 $this->expectExceptionMessage('The lexer returned an invalid token (id=999, value=foobar)');
|
Chris@0
|
108 $lexer = new InvalidTokenLexer;
|
Chris@0
|
109 $parser = $this->getParser($lexer);
|
Chris@0
|
110 $parser->parse('dummy');
|
Chris@0
|
111 }
|
Chris@0
|
112
|
Chris@0
|
113 /**
|
Chris@0
|
114 * @dataProvider provideTestExtraAttributes
|
Chris@0
|
115 */
|
Chris@0
|
116 public function testExtraAttributes($code, $expectedAttributes) {
|
Chris@17
|
117 $parser = $this->getParser(new Lexer\Emulative);
|
Chris@0
|
118 $stmts = $parser->parse("<?php $code;");
|
Chris@13
|
119 $node = $stmts[0] instanceof Stmt\Expression ? $stmts[0]->expr : $stmts[0];
|
Chris@13
|
120 $attributes = $node->getAttributes();
|
Chris@0
|
121 foreach ($expectedAttributes as $name => $value) {
|
Chris@0
|
122 $this->assertSame($value, $attributes[$name]);
|
Chris@0
|
123 }
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 public function provideTestExtraAttributes() {
|
Chris@13
|
127 return [
|
Chris@13
|
128 ['0', ['kind' => Scalar\LNumber::KIND_DEC]],
|
Chris@13
|
129 ['9', ['kind' => Scalar\LNumber::KIND_DEC]],
|
Chris@13
|
130 ['07', ['kind' => Scalar\LNumber::KIND_OCT]],
|
Chris@13
|
131 ['0xf', ['kind' => Scalar\LNumber::KIND_HEX]],
|
Chris@13
|
132 ['0XF', ['kind' => Scalar\LNumber::KIND_HEX]],
|
Chris@13
|
133 ['0b1', ['kind' => Scalar\LNumber::KIND_BIN]],
|
Chris@13
|
134 ['0B1', ['kind' => Scalar\LNumber::KIND_BIN]],
|
Chris@13
|
135 ['[]', ['kind' => Expr\Array_::KIND_SHORT]],
|
Chris@13
|
136 ['array()', ['kind' => Expr\Array_::KIND_LONG]],
|
Chris@13
|
137 ["'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
|
Chris@13
|
138 ["b'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
|
Chris@13
|
139 ["B'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
|
Chris@13
|
140 ['"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
|
Chris@13
|
141 ['b"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
|
Chris@13
|
142 ['B"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
|
Chris@13
|
143 ['"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
|
Chris@13
|
144 ['b"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
|
Chris@13
|
145 ['B"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
|
Chris@17
|
146 ["<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
147 ["<<<STR\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
148 ["<<<\"STR\"\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
149 ["b<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
150 ["B<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
151 ["<<< \t 'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
152 ["<<<'\xff'\n\xff\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => "\xff", 'docIndentation' => '']],
|
Chris@17
|
153 ["<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
154 ["b<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
155 ["B<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
156 ["<<< \t \"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
|
Chris@17
|
157 ["<<<STR\n STR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => ' ']],
|
Chris@17
|
158 ["<<<STR\n\tSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => "\t"]],
|
Chris@17
|
159 ["<<<'STR'\n Foo\n STR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => ' ']],
|
Chris@13
|
160 ["die", ['kind' => Expr\Exit_::KIND_DIE]],
|
Chris@13
|
161 ["die('done')", ['kind' => Expr\Exit_::KIND_DIE]],
|
Chris@13
|
162 ["exit", ['kind' => Expr\Exit_::KIND_EXIT]],
|
Chris@13
|
163 ["exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]],
|
Chris@13
|
164 ["?>Foo", ['hasLeadingNewline' => false]],
|
Chris@13
|
165 ["?>\nFoo", ['hasLeadingNewline' => true]],
|
Chris@13
|
166 ["namespace Foo;", ['kind' => Stmt\Namespace_::KIND_SEMICOLON]],
|
Chris@13
|
167 ["namespace Foo {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
|
Chris@13
|
168 ["namespace {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
|
Chris@17
|
169 ["(float) 5.0", ['kind' => Expr\Cast\Double::KIND_FLOAT]],
|
Chris@17
|
170 ["(double) 5.0", ['kind' => Expr\Cast\Double::KIND_DOUBLE]],
|
Chris@17
|
171 ["(real) 5.0", ['kind' => Expr\Cast\Double::KIND_REAL]],
|
Chris@17
|
172 [" ( REAL ) 5.0", ['kind' => Expr\Cast\Double::KIND_REAL]],
|
Chris@13
|
173 ];
|
Chris@0
|
174 }
|
Chris@0
|
175 }
|
Chris@0
|
176
|
Chris@13
|
177 class InvalidTokenLexer extends Lexer
|
Chris@13
|
178 {
|
Chris@13
|
179 public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {
|
Chris@0
|
180 $value = 'foobar';
|
Chris@0
|
181 return 999;
|
Chris@0
|
182 }
|
Chris@0
|
183 }
|