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