ParserTest.php 7.5 KB

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