1 <?php declare(strict_types=1);
5 use PhpParser\Node\Expr\Include_;
6 use PhpParser\Node\Stmt\Class_;
7 use PhpParser\Node\Stmt\GroupUse;
8 use PhpParser\Node\Stmt\Use_;
9 use PhpParser\Node\Stmt\UseUse;
13 private $dumpComments;
14 private $dumpPositions;
18 * Constructs a NodeDumper.
21 * * bool dumpComments: Whether comments should be dumped.
22 * * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
23 * information, the code needs to be passed to dump().
25 * @param array $options Options (see description)
27 public function __construct(array $options = []) {
28 $this->dumpComments = !empty($options['dumpComments']);
29 $this->dumpPositions = !empty($options['dumpPositions']);
33 * Dumps a node or array.
35 * @param array|Node $node Node or array to dump
36 * @param string|null $code Code corresponding to dumped AST. This only needs to be passed if
37 * the dumpPositions option is enabled and the dumping of node offsets
40 * @return string Dumped value
42 public function dump($node, string $code = null) : string {
44 return $this->dumpRecursive($node);
47 protected function dumpRecursive($node) {
48 if ($node instanceof Node) {
49 $r = $node->getType();
50 if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
55 foreach ($node->getSubNodeNames() as $key) {
56 $r .= "\n " . $key . ': ';
59 if (null === $value) {
61 } elseif (false === $value) {
63 } elseif (true === $value) {
65 } elseif (is_scalar($value)) {
66 if ('flags' === $key || 'newModifier' === $key) {
67 $r .= $this->dumpFlags($value);
68 } elseif ('type' === $key && $node instanceof Include_) {
69 $r .= $this->dumpIncludeType($value);
70 } elseif ('type' === $key
71 && ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) {
72 $r .= $this->dumpUseType($value);
77 $r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
81 if ($this->dumpComments && $comments = $node->getComments()) {
82 $r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
84 } elseif (is_array($node)) {
87 foreach ($node as $key => $value) {
88 $r .= "\n " . $key . ': ';
90 if (null === $value) {
92 } elseif (false === $value) {
94 } elseif (true === $value) {
96 } elseif (is_scalar($value)) {
99 $r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
102 } elseif ($node instanceof Comment) {
103 return $node->getReformattedText();
105 throw new \InvalidArgumentException('Can only dump nodes and arrays.');
111 protected function dumpFlags($flags) {
113 if ($flags & Class_::MODIFIER_PUBLIC) {
114 $strs[] = 'MODIFIER_PUBLIC';
116 if ($flags & Class_::MODIFIER_PROTECTED) {
117 $strs[] = 'MODIFIER_PROTECTED';
119 if ($flags & Class_::MODIFIER_PRIVATE) {
120 $strs[] = 'MODIFIER_PRIVATE';
122 if ($flags & Class_::MODIFIER_ABSTRACT) {
123 $strs[] = 'MODIFIER_ABSTRACT';
125 if ($flags & Class_::MODIFIER_STATIC) {
126 $strs[] = 'MODIFIER_STATIC';
128 if ($flags & Class_::MODIFIER_FINAL) {
129 $strs[] = 'MODIFIER_FINAL';
133 return implode(' | ', $strs) . ' (' . $flags . ')';
139 protected function dumpIncludeType($type) {
141 Include_::TYPE_INCLUDE => 'TYPE_INCLUDE',
142 Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',
143 Include_::TYPE_REQUIRE => 'TYPE_REQUIRE',
144 Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE',
147 if (!isset($map[$type])) {
150 return $map[$type] . ' (' . $type . ')';
153 protected function dumpUseType($type) {
155 Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
156 Use_::TYPE_NORMAL => 'TYPE_NORMAL',
157 Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',
158 Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',
161 if (!isset($map[$type])) {
164 return $map[$type] . ' (' . $type . ')';
168 * Dump node position, if possible.
170 * @param Node $node Node for which to dump position
172 * @return string|null Dump of position, or null if position information not available
174 protected function dumpPosition(Node $node) {
175 if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) {
179 $start = $node->getStartLine();
180 $end = $node->getEndLine();
181 if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos')
182 && null !== $this->code
184 $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos());
185 $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos());
187 return "[$start - $end]";
190 // Copied from Error class
191 private function toColumn($code, $pos) {
192 if ($pos > strlen($code)) {
193 throw new \RuntimeException('Invalid position information');
196 $lineStartPos = strrpos($code, "\n", $pos - strlen($code));
197 if (false === $lineStartPos) {
201 return $pos - $lineStartPos;