ParserTest.php 7.7 KB

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