ParserTest.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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 */',
  54. 2, 6, 1, 2, 23, 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",
  75. 4, 49, 12, 4, 55, 12),
  76. new Comment("// Comments",
  77. 5, 61, 14, 5, 71, 14),
  78. ],
  79. 'startLine' => 6,
  80. 'endLine' => 6,
  81. 'startTokenPos' => 16,
  82. 'endTokenPos' => 19,
  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. ], $var->getAttributes());
  93. }
  94. public function testInvalidToken() {
  95. $this->expectException(\RangeException::class);
  96. $this->expectExceptionMessage('The lexer returned an invalid token (id=999, value=foobar)');
  97. $lexer = new InvalidTokenLexer;
  98. $parser = $this->getParser($lexer);
  99. $parser->parse('dummy');
  100. }
  101. /**
  102. * @dataProvider provideTestExtraAttributes
  103. */
  104. public function testExtraAttributes($code, $expectedAttributes) {
  105. $parser = $this->getParser(new Lexer\Emulative);
  106. $stmts = $parser->parse("<?php $code;");
  107. $node = $stmts[0] instanceof Stmt\Expression ? $stmts[0]->expr : $stmts[0];
  108. $attributes = $node->getAttributes();
  109. foreach ($expectedAttributes as $name => $value) {
  110. $this->assertSame($value, $attributes[$name]);
  111. }
  112. }
  113. public function provideTestExtraAttributes() {
  114. return [
  115. ['0', ['kind' => Scalar\LNumber::KIND_DEC]],
  116. ['9', ['kind' => Scalar\LNumber::KIND_DEC]],
  117. ['07', ['kind' => Scalar\LNumber::KIND_OCT]],
  118. ['0xf', ['kind' => Scalar\LNumber::KIND_HEX]],
  119. ['0XF', ['kind' => Scalar\LNumber::KIND_HEX]],
  120. ['0b1', ['kind' => Scalar\LNumber::KIND_BIN]],
  121. ['0B1', ['kind' => Scalar\LNumber::KIND_BIN]],
  122. ['0o7', ['kind' => Scalar\LNumber::KIND_OCT]],
  123. ['0O7', ['kind' => Scalar\LNumber::KIND_OCT]],
  124. ['[]', ['kind' => Expr\Array_::KIND_SHORT]],
  125. ['array()', ['kind' => Expr\Array_::KIND_LONG]],
  126. ["'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
  127. ["b'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
  128. ["B'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]],
  129. ['"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
  130. ['b"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
  131. ['B"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
  132. ['"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
  133. ['b"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
  134. ['B"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]],
  135. ["<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  136. ["<<<STR\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  137. ["<<<\"STR\"\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  138. ["b<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  139. ["B<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  140. ["<<< \t 'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  141. ["<<<'\xff'\n\xff\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => "\xff", 'docIndentation' => '']],
  142. ["<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  143. ["b<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  144. ["B<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  145. ["<<< \t \"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => '']],
  146. ["<<<STR\n STR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => ' ']],
  147. ["<<<STR\n\tSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR', 'docIndentation' => "\t"]],
  148. ["<<<'STR'\n Foo\n STR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR', 'docIndentation' => ' ']],
  149. ["die", ['kind' => Expr\Exit_::KIND_DIE]],
  150. ["die('done')", ['kind' => Expr\Exit_::KIND_DIE]],
  151. ["exit", ['kind' => Expr\Exit_::KIND_EXIT]],
  152. ["exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]],
  153. ["?>Foo", ['hasLeadingNewline' => false]],
  154. ["?>\nFoo", ['hasLeadingNewline' => true]],
  155. ["namespace Foo;", ['kind' => Stmt\Namespace_::KIND_SEMICOLON]],
  156. ["namespace Foo {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
  157. ["namespace {}", ['kind' => Stmt\Namespace_::KIND_BRACED]],
  158. ["(float) 5.0", ['kind' => Expr\Cast\Double::KIND_FLOAT]],
  159. ["(double) 5.0", ['kind' => Expr\Cast\Double::KIND_DOUBLE]],
  160. ["(real) 5.0", ['kind' => Expr\Cast\Double::KIND_REAL]],
  161. [" ( REAL ) 5.0", ['kind' => Expr\Cast\Double::KIND_REAL]],
  162. ];
  163. }
  164. }
  165. class InvalidTokenLexer extends Lexer
  166. {
  167. public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {
  168. $value = 'foobar';
  169. return 999;
  170. }
  171. }