| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 | <?php declare(strict_types=1);namespace PhpParser;use PhpParser\Node\Expr\Include_;use PhpParser\Node\Stmt\Class_;use PhpParser\Node\Stmt\GroupUse;use PhpParser\Node\Stmt\Use_;use PhpParser\Node\Stmt\UseUse;class NodeDumper{    private $dumpComments;    private $dumpPositions;    private $code;    /**     * Constructs a NodeDumper.     *     * 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 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, string $code = null) : string {        $this->code = $code;        return $this->dumpRecursive($node);    }    protected function dumpRecursive($node) {        if ($node instanceof Node) {            $r = $node->getType();            if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {                $r .= $p;            }            $r .= '(';            foreach ($node->getSubNodeNames() as $key) {                $r .= "\n    " . $key . ': ';                $value = $node->$key;                if (null === $value) {                    $r .= 'null';                } elseif (false === $value) {                    $r .= 'false';                } elseif (true === $value) {                    $r .= 'true';                } elseif (is_scalar($value)) {                    if ('flags' === $key || 'newModifier' === $key) {                        $r .= $this->dumpFlags($value);                    } elseif ('type' === $key && $node instanceof Include_) {                        $r .= $this->dumpIncludeType($value);                    } elseif ('type' === $key                            && ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) {                        $r .= $this->dumpUseType($value);                    } else {                        $r .= $value;                    }                } else {                    $r .= str_replace("\n", "\n    ", $this->dumpRecursive($value));                }            }            if ($this->dumpComments && $comments = $node->getComments()) {                $r .= "\n    comments: " . str_replace("\n", "\n    ", $this->dumpRecursive($comments));            }        } elseif (is_array($node)) {            $r = 'array(';            foreach ($node as $key => $value) {                $r .= "\n    " . $key . ': ';                if (null === $value) {                    $r .= 'null';                } elseif (false === $value) {                    $r .= 'false';                } elseif (true === $value) {                    $r .= 'true';                } elseif (is_scalar($value)) {                    $r .= $value;                } else {                    $r .= str_replace("\n", "\n    ", $this->dumpRecursive($value));                }            }        } elseif ($node instanceof Comment) {            return $node->getReformattedText();        } else {            throw new \InvalidArgumentException('Can only dump nodes and arrays.');        }        return $r . "\n)";    }    protected function dumpFlags($flags) {        $strs = [];        if ($flags & Class_::MODIFIER_PUBLIC) {            $strs[] = 'MODIFIER_PUBLIC';        }        if ($flags & Class_::MODIFIER_PROTECTED) {            $strs[] = 'MODIFIER_PROTECTED';        }        if ($flags & Class_::MODIFIER_PRIVATE) {            $strs[] = 'MODIFIER_PRIVATE';        }        if ($flags & Class_::MODIFIER_ABSTRACT) {            $strs[] = 'MODIFIER_ABSTRACT';        }        if ($flags & Class_::MODIFIER_STATIC) {            $strs[] = 'MODIFIER_STATIC';        }        if ($flags & Class_::MODIFIER_FINAL) {            $strs[] = 'MODIFIER_FINAL';        }        if ($flags & Class_::MODIFIER_READONLY) {            $strs[] = 'MODIFIER_READONLY';        }        if ($strs) {            return implode(' | ', $strs) . ' (' . $flags . ')';        } else {            return $flags;        }    }    protected function dumpIncludeType($type) {        $map = [            Include_::TYPE_INCLUDE      => 'TYPE_INCLUDE',            Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',            Include_::TYPE_REQUIRE      => 'TYPE_REQUIRE',            Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE',        ];        if (!isset($map[$type])) {            return $type;        }        return $map[$type] . ' (' . $type . ')';    }    protected function dumpUseType($type) {        $map = [            Use_::TYPE_UNKNOWN  => 'TYPE_UNKNOWN',            Use_::TYPE_NORMAL   => 'TYPE_NORMAL',            Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',            Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',        ];        if (!isset($map[$type])) {            return $type;        }        return $map[$type] . ' (' . $type . ')';    }    /**     * Dump node position, if possible.     *     * @param Node $node Node for which to dump position     *     * @return string|null Dump of position, or null if position information not available     */    protected function dumpPosition(Node $node) {        if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) {            return null;        }        $start = $node->getStartLine();        $end = $node->getEndLine();        if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos')            && null !== $this->code        ) {            $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos());            $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos());        }        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;    }}
 |