update v 1.0.7.5

This commit is contained in:
Sujit Prasad
2016-06-13 20:41:55 +05:30
parent aa9786d829
commit 283d97e3ea
5078 changed files with 339851 additions and 175995 deletions

View File

@@ -8,32 +8,9 @@ use PhpParser\Node\Scalar;
/* The autoloader is already active at this point, so we only check effects here. */
class AutoloaderTest extends \PHPUnit_Framework_TestCase {
public function testLegacyNames() {
$lexer = new \PHPParser_Lexer;
$parser = new \PHPParser_Parser($lexer);
$prettyPrinter = new \PHPParser_PrettyPrinter_Default;
$this->assertInstanceof('PhpParser\Lexer', $lexer);
$this->assertInstanceof('PhpParser\Parser', $parser);
$this->assertInstanceof('PhpParser\PrettyPrinter\Standard', $prettyPrinter);
}
public function testPhp7ReservedNames() {
if (version_compare(PHP_VERSION, '7.0-dev', '>=')) {
$this->markTestSkipped('Cannot create aliases to reserved names on PHP 7');
}
$this->assertTrue(new Expr\Cast\Bool_(new Expr\Variable('foo')) instanceof Expr\Cast\Bool);
$this->assertTrue(new Expr\Cast\Int_(new Expr\Variable('foo')) instanceof Expr\Cast\Int);
$this->assertInstanceof('PhpParser\Node\Expr\Cast\Object_', new Expr\Cast\Object(new Expr\Variable('foo')));
$this->assertInstanceof('PhpParser\Node\Expr\Cast\String_', new Expr\Cast\String(new Expr\Variable('foo')));
$this->assertInstanceof('PhpParser\Node\Scalar\String_', new Scalar\String('foobar'));
}
public function testClassExists() {
$this->assertTrue(class_exists('PhpParser\NodeVisitorAbstract'));
$this->assertTrue(class_exists('PHPParser_NodeVisitor_NameResolver'));
$this->assertFalse(class_exists('PHPParser_NodeVisitor_NameResolver'));
$this->assertFalse(class_exists('PhpParser\FooBar'));
$this->assertFalse(class_exists('PHPParser_FooBar'));

View File

@@ -76,6 +76,16 @@ class FunctionTest extends \PHPUnit_Framework_TestCase
)), $node);
}
public function testReturnType() {
$node = $this->createFunctionBuilder('test')
->setReturnType('bool')
->getNode();
$this->assertEquals(new Stmt\Function_('test', array(
'returnType' => 'bool'
), array()), $node);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Expected parameter node, got "Name"

View File

@@ -120,6 +120,15 @@ class MethodTest extends \PHPUnit_Framework_TestCase
)), $node);
}
public function testReturnType() {
$node = $this->createMethodBuilder('test')
->setReturnType('bool')
->getNode();
$this->assertEquals(new Stmt\ClassMethod('test', array(
'returnType' => 'bool'
), array()), $node);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Cannot add statements to an abstract method

View File

@@ -0,0 +1,69 @@
<?php
namespace PhpParser;
use PhpParser\Comment;
require_once __DIR__ . '/CodeTestAbstract.php';
class CodeParsingTest extends CodeTestAbstract
{
/**
* @dataProvider provideTestParse
*/
public function testParse($name, $code, $expected, $mode) {
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
)));
$parser5 = new Parser\Php5($lexer, array(
'throwOnError' => false,
));
$parser7 = new Parser\Php7($lexer, array(
'throwOnError' => false,
));
$output5 = $this->getParseOutput($parser5, $code);
$output7 = $this->getParseOutput($parser7, $code);
if ($mode === 'php5') {
$this->assertSame($expected, $output5, $name);
$this->assertNotSame($expected, $output7, $name);
} else if ($mode === 'php7') {
$this->assertNotSame($expected, $output5, $name);
$this->assertSame($expected, $output7, $name);
} else {
$this->assertSame($expected, $output5, $name);
$this->assertSame($expected, $output7, $name);
}
}
private function getParseOutput(Parser $parser, $code) {
$stmts = $parser->parse($code);
$errors = $parser->getErrors();
$output = '';
foreach ($errors as $error) {
$output .= $this->formatErrorMessage($error, $code) . "\n";
}
if (null !== $stmts) {
$dumper = new NodeDumper(['dumpComments' => true]);
$output .= $dumper->dump($stmts);
}
return canonicalize($output);
}
public function provideTestParse() {
return $this->getTests(__DIR__ . '/../code/parser', 'test');
}
private function formatErrorMessage(Error $e, $code) {
if ($e->hasColumnInfo()) {
return $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
} else {
return $e->getMessage();
}
}
}

View File

@@ -13,41 +13,48 @@ abstract class CodeTestAbstract extends \PHPUnit_Framework_TestCase
foreach ($it as $file) {
$fileName = realpath($file->getPathname());
$fileContents = file_get_contents($fileName);
$fileContents = canonicalize($fileContents);
// evaluate @@{expr}@@ expressions
$fileContents = preg_replace_callback(
'/@@\{(.*?)\}@@/',
array($this, 'evalCallback'),
function($matches) {
return eval('return ' . $matches[1] . ';');
},
$fileContents
);
// parse sections
$parts = array_map('trim', explode('-----', $fileContents));
$parts = preg_split("/\n-----(?:\n|$)/", $fileContents);
// first part is the name
$name = array_shift($parts) . ' (' . $fileName . ')';
$shortName = basename($fileName, '.test');
// multiple sections possible with always two forming a pair
foreach (array_chunk($parts, 2) as $chunk) {
$tests[] = array($name, $chunk[0], $chunk[1]);
$chunks = array_chunk($parts, 2);
foreach ($chunks as $i => $chunk) {
$dataSetName = $shortName . (count($chunks) > 1 ? '#' . $i : '');
list($expected, $mode) = $this->extractMode($chunk[1]);
$tests[$dataSetName] = array($name, $chunk[0], $expected, $mode);
}
}
return $tests;
}
protected function evalCallback($matches) {
return eval('return ' . $matches[1] . ';');
}
private function extractMode($expected) {
$firstNewLine = strpos($expected, "\n");
if (false === $firstNewLine) {
$firstNewLine = strlen($expected);
}
protected function canonicalize($str) {
// trim from both sides
$str = trim($str);
$firstLine = substr($expected, 0, $firstNewLine);
if (0 !== strpos($firstLine, '!!')) {
return [$expected, null];
}
// normalize EOL to \n
$str = str_replace(array("\r\n", "\r"), "\n", $str);
// trim right side of all lines
return implode("\n", array_map('rtrim', explode("\n", $str)));
$expected = (string) substr($expected, $firstNewLine + 1);
return [$expected, substr($firstLine, 2)];
}
}

