ParserTestAbstract.php 9.2 KB

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