@@ -4,177 +4,177 @@ |
||
4 | 4 | |
5 | 5 | class Error extends \RuntimeException |
6 | 6 | { |
7 | - protected $rawMessage; |
|
8 | - protected $attributes; |
|
9 | - |
|
10 | - /** |
|
11 | - * Creates an Exception signifying a parse error. |
|
12 | - * |
|
13 | - * @param string $message Error message |
|
14 | - * @param array|int $attributes Attributes of node/token where error occurred |
|
15 | - * (or start line of error -- deprecated) |
|
16 | - */ |
|
17 | - public function __construct(string $message, $attributes = []) { |
|
18 | - $this->rawMessage = $message; |
|
19 | - if (is_array($attributes)) { |
|
20 | - $this->attributes = $attributes; |
|
21 | - } else { |
|
22 | - $this->attributes = ['startLine' => $attributes]; |
|
23 | - } |
|
24 | - $this->updateMessage(); |
|
25 | - } |
|
26 | - |
|
27 | - /** |
|
28 | - * Gets the error message |
|
29 | - * |
|
30 | - * @return string Error message |
|
31 | - */ |
|
32 | - public function getRawMessage() : string { |
|
33 | - return $this->rawMessage; |
|
34 | - } |
|
35 | - |
|
36 | - /** |
|
37 | - * Gets the line the error starts in. |
|
38 | - * |
|
39 | - * @return int Error start line |
|
40 | - */ |
|
41 | - public function getStartLine() : int { |
|
42 | - return $this->attributes['startLine'] ?? -1; |
|
43 | - } |
|
44 | - |
|
45 | - /** |
|
46 | - * Gets the line the error ends in. |
|
47 | - * |
|
48 | - * @return int Error end line |
|
49 | - */ |
|
50 | - public function getEndLine() : int { |
|
51 | - return $this->attributes['endLine'] ?? -1; |
|
52 | - } |
|
53 | - |
|
54 | - /** |
|
55 | - * Gets the attributes of the node/token the error occurred at. |
|
56 | - * |
|
57 | - * @return array |
|
58 | - */ |
|
59 | - public function getAttributes() : array { |
|
60 | - return $this->attributes; |
|
61 | - } |
|
62 | - |
|
63 | - /** |
|
64 | - * Sets the attributes of the node/token the error occurred at. |
|
65 | - * |
|
66 | - * @param array $attributes |
|
67 | - */ |
|
68 | - public function setAttributes(array $attributes) { |
|
69 | - $this->attributes = $attributes; |
|
70 | - $this->updateMessage(); |
|
71 | - } |
|
72 | - |
|
73 | - /** |
|
74 | - * Sets the line of the PHP file the error occurred in. |
|
75 | - * |
|
76 | - * @param string $message Error message |
|
77 | - */ |
|
78 | - public function setRawMessage(string $message) { |
|
79 | - $this->rawMessage = $message; |
|
80 | - $this->updateMessage(); |
|
81 | - } |
|
82 | - |
|
83 | - /** |
|
84 | - * Sets the line the error starts in. |
|
85 | - * |
|
86 | - * @param int $line Error start line |
|
87 | - */ |
|
88 | - public function setStartLine(int $line) { |
|
89 | - $this->attributes['startLine'] = $line; |
|
90 | - $this->updateMessage(); |
|
91 | - } |
|
92 | - |
|
93 | - /** |
|
94 | - * Returns whether the error has start and end column information. |
|
95 | - * |
|
96 | - * For column information enable the startFilePos and endFilePos in the lexer options. |
|
97 | - * |
|
98 | - * @return bool |
|
99 | - */ |
|
100 | - public function hasColumnInfo() : bool { |
|
101 | - return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); |
|
102 | - } |
|
103 | - |
|
104 | - /** |
|
105 | - * Gets the start column (1-based) into the line where the error started. |
|
106 | - * |
|
107 | - * @param string $code Source code of the file |
|
108 | - * @return int |
|
109 | - */ |
|
110 | - public function getStartColumn(string $code) : int { |
|
111 | - if (!$this->hasColumnInfo()) { |
|
112 | - throw new \RuntimeException('Error does not have column information'); |
|
113 | - } |
|
114 | - |
|
115 | - return $this->toColumn($code, $this->attributes['startFilePos']); |
|
116 | - } |
|
117 | - |
|
118 | - /** |
|
119 | - * Gets the end column (1-based) into the line where the error ended. |
|
120 | - * |
|
121 | - * @param string $code Source code of the file |
|
122 | - * @return int |
|
123 | - */ |
|
124 | - public function getEndColumn(string $code) : int { |
|
125 | - if (!$this->hasColumnInfo()) { |
|
126 | - throw new \RuntimeException('Error does not have column information'); |
|
127 | - } |
|
128 | - |
|
129 | - return $this->toColumn($code, $this->attributes['endFilePos']); |
|
130 | - } |
|
131 | - |
|
132 | - /** |
|
133 | - * Formats message including line and column information. |
|
134 | - * |
|
135 | - * @param string $code Source code associated with the error, for calculation of the columns |
|
136 | - * |
|
137 | - * @return string Formatted message |
|
138 | - */ |
|
139 | - public function getMessageWithColumnInfo(string $code) : string { |
|
140 | - return sprintf( |
|
141 | - '%s from %d:%d to %d:%d', $this->getRawMessage(), |
|
142 | - $this->getStartLine(), $this->getStartColumn($code), |
|
143 | - $this->getEndLine(), $this->getEndColumn($code) |
|
144 | - ); |
|
145 | - } |
|
146 | - |
|
147 | - /** |
|
148 | - * Converts a file offset into a column. |
|
149 | - * |
|
150 | - * @param string $code Source code that $pos indexes into |
|
151 | - * @param int $pos 0-based position in $code |
|
152 | - * |
|
153 | - * @return int 1-based column (relative to start of line) |
|
154 | - */ |
|
155 | - private function toColumn(string $code, int $pos) : int { |
|
156 | - if ($pos > strlen($code)) { |
|
157 | - throw new \RuntimeException('Invalid position information'); |
|
158 | - } |
|
159 | - |
|
160 | - $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); |
|
161 | - if (false === $lineStartPos) { |
|
162 | - $lineStartPos = -1; |
|
163 | - } |
|
164 | - |
|
165 | - return $pos - $lineStartPos; |
|
166 | - } |
|
167 | - |
|
168 | - /** |
|
169 | - * Updates the exception message after a change to rawMessage or rawLine. |
|
170 | - */ |
|
171 | - protected function updateMessage() { |
|
172 | - $this->message = $this->rawMessage; |
|
173 | - |
|
174 | - if (-1 === $this->getStartLine()) { |
|
175 | - $this->message .= ' on unknown line'; |
|
176 | - } else { |
|
177 | - $this->message .= ' on line ' . $this->getStartLine(); |
|
178 | - } |
|
179 | - } |
|
7 | + protected $rawMessage; |
|
8 | + protected $attributes; |
|
9 | + |
|
10 | + /** |
|
11 | + * Creates an Exception signifying a parse error. |
|
12 | + * |
|
13 | + * @param string $message Error message |
|
14 | + * @param array|int $attributes Attributes of node/token where error occurred |
|
15 | + * (or start line of error -- deprecated) |
|
16 | + */ |
|
17 | + public function __construct(string $message, $attributes = []) { |
|
18 | + $this->rawMessage = $message; |
|
19 | + if (is_array($attributes)) { |
|
20 | + $this->attributes = $attributes; |
|
21 | + } else { |
|
22 | + $this->attributes = ['startLine' => $attributes]; |
|
23 | + } |
|
24 | + $this->updateMessage(); |
|
25 | + } |
|
26 | + |
|
27 | + /** |
|
28 | + * Gets the error message |
|
29 | + * |
|
30 | + * @return string Error message |
|
31 | + */ |
|
32 | + public function getRawMessage() : string { |
|
33 | + return $this->rawMessage; |
|
34 | + } |
|
35 | + |
|
36 | + /** |
|
37 | + * Gets the line the error starts in. |
|
38 | + * |
|
39 | + * @return int Error start line |
|
40 | + */ |
|
41 | + public function getStartLine() : int { |
|
42 | + return $this->attributes['startLine'] ?? -1; |
|
43 | + } |
|
44 | + |
|
45 | + /** |
|
46 | + * Gets the line the error ends in. |
|
47 | + * |
|
48 | + * @return int Error end line |
|
49 | + */ |
|
50 | + public function getEndLine() : int { |
|
51 | + return $this->attributes['endLine'] ?? -1; |
|
52 | + } |
|
53 | + |
|
54 | + /** |
|
55 | + * Gets the attributes of the node/token the error occurred at. |
|
56 | + * |
|
57 | + * @return array |
|
58 | + */ |
|
59 | + public function getAttributes() : array { |
|
60 | + return $this->attributes; |
|
61 | + } |
|
62 | + |
|
63 | + /** |
|
64 | + * Sets the attributes of the node/token the error occurred at. |
|
65 | + * |
|
66 | + * @param array $attributes |
|
67 | + */ |
|
68 | + public function setAttributes(array $attributes) { |
|
69 | + $this->attributes = $attributes; |
|
70 | + $this->updateMessage(); |
|
71 | + } |
|
72 | + |
|
73 | + /** |
|
74 | + * Sets the line of the PHP file the error occurred in. |
|
75 | + * |
|
76 | + * @param string $message Error message |
|
77 | + */ |
|
78 | + public function setRawMessage(string $message) { |
|
79 | + $this->rawMessage = $message; |
|
80 | + $this->updateMessage(); |
|
81 | + } |
|
82 | + |
|
83 | + /** |
|
84 | + * Sets the line the error starts in. |
|
85 | + * |
|
86 | + * @param int $line Error start line |
|
87 | + */ |
|
88 | + public function setStartLine(int $line) { |
|
89 | + $this->attributes['startLine'] = $line; |
|
90 | + $this->updateMessage(); |
|
91 | + } |
|
92 | + |
|
93 | + /** |
|
94 | + * Returns whether the error has start and end column information. |
|
95 | + * |
|
96 | + * For column information enable the startFilePos and endFilePos in the lexer options. |
|
97 | + * |
|
98 | + * @return bool |
|
99 | + */ |
|
100 | + public function hasColumnInfo() : bool { |
|
101 | + return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']); |
|
102 | + } |
|
103 | + |
|
104 | + /** |
|
105 | + * Gets the start column (1-based) into the line where the error started. |
|
106 | + * |
|
107 | + * @param string $code Source code of the file |
|
108 | + * @return int |
|
109 | + */ |
|
110 | + public function getStartColumn(string $code) : int { |
|
111 | + if (!$this->hasColumnInfo()) { |
|
112 | + throw new \RuntimeException('Error does not have column information'); |
|
113 | + } |
|
114 | + |
|
115 | + return $this->toColumn($code, $this->attributes['startFilePos']); |
|
116 | + } |
|
117 | + |
|
118 | + /** |
|
119 | + * Gets the end column (1-based) into the line where the error ended. |
|
120 | + * |
|
121 | + * @param string $code Source code of the file |
|
122 | + * @return int |
|
123 | + */ |
|
124 | + public function getEndColumn(string $code) : int { |
|
125 | + if (!$this->hasColumnInfo()) { |
|
126 | + throw new \RuntimeException('Error does not have column information'); |
|
127 | + } |
|
128 | + |
|
129 | + return $this->toColumn($code, $this->attributes['endFilePos']); |
|
130 | + } |
|
131 | + |
|
132 | + /** |
|
133 | + * Formats message including line and column information. |
|
134 | + * |
|
135 | + * @param string $code Source code associated with the error, for calculation of the columns |
|
136 | + * |
|
137 | + * @return string Formatted message |
|
138 | + */ |
|
139 | + public function getMessageWithColumnInfo(string $code) : string { |
|
140 | + return sprintf( |
|
141 | + '%s from %d:%d to %d:%d', $this->getRawMessage(), |
|
142 | + $this->getStartLine(), $this->getStartColumn($code), |
|
143 | + $this->getEndLine(), $this->getEndColumn($code) |
|
144 | + ); |
|
145 | + } |
|
146 | + |
|
147 | + /** |
|
148 | + * Converts a file offset into a column. |
|
149 | + * |
|
150 | + * @param string $code Source code that $pos indexes into |
|
151 | + * @param int $pos 0-based position in $code |
|
152 | + * |
|
153 | + * @return int 1-based column (relative to start of line) |
|
154 | + */ |
|
155 | + private function toColumn(string $code, int $pos) : int { |
|
156 | + if ($pos > strlen($code)) { |
|
157 | + throw new \RuntimeException('Invalid position information'); |
|
158 | + } |
|
159 | + |
|
160 | + $lineStartPos = strrpos($code, "\n", $pos - strlen($code)); |
|
161 | + if (false === $lineStartPos) { |
|
162 | + $lineStartPos = -1; |
|
163 | + } |
|
164 | + |
|
165 | + return $pos - $lineStartPos; |
|
166 | + } |
|
167 | + |
|
168 | + /** |
|
169 | + * Updates the exception message after a change to rawMessage or rawLine. |
|
170 | + */ |
|
171 | + protected function updateMessage() { |
|
172 | + $this->message = $this->rawMessage; |
|
173 | + |
|
174 | + if (-1 === $this->getStartLine()) { |
|
175 | + $this->message .= ' on unknown line'; |
|
176 | + } else { |
|
177 | + $this->message .= ' on line ' . $this->getStartLine(); |
|
178 | + } |
|
179 | + } |
|
180 | 180 | } |
@@ -174,7 +174,7 @@ |
||
174 | 174 | if (-1 === $this->getStartLine()) { |
175 | 175 | $this->message .= ' on unknown line'; |
176 | 176 | } else { |
177 | - $this->message .= ' on line ' . $this->getStartLine(); |
|
177 | + $this->message .= ' on line '.$this->getStartLine(); |
|
178 | 178 | } |
179 | 179 | } |
180 | 180 | } |
@@ -4,15 +4,15 @@ |
||
4 | 4 | |
5 | 5 | interface Parser |
6 | 6 | { |
7 | - /** |
|
8 | - * Parses PHP code into a node tree. |
|
9 | - * |
|
10 | - * @param string $code The source code to parse |
|
11 | - * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults |
|
12 | - * to ErrorHandler\Throwing. |
|
13 | - * |
|
14 | - * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and |
|
15 | - * the parser was unable to recover from an error). |
|
16 | - */ |
|
17 | - public function parse(string $code, ?ErrorHandler $errorHandler = null); |
|
7 | + /** |
|
8 | + * Parses PHP code into a node tree. |
|
9 | + * |
|
10 | + * @param string $code The source code to parse |
|
11 | + * @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults |
|
12 | + * to ErrorHandler\Throwing. |
|
13 | + * |
|
14 | + * @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and |
|
15 | + * the parser was unable to recover from an error). |
|
16 | + */ |
|
17 | + public function parse(string $code, ?ErrorHandler $errorHandler = null); |
|
18 | 18 | } |
@@ -2,8 +2,7 @@ |
||
2 | 2 | |
3 | 3 | namespace PhpParser; |
4 | 4 | |
5 | -interface Parser |
|
6 | -{ |
|
5 | +interface Parser { |
|
7 | 6 | /** |
8 | 7 | * Parses PHP code into a node tree. |
9 | 8 | * |
@@ -8,278 +8,278 @@ |
||
8 | 8 | |
9 | 9 | class NameContext |
10 | 10 | { |
11 | - /** @var null|Name Current namespace */ |
|
12 | - protected $namespace; |
|
13 | - |
|
14 | - /** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */ |
|
15 | - protected $aliases = []; |
|
16 | - |
|
17 | - /** @var Name[][] Same as $aliases but preserving original case */ |
|
18 | - protected $origAliases = []; |
|
19 | - |
|
20 | - /** @var ErrorHandler Error handler */ |
|
21 | - protected $errorHandler; |
|
22 | - |
|
23 | - /** |
|
24 | - * Create a name context. |
|
25 | - * |
|
26 | - * @param ErrorHandler $errorHandler Error handling used to report errors |
|
27 | - */ |
|
28 | - public function __construct(ErrorHandler $errorHandler) { |
|
29 | - $this->errorHandler = $errorHandler; |
|
30 | - } |
|
31 | - |
|
32 | - /** |
|
33 | - * Start a new namespace. |
|
34 | - * |
|
35 | - * This also resets the alias table. |
|
36 | - * |
|
37 | - * @param Name|null $namespace Null is the global namespace |
|
38 | - */ |
|
39 | - public function startNamespace(?Name $namespace = null) { |
|
40 | - $this->namespace = $namespace; |
|
41 | - $this->origAliases = $this->aliases = [ |
|
42 | - Stmt\Use_::TYPE_NORMAL => [], |
|
43 | - Stmt\Use_::TYPE_FUNCTION => [], |
|
44 | - Stmt\Use_::TYPE_CONSTANT => [], |
|
45 | - ]; |
|
46 | - } |
|
47 | - |
|
48 | - /** |
|
49 | - * Add an alias / import. |
|
50 | - * |
|
51 | - * @param Name $name Original name |
|
52 | - * @param string $aliasName Aliased name |
|
53 | - * @param int $type One of Stmt\Use_::TYPE_* |
|
54 | - * @param array $errorAttrs Attributes to use to report an error |
|
55 | - */ |
|
56 | - public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []) { |
|
57 | - // Constant names are case sensitive, everything else case insensitive |
|
58 | - if ($type === Stmt\Use_::TYPE_CONSTANT) { |
|
59 | - $aliasLookupName = $aliasName; |
|
60 | - } else { |
|
61 | - $aliasLookupName = strtolower($aliasName); |
|
62 | - } |
|
63 | - |
|
64 | - if (isset($this->aliases[$type][$aliasLookupName])) { |
|
65 | - $typeStringMap = [ |
|
66 | - Stmt\Use_::TYPE_NORMAL => '', |
|
67 | - Stmt\Use_::TYPE_FUNCTION => 'function ', |
|
68 | - Stmt\Use_::TYPE_CONSTANT => 'const ', |
|
69 | - ]; |
|
70 | - |
|
71 | - $this->errorHandler->handleError(new Error( |
|
72 | - sprintf( |
|
73 | - 'Cannot use %s%s as %s because the name is already in use', |
|
74 | - $typeStringMap[$type], $name, $aliasName |
|
75 | - ), |
|
76 | - $errorAttrs |
|
77 | - )); |
|
78 | - return; |
|
79 | - } |
|
80 | - |
|
81 | - $this->aliases[$type][$aliasLookupName] = $name; |
|
82 | - $this->origAliases[$type][$aliasName] = $name; |
|
83 | - } |
|
84 | - |
|
85 | - /** |
|
86 | - * Get current namespace. |
|
87 | - * |
|
88 | - * @return null|Name Namespace (or null if global namespace) |
|
89 | - */ |
|
90 | - public function getNamespace() { |
|
91 | - return $this->namespace; |
|
92 | - } |
|
93 | - |
|
94 | - /** |
|
95 | - * Get resolved name. |
|
96 | - * |
|
97 | - * @param Name $name Name to resolve |
|
98 | - * @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} |
|
99 | - * |
|
100 | - * @return null|Name Resolved name, or null if static resolution is not possible |
|
101 | - */ |
|
102 | - public function getResolvedName(Name $name, int $type) { |
|
103 | - // don't resolve special class names |
|
104 | - if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { |
|
105 | - if (!$name->isUnqualified()) { |
|
106 | - $this->errorHandler->handleError(new Error( |
|
107 | - sprintf("'\\%s' is an invalid class name", $name->toString()), |
|
108 | - $name->getAttributes() |
|
109 | - )); |
|
110 | - } |
|
111 | - return $name; |
|
112 | - } |
|
113 | - |
|
114 | - // fully qualified names are already resolved |
|
115 | - if ($name->isFullyQualified()) { |
|
116 | - return $name; |
|
117 | - } |
|
118 | - |
|
119 | - // Try to resolve aliases |
|
120 | - if (null !== $resolvedName = $this->resolveAlias($name, $type)) { |
|
121 | - return $resolvedName; |
|
122 | - } |
|
123 | - |
|
124 | - if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { |
|
125 | - if (null === $this->namespace) { |
|
126 | - // outside of a namespace unaliased unqualified is same as fully qualified |
|
127 | - return new FullyQualified($name, $name->getAttributes()); |
|
128 | - } |
|
129 | - |
|
130 | - // Cannot resolve statically |
|
131 | - return null; |
|
132 | - } |
|
133 | - |
|
134 | - // if no alias exists prepend current namespace |
|
135 | - return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); |
|
136 | - } |
|
137 | - |
|
138 | - /** |
|
139 | - * Get resolved class name. |
|
140 | - * |
|
141 | - * @param Name $name Class ame to resolve |
|
142 | - * |
|
143 | - * @return Name Resolved name |
|
144 | - */ |
|
145 | - public function getResolvedClassName(Name $name) : Name { |
|
146 | - return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); |
|
147 | - } |
|
148 | - |
|
149 | - /** |
|
150 | - * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). |
|
151 | - * |
|
152 | - * @param string $name Fully-qualified name (without leading namespace separator) |
|
153 | - * @param int $type One of Stmt\Use_::TYPE_* |
|
154 | - * |
|
155 | - * @return Name[] Possible representations of the name |
|
156 | - */ |
|
157 | - public function getPossibleNames(string $name, int $type) : array { |
|
158 | - $lcName = strtolower($name); |
|
159 | - |
|
160 | - if ($type === Stmt\Use_::TYPE_NORMAL) { |
|
161 | - // self, parent and static must always be unqualified |
|
162 | - if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { |
|
163 | - return [new Name($name)]; |
|
164 | - } |
|
165 | - } |
|
166 | - |
|
167 | - // Collect possible ways to write this name, starting with the fully-qualified name |
|
168 | - $possibleNames = [new FullyQualified($name)]; |
|
169 | - |
|
170 | - if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) { |
|
171 | - // Make sure there is no alias that makes the normally namespace-relative name |
|
172 | - // into something else |
|
173 | - if (null === $this->resolveAlias($nsRelativeName, $type)) { |
|
174 | - $possibleNames[] = $nsRelativeName; |
|
175 | - } |
|
176 | - } |
|
177 | - |
|
178 | - // Check for relevant namespace use statements |
|
179 | - foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { |
|
180 | - $lcOrig = $orig->toLowerString(); |
|
181 | - if (0 === strpos($lcName, $lcOrig . '\\')) { |
|
182 | - $possibleNames[] = new Name($alias . substr($name, strlen($lcOrig))); |
|
183 | - } |
|
184 | - } |
|
185 | - |
|
186 | - // Check for relevant type-specific use statements |
|
187 | - foreach ($this->origAliases[$type] as $alias => $orig) { |
|
188 | - if ($type === Stmt\Use_::TYPE_CONSTANT) { |
|
189 | - // Constants are are complicated-sensitive |
|
190 | - $normalizedOrig = $this->normalizeConstName($orig->toString()); |
|
191 | - if ($normalizedOrig === $this->normalizeConstName($name)) { |
|
192 | - $possibleNames[] = new Name($alias); |
|
193 | - } |
|
194 | - } else { |
|
195 | - // Everything else is case-insensitive |
|
196 | - if ($orig->toLowerString() === $lcName) { |
|
197 | - $possibleNames[] = new Name($alias); |
|
198 | - } |
|
199 | - } |
|
200 | - } |
|
201 | - |
|
202 | - return $possibleNames; |
|
203 | - } |
|
204 | - |
|
205 | - /** |
|
206 | - * Get shortest representation of this fully-qualified name. |
|
207 | - * |
|
208 | - * @param string $name Fully-qualified name (without leading namespace separator) |
|
209 | - * @param int $type One of Stmt\Use_::TYPE_* |
|
210 | - * |
|
211 | - * @return Name Shortest representation |
|
212 | - */ |
|
213 | - public function getShortName(string $name, int $type) : Name { |
|
214 | - $possibleNames = $this->getPossibleNames($name, $type); |
|
215 | - |
|
216 | - // Find shortest name |
|
217 | - $shortestName = null; |
|
218 | - $shortestLength = \INF; |
|
219 | - foreach ($possibleNames as $possibleName) { |
|
220 | - $length = strlen($possibleName->toCodeString()); |
|
221 | - if ($length < $shortestLength) { |
|
222 | - $shortestName = $possibleName; |
|
223 | - $shortestLength = $length; |
|
224 | - } |
|
225 | - } |
|
226 | - |
|
227 | - return $shortestName; |
|
228 | - } |
|
229 | - |
|
230 | - private function resolveAlias(Name $name, $type) { |
|
231 | - $firstPart = $name->getFirst(); |
|
232 | - |
|
233 | - if ($name->isQualified()) { |
|
234 | - // resolve aliases for qualified names, always against class alias table |
|
235 | - $checkName = strtolower($firstPart); |
|
236 | - if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { |
|
237 | - $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; |
|
238 | - return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); |
|
239 | - } |
|
240 | - } elseif ($name->isUnqualified()) { |
|
241 | - // constant aliases are case-sensitive, function aliases case-insensitive |
|
242 | - $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart); |
|
243 | - if (isset($this->aliases[$type][$checkName])) { |
|
244 | - // resolve unqualified aliases |
|
245 | - return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); |
|
246 | - } |
|
247 | - } |
|
248 | - |
|
249 | - // No applicable aliases |
|
250 | - return null; |
|
251 | - } |
|
252 | - |
|
253 | - private function getNamespaceRelativeName(string $name, string $lcName, int $type) { |
|
254 | - if (null === $this->namespace) { |
|
255 | - return new Name($name); |
|
256 | - } |
|
257 | - |
|
258 | - if ($type === Stmt\Use_::TYPE_CONSTANT) { |
|
259 | - // The constants true/false/null always resolve to the global symbols, even inside a |
|
260 | - // namespace, so they may be used without qualification |
|
261 | - if ($lcName === "true" || $lcName === "false" || $lcName === "null") { |
|
262 | - return new Name($name); |
|
263 | - } |
|
264 | - } |
|
265 | - |
|
266 | - $namespacePrefix = strtolower($this->namespace . '\\'); |
|
267 | - if (0 === strpos($lcName, $namespacePrefix)) { |
|
268 | - return new Name(substr($name, strlen($namespacePrefix))); |
|
269 | - } |
|
270 | - |
|
271 | - return null; |
|
272 | - } |
|
273 | - |
|
274 | - private function normalizeConstName(string $name) { |
|
275 | - $nsSep = strrpos($name, '\\'); |
|
276 | - if (false === $nsSep) { |
|
277 | - return $name; |
|
278 | - } |
|
279 | - |
|
280 | - // Constants have case-insensitive namespace and case-sensitive short-name |
|
281 | - $ns = substr($name, 0, $nsSep); |
|
282 | - $shortName = substr($name, $nsSep + 1); |
|
283 | - return strtolower($ns) . '\\' . $shortName; |
|
284 | - } |
|
11 | + /** @var null|Name Current namespace */ |
|
12 | + protected $namespace; |
|
13 | + |
|
14 | + /** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */ |
|
15 | + protected $aliases = []; |
|
16 | + |
|
17 | + /** @var Name[][] Same as $aliases but preserving original case */ |
|
18 | + protected $origAliases = []; |
|
19 | + |
|
20 | + /** @var ErrorHandler Error handler */ |
|
21 | + protected $errorHandler; |
|
22 | + |
|
23 | + /** |
|
24 | + * Create a name context. |
|
25 | + * |
|
26 | + * @param ErrorHandler $errorHandler Error handling used to report errors |
|
27 | + */ |
|
28 | + public function __construct(ErrorHandler $errorHandler) { |
|
29 | + $this->errorHandler = $errorHandler; |
|
30 | + } |
|
31 | + |
|
32 | + /** |
|
33 | + * Start a new namespace. |
|
34 | + * |
|
35 | + * This also resets the alias table. |
|
36 | + * |
|
37 | + * @param Name|null $namespace Null is the global namespace |
|
38 | + */ |
|
39 | + public function startNamespace(?Name $namespace = null) { |
|
40 | + $this->namespace = $namespace; |
|
41 | + $this->origAliases = $this->aliases = [ |
|
42 | + Stmt\Use_::TYPE_NORMAL => [], |
|
43 | + Stmt\Use_::TYPE_FUNCTION => [], |
|
44 | + Stmt\Use_::TYPE_CONSTANT => [], |
|
45 | + ]; |
|
46 | + } |
|
47 | + |
|
48 | + /** |
|
49 | + * Add an alias / import. |
|
50 | + * |
|
51 | + * @param Name $name Original name |
|
52 | + * @param string $aliasName Aliased name |
|
53 | + * @param int $type One of Stmt\Use_::TYPE_* |
|
54 | + * @param array $errorAttrs Attributes to use to report an error |
|
55 | + */ |
|
56 | + public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []) { |
|
57 | + // Constant names are case sensitive, everything else case insensitive |
|
58 | + if ($type === Stmt\Use_::TYPE_CONSTANT) { |
|
59 | + $aliasLookupName = $aliasName; |
|
60 | + } else { |
|
61 | + $aliasLookupName = strtolower($aliasName); |
|
62 | + } |
|
63 | + |
|
64 | + if (isset($this->aliases[$type][$aliasLookupName])) { |
|
65 | + $typeStringMap = [ |
|
66 | + Stmt\Use_::TYPE_NORMAL => '', |
|
67 | + Stmt\Use_::TYPE_FUNCTION => 'function ', |
|
68 | + Stmt\Use_::TYPE_CONSTANT => 'const ', |
|
69 | + ]; |
|
70 | + |
|
71 | + $this->errorHandler->handleError(new Error( |
|
72 | + sprintf( |
|
73 | + 'Cannot use %s%s as %s because the name is already in use', |
|
74 | + $typeStringMap[$type], $name, $aliasName |
|
75 | + ), |
|
76 | + $errorAttrs |
|
77 | + )); |
|
78 | + return; |
|
79 | + } |
|
80 | + |
|
81 | + $this->aliases[$type][$aliasLookupName] = $name; |
|
82 | + $this->origAliases[$type][$aliasName] = $name; |
|
83 | + } |
|
84 | + |
|
85 | + /** |
|
86 | + * Get current namespace. |
|
87 | + * |
|
88 | + * @return null|Name Namespace (or null if global namespace) |
|
89 | + */ |
|
90 | + public function getNamespace() { |
|
91 | + return $this->namespace; |
|
92 | + } |
|
93 | + |
|
94 | + /** |
|
95 | + * Get resolved name. |
|
96 | + * |
|
97 | + * @param Name $name Name to resolve |
|
98 | + * @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT} |
|
99 | + * |
|
100 | + * @return null|Name Resolved name, or null if static resolution is not possible |
|
101 | + */ |
|
102 | + public function getResolvedName(Name $name, int $type) { |
|
103 | + // don't resolve special class names |
|
104 | + if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) { |
|
105 | + if (!$name->isUnqualified()) { |
|
106 | + $this->errorHandler->handleError(new Error( |
|
107 | + sprintf("'\\%s' is an invalid class name", $name->toString()), |
|
108 | + $name->getAttributes() |
|
109 | + )); |
|
110 | + } |
|
111 | + return $name; |
|
112 | + } |
|
113 | + |
|
114 | + // fully qualified names are already resolved |
|
115 | + if ($name->isFullyQualified()) { |
|
116 | + return $name; |
|
117 | + } |
|
118 | + |
|
119 | + // Try to resolve aliases |
|
120 | + if (null !== $resolvedName = $this->resolveAlias($name, $type)) { |
|
121 | + return $resolvedName; |
|
122 | + } |
|
123 | + |
|
124 | + if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) { |
|
125 | + if (null === $this->namespace) { |
|
126 | + // outside of a namespace unaliased unqualified is same as fully qualified |
|
127 | + return new FullyQualified($name, $name->getAttributes()); |
|
128 | + } |
|
129 | + |
|
130 | + // Cannot resolve statically |
|
131 | + return null; |
|
132 | + } |
|
133 | + |
|
134 | + // if no alias exists prepend current namespace |
|
135 | + return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); |
|
136 | + } |
|
137 | + |
|
138 | + /** |
|
139 | + * Get resolved class name. |
|
140 | + * |
|
141 | + * @param Name $name Class ame to resolve |
|
142 | + * |
|
143 | + * @return Name Resolved name |
|
144 | + */ |
|
145 | + public function getResolvedClassName(Name $name) : Name { |
|
146 | + return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL); |
|
147 | + } |
|
148 | + |
|
149 | + /** |
|
150 | + * Get possible ways of writing a fully qualified name (e.g., by making use of aliases). |
|
151 | + * |
|
152 | + * @param string $name Fully-qualified name (without leading namespace separator) |
|
153 | + * @param int $type One of Stmt\Use_::TYPE_* |
|
154 | + * |
|
155 | + * @return Name[] Possible representations of the name |
|
156 | + */ |
|
157 | + public function getPossibleNames(string $name, int $type) : array { |
|
158 | + $lcName = strtolower($name); |
|
159 | + |
|
160 | + if ($type === Stmt\Use_::TYPE_NORMAL) { |
|
161 | + // self, parent and static must always be unqualified |
|
162 | + if ($lcName === "self" || $lcName === "parent" || $lcName === "static") { |
|
163 | + return [new Name($name)]; |
|
164 | + } |
|
165 | + } |
|
166 | + |
|
167 | + // Collect possible ways to write this name, starting with the fully-qualified name |
|
168 | + $possibleNames = [new FullyQualified($name)]; |
|
169 | + |
|
170 | + if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) { |
|
171 | + // Make sure there is no alias that makes the normally namespace-relative name |
|
172 | + // into something else |
|
173 | + if (null === $this->resolveAlias($nsRelativeName, $type)) { |
|
174 | + $possibleNames[] = $nsRelativeName; |
|
175 | + } |
|
176 | + } |
|
177 | + |
|
178 | + // Check for relevant namespace use statements |
|
179 | + foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { |
|
180 | + $lcOrig = $orig->toLowerString(); |
|
181 | + if (0 === strpos($lcName, $lcOrig . '\\')) { |
|
182 | + $possibleNames[] = new Name($alias . substr($name, strlen($lcOrig))); |
|
183 | + } |
|
184 | + } |
|
185 | + |
|
186 | + // Check for relevant type-specific use statements |
|
187 | + foreach ($this->origAliases[$type] as $alias => $orig) { |
|
188 | + if ($type === Stmt\Use_::TYPE_CONSTANT) { |
|
189 | + // Constants are are complicated-sensitive |
|
190 | + $normalizedOrig = $this->normalizeConstName($orig->toString()); |
|
191 | + if ($normalizedOrig === $this->normalizeConstName($name)) { |
|
192 | + $possibleNames[] = new Name($alias); |
|
193 | + } |
|
194 | + } else { |
|
195 | + // Everything else is case-insensitive |
|
196 | + if ($orig->toLowerString() === $lcName) { |
|
197 | + $possibleNames[] = new Name($alias); |
|
198 | + } |
|
199 | + } |
|
200 | + } |
|
201 | + |
|
202 | + return $possibleNames; |
|
203 | + } |
|
204 | + |
|
205 | + /** |
|
206 | + * Get shortest representation of this fully-qualified name. |
|
207 | + * |
|
208 | + * @param string $name Fully-qualified name (without leading namespace separator) |
|
209 | + * @param int $type One of Stmt\Use_::TYPE_* |
|
210 | + * |
|
211 | + * @return Name Shortest representation |
|
212 | + */ |
|
213 | + public function getShortName(string $name, int $type) : Name { |
|
214 | + $possibleNames = $this->getPossibleNames($name, $type); |
|
215 | + |
|
216 | + // Find shortest name |
|
217 | + $shortestName = null; |
|
218 | + $shortestLength = \INF; |
|
219 | + foreach ($possibleNames as $possibleName) { |
|
220 | + $length = strlen($possibleName->toCodeString()); |
|
221 | + if ($length < $shortestLength) { |
|
222 | + $shortestName = $possibleName; |
|
223 | + $shortestLength = $length; |
|
224 | + } |
|
225 | + } |
|
226 | + |
|
227 | + return $shortestName; |
|
228 | + } |
|
229 | + |
|
230 | + private function resolveAlias(Name $name, $type) { |
|
231 | + $firstPart = $name->getFirst(); |
|
232 | + |
|
233 | + if ($name->isQualified()) { |
|
234 | + // resolve aliases for qualified names, always against class alias table |
|
235 | + $checkName = strtolower($firstPart); |
|
236 | + if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) { |
|
237 | + $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName]; |
|
238 | + return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); |
|
239 | + } |
|
240 | + } elseif ($name->isUnqualified()) { |
|
241 | + // constant aliases are case-sensitive, function aliases case-insensitive |
|
242 | + $checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart); |
|
243 | + if (isset($this->aliases[$type][$checkName])) { |
|
244 | + // resolve unqualified aliases |
|
245 | + return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes()); |
|
246 | + } |
|
247 | + } |
|
248 | + |
|
249 | + // No applicable aliases |
|
250 | + return null; |
|
251 | + } |
|
252 | + |
|
253 | + private function getNamespaceRelativeName(string $name, string $lcName, int $type) { |
|
254 | + if (null === $this->namespace) { |
|
255 | + return new Name($name); |
|
256 | + } |
|
257 | + |
|
258 | + if ($type === Stmt\Use_::TYPE_CONSTANT) { |
|
259 | + // The constants true/false/null always resolve to the global symbols, even inside a |
|
260 | + // namespace, so they may be used without qualification |
|
261 | + if ($lcName === "true" || $lcName === "false" || $lcName === "null") { |
|
262 | + return new Name($name); |
|
263 | + } |
|
264 | + } |
|
265 | + |
|
266 | + $namespacePrefix = strtolower($this->namespace . '\\'); |
|
267 | + if (0 === strpos($lcName, $namespacePrefix)) { |
|
268 | + return new Name(substr($name, strlen($namespacePrefix))); |
|
269 | + } |
|
270 | + |
|
271 | + return null; |
|
272 | + } |
|
273 | + |
|
274 | + private function normalizeConstName(string $name) { |
|
275 | + $nsSep = strrpos($name, '\\'); |
|
276 | + if (false === $nsSep) { |
|
277 | + return $name; |
|
278 | + } |
|
279 | + |
|
280 | + // Constants have case-insensitive namespace and case-sensitive short-name |
|
281 | + $ns = substr($name, 0, $nsSep); |
|
282 | + $shortName = substr($name, $nsSep + 1); |
|
283 | + return strtolower($ns) . '\\' . $shortName; |
|
284 | + } |
|
285 | 285 | } |
@@ -178,8 +178,8 @@ discard block |
||
178 | 178 | // Check for relevant namespace use statements |
179 | 179 | foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) { |
180 | 180 | $lcOrig = $orig->toLowerString(); |
181 | - if (0 === strpos($lcName, $lcOrig . '\\')) { |
|
182 | - $possibleNames[] = new Name($alias . substr($name, strlen($lcOrig))); |
|
181 | + if (0 === strpos($lcName, $lcOrig.'\\')) { |
|
182 | + $possibleNames[] = new Name($alias.substr($name, strlen($lcOrig))); |
|
183 | 183 | } |
184 | 184 | } |
185 | 185 | |
@@ -263,7 +263,7 @@ discard block |
||
263 | 263 | } |
264 | 264 | } |
265 | 265 | |
266 | - $namespacePrefix = strtolower($this->namespace . '\\'); |
|
266 | + $namespacePrefix = strtolower($this->namespace.'\\'); |
|
267 | 267 | if (0 === strpos($lcName, $namespacePrefix)) { |
268 | 268 | return new Name(substr($name, strlen($namespacePrefix))); |
269 | 269 | } |
@@ -280,6 +280,6 @@ discard block |
||
280 | 280 | // Constants have case-insensitive namespace and case-sensitive short-name |
281 | 281 | $ns = substr($name, 0, $nsSep); |
282 | 282 | $shortName = substr($name, $nsSep + 1); |
283 | - return strtolower($ns) . '\\' . $shortName; |
|
283 | + return strtolower($ns).'\\'.$shortName; |
|
284 | 284 | } |
285 | 285 | } |
@@ -6,8 +6,7 @@ |
||
6 | 6 | use PhpParser\Node\Name\FullyQualified; |
7 | 7 | use PhpParser\Node\Stmt; |
8 | 8 | |
9 | -class NameContext |
|
10 | -{ |
|
9 | +class NameContext { |
|
11 | 10 | /** @var null|Name Current namespace */ |
12 | 11 | protected $namespace; |
13 | 12 |
@@ -7,19 +7,19 @@ |
||
7 | 7 | */ |
8 | 8 | class NodeVisitorAbstract implements NodeVisitor |
9 | 9 | { |
10 | - public function beforeTraverse(array $nodes) { |
|
11 | - return null; |
|
12 | - } |
|
10 | + public function beforeTraverse(array $nodes) { |
|
11 | + return null; |
|
12 | + } |
|
13 | 13 | |
14 | - public function enterNode(Node $node) { |
|
15 | - return null; |
|
16 | - } |
|
14 | + public function enterNode(Node $node) { |
|
15 | + return null; |
|
16 | + } |
|
17 | 17 | |
18 | - public function leaveNode(Node $node) { |
|
19 | - return null; |
|
20 | - } |
|
18 | + public function leaveNode(Node $node) { |
|
19 | + return null; |
|
20 | + } |
|
21 | 21 | |
22 | - public function afterTraverse(array $nodes) { |
|
23 | - return null; |
|
24 | - } |
|
22 | + public function afterTraverse(array $nodes) { |
|
23 | + return null; |
|
24 | + } |
|
25 | 25 | } |
@@ -5,8 +5,7 @@ |
||
5 | 5 | /** |
6 | 6 | * @codeCoverageIgnore |
7 | 7 | */ |
8 | -class NodeVisitorAbstract implements NodeVisitor |
|
9 | -{ |
|
8 | +class NodeVisitorAbstract implements NodeVisitor { |
|
10 | 9 | public function beforeTraverse(array $nodes) { |
11 | 10 | return null; |
12 | 11 | } |
@@ -4,236 +4,236 @@ |
||
4 | 4 | |
5 | 5 | class Comment implements \JsonSerializable |
6 | 6 | { |
7 | - protected $text; |
|
8 | - protected $startLine; |
|
9 | - protected $startFilePos; |
|
10 | - protected $startTokenPos; |
|
11 | - protected $endLine; |
|
12 | - protected $endFilePos; |
|
13 | - protected $endTokenPos; |
|
14 | - |
|
15 | - /** |
|
16 | - * Constructs a comment node. |
|
17 | - * |
|
18 | - * @param string $text Comment text (including comment delimiters like /*) |
|
19 | - * @param int $startLine Line number the comment started on |
|
20 | - * @param int $startFilePos File offset the comment started on |
|
21 | - * @param int $startTokenPos Token offset the comment started on |
|
22 | - */ |
|
23 | - public function __construct( |
|
24 | - string $text, |
|
25 | - int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1, |
|
26 | - int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1 |
|
27 | - ) { |
|
28 | - $this->text = $text; |
|
29 | - $this->startLine = $startLine; |
|
30 | - $this->startFilePos = $startFilePos; |
|
31 | - $this->startTokenPos = $startTokenPos; |
|
32 | - $this->endLine = $endLine; |
|
33 | - $this->endFilePos = $endFilePos; |
|
34 | - $this->endTokenPos = $endTokenPos; |
|
35 | - } |
|
36 | - |
|
37 | - /** |
|
38 | - * Gets the comment text. |
|
39 | - * |
|
40 | - * @return string The comment text (including comment delimiters like /*) |
|
41 | - */ |
|
42 | - public function getText() : string { |
|
43 | - return $this->text; |
|
44 | - } |
|
45 | - |
|
46 | - /** |
|
47 | - * Gets the line number the comment started on. |
|
48 | - * |
|
49 | - * @return int Line number (or -1 if not available) |
|
50 | - */ |
|
51 | - public function getStartLine() : int { |
|
52 | - return $this->startLine; |
|
53 | - } |
|
54 | - |
|
55 | - /** |
|
56 | - * Gets the file offset the comment started on. |
|
57 | - * |
|
58 | - * @return int File offset (or -1 if not available) |
|
59 | - */ |
|
60 | - public function getStartFilePos() : int { |
|
61 | - return $this->startFilePos; |
|
62 | - } |
|
63 | - |
|
64 | - /** |
|
65 | - * Gets the token offset the comment started on. |
|
66 | - * |
|
67 | - * @return int Token offset (or -1 if not available) |
|
68 | - */ |
|
69 | - public function getStartTokenPos() : int { |
|
70 | - return $this->startTokenPos; |
|
71 | - } |
|
72 | - |
|
73 | - /** |
|
74 | - * Gets the line number the comment ends on. |
|
75 | - * |
|
76 | - * @return int Line number (or -1 if not available) |
|
77 | - */ |
|
78 | - public function getEndLine() : int { |
|
79 | - return $this->endLine; |
|
80 | - } |
|
81 | - |
|
82 | - /** |
|
83 | - * Gets the file offset the comment ends on. |
|
84 | - * |
|
85 | - * @return int File offset (or -1 if not available) |
|
86 | - */ |
|
87 | - public function getEndFilePos() : int { |
|
88 | - return $this->endFilePos; |
|
89 | - } |
|
90 | - |
|
91 | - /** |
|
92 | - * Gets the token offset the comment ends on. |
|
93 | - * |
|
94 | - * @return int Token offset (or -1 if not available) |
|
95 | - */ |
|
96 | - public function getEndTokenPos() : int { |
|
97 | - return $this->endTokenPos; |
|
98 | - } |
|
99 | - |
|
100 | - /** |
|
101 | - * Gets the line number the comment started on. |
|
102 | - * |
|
103 | - * @deprecated Use getStartLine() instead |
|
104 | - * |
|
105 | - * @return int Line number |
|
106 | - */ |
|
107 | - public function getLine() : int { |
|
108 | - return $this->startLine; |
|
109 | - } |
|
110 | - |
|
111 | - /** |
|
112 | - * Gets the file offset the comment started on. |
|
113 | - * |
|
114 | - * @deprecated Use getStartFilePos() instead |
|
115 | - * |
|
116 | - * @return int File offset |
|
117 | - */ |
|
118 | - public function getFilePos() : int { |
|
119 | - return $this->startFilePos; |
|
120 | - } |
|
121 | - |
|
122 | - /** |
|
123 | - * Gets the token offset the comment started on. |
|
124 | - * |
|
125 | - * @deprecated Use getStartTokenPos() instead |
|
126 | - * |
|
127 | - * @return int Token offset |
|
128 | - */ |
|
129 | - public function getTokenPos() : int { |
|
130 | - return $this->startTokenPos; |
|
131 | - } |
|
132 | - |
|
133 | - /** |
|
134 | - * Gets the comment text. |
|
135 | - * |
|
136 | - * @return string The comment text (including comment delimiters like /*) |
|
137 | - */ |
|
138 | - public function __toString() : string { |
|
139 | - return $this->text; |
|
140 | - } |
|
141 | - |
|
142 | - /** |
|
143 | - * Gets the reformatted comment text. |
|
144 | - * |
|
145 | - * "Reformatted" here means that we try to clean up the whitespace at the |
|
146 | - * starts of the lines. This is necessary because we receive the comments |
|
147 | - * without trailing whitespace on the first line, but with trailing whitespace |
|
148 | - * on all subsequent lines. |
|
149 | - * |
|
150 | - * @return mixed|string |
|
151 | - */ |
|
152 | - public function getReformattedText() { |
|
153 | - $text = trim($this->text); |
|
154 | - $newlinePos = strpos($text, "\n"); |
|
155 | - if (false === $newlinePos) { |
|
156 | - // Single line comments don't need further processing |
|
157 | - return $text; |
|
158 | - } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) { |
|
159 | - // Multi line comment of the type |
|
160 | - // |
|
161 | - // /* |
|
162 | - // * Some text. |
|
163 | - // * Some more text. |
|
164 | - // */ |
|
165 | - // |
|
166 | - // is handled by replacing the whitespace sequences before the * by a single space |
|
167 | - return preg_replace('(^\s+\*)m', ' *', $this->text); |
|
168 | - } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) { |
|
169 | - // Multi line comment of the type |
|
170 | - // |
|
171 | - // /* |
|
172 | - // Some text. |
|
173 | - // Some more text. |
|
174 | - // */ |
|
175 | - // |
|
176 | - // is handled by removing the whitespace sequence on the line before the closing |
|
177 | - // */ on all lines. So if the last line is " */", then " " is removed at the |
|
178 | - // start of all lines. |
|
179 | - return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text); |
|
180 | - } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) { |
|
181 | - // Multi line comment of the type |
|
182 | - // |
|
183 | - // /* Some text. |
|
184 | - // Some more text. |
|
185 | - // Indented text. |
|
186 | - // Even more text. */ |
|
187 | - // |
|
188 | - // is handled by removing the difference between the shortest whitespace prefix on all |
|
189 | - // lines and the length of the "/* " opening sequence. |
|
190 | - $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1)); |
|
191 | - $removeLen = $prefixLen - strlen($matches[0]); |
|
192 | - return preg_replace('(^\s{' . $removeLen . '})m', '', $text); |
|
193 | - } |
|
194 | - |
|
195 | - // No idea how to format this comment, so simply return as is |
|
196 | - return $text; |
|
197 | - } |
|
198 | - |
|
199 | - /** |
|
200 | - * Get length of shortest whitespace prefix (at the start of a line). |
|
201 | - * |
|
202 | - * If there is a line with no prefix whitespace, 0 is a valid return value. |
|
203 | - * |
|
204 | - * @param string $str String to check |
|
205 | - * @return int Length in characters. Tabs count as single characters. |
|
206 | - */ |
|
207 | - private function getShortestWhitespacePrefixLen(string $str) : int { |
|
208 | - $lines = explode("\n", $str); |
|
209 | - $shortestPrefixLen = \INF; |
|
210 | - foreach ($lines as $line) { |
|
211 | - preg_match('(^\s*)', $line, $matches); |
|
212 | - $prefixLen = strlen($matches[0]); |
|
213 | - if ($prefixLen < $shortestPrefixLen) { |
|
214 | - $shortestPrefixLen = $prefixLen; |
|
215 | - } |
|
216 | - } |
|
217 | - return $shortestPrefixLen; |
|
218 | - } |
|
219 | - |
|
220 | - /** |
|
221 | - * @return array |
|
222 | - * @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} |
|
223 | - */ |
|
224 | - public function jsonSerialize() : array { |
|
225 | - // Technically not a node, but we make it look like one anyway |
|
226 | - $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; |
|
227 | - return [ |
|
228 | - 'nodeType' => $type, |
|
229 | - 'text' => $this->text, |
|
230 | - // TODO: Rename these to include "start". |
|
231 | - 'line' => $this->startLine, |
|
232 | - 'filePos' => $this->startFilePos, |
|
233 | - 'tokenPos' => $this->startTokenPos, |
|
234 | - 'endLine' => $this->endLine, |
|
235 | - 'endFilePos' => $this->endFilePos, |
|
236 | - 'endTokenPos' => $this->endTokenPos, |
|
237 | - ]; |
|
238 | - } |
|
7 | + protected $text; |
|
8 | + protected $startLine; |
|
9 | + protected $startFilePos; |
|
10 | + protected $startTokenPos; |
|
11 | + protected $endLine; |
|
12 | + protected $endFilePos; |
|
13 | + protected $endTokenPos; |
|
14 | + |
|
15 | + /** |
|
16 | + * Constructs a comment node. |
|
17 | + * |
|
18 | + * @param string $text Comment text (including comment delimiters like /*) |
|
19 | + * @param int $startLine Line number the comment started on |
|
20 | + * @param int $startFilePos File offset the comment started on |
|
21 | + * @param int $startTokenPos Token offset the comment started on |
|
22 | + */ |
|
23 | + public function __construct( |
|
24 | + string $text, |
|
25 | + int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1, |
|
26 | + int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1 |
|
27 | + ) { |
|
28 | + $this->text = $text; |
|
29 | + $this->startLine = $startLine; |
|
30 | + $this->startFilePos = $startFilePos; |
|
31 | + $this->startTokenPos = $startTokenPos; |
|
32 | + $this->endLine = $endLine; |
|
33 | + $this->endFilePos = $endFilePos; |
|
34 | + $this->endTokenPos = $endTokenPos; |
|
35 | + } |
|
36 | + |
|
37 | + /** |
|
38 | + * Gets the comment text. |
|
39 | + * |
|
40 | + * @return string The comment text (including comment delimiters like /*) |
|
41 | + */ |
|
42 | + public function getText() : string { |
|
43 | + return $this->text; |
|
44 | + } |
|
45 | + |
|
46 | + /** |
|
47 | + * Gets the line number the comment started on. |
|
48 | + * |
|
49 | + * @return int Line number (or -1 if not available) |
|
50 | + */ |
|
51 | + public function getStartLine() : int { |
|
52 | + return $this->startLine; |
|
53 | + } |
|
54 | + |
|
55 | + /** |
|
56 | + * Gets the file offset the comment started on. |
|
57 | + * |
|
58 | + * @return int File offset (or -1 if not available) |
|
59 | + */ |
|
60 | + public function getStartFilePos() : int { |
|
61 | + return $this->startFilePos; |
|
62 | + } |
|
63 | + |
|
64 | + /** |
|
65 | + * Gets the token offset the comment started on. |
|
66 | + * |
|
67 | + * @return int Token offset (or -1 if not available) |
|
68 | + */ |
|
69 | + public function getStartTokenPos() : int { |
|
70 | + return $this->startTokenPos; |
|
71 | + } |
|
72 | + |
|
73 | + /** |
|
74 | + * Gets the line number the comment ends on. |
|
75 | + * |
|
76 | + * @return int Line number (or -1 if not available) |
|
77 | + */ |
|
78 | + public function getEndLine() : int { |
|
79 | + return $this->endLine; |
|
80 | + } |
|
81 | + |
|
82 | + /** |
|
83 | + * Gets the file offset the comment ends on. |
|
84 | + * |
|
85 | + * @return int File offset (or -1 if not available) |
|
86 | + */ |
|
87 | + public function getEndFilePos() : int { |
|
88 | + return $this->endFilePos; |
|
89 | + } |
|
90 | + |
|
91 | + /** |
|
92 | + * Gets the token offset the comment ends on. |
|
93 | + * |
|
94 | + * @return int Token offset (or -1 if not available) |
|
95 | + */ |
|
96 | + public function getEndTokenPos() : int { |
|
97 | + return $this->endTokenPos; |
|
98 | + } |
|
99 | + |
|
100 | + /** |
|
101 | + * Gets the line number the comment started on. |
|
102 | + * |
|
103 | + * @deprecated Use getStartLine() instead |
|
104 | + * |
|
105 | + * @return int Line number |
|
106 | + */ |
|
107 | + public function getLine() : int { |
|
108 | + return $this->startLine; |
|
109 | + } |
|
110 | + |
|
111 | + /** |
|
112 | + * Gets the file offset the comment started on. |
|
113 | + * |
|
114 | + * @deprecated Use getStartFilePos() instead |
|
115 | + * |
|
116 | + * @return int File offset |
|
117 | + */ |
|
118 | + public function getFilePos() : int { |
|
119 | + return $this->startFilePos; |
|
120 | + } |
|
121 | + |
|
122 | + /** |
|
123 | + * Gets the token offset the comment started on. |
|
124 | + * |
|
125 | + * @deprecated Use getStartTokenPos() instead |
|
126 | + * |
|
127 | + * @return int Token offset |
|
128 | + */ |
|
129 | + public function getTokenPos() : int { |
|
130 | + return $this->startTokenPos; |
|
131 | + } |
|
132 | + |
|
133 | + /** |
|
134 | + * Gets the comment text. |
|
135 | + * |
|
136 | + * @return string The comment text (including comment delimiters like /*) |
|
137 | + */ |
|
138 | + public function __toString() : string { |
|
139 | + return $this->text; |
|
140 | + } |
|
141 | + |
|
142 | + /** |
|
143 | + * Gets the reformatted comment text. |
|
144 | + * |
|
145 | + * "Reformatted" here means that we try to clean up the whitespace at the |
|
146 | + * starts of the lines. This is necessary because we receive the comments |
|
147 | + * without trailing whitespace on the first line, but with trailing whitespace |
|
148 | + * on all subsequent lines. |
|
149 | + * |
|
150 | + * @return mixed|string |
|
151 | + */ |
|
152 | + public function getReformattedText() { |
|
153 | + $text = trim($this->text); |
|
154 | + $newlinePos = strpos($text, "\n"); |
|
155 | + if (false === $newlinePos) { |
|
156 | + // Single line comments don't need further processing |
|
157 | + return $text; |
|
158 | + } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) { |
|
159 | + // Multi line comment of the type |
|
160 | + // |
|
161 | + // /* |
|
162 | + // * Some text. |
|
163 | + // * Some more text. |
|
164 | + // */ |
|
165 | + // |
|
166 | + // is handled by replacing the whitespace sequences before the * by a single space |
|
167 | + return preg_replace('(^\s+\*)m', ' *', $this->text); |
|
168 | + } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) { |
|
169 | + // Multi line comment of the type |
|
170 | + // |
|
171 | + // /* |
|
172 | + // Some text. |
|
173 | + // Some more text. |
|
174 | + // */ |
|
175 | + // |
|
176 | + // is handled by removing the whitespace sequence on the line before the closing |
|
177 | + // */ on all lines. So if the last line is " */", then " " is removed at the |
|
178 | + // start of all lines. |
|
179 | + return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text); |
|
180 | + } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) { |
|
181 | + // Multi line comment of the type |
|
182 | + // |
|
183 | + // /* Some text. |
|
184 | + // Some more text. |
|
185 | + // Indented text. |
|
186 | + // Even more text. */ |
|
187 | + // |
|
188 | + // is handled by removing the difference between the shortest whitespace prefix on all |
|
189 | + // lines and the length of the "/* " opening sequence. |
|
190 | + $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1)); |
|
191 | + $removeLen = $prefixLen - strlen($matches[0]); |
|
192 | + return preg_replace('(^\s{' . $removeLen . '})m', '', $text); |
|
193 | + } |
|
194 | + |
|
195 | + // No idea how to format this comment, so simply return as is |
|
196 | + return $text; |
|
197 | + } |
|
198 | + |
|
199 | + /** |
|
200 | + * Get length of shortest whitespace prefix (at the start of a line). |
|
201 | + * |
|
202 | + * If there is a line with no prefix whitespace, 0 is a valid return value. |
|
203 | + * |
|
204 | + * @param string $str String to check |
|
205 | + * @return int Length in characters. Tabs count as single characters. |
|
206 | + */ |
|
207 | + private function getShortestWhitespacePrefixLen(string $str) : int { |
|
208 | + $lines = explode("\n", $str); |
|
209 | + $shortestPrefixLen = \INF; |
|
210 | + foreach ($lines as $line) { |
|
211 | + preg_match('(^\s*)', $line, $matches); |
|
212 | + $prefixLen = strlen($matches[0]); |
|
213 | + if ($prefixLen < $shortestPrefixLen) { |
|
214 | + $shortestPrefixLen = $prefixLen; |
|
215 | + } |
|
216 | + } |
|
217 | + return $shortestPrefixLen; |
|
218 | + } |
|
219 | + |
|
220 | + /** |
|
221 | + * @return array |
|
222 | + * @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed} |
|
223 | + */ |
|
224 | + public function jsonSerialize() : array { |
|
225 | + // Technically not a node, but we make it look like one anyway |
|
226 | + $type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment'; |
|
227 | + return [ |
|
228 | + 'nodeType' => $type, |
|
229 | + 'text' => $this->text, |
|
230 | + // TODO: Rename these to include "start". |
|
231 | + 'line' => $this->startLine, |
|
232 | + 'filePos' => $this->startFilePos, |
|
233 | + 'tokenPos' => $this->startTokenPos, |
|
234 | + 'endLine' => $this->endLine, |
|
235 | + 'endFilePos' => $this->endFilePos, |
|
236 | + 'endTokenPos' => $this->endTokenPos, |
|
237 | + ]; |
|
238 | + } |
|
239 | 239 | } |
@@ -176,7 +176,7 @@ discard block |
||
176 | 176 | // is handled by removing the whitespace sequence on the line before the closing |
177 | 177 | // */ on all lines. So if the last line is " */", then " " is removed at the |
178 | 178 | // start of all lines. |
179 | - return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text); |
|
179 | + return preg_replace('(^'.preg_quote($matches[1]).')m', '', $text); |
|
180 | 180 | } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) { |
181 | 181 | // Multi line comment of the type |
182 | 182 | // |
@@ -189,7 +189,7 @@ discard block |
||
189 | 189 | // lines and the length of the "/* " opening sequence. |
190 | 190 | $prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1)); |
191 | 191 | $removeLen = $prefixLen - strlen($matches[0]); |
192 | - return preg_replace('(^\s{' . $removeLen . '})m', '', $text); |
|
192 | + return preg_replace('(^\s{'.$removeLen.'})m', '', $text); |
|
193 | 193 | } |
194 | 194 | |
195 | 195 | // No idea how to format this comment, so simply return as is |
@@ -17,319 +17,319 @@ |
||
17 | 17 | */ |
18 | 18 | final class BuilderHelpers |
19 | 19 | { |
20 | - /** |
|
21 | - * Normalizes a node: Converts builder objects to nodes. |
|
22 | - * |
|
23 | - * @param Node|Builder $node The node to normalize |
|
24 | - * |
|
25 | - * @return Node The normalized node |
|
26 | - */ |
|
27 | - public static function normalizeNode($node) : Node { |
|
28 | - if ($node instanceof Builder) { |
|
29 | - return $node->getNode(); |
|
30 | - } |
|
31 | - |
|
32 | - if ($node instanceof Node) { |
|
33 | - return $node; |
|
34 | - } |
|
35 | - |
|
36 | - throw new \LogicException('Expected node or builder object'); |
|
37 | - } |
|
38 | - |
|
39 | - /** |
|
40 | - * Normalizes a node to a statement. |
|
41 | - * |
|
42 | - * Expressions are wrapped in a Stmt\Expression node. |
|
43 | - * |
|
44 | - * @param Node|Builder $node The node to normalize |
|
45 | - * |
|
46 | - * @return Stmt The normalized statement node |
|
47 | - */ |
|
48 | - public static function normalizeStmt($node) : Stmt { |
|
49 | - $node = self::normalizeNode($node); |
|
50 | - if ($node instanceof Stmt) { |
|
51 | - return $node; |
|
52 | - } |
|
53 | - |
|
54 | - if ($node instanceof Expr) { |
|
55 | - return new Stmt\Expression($node); |
|
56 | - } |
|
57 | - |
|
58 | - throw new \LogicException('Expected statement or expression node'); |
|
59 | - } |
|
60 | - |
|
61 | - /** |
|
62 | - * Normalizes strings to Identifier. |
|
63 | - * |
|
64 | - * @param string|Identifier $name The identifier to normalize |
|
65 | - * |
|
66 | - * @return Identifier The normalized identifier |
|
67 | - */ |
|
68 | - public static function normalizeIdentifier($name) : Identifier { |
|
69 | - if ($name instanceof Identifier) { |
|
70 | - return $name; |
|
71 | - } |
|
72 | - |
|
73 | - if (\is_string($name)) { |
|
74 | - return new Identifier($name); |
|
75 | - } |
|
76 | - |
|
77 | - throw new \LogicException('Expected string or instance of Node\Identifier'); |
|
78 | - } |
|
79 | - |
|
80 | - /** |
|
81 | - * Normalizes strings to Identifier, also allowing expressions. |
|
82 | - * |
|
83 | - * @param string|Identifier|Expr $name The identifier to normalize |
|
84 | - * |
|
85 | - * @return Identifier|Expr The normalized identifier or expression |
|
86 | - */ |
|
87 | - public static function normalizeIdentifierOrExpr($name) { |
|
88 | - if ($name instanceof Identifier || $name instanceof Expr) { |
|
89 | - return $name; |
|
90 | - } |
|
91 | - |
|
92 | - if (\is_string($name)) { |
|
93 | - return new Identifier($name); |
|
94 | - } |
|
95 | - |
|
96 | - throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr'); |
|
97 | - } |
|
98 | - |
|
99 | - /** |
|
100 | - * Normalizes a name: Converts string names to Name nodes. |
|
101 | - * |
|
102 | - * @param Name|string $name The name to normalize |
|
103 | - * |
|
104 | - * @return Name The normalized name |
|
105 | - */ |
|
106 | - public static function normalizeName($name) : Name { |
|
107 | - if ($name instanceof Name) { |
|
108 | - return $name; |
|
109 | - } |
|
110 | - |
|
111 | - if (is_string($name)) { |
|
112 | - if (!$name) { |
|
113 | - throw new \LogicException('Name cannot be empty'); |
|
114 | - } |
|
115 | - |
|
116 | - if ($name[0] === '\\') { |
|
117 | - return new Name\FullyQualified(substr($name, 1)); |
|
118 | - } |
|
119 | - |
|
120 | - if (0 === strpos($name, 'namespace\\')) { |
|
121 | - return new Name\Relative(substr($name, strlen('namespace\\'))); |
|
122 | - } |
|
123 | - |
|
124 | - return new Name($name); |
|
125 | - } |
|
126 | - |
|
127 | - throw new \LogicException('Name must be a string or an instance of Node\Name'); |
|
128 | - } |
|
129 | - |
|
130 | - /** |
|
131 | - * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. |
|
132 | - * |
|
133 | - * @param Expr|Name|string $name The name to normalize |
|
134 | - * |
|
135 | - * @return Name|Expr The normalized name or expression |
|
136 | - */ |
|
137 | - public static function normalizeNameOrExpr($name) { |
|
138 | - if ($name instanceof Expr) { |
|
139 | - return $name; |
|
140 | - } |
|
141 | - |
|
142 | - if (!is_string($name) && !($name instanceof Name)) { |
|
143 | - throw new \LogicException( |
|
144 | - 'Name must be a string or an instance of Node\Name or Node\Expr' |
|
145 | - ); |
|
146 | - } |
|
147 | - |
|
148 | - return self::normalizeName($name); |
|
149 | - } |
|
150 | - |
|
151 | - /** |
|
152 | - * Normalizes a type: Converts plain-text type names into proper AST representation. |
|
153 | - * |
|
154 | - * In particular, builtin types become Identifiers, custom types become Names and nullables |
|
155 | - * are wrapped in NullableType nodes. |
|
156 | - * |
|
157 | - * @param string|Name|Identifier|ComplexType $type The type to normalize |
|
158 | - * |
|
159 | - * @return Name|Identifier|ComplexType The normalized type |
|
160 | - */ |
|
161 | - public static function normalizeType($type) { |
|
162 | - if (!is_string($type)) { |
|
163 | - if ( |
|
164 | - !$type instanceof Name && !$type instanceof Identifier && |
|
165 | - !$type instanceof ComplexType |
|
166 | - ) { |
|
167 | - throw new \LogicException( |
|
168 | - 'Type must be a string, or an instance of Name, Identifier or ComplexType' |
|
169 | - ); |
|
170 | - } |
|
171 | - return $type; |
|
172 | - } |
|
173 | - |
|
174 | - $nullable = false; |
|
175 | - if (strlen($type) > 0 && $type[0] === '?') { |
|
176 | - $nullable = true; |
|
177 | - $type = substr($type, 1); |
|
178 | - } |
|
179 | - |
|
180 | - $builtinTypes = [ |
|
181 | - 'array', |
|
182 | - 'callable', |
|
183 | - 'bool', |
|
184 | - 'int', |
|
185 | - 'float', |
|
186 | - 'string', |
|
187 | - 'iterable', |
|
188 | - 'void', |
|
189 | - 'object', |
|
190 | - 'null', |
|
191 | - 'false', |
|
192 | - 'mixed', |
|
193 | - 'never', |
|
194 | - 'true', |
|
195 | - ]; |
|
196 | - |
|
197 | - $lowerType = strtolower($type); |
|
198 | - if (in_array($lowerType, $builtinTypes)) { |
|
199 | - $type = new Identifier($lowerType); |
|
200 | - } else { |
|
201 | - $type = self::normalizeName($type); |
|
202 | - } |
|
203 | - |
|
204 | - $notNullableTypes = [ |
|
205 | - 'void', 'mixed', 'never', |
|
206 | - ]; |
|
207 | - if ($nullable && in_array((string) $type, $notNullableTypes)) { |
|
208 | - throw new \LogicException(sprintf('%s type cannot be nullable', $type)); |
|
209 | - } |
|
210 | - |
|
211 | - return $nullable ? new NullableType($type) : $type; |
|
212 | - } |
|
213 | - |
|
214 | - /** |
|
215 | - * Normalizes a value: Converts nulls, booleans, integers, |
|
216 | - * floats, strings and arrays into their respective nodes |
|
217 | - * |
|
218 | - * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize |
|
219 | - * |
|
220 | - * @return Expr The normalized value |
|
221 | - */ |
|
222 | - public static function normalizeValue($value) : Expr { |
|
223 | - if ($value instanceof Node\Expr) { |
|
224 | - return $value; |
|
225 | - } |
|
226 | - |
|
227 | - if (is_null($value)) { |
|
228 | - return new Expr\ConstFetch( |
|
229 | - new Name('null') |
|
230 | - ); |
|
231 | - } |
|
232 | - |
|
233 | - if (is_bool($value)) { |
|
234 | - return new Expr\ConstFetch( |
|
235 | - new Name($value ? 'true' : 'false') |
|
236 | - ); |
|
237 | - } |
|
238 | - |
|
239 | - if (is_int($value)) { |
|
240 | - return new Scalar\LNumber($value); |
|
241 | - } |
|
242 | - |
|
243 | - if (is_float($value)) { |
|
244 | - return new Scalar\DNumber($value); |
|
245 | - } |
|
246 | - |
|
247 | - if (is_string($value)) { |
|
248 | - return new Scalar\String_($value); |
|
249 | - } |
|
250 | - |
|
251 | - if (is_array($value)) { |
|
252 | - $items = []; |
|
253 | - $lastKey = -1; |
|
254 | - foreach ($value as $itemKey => $itemValue) { |
|
255 | - // for consecutive, numeric keys don't generate keys |
|
256 | - if (null !== $lastKey && ++$lastKey === $itemKey) { |
|
257 | - $items[] = new Expr\ArrayItem( |
|
258 | - self::normalizeValue($itemValue) |
|
259 | - ); |
|
260 | - } else { |
|
261 | - $lastKey = null; |
|
262 | - $items[] = new Expr\ArrayItem( |
|
263 | - self::normalizeValue($itemValue), |
|
264 | - self::normalizeValue($itemKey) |
|
265 | - ); |
|
266 | - } |
|
267 | - } |
|
268 | - |
|
269 | - return new Expr\Array_($items); |
|
270 | - } |
|
271 | - |
|
272 | - throw new \LogicException('Invalid value'); |
|
273 | - } |
|
274 | - |
|
275 | - /** |
|
276 | - * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. |
|
277 | - * |
|
278 | - * @param Comment\Doc|string $docComment The doc comment to normalize |
|
279 | - * |
|
280 | - * @return Comment\Doc The normalized doc comment |
|
281 | - */ |
|
282 | - public static function normalizeDocComment($docComment) : Comment\Doc { |
|
283 | - if ($docComment instanceof Comment\Doc) { |
|
284 | - return $docComment; |
|
285 | - } |
|
286 | - |
|
287 | - if (is_string($docComment)) { |
|
288 | - return new Comment\Doc($docComment); |
|
289 | - } |
|
290 | - |
|
291 | - throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); |
|
292 | - } |
|
293 | - |
|
294 | - /** |
|
295 | - * Normalizes a attribute: Converts attribute to the Attribute Group if needed. |
|
296 | - * |
|
297 | - * @param Node\Attribute|Node\AttributeGroup $attribute |
|
298 | - * |
|
299 | - * @return Node\AttributeGroup The Attribute Group |
|
300 | - */ |
|
301 | - public static function normalizeAttribute($attribute) : Node\AttributeGroup |
|
302 | - { |
|
303 | - if ($attribute instanceof Node\AttributeGroup) { |
|
304 | - return $attribute; |
|
305 | - } |
|
306 | - |
|
307 | - if (!($attribute instanceof Node\Attribute)) { |
|
308 | - throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup'); |
|
309 | - } |
|
310 | - |
|
311 | - return new Node\AttributeGroup([$attribute]); |
|
312 | - } |
|
313 | - |
|
314 | - /** |
|
315 | - * Adds a modifier and returns new modifier bitmask. |
|
316 | - * |
|
317 | - * @param int $modifiers Existing modifiers |
|
318 | - * @param int $modifier Modifier to set |
|
319 | - * |
|
320 | - * @return int New modifiers |
|
321 | - */ |
|
322 | - public static function addModifier(int $modifiers, int $modifier) : int { |
|
323 | - Stmt\Class_::verifyModifier($modifiers, $modifier); |
|
324 | - return $modifiers | $modifier; |
|
325 | - } |
|
326 | - |
|
327 | - /** |
|
328 | - * Adds a modifier and returns new modifier bitmask. |
|
329 | - * @return int New modifiers |
|
330 | - */ |
|
331 | - public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int { |
|
332 | - Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet); |
|
333 | - return $existingModifiers | $modifierToSet; |
|
334 | - } |
|
20 | + /** |
|
21 | + * Normalizes a node: Converts builder objects to nodes. |
|
22 | + * |
|
23 | + * @param Node|Builder $node The node to normalize |
|
24 | + * |
|
25 | + * @return Node The normalized node |
|
26 | + */ |
|
27 | + public static function normalizeNode($node) : Node { |
|
28 | + if ($node instanceof Builder) { |
|
29 | + return $node->getNode(); |
|
30 | + } |
|
31 | + |
|
32 | + if ($node instanceof Node) { |
|
33 | + return $node; |
|
34 | + } |
|
35 | + |
|
36 | + throw new \LogicException('Expected node or builder object'); |
|
37 | + } |
|
38 | + |
|
39 | + /** |
|
40 | + * Normalizes a node to a statement. |
|
41 | + * |
|
42 | + * Expressions are wrapped in a Stmt\Expression node. |
|
43 | + * |
|
44 | + * @param Node|Builder $node The node to normalize |
|
45 | + * |
|
46 | + * @return Stmt The normalized statement node |
|
47 | + */ |
|
48 | + public static function normalizeStmt($node) : Stmt { |
|
49 | + $node = self::normalizeNode($node); |
|
50 | + if ($node instanceof Stmt) { |
|
51 | + return $node; |
|
52 | + } |
|
53 | + |
|
54 | + if ($node instanceof Expr) { |
|
55 | + return new Stmt\Expression($node); |
|
56 | + } |
|
57 | + |
|
58 | + throw new \LogicException('Expected statement or expression node'); |
|
59 | + } |
|
60 | + |
|
61 | + /** |
|
62 | + * Normalizes strings to Identifier. |
|
63 | + * |
|
64 | + * @param string|Identifier $name The identifier to normalize |
|
65 | + * |
|
66 | + * @return Identifier The normalized identifier |
|
67 | + */ |
|
68 | + public static function normalizeIdentifier($name) : Identifier { |
|
69 | + if ($name instanceof Identifier) { |
|
70 | + return $name; |
|
71 | + } |
|
72 | + |
|
73 | + if (\is_string($name)) { |
|
74 | + return new Identifier($name); |
|
75 | + } |
|
76 | + |
|
77 | + throw new \LogicException('Expected string or instance of Node\Identifier'); |
|
78 | + } |
|
79 | + |
|
80 | + /** |
|
81 | + * Normalizes strings to Identifier, also allowing expressions. |
|
82 | + * |
|
83 | + * @param string|Identifier|Expr $name The identifier to normalize |
|
84 | + * |
|
85 | + * @return Identifier|Expr The normalized identifier or expression |
|
86 | + */ |
|
87 | + public static function normalizeIdentifierOrExpr($name) { |
|
88 | + if ($name instanceof Identifier || $name instanceof Expr) { |
|
89 | + return $name; |
|
90 | + } |
|
91 | + |
|
92 | + if (\is_string($name)) { |
|
93 | + return new Identifier($name); |
|
94 | + } |
|
95 | + |
|
96 | + throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr'); |
|
97 | + } |
|
98 | + |
|
99 | + /** |
|
100 | + * Normalizes a name: Converts string names to Name nodes. |
|
101 | + * |
|
102 | + * @param Name|string $name The name to normalize |
|
103 | + * |
|
104 | + * @return Name The normalized name |
|
105 | + */ |
|
106 | + public static function normalizeName($name) : Name { |
|
107 | + if ($name instanceof Name) { |
|
108 | + return $name; |
|
109 | + } |
|
110 | + |
|
111 | + if (is_string($name)) { |
|
112 | + if (!$name) { |
|
113 | + throw new \LogicException('Name cannot be empty'); |
|
114 | + } |
|
115 | + |
|
116 | + if ($name[0] === '\\') { |
|
117 | + return new Name\FullyQualified(substr($name, 1)); |
|
118 | + } |
|
119 | + |
|
120 | + if (0 === strpos($name, 'namespace\\')) { |
|
121 | + return new Name\Relative(substr($name, strlen('namespace\\'))); |
|
122 | + } |
|
123 | + |
|
124 | + return new Name($name); |
|
125 | + } |
|
126 | + |
|
127 | + throw new \LogicException('Name must be a string or an instance of Node\Name'); |
|
128 | + } |
|
129 | + |
|
130 | + /** |
|
131 | + * Normalizes a name: Converts string names to Name nodes, while also allowing expressions. |
|
132 | + * |
|
133 | + * @param Expr|Name|string $name The name to normalize |
|
134 | + * |
|
135 | + * @return Name|Expr The normalized name or expression |
|
136 | + */ |
|
137 | + public static function normalizeNameOrExpr($name) { |
|
138 | + if ($name instanceof Expr) { |
|
139 | + return $name; |
|
140 | + } |
|
141 | + |
|
142 | + if (!is_string($name) && !($name instanceof Name)) { |
|
143 | + throw new \LogicException( |
|
144 | + 'Name must be a string or an instance of Node\Name or Node\Expr' |
|
145 | + ); |
|
146 | + } |
|
147 | + |
|
148 | + return self::normalizeName($name); |
|
149 | + } |
|
150 | + |
|
151 | + /** |
|
152 | + * Normalizes a type: Converts plain-text type names into proper AST representation. |
|
153 | + * |
|
154 | + * In particular, builtin types become Identifiers, custom types become Names and nullables |
|
155 | + * are wrapped in NullableType nodes. |
|
156 | + * |
|
157 | + * @param string|Name|Identifier|ComplexType $type The type to normalize |
|
158 | + * |
|
159 | + * @return Name|Identifier|ComplexType The normalized type |
|
160 | + */ |
|
161 | + public static function normalizeType($type) { |
|
162 | + if (!is_string($type)) { |
|
163 | + if ( |
|
164 | + !$type instanceof Name && !$type instanceof Identifier && |
|
165 | + !$type instanceof ComplexType |
|
166 | + ) { |
|
167 | + throw new \LogicException( |
|
168 | + 'Type must be a string, or an instance of Name, Identifier or ComplexType' |
|
169 | + ); |
|
170 | + } |
|
171 | + return $type; |
|
172 | + } |
|
173 | + |
|
174 | + $nullable = false; |
|
175 | + if (strlen($type) > 0 && $type[0] === '?') { |
|
176 | + $nullable = true; |
|
177 | + $type = substr($type, 1); |
|
178 | + } |
|
179 | + |
|
180 | + $builtinTypes = [ |
|
181 | + 'array', |
|
182 | + 'callable', |
|
183 | + 'bool', |
|
184 | + 'int', |
|
185 | + 'float', |
|
186 | + 'string', |
|
187 | + 'iterable', |
|
188 | + 'void', |
|
189 | + 'object', |
|
190 | + 'null', |
|
191 | + 'false', |
|
192 | + 'mixed', |
|
193 | + 'never', |
|
194 | + 'true', |
|
195 | + ]; |
|
196 | + |
|
197 | + $lowerType = strtolower($type); |
|
198 | + if (in_array($lowerType, $builtinTypes)) { |
|
199 | + $type = new Identifier($lowerType); |
|
200 | + } else { |
|
201 | + $type = self::normalizeName($type); |
|
202 | + } |
|
203 | + |
|
204 | + $notNullableTypes = [ |
|
205 | + 'void', 'mixed', 'never', |
|
206 | + ]; |
|
207 | + if ($nullable && in_array((string) $type, $notNullableTypes)) { |
|
208 | + throw new \LogicException(sprintf('%s type cannot be nullable', $type)); |
|
209 | + } |
|
210 | + |
|
211 | + return $nullable ? new NullableType($type) : $type; |
|
212 | + } |
|
213 | + |
|
214 | + /** |
|
215 | + * Normalizes a value: Converts nulls, booleans, integers, |
|
216 | + * floats, strings and arrays into their respective nodes |
|
217 | + * |
|
218 | + * @param Node\Expr|bool|null|int|float|string|array $value The value to normalize |
|
219 | + * |
|
220 | + * @return Expr The normalized value |
|
221 | + */ |
|
222 | + public static function normalizeValue($value) : Expr { |
|
223 | + if ($value instanceof Node\Expr) { |
|
224 | + return $value; |
|
225 | + } |
|
226 | + |
|
227 | + if (is_null($value)) { |
|
228 | + return new Expr\ConstFetch( |
|
229 | + new Name('null') |
|
230 | + ); |
|
231 | + } |
|
232 | + |
|
233 | + if (is_bool($value)) { |
|
234 | + return new Expr\ConstFetch( |
|
235 | + new Name($value ? 'true' : 'false') |
|
236 | + ); |
|
237 | + } |
|
238 | + |
|
239 | + if (is_int($value)) { |
|
240 | + return new Scalar\LNumber($value); |
|
241 | + } |
|
242 | + |
|
243 | + if (is_float($value)) { |
|
244 | + return new Scalar\DNumber($value); |
|
245 | + } |
|
246 | + |
|
247 | + if (is_string($value)) { |
|
248 | + return new Scalar\String_($value); |
|
249 | + } |
|
250 | + |
|
251 | + if (is_array($value)) { |
|
252 | + $items = []; |
|
253 | + $lastKey = -1; |
|
254 | + foreach ($value as $itemKey => $itemValue) { |
|
255 | + // for consecutive, numeric keys don't generate keys |
|
256 | + if (null !== $lastKey && ++$lastKey === $itemKey) { |
|
257 | + $items[] = new Expr\ArrayItem( |
|
258 | + self::normalizeValue($itemValue) |
|
259 | + ); |
|
260 | + } else { |
|
261 | + $lastKey = null; |
|
262 | + $items[] = new Expr\ArrayItem( |
|
263 | + self::normalizeValue($itemValue), |
|
264 | + self::normalizeValue($itemKey) |
|
265 | + ); |
|
266 | + } |
|
267 | + } |
|
268 | + |
|
269 | + return new Expr\Array_($items); |
|
270 | + } |
|
271 | + |
|
272 | + throw new \LogicException('Invalid value'); |
|
273 | + } |
|
274 | + |
|
275 | + /** |
|
276 | + * Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc. |
|
277 | + * |
|
278 | + * @param Comment\Doc|string $docComment The doc comment to normalize |
|
279 | + * |
|
280 | + * @return Comment\Doc The normalized doc comment |
|
281 | + */ |
|
282 | + public static function normalizeDocComment($docComment) : Comment\Doc { |
|
283 | + if ($docComment instanceof Comment\Doc) { |
|
284 | + return $docComment; |
|
285 | + } |
|
286 | + |
|
287 | + if (is_string($docComment)) { |
|
288 | + return new Comment\Doc($docComment); |
|
289 | + } |
|
290 | + |
|
291 | + throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc'); |
|
292 | + } |
|
293 | + |
|
294 | + /** |
|
295 | + * Normalizes a attribute: Converts attribute to the Attribute Group if needed. |
|
296 | + * |
|
297 | + * @param Node\Attribute|Node\AttributeGroup $attribute |
|
298 | + * |
|
299 | + * @return Node\AttributeGroup The Attribute Group |
|
300 | + */ |
|
301 | + public static function normalizeAttribute($attribute) : Node\AttributeGroup |
|
302 | + { |
|
303 | + if ($attribute instanceof Node\AttributeGroup) { |
|
304 | + return $attribute; |
|
305 | + } |
|
306 | + |
|
307 | + if (!($attribute instanceof Node\Attribute)) { |
|
308 | + throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup'); |
|
309 | + } |
|
310 | + |
|
311 | + return new Node\AttributeGroup([$attribute]); |
|
312 | + } |
|
313 | + |
|
314 | + /** |
|
315 | + * Adds a modifier and returns new modifier bitmask. |
|
316 | + * |
|
317 | + * @param int $modifiers Existing modifiers |
|
318 | + * @param int $modifier Modifier to set |
|
319 | + * |
|
320 | + * @return int New modifiers |
|
321 | + */ |
|
322 | + public static function addModifier(int $modifiers, int $modifier) : int { |
|
323 | + Stmt\Class_::verifyModifier($modifiers, $modifier); |
|
324 | + return $modifiers | $modifier; |
|
325 | + } |
|
326 | + |
|
327 | + /** |
|
328 | + * Adds a modifier and returns new modifier bitmask. |
|
329 | + * @return int New modifiers |
|
330 | + */ |
|
331 | + public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int { |
|
332 | + Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet); |
|
333 | + return $existingModifiers | $modifierToSet; |
|
334 | + } |
|
335 | 335 | } |
@@ -204,7 +204,7 @@ |
||
204 | 204 | $notNullableTypes = [ |
205 | 205 | 'void', 'mixed', 'never', |
206 | 206 | ]; |
207 | - if ($nullable && in_array((string) $type, $notNullableTypes)) { |
|
207 | + if ($nullable && in_array((string)$type, $notNullableTypes)) { |
|
208 | 208 | throw new \LogicException(sprintf('%s type cannot be nullable', $type)); |
209 | 209 | } |
210 | 210 |
@@ -15,8 +15,7 @@ |
||
15 | 15 | * |
16 | 16 | * @internal |
17 | 17 | */ |
18 | -final class BuilderHelpers |
|
19 | -{ |
|
18 | +final class BuilderHelpers { |
|
20 | 19 | /** |
21 | 20 | * Normalizes a node: Converts builder objects to nodes. |
22 | 21 | * |
@@ -4,26 +4,26 @@ |
||
4 | 4 | |
5 | 5 | interface NodeTraverserInterface |
6 | 6 | { |
7 | - /** |
|
8 | - * Adds a visitor. |
|
9 | - * |
|
10 | - * @param NodeVisitor $visitor Visitor to add |
|
11 | - */ |
|
12 | - public function addVisitor(NodeVisitor $visitor); |
|
7 | + /** |
|
8 | + * Adds a visitor. |
|
9 | + * |
|
10 | + * @param NodeVisitor $visitor Visitor to add |
|
11 | + */ |
|
12 | + public function addVisitor(NodeVisitor $visitor); |
|
13 | 13 | |
14 | - /** |
|
15 | - * Removes an added visitor. |
|
16 | - * |
|
17 | - * @param NodeVisitor $visitor |
|
18 | - */ |
|
19 | - public function removeVisitor(NodeVisitor $visitor); |
|
14 | + /** |
|
15 | + * Removes an added visitor. |
|
16 | + * |
|
17 | + * @param NodeVisitor $visitor |
|
18 | + */ |
|
19 | + public function removeVisitor(NodeVisitor $visitor); |
|
20 | 20 | |
21 | - /** |
|
22 | - * Traverses an array of nodes using the registered visitors. |
|
23 | - * |
|
24 | - * @param Node[] $nodes Array of nodes |
|
25 | - * |
|
26 | - * @return Node[] Traversed array of nodes |
|
27 | - */ |
|
28 | - public function traverse(array $nodes) : array; |
|
21 | + /** |
|
22 | + * Traverses an array of nodes using the registered visitors. |
|
23 | + * |
|
24 | + * @param Node[] $nodes Array of nodes |
|
25 | + * |
|
26 | + * @return Node[] Traversed array of nodes |
|
27 | + */ |
|
28 | + public function traverse(array $nodes) : array; |
|
29 | 29 | } |
@@ -2,8 +2,7 @@ |
||
2 | 2 | |
3 | 3 | namespace PhpParser; |
4 | 4 | |
5 | -interface NodeTraverserInterface |
|
6 | -{ |
|
5 | +interface NodeTraverserInterface { |
|
7 | 6 | /** |
8 | 7 | * Adds a visitor. |
9 | 8 | * |
@@ -10,197 +10,197 @@ |
||
10 | 10 | |
11 | 11 | class NodeDumper |
12 | 12 | { |
13 | - private $dumpComments; |
|
14 | - private $dumpPositions; |
|
15 | - private $code; |
|
16 | - |
|
17 | - /** |
|
18 | - * Constructs a NodeDumper. |
|
19 | - * |
|
20 | - * Supported options: |
|
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(). |
|
24 | - * |
|
25 | - * @param array $options Options (see description) |
|
26 | - */ |
|
27 | - public function __construct(array $options = []) { |
|
28 | - $this->dumpComments = !empty($options['dumpComments']); |
|
29 | - $this->dumpPositions = !empty($options['dumpPositions']); |
|
30 | - } |
|
31 | - |
|
32 | - /** |
|
33 | - * Dumps a node or array. |
|
34 | - * |
|
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 |
|
38 | - * is desired. |
|
39 | - * |
|
40 | - * @return string Dumped value |
|
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 | - * Dump node position, if possible. |
|
172 | - * |
|
173 | - * @param Node $node Node for which to dump position |
|
174 | - * |
|
175 | - * @return string|null Dump of position, or null if position information not available |
|
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 | - // Copied from Error class |
|
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 | - } |
|
13 | + private $dumpComments; |
|
14 | + private $dumpPositions; |
|
15 | + private $code; |
|
16 | + |
|
17 | + /** |
|
18 | + * Constructs a NodeDumper. |
|
19 | + * |
|
20 | + * Supported options: |
|
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(). |
|
24 | + * |
|
25 | + * @param array $options Options (see description) |
|
26 | + */ |
|
27 | + public function __construct(array $options = []) { |
|
28 | + $this->dumpComments = !empty($options['dumpComments']); |
|
29 | + $this->dumpPositions = !empty($options['dumpPositions']); |
|
30 | + } |
|
31 | + |
|
32 | + /** |
|
33 | + * Dumps a node or array. |
|
34 | + * |
|
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 |
|
38 | + * is desired. |
|
39 | + * |
|
40 | + * @return string Dumped value |
|
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 | + * Dump node position, if possible. |
|
172 | + * |
|
173 | + * @param Node $node Node for which to dump position |
|
174 | + * |
|
175 | + * @return string|null Dump of position, or null if position information not available |
|
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 | + // Copied from Error class |
|
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 | 206 | } |
@@ -53,7 +53,7 @@ discard block |
||
53 | 53 | $r .= '('; |
54 | 54 | |
55 | 55 | foreach ($node->getSubNodeNames() as $key) { |
56 | - $r .= "\n " . $key . ': '; |
|
56 | + $r .= "\n ".$key.': '; |
|
57 | 57 | |
58 | 58 | $value = $node->$key; |
59 | 59 | if (null === $value) { |
@@ -79,13 +79,13 @@ discard block |
||
79 | 79 | } |
80 | 80 | |
81 | 81 | if ($this->dumpComments && $comments = $node->getComments()) { |
82 | - $r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments)); |
|
82 | + $r .= "\n comments: ".str_replace("\n", "\n ", $this->dumpRecursive($comments)); |
|
83 | 83 | } |
84 | 84 | } elseif (is_array($node)) { |
85 | 85 | $r = 'array('; |
86 | 86 | |
87 | 87 | foreach ($node as $key => $value) { |
88 | - $r .= "\n " . $key . ': '; |
|
88 | + $r .= "\n ".$key.': '; |
|
89 | 89 | |
90 | 90 | if (null === $value) { |
91 | 91 | $r .= 'null'; |
@@ -105,7 +105,7 @@ discard block |
||
105 | 105 | throw new \InvalidArgumentException('Can only dump nodes and arrays.'); |
106 | 106 | } |
107 | 107 | |
108 | - return $r . "\n)"; |
|
108 | + return $r."\n)"; |
|
109 | 109 | } |
110 | 110 | |
111 | 111 | protected function dumpFlags($flags) { |
@@ -133,7 +133,7 @@ discard block |
||
133 | 133 | } |
134 | 134 | |
135 | 135 | if ($strs) { |
136 | - return implode(' | ', $strs) . ' (' . $flags . ')'; |
|
136 | + return implode(' | ', $strs).' ('.$flags.')'; |
|
137 | 137 | } else { |
138 | 138 | return $flags; |
139 | 139 | } |
@@ -150,7 +150,7 @@ discard block |
||
150 | 150 | if (!isset($map[$type])) { |
151 | 151 | return $type; |
152 | 152 | } |
153 | - return $map[$type] . ' (' . $type . ')'; |
|
153 | + return $map[$type].' ('.$type.')'; |
|
154 | 154 | } |
155 | 155 | |
156 | 156 | protected function dumpUseType($type) { |
@@ -164,7 +164,7 @@ discard block |
||
164 | 164 | if (!isset($map[$type])) { |
165 | 165 | return $type; |
166 | 166 | } |
167 | - return $map[$type] . ' (' . $type . ')'; |
|
167 | + return $map[$type].' ('.$type.')'; |
|
168 | 168 | } |
169 | 169 | |
170 | 170 | /** |
@@ -184,8 +184,8 @@ discard block |
||
184 | 184 | if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos') |
185 | 185 | && null !== $this->code |
186 | 186 | ) { |
187 | - $start .= ':' . $this->toColumn($this->code, $node->getStartFilePos()); |
|
188 | - $end .= ':' . $this->toColumn($this->code, $node->getEndFilePos()); |
|
187 | + $start .= ':'.$this->toColumn($this->code, $node->getStartFilePos()); |
|
188 | + $end .= ':'.$this->toColumn($this->code, $node->getEndFilePos()); |
|
189 | 189 | } |
190 | 190 | return "[$start - $end]"; |
191 | 191 | } |
@@ -8,8 +8,7 @@ |
||
8 | 8 | use PhpParser\Node\Stmt\Use_; |
9 | 9 | use PhpParser\Node\Stmt\UseUse; |
10 | 10 | |
11 | -class NodeDumper |
|
12 | -{ |
|
11 | +class NodeDumper { |
|
13 | 12 | private $dumpComments; |
14 | 13 | private $dumpPositions; |
15 | 14 | private $code; |
@@ -4,69 +4,69 @@ |
||
4 | 4 | |
5 | 5 | interface NodeVisitor |
6 | 6 | { |
7 | - /** |
|
8 | - * Called once before traversal. |
|
9 | - * |
|
10 | - * Return value semantics: |
|
11 | - * * null: $nodes stays as-is |
|
12 | - * * otherwise: $nodes is set to the return value |
|
13 | - * |
|
14 | - * @param Node[] $nodes Array of nodes |
|
15 | - * |
|
16 | - * @return null|Node[] Array of nodes |
|
17 | - */ |
|
18 | - public function beforeTraverse(array $nodes); |
|
7 | + /** |
|
8 | + * Called once before traversal. |
|
9 | + * |
|
10 | + * Return value semantics: |
|
11 | + * * null: $nodes stays as-is |
|
12 | + * * otherwise: $nodes is set to the return value |
|
13 | + * |
|
14 | + * @param Node[] $nodes Array of nodes |
|
15 | + * |
|
16 | + * @return null|Node[] Array of nodes |
|
17 | + */ |
|
18 | + public function beforeTraverse(array $nodes); |
|
19 | 19 | |
20 | - /** |
|
21 | - * Called when entering a node. |
|
22 | - * |
|
23 | - * Return value semantics: |
|
24 | - * * null |
|
25 | - * => $node stays as-is |
|
26 | - * * NodeTraverser::DONT_TRAVERSE_CHILDREN |
|
27 | - * => Children of $node are not traversed. $node stays as-is |
|
28 | - * * NodeTraverser::STOP_TRAVERSAL |
|
29 | - * => Traversal is aborted. $node stays as-is |
|
30 | - * * otherwise |
|
31 | - * => $node is set to the return value |
|
32 | - * |
|
33 | - * @param Node $node Node |
|
34 | - * |
|
35 | - * @return null|int|Node Replacement node (or special return value) |
|
36 | - */ |
|
37 | - public function enterNode(Node $node); |
|
20 | + /** |
|
21 | + * Called when entering a node. |
|
22 | + * |
|
23 | + * Return value semantics: |
|
24 | + * * null |
|
25 | + * => $node stays as-is |
|
26 | + * * NodeTraverser::DONT_TRAVERSE_CHILDREN |
|
27 | + * => Children of $node are not traversed. $node stays as-is |
|
28 | + * * NodeTraverser::STOP_TRAVERSAL |
|
29 | + * => Traversal is aborted. $node stays as-is |
|
30 | + * * otherwise |
|
31 | + * => $node is set to the return value |
|
32 | + * |
|
33 | + * @param Node $node Node |
|
34 | + * |
|
35 | + * @return null|int|Node Replacement node (or special return value) |
|
36 | + */ |
|
37 | + public function enterNode(Node $node); |
|
38 | 38 | |
39 | - /** |
|
40 | - * Called when leaving a node. |
|
41 | - * |
|
42 | - * Return value semantics: |
|
43 | - * * null |
|
44 | - * => $node stays as-is |
|
45 | - * * NodeTraverser::REMOVE_NODE |
|
46 | - * => $node is removed from the parent array |
|
47 | - * * NodeTraverser::STOP_TRAVERSAL |
|
48 | - * => Traversal is aborted. $node stays as-is |
|
49 | - * * array (of Nodes) |
|
50 | - * => The return value is merged into the parent array (at the position of the $node) |
|
51 | - * * otherwise |
|
52 | - * => $node is set to the return value |
|
53 | - * |
|
54 | - * @param Node $node Node |
|
55 | - * |
|
56 | - * @return null|int|Node|Node[] Replacement node (or special return value) |
|
57 | - */ |
|
58 | - public function leaveNode(Node $node); |
|
39 | + /** |
|
40 | + * Called when leaving a node. |
|
41 | + * |
|
42 | + * Return value semantics: |
|
43 | + * * null |
|
44 | + * => $node stays as-is |
|
45 | + * * NodeTraverser::REMOVE_NODE |
|
46 | + * => $node is removed from the parent array |
|
47 | + * * NodeTraverser::STOP_TRAVERSAL |
|
48 | + * => Traversal is aborted. $node stays as-is |
|
49 | + * * array (of Nodes) |
|
50 | + * => The return value is merged into the parent array (at the position of the $node) |
|
51 | + * * otherwise |
|
52 | + * => $node is set to the return value |
|
53 | + * |
|
54 | + * @param Node $node Node |
|
55 | + * |
|
56 | + * @return null|int|Node|Node[] Replacement node (or special return value) |
|
57 | + */ |
|
58 | + public function leaveNode(Node $node); |
|
59 | 59 | |
60 | - /** |
|
61 | - * Called once after traversal. |
|
62 | - * |
|
63 | - * Return value semantics: |
|
64 | - * * null: $nodes stays as-is |
|
65 | - * * otherwise: $nodes is set to the return value |
|
66 | - * |
|
67 | - * @param Node[] $nodes Array of nodes |
|
68 | - * |
|
69 | - * @return null|Node[] Array of nodes |
|
70 | - */ |
|
71 | - public function afterTraverse(array $nodes); |
|
60 | + /** |
|
61 | + * Called once after traversal. |
|
62 | + * |
|
63 | + * Return value semantics: |
|
64 | + * * null: $nodes stays as-is |
|
65 | + * * otherwise: $nodes is set to the return value |
|
66 | + * |
|
67 | + * @param Node[] $nodes Array of nodes |
|
68 | + * |
|
69 | + * @return null|Node[] Array of nodes |
|
70 | + */ |
|
71 | + public function afterTraverse(array $nodes); |
|
72 | 72 | } |
@@ -2,8 +2,7 @@ |
||
2 | 2 | |
3 | 3 | namespace PhpParser; |
4 | 4 | |
5 | -interface NodeVisitor |
|
6 | -{ |
|
5 | +interface NodeVisitor { |
|
7 | 6 | /** |
8 | 7 | * Called once before traversal. |
9 | 8 | * |