View File

@@ -5,11 +5,12 @@ namespace PhpParser;
class CommentTest extends \PHPUnit_Framework_TestCase
{
public function testGetSet() {
$comment = new Comment('/* Some comment */', 1);
$comment = new Comment('/* Some comment */', 1, 10);
$this->assertSame('/* Some comment */', $comment->getText());
$this->assertSame('/* Some comment */', (string) $comment);
$this->assertSame(1, $comment->getLine());
$this->assertSame(10, $comment->getFilePos());
$comment->setText('/* Some other comment */');
$comment->setLine(10);
@@ -58,6 +59,14 @@ class CommentTest extends \PHPUnit_Framework_TestCase
'/* Some text.
More text.
Even more text. */'
),
array(
'/* Some text.
More text.
Indented text. */',
'/* Some text.
More text.
Indented text. */',
),
// invalid comment -> no reformatting
array(

View File

@@ -3,7 +3,7 @@
namespace PhpParser\Lexer;
use PhpParser\LexerTest;
use PhpParser\Parser;
use PhpParser\Parser\Tokens;
require_once __DIR__ . '/../LexerTest.php';
@@ -31,28 +31,28 @@ class EmulativeTest extends LexerTest
$lexer = $this->getLexer();
$lexer->startLexing('<?php ->' . $keyword);
$this->assertSame(Parser::T_OBJECT_OPERATOR, $lexer->getNextToken());
$this->assertSame(Parser::T_STRING, $lexer->getNextToken());
$this->assertSame(Tokens::T_OBJECT_OPERATOR, $lexer->getNextToken());
$this->assertSame(Tokens::T_STRING, $lexer->getNextToken());
$this->assertSame(0, $lexer->getNextToken());
}
public function provideTestReplaceKeywords() {
return array(
// PHP 5.5
array('finally', Parser::T_FINALLY),
array('yield', Parser::T_YIELD),
array('finally', Tokens::T_FINALLY),
array('yield', Tokens::T_YIELD),
// PHP 5.4
array('callable', Parser::T_CALLABLE),
array('insteadof', Parser::T_INSTEADOF),
array('trait', Parser::T_TRAIT),
array('__TRAIT__', Parser::T_TRAIT_C),
array('callable', Tokens::T_CALLABLE),
array('insteadof', Tokens::T_INSTEADOF),
array('trait', Tokens::T_TRAIT),
array('__TRAIT__', Tokens::T_TRAIT_C),
// PHP 5.3
array('__DIR__', Parser::T_DIR),
array('goto', Parser::T_GOTO),
array('namespace', Parser::T_NAMESPACE),
array('__NAMESPACE__', Parser::T_NS_C),
array('__DIR__', Tokens::T_DIR),
array('goto', Tokens::T_GOTO),
array('namespace', Tokens::T_NAMESPACE),
array('__NAMESPACE__', Tokens::T_NS_C),
);
}
@@ -80,7 +80,7 @@ class EmulativeTest extends LexerTest
$lexer = $this->getLexer();
$lexer->startLexing('<?php ' . $stringifiedToken);
$this->assertSame(Parser::T_CONSTANT_ENCAPSED_STRING, $lexer->getNextToken($text));
$this->assertSame(Tokens::T_CONSTANT_ENCAPSED_STRING, $lexer->getNextToken($text));
$this->assertSame($stringifiedToken, $text);
$this->assertSame(0, $lexer->getNextToken());
}
@@ -88,44 +88,44 @@ class EmulativeTest extends LexerTest
public function provideTestLexNewFeatures() {
return array(
array('yield from', array(
array(Parser::T_YIELD_FROM, 'yield from'),
array(Tokens::T_YIELD_FROM, 'yield from'),
)),
array("yield\r\nfrom", array(
array(Parser::T_YIELD_FROM, "yield\r\nfrom"),
array(Tokens::T_YIELD_FROM, "yield\r\nfrom"),
)),
array('...', array(
array(Parser::T_ELLIPSIS, '...'),
array(Tokens::T_ELLIPSIS, '...'),
)),
array('**', array(
array(Parser::T_POW, '**'),
array(Tokens::T_POW, '**'),
)),
array('**=', array(
array(Parser::T_POW_EQUAL, '**='),
array(Tokens::T_POW_EQUAL, '**='),
)),
array('??', array(
array(Parser::T_COALESCE, '??'),
array(Tokens::T_COALESCE, '??'),
)),
array('<=>', array(
array(Parser::T_SPACESHIP, '<=>'),
array(Tokens::T_SPACESHIP, '<=>'),
)),
array('0b1010110', array(
array(Parser::T_LNUMBER, '0b1010110'),
array(Tokens::T_LNUMBER, '0b1010110'),
)),
array('0b1011010101001010110101010010101011010101010101101011001110111100', array(
array(Parser::T_DNUMBER, '0b1011010101001010110101010010101011010101010101101011001110111100'),
array(Tokens::T_DNUMBER, '0b1011010101001010110101010010101011010101010101101011001110111100'),
)),
array('\\', array(
array(Parser::T_NS_SEPARATOR, '\\'),
array(Tokens::T_NS_SEPARATOR, '\\'),
)),
array("<<<'NOWDOC'\nNOWDOC;\n", array(
array(Parser::T_START_HEREDOC, "<<<'NOWDOC'\n"),
array(Parser::T_END_HEREDOC, 'NOWDOC'),
array(Tokens::T_START_HEREDOC, "<<<'NOWDOC'\n"),
array(Tokens::T_END_HEREDOC, 'NOWDOC'),
array(ord(';'), ';'),
)),
array("<<<'NOWDOC'\nFoobar\nNOWDOC;\n", array(
array(Parser::T_START_HEREDOC, "<<<'NOWDOC'\n"),
array(Parser::T_ENCAPSED_AND_WHITESPACE, "Foobar\n"),
array(Parser::T_END_HEREDOC, 'NOWDOC'),
array(Tokens::T_START_HEREDOC, "<<<'NOWDOC'\n"),
array(Tokens::T_ENCAPSED_AND_WHITESPACE, "Foobar\n"),
array(Tokens::T_END_HEREDOC, 'NOWDOC'),
array(ord(';'), ';'),
)),
);

View File

@@ -2,6 +2,8 @@
namespace PhpParser;
use PhpParser\Parser\Tokens;
class LexerTest extends \PHPUnit_Framework_TestCase
{
/* To allow overwriting in parent class */
@@ -61,7 +63,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array(),
array(
array(
Parser::T_STRING, 'tokens',
Tokens::T_STRING, 'tokens',
array('startLine' => 1), array('endLine' => 1)
),
array(
@@ -69,7 +71,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array('startLine' => 1), array('endLine' => 1)
),
array(
Parser::T_INLINE_HTML, 'plaintext',
Tokens::T_INLINE_HTML, 'plaintext',
array('startLine' => 1), array('endLine' => 1)
),
)
@@ -84,14 +86,16 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array('startLine' => 2), array('endLine' => 2)
),
array(
Parser::T_STRING, 'token',
Tokens::T_STRING, 'token',
array('startLine' => 2), array('endLine' => 2)
),
array(
ord('$'), '$',
array(
'startLine' => 3,
'comments' => array(new Comment\Doc('/** doc' . "\n" . 'comment */', 2))
'comments' => array(
new Comment\Doc('/** doc' . "\n" . 'comment */', 2, 14),
)
),
array('endLine' => 3)
),
@@ -103,14 +107,14 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array(),
array(
array(
Parser::T_STRING, 'token',
Tokens::T_STRING, 'token',
array(
'startLine' => 2,
'comments' => array(
new Comment('/* comment */', 1),
new Comment('// comment' . "\n", 1),
new Comment\Doc('/** docComment 1 */', 2),
new Comment\Doc('/** docComment 2 */', 2),
new Comment('/* comment */', 1, 6),
new Comment('// comment' . "\n", 1, 20),
new Comment\Doc('/** docComment 1 */', 2, 31),
new Comment\Doc('/** docComment 2 */', 2, 50),
),
),
array('endLine' => 2)
@@ -123,7 +127,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array(),
array(
array(
Parser::T_CONSTANT_ENCAPSED_STRING, '"foo' . "\n" . 'bar"',
Tokens::T_CONSTANT_ENCAPSED_STRING, '"foo' . "\n" . 'bar"',
array('startLine' => 1), array('endLine' => 2)
),
)
@@ -134,7 +138,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array('usedAttributes' => array('startFilePos', 'endFilePos')),
array(
array(
Parser::T_CONSTANT_ENCAPSED_STRING, '"a"',
Tokens::T_CONSTANT_ENCAPSED_STRING, '"a"',
array('startFilePos' => 6), array('endFilePos' => 8)
),
array(
@@ -142,7 +146,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array('startFilePos' => 9), array('endFilePos' => 9)
),
array(
Parser::T_CONSTANT_ENCAPSED_STRING, '"b"',
Tokens::T_CONSTANT_ENCAPSED_STRING, '"b"',
array('startFilePos' => 18), array('endFilePos' => 20)
),
array(
@@ -157,7 +161,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array('usedAttributes' => array('startTokenPos', 'endTokenPos')),
array(
array(
Parser::T_CONSTANT_ENCAPSED_STRING, '"a"',
Tokens::T_CONSTANT_ENCAPSED_STRING, '"a"',
array('startTokenPos' => 1), array('endTokenPos' => 1)
),
array(
@@ -165,7 +169,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array('startTokenPos' => 2), array('endTokenPos' => 2)
),
array(
Parser::T_CONSTANT_ENCAPSED_STRING, '"b"',
Tokens::T_CONSTANT_ENCAPSED_STRING, '"b"',
array('startTokenPos' => 5), array('endTokenPos' => 5)
),
array(
@@ -180,7 +184,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array('usedAttributes' => array()),
array(
array(
Parser::T_VARIABLE, '$bar',
Tokens::T_VARIABLE, '$bar',
array(), array()
),
array(
@@ -199,7 +203,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
$lexer = $this->getLexer();
$lexer->startLexing($code);
while (Parser::T_HALT_COMPILER !== $lexer->getNextToken());
while (Tokens::T_HALT_COMPILER !== $lexer->getNextToken());
$this->assertSame($remaining, $lexer->handleHaltCompiler());
$this->assertSame(0, $lexer->getNextToken());
@@ -223,7 +227,7 @@ class LexerTest extends \PHPUnit_Framework_TestCase
$lexer = $this->getLexer();
$lexer->startLexing('<?php ... __halt_compiler invalid ();');
while (Parser::T_HALT_COMPILER !== $lexer->getNextToken());
while (Tokens::T_HALT_COMPILER !== $lexer->getNextToken());
$lexer->handleHaltCompiler();
}

View File

@@ -95,6 +95,38 @@ class NameTest extends \PHPUnit_Framework_TestCase
$this->assertSame('foo\bar\bar\foo', $name->toString());
}
public function testSlice() {
$name = new Name('foo\bar');
$this->assertEquals(new Name('foo\bar'), $name->slice(0));
$this->assertEquals(new Name('bar'), $name->slice(1));
$this->assertEquals(new Name([]), $name->slice(2));
}
/**
* @expectedException \OutOfBoundsException
* @expectedExceptionMessage Offset 4 is out of bounds
*/
public function testSliceException() {
(new Name('foo\bar\baz'))->slice(4);
}
public function testConcat() {
$this->assertEquals(new Name('foo\bar\baz'), Name::concat('foo', 'bar\baz'));
$this->assertEquals(
new Name\FullyQualified('foo\bar'),
Name\FullyQualified::concat(['foo'], new Name('bar'))
);
$attributes = ['foo' => 'bar'];
$this->assertEquals(
new Name\Relative('foo\bar\baz', $attributes),
Name\Relative::concat(new Name\FullyQualified('foo\bar'), 'baz', $attributes)
);
$this->assertEquals(new Name('foo'), Name::concat([], 'foo'));
$this->assertEquals(new Name([]), Name::concat([], []));
}
public function testIs() {
$name = new Name('foo');
$this->assertTrue ($name->isUnqualified());

View File

@@ -7,7 +7,7 @@ class DummyNode extends NodeAbstract {
public $subNode2;
public function __construct($subNode1, $subNode2, $attributes) {
parent::__construct(null, $attributes);
parent::__construct($attributes);
$this->subNode1 = $subNode1;
$this->subNode2 = $subNode2;
}
@@ -33,25 +33,11 @@ class NodeAbstractTest extends \PHPUnit_Framework_TestCase
),
);
$node1 = $this->getMockForAbstractClass(
'PhpParser\NodeAbstract',
array(
array(
'subNode1' => 'value1',
'subNode2' => 'value2',
),
$attributes
),
'PhpParser_Node_Dummy'
);
$node1->notSubNode = 'value3';
$node2 = new DummyNode('value1', 'value2', $attributes);
$node2->notSubNode = 'value3';
$node = new DummyNode('value1', 'value2', $attributes);
$node->notSubNode = 'value3';
return array(
array($attributes, $node1),
array($attributes, $node2),
array($attributes, $node),
);
}

View File

@@ -185,19 +185,34 @@ class NodeTraverserTest extends \PHPUnit_Framework_TestCase
$this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal');
}
public function testCloneNodesByDefault() {
public function testCloneNodes() {
$stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));
$traverser = new NodeTraverser;
$traverser = new NodeTraverser(true);
$this->assertNotSame($stmts, $traverser->traverse($stmts));
}
public function testCloneNodesDisabled() {
public function testNoCloneNodesByDefault() {
$stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar'))));
$traverser = new NodeTraverser(false);
$traverser = new NodeTraverser;
$this->assertSame($stmts, $traverser->traverse($stmts));
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage leaveNode() may only return an array if the parent structure is an array
*/
public function testReplaceByArrayOnlyAllowedIfParentIsArray() {
$stmts = array(new Node\Expr\UnaryMinus(new Node\Scalar\LNumber(42)));
$visitor = $this->getMock('PhpParser\NodeVisitor');
$visitor->method('leaveNode')->willReturn(array(new Node\Scalar\DNumber(42.0)));
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->traverse($stmts);
}
}

View File

@@ -76,6 +76,25 @@ namespace Bar {
BAR\FOO;
BAZ\FOO;
}
namespace Baz {
use A\T\{B\C, D\E};
use function X\T\{b\c, d\e};
use const Y\T\{B\C, D\E};
use Z\T\{G, function f, const K};
new C;
new E;
new C\D;
new E\F;
new G;
c();
e();
f();
C;
E;
K;
}
EOC;
$expectedCode = <<<'EOC'
namespace Foo {
@@ -127,9 +146,26 @@ namespace Bar {
\foo\FOO;
\Bar\BAZ\FOO;
}
namespace Baz {
use A\T\{B\C, D\E};
use function X\T\{b\c, d\e};
use const Y\T\{B\C, D\E};
use Z\T\{G, function f, const K};
new \A\T\B\C();
new \A\T\D\E();
new \A\T\B\C\D();
new \A\T\D\E\F();
new \Z\T\G();
\X\T\b\c();
\X\T\d\e();
\Z\T\f();
\Y\T\B\C;
\Y\T\D\E;
\Z\T\K;
}
EOC;
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
@@ -219,7 +255,7 @@ try {
}
EOC;
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$prettyPrinter = new PhpParser\PrettyPrinter\Standard;
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
@@ -242,53 +278,36 @@ EOC;
$this->assertEquals($stmts, $traverser->traverse($stmts));
}
protected function createNamespacedAndNonNamespaced(array $stmts) {
return array(
new Stmt\Namespace_(new Name('NS'), $stmts),
new Stmt\Namespace_(null, $stmts),
);
}
public function testAddNamespacedName() {
$stmts = $this->createNamespacedAndNonNamespaced(array(
$nsStmts = array(
new Stmt\Class_('A'),
new Stmt\Interface_('B'),
new Stmt\Function_('C'),
new Stmt\Const_(array(
new Node\Const_('D', new Node\Scalar\String_('E'))
new Node\Const_('D', new Node\Scalar\LNumber(42))
)),
new Stmt\Trait_('E'),
new Expr\New_(new Stmt\Class_(null)),
));
);
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $traverser->traverse($stmts);
$stmts = $traverser->traverse([new Stmt\Namespace_(new Name('NS'), $nsStmts)]);
$this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertSame('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
$this->assertSame('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertSame('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[4]->class);
$this->assertSame('A', (string) $stmts[1]->stmts[0]->namespacedName);
$this->assertSame('B', (string) $stmts[1]->stmts[1]->namespacedName);
$this->assertSame('C', (string) $stmts[1]->stmts[2]->namespacedName);
$this->assertSame('D', (string) $stmts[1]->stmts[3]->consts[0]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[1]->stmts[4]->class);
}
$this->assertSame('NS\\E', (string) $stmts[0]->stmts[4]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
public function testAddTraitNamespacedName() {
$stmts = $this->createNamespacedAndNonNamespaced(array(
new Stmt\Trait_('A')
));
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor(new NameResolver);
$stmts = $traverser->traverse($stmts);
$this->assertSame('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertSame('A', (string) $stmts[1]->stmts[0]->namespacedName);
$stmts = $traverser->traverse([new Stmt\Namespace_(null, $nsStmts)]);
$this->assertSame('A', (string) $stmts[0]->stmts[0]->namespacedName);
$this->assertSame('B', (string) $stmts[0]->stmts[1]->namespacedName);
$this->assertSame('C', (string) $stmts[0]->stmts[2]->namespacedName);
$this->assertSame('D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
$this->assertSame('E', (string) $stmts[0]->stmts[4]->namespacedName);
$this->assertObjectNotHasAttribute('namespacedName', $stmts[0]->stmts[5]->class);
}
/**
@@ -306,22 +325,22 @@ EOC;
return array(
array(
new Stmt\Use_(array(
new Stmt\UseUse(new Name('A\B'), 'B', array('startLine' => 1)),
new Stmt\UseUse(new Name('C\D'), 'B', array('startLine' => 2)),
new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
), Stmt\Use_::TYPE_NORMAL),
'Cannot use C\D as B because the name is already in use on line 2'
),
array(
new Stmt\Use_(array(
new Stmt\UseUse(new Name('a\b'), 'b', array('startLine' => 1)),
new Stmt\UseUse(new Name('c\d'), 'B', array('startLine' => 2)),
new Stmt\UseUse(new Name('a\b'), 'b', 0, array('startLine' => 1)),
new Stmt\UseUse(new Name('c\d'), 'B', 0, array('startLine' => 2)),
), Stmt\Use_::TYPE_FUNCTION),
'Cannot use function c\d as B because the name is already in use on line 2'
),
array(
new Stmt\Use_(array(
new Stmt\UseUse(new Name('A\B'), 'B', array('startLine' => 1)),
new Stmt\UseUse(new Name('C\D'), 'B', array('startLine' => 2)),
new Stmt\UseUse(new Name('A\B'), 'B', 0, array('startLine' => 1)),
new Stmt\UseUse(new Name('C\D'), 'B', 0, array('startLine' => 2)),
), Stmt\Use_::TYPE_CONSTANT),
'Cannot use const C\D as B because the name is already in use on line 2'
),
@@ -353,7 +372,7 @@ use Bar\Baz;
$test = new baz();
EOC;
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$stmts = $parser->parse($source);
$traverser = new PhpParser\NodeTraverser;
@@ -381,7 +400,7 @@ class Bar
}
EOC;
$parser = new PhpParser\Parser(new PhpParser\Lexer\Emulative);
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer\Emulative);
$stmts = $parser->parse($source);
$traverser = new PhpParser\NodeTraverser;

View File

@@ -0,0 +1,113 @@
<?php
namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\Lexer;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\ParserTest;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
require_once __DIR__ . '/../ParserTest.php';
class MultipleTest extends ParserTest {
// This provider is for the generic parser tests, just pick an arbitrary order here
protected function getParser(Lexer $lexer) {
return new Multiple([new Php5($lexer), new Php7($lexer)]);
}
private function getPrefer7() {
$lexer = new Lexer(['usedAttributes' => []]);
return new Multiple([new Php7($lexer), new Php5($lexer)]);
}
private function getPrefer5() {
$lexer = new Lexer(['usedAttributes' => []]);
return new Multiple([new Php5($lexer), new Php7($lexer)]);
}
/** @dataProvider provideTestParse */
public function testParse($code, Multiple $parser, $expected) {
$this->assertEquals($expected, $parser->parse($code));
$this->assertSame([], $parser->getErrors());
}
public function provideTestParse() {
return [
[
// PHP 7 only code
'<?php class Test { function function() {} }',
$this->getPrefer5(),
[
new Stmt\Class_('Test', ['stmts' => [
new Stmt\ClassMethod('function')
]]),
]
],
[
// PHP 5 only code
'<?php global $$a->b;',
$this->getPrefer7(),
[
new Stmt\Global_([
new Expr\Variable(new Expr\PropertyFetch(new Expr\Variable('a'), 'b'))
])
]
],
[
// Different meaning (PHP 5)
'<?php $$a[0];',
$this->getPrefer5(),
[
new Expr\Variable(
new Expr\ArrayDimFetch(new Expr\Variable('a'), LNumber::fromString('0'))
)
]
],
[
// Different meaning (PHP 7)
'<?php $$a[0];',
$this->getPrefer7(),
[
new Expr\ArrayDimFetch(
new Expr\Variable(new Expr\Variable('a')), LNumber::fromString('0')
)
]
],
];
}
public function testThrownError() {
$this->setExpectedException('PhpParser\Error', 'FAIL A');
$parserA = $this->getMockBuilder('PhpParser\Parser')->getMock();
$parserA->expects($this->at(0))
->method('parse')->will($this->throwException(new Error('FAIL A')));
$parserB = $this->getMockBuilder('PhpParser\Parser')->getMock();
$parserB->expects($this->at(0))
->method('parse')->will($this->throwException(new Error('FAIL B')));
$parser = new Multiple([$parserA, $parserB]);
$parser->parse('dummy');
}
public function testGetErrors() {
$errorsA = [new Error('A1'), new Error('A2')];
$parserA = $this->getMockBuilder('PhpParser\Parser')->getMock();
$parserA->expects($this->at(0))->method('parse');
$parserA->expects($this->at(1))
->method('getErrors')->will($this->returnValue($errorsA));
$errorsB = [new Error('B1'), new Error('B2')];
$parserB = $this->getMockBuilder('PhpParser\Parser')->getMock();
$parserB->expects($this->at(0))->method('parse');
$parserB->expects($this->at(1))
->method('getErrors')->will($this->returnValue($errorsB));
$parser = new Multiple([$parserA, $parserB]);
$parser->parse('dummy');
$this->assertSame($errorsA, $parser->getErrors());
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace PhpParser\Parser;
use PhpParser\Lexer;
use PhpParser\ParserTest;
require_once __DIR__ . '/../ParserTest.php';
class Php5Test extends ParserTest {
protected function getParser(Lexer $lexer) {
return new Php5($lexer);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace PhpParser\Parser;
use PhpParser\Lexer;
use PhpParser\ParserTest;
require_once __DIR__ . '/../ParserTest.php';
class Php7Test extends ParserTest {
protected function getParser(Lexer $lexer) {
return new Php7($lexer);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace PhpParser;
/* This test is very weak, because PHPUnit's assertEquals assertion is way too slow dealing with the
* large objects involved here. So we just do some basic instanceof tests instead. */
class ParserFactoryTest extends \PHPUnit_Framework_TestCase {
/** @dataProvider provideTestCreate */
public function testCreate($kind, $lexer, $expected) {
$this->assertInstanceOf($expected, (new ParserFactory)->create($kind, $lexer));
}
public function provideTestCreate() {
$lexer = new Lexer();
return [
[
ParserFactory::PREFER_PHP7, $lexer,
'PhpParser\Parser\Multiple'
],
[
ParserFactory::PREFER_PHP5, null,
'PhpParser\Parser\Multiple'
],
[
ParserFactory::ONLY_PHP7, null,
'PhpParser\Parser\Php7'
],
[
ParserFactory::ONLY_PHP5, $lexer,
'PhpParser\Parser\Php5'
]
];
}
}

View File

@@ -3,57 +3,21 @@
namespace PhpParser;
use PhpParser\Comment;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\String_;
require_once __DIR__ . '/CodeTestAbstract.php';
class ParserTest extends CodeTestAbstract
abstract class ParserTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider provideTestParse
*/
public function testParse($name, $code, $expected) {
$lexer = new Lexer\Emulative(array('usedAttributes' => array(
'startLine', 'endLine', 'startFilePos', 'endFilePos'
)));
$parser = new Parser($lexer, array(
'throwOnError' => false,
));
$stmts = $parser->parse($code);
$errors = $parser->getErrors();
$output = '';
foreach ($errors as $error) {
$output .= $this->formatErrorMessage($error, $code) . "\n";
}
if (null !== $stmts) {
$dumper = new NodeDumper;
$output .= $dumper->dump($stmts);
}
$this->assertSame($this->canonicalize($expected), $this->canonicalize($output), $name);
}
public function provideTestParse() {
return $this->getTests(__DIR__ . '/../code/parser', 'test');
}
private function formatErrorMessage(Error $e, $code) {
if ($e->hasColumnInfo()) {
return $e->getRawMessage() . ' from ' . $e->getStartLine() . ':' . $e->getStartColumn($code)
. ' to ' . $e->getEndLine() . ':' . $e->getEndColumn($code);
} else {
return $e->getMessage();
}
}
/** @returns Parser */
abstract protected function getParser(Lexer $lexer);
/**
* @expectedException \PhpParser\Error
* @expectedExceptionMessage Syntax error, unexpected EOF on line 1
*/
public function testParserThrowsSyntaxError() {
$parser = new Parser(new Lexer());
$parser = $this->getParser(new Lexer());
$parser->parse('<?php foo');
}
@@ -62,7 +26,7 @@ class ParserTest extends CodeTestAbstract
* @expectedExceptionMessage Cannot use foo as self because 'self' is a special class name on line 1
*/
public function testParserThrowsSpecialError() {
$parser = new Parser(new Lexer());
$parser = $this->getParser(new Lexer());
$parser->parse('<?php use foo as self;');
}
@@ -83,9 +47,9 @@ function test($a) {
echo $a;
}
EOC;
$code = $this->canonicalize($code);
$code = canonicalize($code);
$parser = new Parser($lexer);
$parser = $this->getParser($lexer);
$stmts = $parser->parse($code);
/** @var \PhpParser\Node\Stmt\Function_ $fn */
@@ -93,7 +57,7 @@ EOC;
$this->assertInstanceOf('PhpParser\Node\Stmt\Function_', $fn);
$this->assertEquals(array(
'comments' => array(
new Comment\Doc('/** Doc comment */', 2),
new Comment\Doc('/** Doc comment */', 2, 6),
),
'startLine' => 3,
'endLine' => 7,
@@ -115,8 +79,8 @@ EOC;
$this->assertInstanceOf('PhpParser\Node\Stmt\Echo_', $echo);
$this->assertEquals(array(
'comments' => array(
new Comment("// Line\n", 4),
new Comment("// Comments\n", 5),
new Comment("// Line\n", 4, 49),
new Comment("// Comments\n", 5, 61),
),
'startLine' => 6,
'endLine' => 6,
@@ -141,9 +105,61 @@ EOC;
*/
public function testInvalidToken() {
$lexer = new InvalidTokenLexer;
$parser = new Parser($lexer);
$parser = $this->getParser($lexer);
$parser->parse('dummy');
}
/**
* @dataProvider provideTestKindAttributes
*/
public function testKindAttributes($code, $expectedAttributes) {
$parser = $this->getParser(new Lexer);
$stmts = $parser->parse("<?php $code;");
$attributes = $stmts[0]->getAttributes();
foreach ($expectedAttributes as $name => $value) {
$this->assertSame($value, $attributes[$name]);
}
}
public function provideTestKindAttributes() {
return array(
array('0', ['kind' => Scalar\LNumber::KIND_DEC]),
array('9', ['kind' => Scalar\LNumber::KIND_DEC]),
array('07', ['kind' => Scalar\LNumber::KIND_OCT]),
array('0xf', ['kind' => Scalar\LNumber::KIND_HEX]),
array('0XF', ['kind' => Scalar\LNumber::KIND_HEX]),
array('0b1', ['kind' => Scalar\LNumber::KIND_BIN]),
array('0B1', ['kind' => Scalar\LNumber::KIND_BIN]),
array('[]', ['kind' => Expr\Array_::KIND_SHORT]),
array('array()', ['kind' => Expr\Array_::KIND_LONG]),
array("'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]),
array("b'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]),
array("B'foo'", ['kind' => String_::KIND_SINGLE_QUOTED]),
array('"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
array('b"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
array('B"foo"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
array('"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
array('b"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
array('B"foo$bar"', ['kind' => String_::KIND_DOUBLE_QUOTED]),
array("<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
array("<<<STR\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
array("<<<\"STR\"\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
array("b<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
array("B<<<'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
array("<<< \t 'STR'\nSTR\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']),
// HHVM doesn't support this due to a lexer bug
// (https://github.com/facebook/hhvm/issues/6970)
// array("<<<'\xff'\n\xff\n", ['kind' => String_::KIND_NOWDOC, 'docLabel' => "\xff"]),
array("<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
array("b<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
array("B<<<\"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
array("<<< \t \"STR\"\n\$a\nSTR\n", ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR']),
array("die", ['kind' => Expr\Exit_::KIND_DIE]),
array("die('done')", ['kind' => Expr\Exit_::KIND_DIE]),
array("exit", ['kind' => Expr\Exit_::KIND_EXIT]),
array("exit(1)", ['kind' => Expr\Exit_::KIND_EXIT]),
);
}
}
class InvalidTokenLexer extends Lexer {

View File

@@ -2,7 +2,10 @@
namespace PhpParser;
use PhpParser\Comment;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar\Encapsed;
use PhpParser\Node\Scalar\EncapsedStringPart;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt;
use PhpParser\PrettyPrinter\Standard;
@@ -11,32 +14,58 @@ require_once __DIR__ . '/CodeTestAbstract.php';
class PrettyPrinterTest extends CodeTestAbstract
{
protected function doTestPrettyPrintMethod($method, $name, $code, $dump) {
$parser = new Parser(new Lexer\Emulative);
$prettyPrinter = new Standard;
protected function doTestPrettyPrintMethod($method, $name, $code, $expected, $modeLine) {
$lexer = new Lexer\Emulative;
$parser5 = new Parser\Php5($lexer);
$parser7 = new Parser\Php7($lexer);
$stmts = $parser->parse($code);
$this->assertSame(
$this->canonicalize($dump),
$this->canonicalize($prettyPrinter->$method($stmts)),
$name
);
list($version, $options) = $this->parseModeLine($modeLine);
$prettyPrinter = new Standard($options);
try {
$output5 = canonicalize($prettyPrinter->$method($parser5->parse($code)));
} catch (Error $e) {
$output5 = null;
if ('php7' !== $version) {
throw $e;
}
}
try {
$output7 = canonicalize($prettyPrinter->$method($parser7->parse($code)));
} catch (Error $e) {
$output7 = null;
if ('php5' !== $version) {
throw $e;
}
}
if ('php5' === $version) {
$this->assertSame($expected, $output5, $name);
$this->assertNotSame($expected, $output7, $name);
} else if ('php7' === $version) {
$this->assertSame($expected, $output7, $name);
$this->assertNotSame($expected, $output5, $name);
} else {
$this->assertSame($expected, $output5, $name);
$this->assertSame($expected, $output7, $name);
}
}
/**
* @dataProvider provideTestPrettyPrint
* @covers PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrint($name, $code, $dump) {
$this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $dump);
public function testPrettyPrint($name, $code, $expected, $mode) {
$this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $expected, $mode);
}
/**
* @dataProvider provideTestPrettyPrintFile
* @covers PhpParser\PrettyPrinter\Standard<extended>
*/
public function testPrettyPrintFile($name, $code, $dump) {
$this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $dump);
public function testPrettyPrintFile($name, $code, $expected, $mode) {
$this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $expected, $mode);
}
public function provideTestPrettyPrint() {
@@ -60,4 +89,76 @@ class PrettyPrinterTest extends CodeTestAbstract
));
$this->assertEquals("function () {\n return 'a\nb';\n}", $prettyPrinter->prettyPrintExpr($expr));
}
public function testCommentBeforeInlineHTML() {
$prettyPrinter = new PrettyPrinter\Standard;
$comment = new Comment\Doc("/**\n * This is a comment\n */");
$stmts = [new Stmt\InlineHTML('Hello World!', ['comments' => [$comment]])];
$expected = "<?php\n\n/**\n * This is a comment\n */\n?>\nHello World!";
$this->assertSame($expected, $prettyPrinter->prettyPrintFile($stmts));
}
private function parseModeLine($modeLine) {
$parts = explode(' ', $modeLine, 2);
$version = isset($parts[0]) ? $parts[0] : 'both';
$options = isset($parts[1]) ? json_decode($parts[1], true) : [];
return [$version, $options];
}
public function testArraySyntaxDefault() {
$prettyPrinter = new Standard(['shortArraySyntax' => true]);
$expr = new Expr\Array_([
new Expr\ArrayItem(new String_('val'), new String_('key'))
]);
$expected = "['key' => 'val']";
$this->assertSame($expected, $prettyPrinter->prettyPrintExpr($expr));
}
/**
* @dataProvider provideTestKindAttributes
*/
public function testKindAttributes($node, $expected) {
$prttyPrinter = new PrettyPrinter\Standard;
$result = $prttyPrinter->prettyPrintExpr($node);
$this->assertSame($expected, $result);
}
public function provideTestKindAttributes() {
$nowdoc = ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR'];
$heredoc = ['kind' => String_::KIND_HEREDOC, 'docLabel' => 'STR'];
return [
// Defaults to single quoted
[new String_('foo'), "'foo'"],
// Explicit single/double quoted
[new String_('foo', ['kind' => String_::KIND_SINGLE_QUOTED]), "'foo'"],
[new String_('foo', ['kind' => String_::KIND_DOUBLE_QUOTED]), '"foo"'],
// Fallback from doc string if no label
[new String_('foo', ['kind' => String_::KIND_NOWDOC]), "'foo'"],
[new String_('foo', ['kind' => String_::KIND_HEREDOC]), '"foo"'],
// Fallback if string contains label
[new String_("A\nB\nC", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'A']), "'A\nB\nC'"],
[new String_("A\nB\nC", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'B']), "'A\nB\nC'"],
[new String_("A\nB\nC", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'C']), "'A\nB\nC'"],
[new String_("STR;", ['kind' => String_::KIND_NOWDOC, 'docLabel' => 'STR']), "'STR;'"],
// Doc string if label not contained (or not in ending position)
[new String_("foo", $nowdoc), "<<<'STR'\nfoo\nSTR\n"],
[new String_("foo", $heredoc), "<<<STR\nfoo\nSTR\n"],
[new String_("STRx", $nowdoc), "<<<'STR'\nSTRx\nSTR\n"],
[new String_("xSTR", $nowdoc), "<<<'STR'\nxSTR\nSTR\n"],
// Empty doc string variations (encapsed variant does not occur naturally)
[new String_("", $nowdoc), "<<<'STR'\nSTR\n"],
[new String_("", $heredoc), "<<<STR\nSTR\n"],
[new Encapsed([new EncapsedStringPart('')], $heredoc), "<<<STR\nSTR\n"],
// Encapsed doc string variations
[new Encapsed([new EncapsedStringPart('foo')], $heredoc), "<<<STR\nfoo\nSTR\n"],
[new Encapsed([new EncapsedStringPart('foo'), new Expr\Variable('y')], $heredoc), "<<<STR\nfoo{\$y}\nSTR\n"],
[new Encapsed([new EncapsedStringPart("\nSTR"), new Expr\Variable('y')], $heredoc), "<<<STR\n\nSTR{\$y}\nSTR\n"],
[new Encapsed([new EncapsedStringPart("\nSTR"), new Expr\Variable('y')], $heredoc), "<<<STR\n\nSTR{\$y}\nSTR\n"],
[new Encapsed([new Expr\Variable('y'), new EncapsedStringPart("STR\n")], $heredoc), "<<<STR\n{\$y}STR\n\nSTR\n"],
// Encapsed doc string fallback
[new Encapsed([new Expr\Variable('y'), new EncapsedStringPart("\nSTR")], $heredoc), '"{$y}\\nSTR"'],
[new Encapsed([new EncapsedStringPart("STR\n"), new Expr\Variable('y')], $heredoc), '"STR\\n{$y}"'],
[new Encapsed([new EncapsedStringPart("STR")], $heredoc), '"STR"'],
];
}
}

View File

@@ -23,6 +23,9 @@ CODE;
<AST xmlns:node="http://nikic.github.com/PHPParser/XML/node" xmlns:subNode="http://nikic.github.com/PHPParser/XML/subNode" xmlns:attribute="http://nikic.github.com/PHPParser/XML/attribute" xmlns:scalar="http://nikic.github.com/PHPParser/XML/scalar">
<scalar:array>
<node:Stmt_Function>
<attribute:startLine>
<scalar:int>4</scalar:int>
</attribute:startLine>
<attribute:comments>
<scalar:array>
<comment isDocComment="false" line="2">// comment
@@ -30,9 +33,6 @@ CODE;
<comment isDocComment="true" line="3">/** doc comment */</comment>
</scalar:array>
</attribute:comments>
<attribute:startLine>
<scalar:int>4</scalar:int>
</attribute:startLine>
<attribute:endLine>
<scalar:int>6</scalar:int>
</attribute:endLine>
@@ -71,6 +71,9 @@ CODE;
<attribute:endLine>
<scalar:int>4</scalar:int>
</attribute:endLine>
<attribute:kind>
<scalar:int>10</scalar:int>
</attribute:kind>
<subNode:value>
<scalar:int>0</scalar:int>
</subNode:value>
@@ -113,7 +116,7 @@ CODE;
</scalar:array>
</subNode:params>
<subNode:returnType>
<scalar:null/>
<scalar:null/>
</subNode:returnType>
<subNode:stmts>
<scalar:array>
@@ -133,6 +136,9 @@ CODE;
<attribute:endLine>
<scalar:int>5</scalar:int>
</attribute:endLine>
<attribute:kind>
<scalar:int>1</scalar:int>
</attribute:kind>
<subNode:value>
<scalar:string>Foo</scalar:string>
</subNode:value>
@@ -147,9 +153,10 @@ CODE;
</AST>
XML;
$parser = new PhpParser\Parser(new PhpParser\Lexer);
$parser = new PhpParser\Parser\Php7(new PhpParser\Lexer);
$serializer = new XML;
$code = str_replace("\r\n", "\n", $code);
$stmts = $parser->parse($code);
$this->assertXmlStringEqualsXmlString($xml, $serializer->serialize($stmts));
}