Skip to content

Commit

Permalink
Add support for dumping positions in NodeDumper
Browse files Browse the repository at this point in the history
  • Loading branch information
nikic committed Nov 23, 2016
1 parent e52ffc4 commit b02f8ac
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 9 deletions.
65 changes: 57 additions & 8 deletions lib/PhpParser/NodeDumper.php
Expand Up @@ -11,27 +11,46 @@
class NodeDumper
{
private $dumpComments;
private $dumpPositions;
private $code;

/**
* Constructs a NodeDumper.
*
* @param array $options Boolean option 'dumpComments' controls whether comments should be
* dumped
* Supported options:
* * bool dumpComments: Whether comments should be dumped.
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
* information, the code needs to be passed to dump().
*
* @param array $options Options (see description)
*/
public function __construct(array $options = []) {
$this->dumpComments = !empty($options['dumpComments']);
$this->dumpPositions = !empty($options['dumpPositions']);
}

/**
* Dumps a node or array.
*
* @param array|Node $node Node or array to dump
* @param array|Node $node Node or array to dump
* @param string|null $code Code corresponding to dumped AST. This only needs to be passed if
* the dumpPositions option is enabled and the dumping of node offsets
* is desired.
*
* @return string Dumped value
*/
public function dump($node) {
public function dump($node, $code = null) {
$this->code = $code;
return $this->dumpRecursive($node);
}

protected function dumpRecursive($node) {
if ($node instanceof Node) {
$r = $node->getType() . '(';
$r = $node->getType();
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
$r .= $p;
}
$r .= '(';

foreach ($node->getSubNodeNames() as $key) {
$r .= "\n " . $key . ': ';
Expand All @@ -55,12 +74,12 @@ public function dump($node) {
$r .= $value;
}
} else {
$r .= str_replace("\n", "\n ", $this->dump($value));
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
}

if ($this->dumpComments && $comments = $node->getAttribute('comments')) {
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dump($comments));
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
}
} elseif (is_array($node)) {
$r = 'array(';
Expand All @@ -77,7 +96,7 @@ public function dump($node) {
} elseif (is_scalar($value)) {
$r .= $value;
} else {
$r .= str_replace("\n", "\n ", $this->dump($value));
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
}
} elseif ($node instanceof Comment) {
Expand Down Expand Up @@ -144,4 +163,34 @@ protected function dumpUseType($type) {
}
return $map[$type] . ' (' . $type . ')';
}

protected function dumpPosition(Node $node) {
if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) {
return null;
}

$start = $node->getAttribute('startLine');
$end = $node->getAttribute('endLine');
if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos')
&& null !== $this->code
) {
$start .= ':' . $this->toColumn($this->code, $node->getAttribute('startFilePos'));
$end .= ':' . $this->toColumn($this->code, $node->getAttribute('endFilePos'));
}
return "[$start - $end]";
}

// Copied from Error class
private function toColumn($code, $pos) {
if ($pos > strlen($code)) {
throw new \RuntimeException('Invalid position information');
}

$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
if (false === $lineStartPos) {
$lineStartPos = -1;
}

return $pos - $lineStartPos;
}
}
37 changes: 36 additions & 1 deletion test/PhpParser/NodeDumperTest.php
Expand Up @@ -2,6 +2,8 @@

namespace PhpParser;

use SebastianBergmann\Diff\Parser;

class NodeDumperTest extends \PHPUnit_Framework_TestCase
{
private function canonicalize($string) {
Expand All @@ -10,7 +12,6 @@ private function canonicalize($string) {

/**
* @dataProvider provideTestDump
* @covers PhpParser\NodeDumper::dump
*/
public function testDump($node, $dump) {
$dumper = new NodeDumper;
Expand Down Expand Up @@ -61,6 +62,40 @@ public function provideTestDump() {
);
}

public function testDumpWithPositions() {
$parser = (new ParserFactory)->create(
ParserFactory::ONLY_PHP7,
new Lexer(['usedAttributes' => ['startLine', 'endLine', 'startFilePos', 'endFilePos']])
);
$dumper = new NodeDumper(['dumpPositions' => true]);

$code = "<?php\n\$a = 1;\necho \$a;";
$expected = <<<'OUT'
array(
0: Expr_Assign[2:1 - 2:6](
var: Expr_Variable[2:1 - 2:2](
name: a
)
expr: Scalar_LNumber[2:6 - 2:6](
value: 1
)
)
1: Stmt_Echo[3:1 - 3:8](
exprs: array(
0: Expr_Variable[3:6 - 3:7](
name: a
)
)
)
)
OUT;

$stmts = $parser->parse($code);
$dump = $dumper->dump($stmts, $code);

$this->assertSame($this->canonicalize($expected), $this->canonicalize($dump));
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Can only dump nodes and arrays.
Expand Down

0 comments on commit b02f8ac

Please sign in to comment.