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