|   1:  | <?php declare(strict_types=1); | 
|   2:  |  | 
|   3:  | namespace PhpParser; | 
|   4:  |  | 
|   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; | 
|  10:  |  | 
|  11:  | class NodeDumper | 
|  12:  | { | 
|  13:  |     private $dumpComments; | 
|  14:  |     private $dumpPositions; | 
|  15:  |     private $code; | 
|  16:  |  | 
|  17:  |      | 
|  18:  |  | 
|  19:  |  | 
|  20:  |  | 
|  21:  |  | 
|  22:  |  | 
|  23:  |  | 
|  24:  |  | 
|  25:  |  | 
|  26:  |  | 
|  27:  |     public function __construct(array $options = []) { | 
|  28:  |         $this->dumpComments = !empty($options['dumpComments']); | 
|  29:  |         $this->dumpPositions = !empty($options['dumpPositions']); | 
|  30:  |     } | 
|  31:  |  | 
|  32:  |      | 
|  33:  |  | 
|  34:  |  | 
|  35:  |  | 
|  36:  |  | 
|  37:  |  | 
|  38:  |  | 
|  39:  |  | 
|  40:  |  | 
|  41:  |  | 
|  42:  |     public function dump($node, ?string $code = null) : string { | 
|  43:  |         $this->code = $code; | 
|  44:  |         return $this->dumpRecursive($node); | 
|  45:  |     } | 
|  46:  |  | 
|  47:  |     protected function dumpRecursive($node) { | 
|  48:  |         if ($node instanceof Node) { | 
|  49:  |             $r = $node->getType(); | 
|  50:  |             if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) { | 
|  51:  |                 $r .= $p; | 
|  52:  |             } | 
|  53:  |             $r .= '('; | 
|  54:  |  | 
|  55:  |             foreach ($node->getSubNodeNames() as $key) { | 
|  56:  |                 $r .= "\n    " . $key . ': '; | 
|  57:  |  | 
|  58:  |                 $value = $node->$key; | 
|  59:  |                 if (null === $value) { | 
|  60:  |                     $r .= 'null'; | 
|  61:  |                 } elseif (false === $value) { | 
|  62:  |                     $r .= 'false'; | 
|  63:  |                 } elseif (true === $value) { | 
|  64:  |                     $r .= 'true'; | 
|  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); | 
|  73:  |                     } else { | 
|  74:  |                         $r .= $value; | 
|  75:  |                     } | 
|  76:  |                 } else { | 
|  77:  |                     $r .= str_replace("\n", "\n    ", $this->dumpRecursive($value)); | 
|  78:  |                 } | 
|  79:  |             } | 
|  80:  |  | 
|  81:  |             if ($this->dumpComments && $comments = $node->getComments()) { | 
|  82:  |                 $r .= "\n    comments: " . str_replace("\n", "\n    ", $this->dumpRecursive($comments)); | 
|  83:  |             } | 
|  84:  |         } elseif (is_array($node)) { | 
|  85:  |             $r = 'array('; | 
|  86:  |  | 
|  87:  |             foreach ($node as $key => $value) { | 
|  88:  |                 $r .= "\n    " . $key . ': '; | 
|  89:  |  | 
|  90:  |                 if (null === $value) { | 
|  91:  |                     $r .= 'null'; | 
|  92:  |                 } elseif (false === $value) { | 
|  93:  |                     $r .= 'false'; | 
|  94:  |                 } elseif (true === $value) { | 
|  95:  |                     $r .= 'true'; | 
|  96:  |                 } elseif (is_scalar($value)) { | 
|  97:  |                     $r .= $value; | 
|  98:  |                 } else { | 
|  99:  |                     $r .= str_replace("\n", "\n    ", $this->dumpRecursive($value)); | 
| 100:  |                 } | 
| 101:  |             } | 
| 102:  |         } elseif ($node instanceof Comment) { | 
| 103:  |             return $node->getReformattedText(); | 
| 104:  |         } else { | 
| 105:  |             throw new \InvalidArgumentException('Can only dump nodes and arrays.'); | 
| 106:  |         } | 
| 107:  |  | 
| 108:  |         return $r . "\n)"; | 
| 109:  |     } | 
| 110:  |  | 
| 111:  |     protected function dumpFlags($flags) { | 
| 112:  |         $strs = []; | 
| 113:  |         if ($flags & Class_::MODIFIER_PUBLIC) { | 
| 114:  |             $strs[] = 'MODIFIER_PUBLIC'; | 
| 115:  |         } | 
| 116:  |         if ($flags & Class_::MODIFIER_PROTECTED) { | 
| 117:  |             $strs[] = 'MODIFIER_PROTECTED'; | 
| 118:  |         } | 
| 119:  |         if ($flags & Class_::MODIFIER_PRIVATE) { | 
| 120:  |             $strs[] = 'MODIFIER_PRIVATE'; | 
| 121:  |         } | 
| 122:  |         if ($flags & Class_::MODIFIER_ABSTRACT) { | 
| 123:  |             $strs[] = 'MODIFIER_ABSTRACT'; | 
| 124:  |         } | 
| 125:  |         if ($flags & Class_::MODIFIER_STATIC) { | 
| 126:  |             $strs[] = 'MODIFIER_STATIC'; | 
| 127:  |         } | 
| 128:  |         if ($flags & Class_::MODIFIER_FINAL) { | 
| 129:  |             $strs[] = 'MODIFIER_FINAL'; | 
| 130:  |         } | 
| 131:  |         if ($flags & Class_::MODIFIER_READONLY) { | 
| 132:  |             $strs[] = 'MODIFIER_READONLY'; | 
| 133:  |         } | 
| 134:  |  | 
| 135:  |         if ($strs) { | 
| 136:  |             return implode(' | ', $strs) . ' (' . $flags . ')'; | 
| 137:  |         } else { | 
| 138:  |             return $flags; | 
| 139:  |         } | 
| 140:  |     } | 
| 141:  |  | 
| 142:  |     protected function dumpIncludeType($type) { | 
| 143:  |         $map = [ | 
| 144:  |             Include_::TYPE_INCLUDE      => 'TYPE_INCLUDE', | 
| 145:  |             Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', | 
| 146:  |             Include_::TYPE_REQUIRE      => 'TYPE_REQUIRE', | 
| 147:  |             Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE', | 
| 148:  |         ]; | 
| 149:  |  | 
| 150:  |         if (!isset($map[$type])) { | 
| 151:  |             return $type; | 
| 152:  |         } | 
| 153:  |         return $map[$type] . ' (' . $type . ')'; | 
| 154:  |     } | 
| 155:  |  | 
| 156:  |     protected function dumpUseType($type) { | 
| 157:  |         $map = [ | 
| 158:  |             Use_::TYPE_UNKNOWN  => 'TYPE_UNKNOWN', | 
| 159:  |             Use_::TYPE_NORMAL   => 'TYPE_NORMAL', | 
| 160:  |             Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', | 
| 161:  |             Use_::TYPE_CONSTANT => 'TYPE_CONSTANT', | 
| 162:  |         ]; | 
| 163:  |  | 
| 164:  |         if (!isset($map[$type])) { | 
| 165:  |             return $type; | 
| 166:  |         } | 
| 167:  |         return $map[$type] . ' (' . $type . ')'; | 
| 168:  |     } | 
| 169:  |  | 
| 170:  |      | 
| 171:  |  | 
| 172:  |  | 
| 173:  |  | 
| 174:  |  | 
| 175:  |  | 
| 176:  |  | 
| 177:  |     protected function dumpPosition(Node $node) { | 
| 178:  |         if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) { | 
| 179:  |             return null; | 
| 180:  |         } | 
| 181:  |  | 
| 182:  |         $start = $node->getStartLine(); | 
| 183:  |         $end = $node->getEndLine(); | 
| 184:  |         if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') | 
| 185:  |             && null !== $this->code | 
| 186:  |         ) { | 
| 187:  |             $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); | 
| 188:  |             $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); | 
| 189:  |         } | 
| 190:  |         return "[$start - $end]"; | 
| 191:  |     } | 
| 192:  |  | 
| 193:  |      | 
| 194:  |     private function toColumn($code, $pos) { | 
| 195:  |         if ($pos > strlen($code)) { | 
| 196:  |             throw new \RuntimeException('Invalid position information'); | 
| 197:  |         } | 
| 198:  |  | 
| 199:  |         $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); | 
| 200:  |         if (false === $lineStartPos) { | 
| 201:  |             $lineStartPos = -1; | 
| 202:  |         } | 
| 203:  |  | 
| 204:  |         return $pos - $lineStartPos; | 
| 205:  |     } | 
| 206:  | } | 
| 207:  |  |