Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace PhpParser\NodeVisitor;
|
Chris@0
|
4
|
Chris@0
|
5 use PhpParser\Error;
|
Chris@0
|
6 use PhpParser\ErrorHandler;
|
Chris@0
|
7 use PhpParser\Node;
|
Chris@0
|
8 use PhpParser\Node\Expr;
|
Chris@0
|
9 use PhpParser\Node\Name;
|
Chris@0
|
10 use PhpParser\Node\Name\FullyQualified;
|
Chris@0
|
11 use PhpParser\Node\Stmt;
|
Chris@0
|
12 use PhpParser\NodeVisitorAbstract;
|
Chris@0
|
13
|
Chris@0
|
14 class NameResolver extends NodeVisitorAbstract
|
Chris@0
|
15 {
|
Chris@0
|
16 /** @var null|Name Current namespace */
|
Chris@0
|
17 protected $namespace;
|
Chris@0
|
18
|
Chris@0
|
19 /** @var array Map of format [aliasType => [aliasName => originalName]] */
|
Chris@0
|
20 protected $aliases;
|
Chris@0
|
21
|
Chris@0
|
22 /** @var ErrorHandler Error handler */
|
Chris@0
|
23 protected $errorHandler;
|
Chris@0
|
24
|
Chris@0
|
25 /** @var bool Whether to preserve original names */
|
Chris@0
|
26 protected $preserveOriginalNames;
|
Chris@0
|
27
|
Chris@0
|
28 /**
|
Chris@0
|
29 * Constructs a name resolution visitor.
|
Chris@0
|
30 *
|
Chris@0
|
31 * Options: If "preserveOriginalNames" is enabled, an "originalName" attribute will be added to
|
Chris@0
|
32 * all name nodes that underwent resolution.
|
Chris@0
|
33 *
|
Chris@0
|
34 * @param ErrorHandler|null $errorHandler Error handler
|
Chris@0
|
35 * @param array $options Options
|
Chris@0
|
36 */
|
Chris@0
|
37 public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
|
Chris@0
|
38 $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
|
Chris@0
|
39 $this->preserveOriginalNames = !empty($options['preserveOriginalNames']);
|
Chris@0
|
40 }
|
Chris@0
|
41
|
Chris@0
|
42 public function beforeTraverse(array $nodes) {
|
Chris@0
|
43 $this->resetState();
|
Chris@0
|
44 }
|
Chris@0
|
45
|
Chris@0
|
46 public function enterNode(Node $node) {
|
Chris@0
|
47 if ($node instanceof Stmt\Namespace_) {
|
Chris@0
|
48 $this->resetState($node->name);
|
Chris@0
|
49 } elseif ($node instanceof Stmt\Use_) {
|
Chris@0
|
50 foreach ($node->uses as $use) {
|
Chris@0
|
51 $this->addAlias($use, $node->type, null);
|
Chris@0
|
52 }
|
Chris@0
|
53 } elseif ($node instanceof Stmt\GroupUse) {
|
Chris@0
|
54 foreach ($node->uses as $use) {
|
Chris@0
|
55 $this->addAlias($use, $node->type, $node->prefix);
|
Chris@0
|
56 }
|
Chris@0
|
57 } elseif ($node instanceof Stmt\Class_) {
|
Chris@0
|
58 if (null !== $node->extends) {
|
Chris@0
|
59 $node->extends = $this->resolveClassName($node->extends);
|
Chris@0
|
60 }
|
Chris@0
|
61
|
Chris@0
|
62 foreach ($node->implements as &$interface) {
|
Chris@0
|
63 $interface = $this->resolveClassName($interface);
|
Chris@0
|
64 }
|
Chris@0
|
65
|
Chris@0
|
66 if (null !== $node->name) {
|
Chris@0
|
67 $this->addNamespacedName($node);
|
Chris@0
|
68 }
|
Chris@0
|
69 } elseif ($node instanceof Stmt\Interface_) {
|
Chris@0
|
70 foreach ($node->extends as &$interface) {
|
Chris@0
|
71 $interface = $this->resolveClassName($interface);
|
Chris@0
|
72 }
|
Chris@0
|
73
|
Chris@0
|
74 $this->addNamespacedName($node);
|
Chris@0
|
75 } elseif ($node instanceof Stmt\Trait_) {
|
Chris@0
|
76 $this->addNamespacedName($node);
|
Chris@0
|
77 } elseif ($node instanceof Stmt\Function_) {
|
Chris@0
|
78 $this->addNamespacedName($node);
|
Chris@0
|
79 $this->resolveSignature($node);
|
Chris@0
|
80 } elseif ($node instanceof Stmt\ClassMethod
|
Chris@0
|
81 || $node instanceof Expr\Closure
|
Chris@0
|
82 ) {
|
Chris@0
|
83 $this->resolveSignature($node);
|
Chris@0
|
84 } elseif ($node instanceof Stmt\Const_) {
|
Chris@0
|
85 foreach ($node->consts as $const) {
|
Chris@0
|
86 $this->addNamespacedName($const);
|
Chris@0
|
87 }
|
Chris@0
|
88 } elseif ($node instanceof Expr\StaticCall
|
Chris@0
|
89 || $node instanceof Expr\StaticPropertyFetch
|
Chris@0
|
90 || $node instanceof Expr\ClassConstFetch
|
Chris@0
|
91 || $node instanceof Expr\New_
|
Chris@0
|
92 || $node instanceof Expr\Instanceof_
|
Chris@0
|
93 ) {
|
Chris@0
|
94 if ($node->class instanceof Name) {
|
Chris@0
|
95 $node->class = $this->resolveClassName($node->class);
|
Chris@0
|
96 }
|
Chris@0
|
97 } elseif ($node instanceof Stmt\Catch_) {
|
Chris@0
|
98 foreach ($node->types as &$type) {
|
Chris@0
|
99 $type = $this->resolveClassName($type);
|
Chris@0
|
100 }
|
Chris@0
|
101 } elseif ($node instanceof Expr\FuncCall) {
|
Chris@0
|
102 if ($node->name instanceof Name) {
|
Chris@0
|
103 $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION);
|
Chris@0
|
104 }
|
Chris@0
|
105 } elseif ($node instanceof Expr\ConstFetch) {
|
Chris@0
|
106 $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT);
|
Chris@0
|
107 } elseif ($node instanceof Stmt\TraitUse) {
|
Chris@0
|
108 foreach ($node->traits as &$trait) {
|
Chris@0
|
109 $trait = $this->resolveClassName($trait);
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 foreach ($node->adaptations as $adaptation) {
|
Chris@0
|
113 if (null !== $adaptation->trait) {
|
Chris@0
|
114 $adaptation->trait = $this->resolveClassName($adaptation->trait);
|
Chris@0
|
115 }
|
Chris@0
|
116
|
Chris@0
|
117 if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
|
Chris@0
|
118 foreach ($adaptation->insteadof as &$insteadof) {
|
Chris@0
|
119 $insteadof = $this->resolveClassName($insteadof);
|
Chris@0
|
120 }
|
Chris@0
|
121 }
|
Chris@0
|
122 }
|
Chris@0
|
123 }
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 protected function resetState(Name $namespace = null) {
|
Chris@0
|
127 $this->namespace = $namespace;
|
Chris@0
|
128 $this->aliases = array(
|
Chris@0
|
129 Stmt\Use_::TYPE_NORMAL => array(),
|
Chris@0
|
130 Stmt\Use_::TYPE_FUNCTION => array(),
|
Chris@0
|
131 Stmt\Use_::TYPE_CONSTANT => array(),
|
Chris@0
|
132 );
|
Chris@0
|
133 }
|
Chris@0
|
134
|
Chris@0
|
135 protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
|
Chris@0
|
136 // Add prefix for group uses
|
Chris@0
|
137 $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
|
Chris@0
|
138 // Type is determined either by individual element or whole use declaration
|
Chris@0
|
139 $type |= $use->type;
|
Chris@0
|
140
|
Chris@0
|
141 // Constant names are case sensitive, everything else case insensitive
|
Chris@0
|
142 if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
Chris@0
|
143 $aliasName = $use->alias;
|
Chris@0
|
144 } else {
|
Chris@0
|
145 $aliasName = strtolower($use->alias);
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 if (isset($this->aliases[$type][$aliasName])) {
|
Chris@0
|
149 $typeStringMap = array(
|
Chris@0
|
150 Stmt\Use_::TYPE_NORMAL => '',
|
Chris@0
|
151 Stmt\Use_::TYPE_FUNCTION => 'function ',
|
Chris@0
|
152 Stmt\Use_::TYPE_CONSTANT => 'const ',
|
Chris@0
|
153 );
|
Chris@0
|
154
|
Chris@0
|
155 $this->errorHandler->handleError(new Error(
|
Chris@0
|
156 sprintf(
|
Chris@0
|
157 'Cannot use %s%s as %s because the name is already in use',
|
Chris@0
|
158 $typeStringMap[$type], $name, $use->alias
|
Chris@0
|
159 ),
|
Chris@0
|
160 $use->getAttributes()
|
Chris@0
|
161 ));
|
Chris@0
|
162 return;
|
Chris@0
|
163 }
|
Chris@0
|
164
|
Chris@0
|
165 $this->aliases[$type][$aliasName] = $name;
|
Chris@0
|
166 }
|
Chris@0
|
167
|
Chris@0
|
168 /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
|
Chris@0
|
169 private function resolveSignature($node) {
|
Chris@0
|
170 foreach ($node->params as $param) {
|
Chris@0
|
171 $param->type = $this->resolveType($param->type);
|
Chris@0
|
172 }
|
Chris@0
|
173 $node->returnType = $this->resolveType($node->returnType);
|
Chris@0
|
174 }
|
Chris@0
|
175
|
Chris@0
|
176 private function resolveType($node) {
|
Chris@0
|
177 if ($node instanceof Node\NullableType) {
|
Chris@0
|
178 $node->type = $this->resolveType($node->type);
|
Chris@0
|
179 return $node;
|
Chris@0
|
180 }
|
Chris@0
|
181 if ($node instanceof Name) {
|
Chris@0
|
182 return $this->resolveClassName($node);
|
Chris@0
|
183 }
|
Chris@0
|
184 return $node;
|
Chris@0
|
185 }
|
Chris@0
|
186
|
Chris@0
|
187 protected function resolveClassName(Name $name) {
|
Chris@0
|
188 if ($this->preserveOriginalNames) {
|
Chris@0
|
189 // Save the original name
|
Chris@0
|
190 $originalName = $name;
|
Chris@0
|
191 $name = clone $originalName;
|
Chris@0
|
192 $name->setAttribute('originalName', $originalName);
|
Chris@0
|
193 }
|
Chris@0
|
194
|
Chris@0
|
195 // don't resolve special class names
|
Chris@0
|
196 if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
|
Chris@0
|
197 if (!$name->isUnqualified()) {
|
Chris@0
|
198 $this->errorHandler->handleError(new Error(
|
Chris@0
|
199 sprintf("'\\%s' is an invalid class name", $name->toString()),
|
Chris@0
|
200 $name->getAttributes()
|
Chris@0
|
201 ));
|
Chris@0
|
202 }
|
Chris@0
|
203 return $name;
|
Chris@0
|
204 }
|
Chris@0
|
205
|
Chris@0
|
206 // fully qualified names are already resolved
|
Chris@0
|
207 if ($name->isFullyQualified()) {
|
Chris@0
|
208 return $name;
|
Chris@0
|
209 }
|
Chris@0
|
210
|
Chris@0
|
211 $aliasName = strtolower($name->getFirst());
|
Chris@0
|
212 if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
|
Chris@0
|
213 // resolve aliases (for non-relative names)
|
Chris@0
|
214 $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
|
Chris@0
|
215 return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 // if no alias exists prepend current namespace
|
Chris@0
|
219 return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
Chris@0
|
220 }
|
Chris@0
|
221
|
Chris@0
|
222 protected function resolveOtherName(Name $name, $type) {
|
Chris@0
|
223 if ($this->preserveOriginalNames) {
|
Chris@0
|
224 // Save the original name
|
Chris@0
|
225 $originalName = $name;
|
Chris@0
|
226 $name = clone $originalName;
|
Chris@0
|
227 $name->setAttribute('originalName', $originalName);
|
Chris@0
|
228 }
|
Chris@0
|
229
|
Chris@0
|
230 // fully qualified names are already resolved
|
Chris@0
|
231 if ($name->isFullyQualified()) {
|
Chris@0
|
232 return $name;
|
Chris@0
|
233 }
|
Chris@0
|
234
|
Chris@0
|
235 // resolve aliases for qualified names
|
Chris@0
|
236 $aliasName = strtolower($name->getFirst());
|
Chris@0
|
237 if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
|
Chris@0
|
238 $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
|
Chris@0
|
239 return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
|
Chris@0
|
240 }
|
Chris@0
|
241
|
Chris@0
|
242 if ($name->isUnqualified()) {
|
Chris@0
|
243 if ($type === Stmt\Use_::TYPE_CONSTANT) {
|
Chris@0
|
244 // constant aliases are case-sensitive, function aliases case-insensitive
|
Chris@0
|
245 $aliasName = $name->getFirst();
|
Chris@0
|
246 }
|
Chris@0
|
247
|
Chris@0
|
248 if (isset($this->aliases[$type][$aliasName])) {
|
Chris@0
|
249 // resolve unqualified aliases
|
Chris@0
|
250 return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
|
Chris@0
|
251 }
|
Chris@0
|
252
|
Chris@0
|
253 if (null === $this->namespace) {
|
Chris@0
|
254 // outside of a namespace unaliased unqualified is same as fully qualified
|
Chris@0
|
255 return new FullyQualified($name, $name->getAttributes());
|
Chris@0
|
256 }
|
Chris@0
|
257
|
Chris@0
|
258 // unqualified names inside a namespace cannot be resolved at compile-time
|
Chris@0
|
259 // add the namespaced version of the name as an attribute
|
Chris@0
|
260 $name->setAttribute('namespacedName',
|
Chris@0
|
261 FullyQualified::concat($this->namespace, $name, $name->getAttributes()));
|
Chris@0
|
262 return $name;
|
Chris@0
|
263 }
|
Chris@0
|
264
|
Chris@0
|
265 // if no alias exists prepend current namespace
|
Chris@0
|
266 return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
|
Chris@0
|
267 }
|
Chris@0
|
268
|
Chris@0
|
269 protected function addNamespacedName(Node $node) {
|
Chris@0
|
270 $node->namespacedName = Name::concat($this->namespace, $node->name);
|
Chris@0
|
271 }
|
Chris@0
|
272 }
|