Chris@13
|
1 <?php declare(strict_types=1);
|
Chris@0
|
2
|
Chris@0
|
3 namespace PhpParser\NodeVisitor;
|
Chris@0
|
4
|
Chris@0
|
5 use PhpParser\ErrorHandler;
|
Chris@13
|
6 use PhpParser\NameContext;
|
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@13
|
16 /** @var NameContext Naming context */
|
Chris@13
|
17 protected $nameContext;
|
Chris@0
|
18
|
Chris@0
|
19 /** @var bool Whether to preserve original names */
|
Chris@0
|
20 protected $preserveOriginalNames;
|
Chris@0
|
21
|
Chris@13
|
22 /** @var bool Whether to replace resolved nodes in place, or to add resolvedNode attributes */
|
Chris@13
|
23 protected $replaceNodes;
|
Chris@13
|
24
|
Chris@0
|
25 /**
|
Chris@0
|
26 * Constructs a name resolution visitor.
|
Chris@0
|
27 *
|
Chris@13
|
28 * Options:
|
Chris@13
|
29 * * preserveOriginalNames (default false): An "originalName" attribute will be added to
|
Chris@13
|
30 * all name nodes that underwent resolution.
|
Chris@13
|
31 * * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
|
Chris@13
|
32 * resolvedName attribute is added. (Names that cannot be statically resolved receive a
|
Chris@13
|
33 * namespacedName attribute, as usual.)
|
Chris@0
|
34 *
|
Chris@0
|
35 * @param ErrorHandler|null $errorHandler Error handler
|
Chris@0
|
36 * @param array $options Options
|
Chris@0
|
37 */
|
Chris@0
|
38 public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
|
Chris@13
|
39 $this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
|
Chris@13
|
40 $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
|
Chris@13
|
41 $this->replaceNodes = $options['replaceNodes'] ?? true;
|
Chris@13
|
42 }
|
Chris@13
|
43
|
Chris@13
|
44 /**
|
Chris@13
|
45 * Get name resolution context.
|
Chris@13
|
46 *
|
Chris@13
|
47 * @return NameContext
|
Chris@13
|
48 */
|
Chris@13
|
49 public function getNameContext() : NameContext {
|
Chris@13
|
50 return $this->nameContext;
|
Chris@0
|
51 }
|
Chris@0
|
52
|
Chris@0
|
53 public function beforeTraverse(array $nodes) {
|
Chris@13
|
54 $this->nameContext->startNamespace();
|
Chris@13
|
55 return null;
|
Chris@0
|
56 }
|
Chris@0
|
57
|
Chris@0
|
58 public function enterNode(Node $node) {
|
Chris@0
|
59 if ($node instanceof Stmt\Namespace_) {
|
Chris@13
|
60 $this->nameContext->startNamespace($node->name);
|
Chris@0
|
61 } elseif ($node instanceof Stmt\Use_) {
|
Chris@0
|
62 foreach ($node->uses as $use) {
|
Chris@0
|
63 $this->addAlias($use, $node->type, null);
|
Chris@0
|
64 }
|
Chris@0
|
65 } elseif ($node instanceof Stmt\GroupUse) {
|
Chris@0
|
66 foreach ($node->uses as $use) {
|
Chris@0
|
67 $this->addAlias($use, $node->type, $node->prefix);
|
Chris@0
|
68 }
|
Chris@0
|
69 } elseif ($node instanceof Stmt\Class_) {
|
Chris@0
|
70 if (null !== $node->extends) {
|
Chris@0
|
71 $node->extends = $this->resolveClassName($node->extends);
|
Chris@0
|
72 }
|
Chris@0
|
73
|
Chris@0
|
74 foreach ($node->implements as &$interface) {
|
Chris@0
|
75 $interface = $this->resolveClassName($interface);
|
Chris@0
|
76 }
|
Chris@0
|
77
|
Chris@0
|
78 if (null !== $node->name) {
|
Chris@0
|
79 $this->addNamespacedName($node);
|
Chris@0
|
80 }
|
Chris@0
|
81 } elseif ($node instanceof Stmt\Interface_) {
|
Chris@0
|
82 foreach ($node->extends as &$interface) {
|
Chris@0
|
83 $interface = $this->resolveClassName($interface);
|
Chris@0
|
84 }
|
Chris@0
|
85
|
Chris@0
|
86 $this->addNamespacedName($node);
|
Chris@0
|
87 } elseif ($node instanceof Stmt\Trait_) {
|
Chris@0
|
88 $this->addNamespacedName($node);
|
Chris@0
|
89 } elseif ($node instanceof Stmt\Function_) {
|
Chris@0
|
90 $this->addNamespacedName($node);
|
Chris@0
|
91 $this->resolveSignature($node);
|
Chris@0
|
92 } elseif ($node instanceof Stmt\ClassMethod
|
Chris@0
|
93 || $node instanceof Expr\Closure
|
Chris@0
|
94 ) {
|
Chris@0
|
95 $this->resolveSignature($node);
|
Chris@17
|
96 } elseif ($node instanceof Stmt\Property) {
|
Chris@17
|
97 if (null !== $node->type) {
|
Chris@17
|
98 $node->type = $this->resolveType($node->type);
|
Chris@17
|
99 }
|
Chris@0
|
100 } elseif ($node instanceof Stmt\Const_) {
|
Chris@0
|
101 foreach ($node->consts as $const) {
|
Chris@0
|
102 $this->addNamespacedName($const);
|
Chris@0
|
103 }
|
Chris@0
|
104 } elseif ($node instanceof Expr\StaticCall
|
Chris@0
|
105 || $node instanceof Expr\StaticPropertyFetch
|
Chris@0
|
106 || $node instanceof Expr\ClassConstFetch
|
Chris@0
|
107 || $node instanceof Expr\New_
|
Chris@0
|
108 || $node instanceof Expr\Instanceof_
|
Chris@0
|
109 ) {
|
Chris@0
|
110 if ($node->class instanceof Name) {
|
Chris@0
|
111 $node->class = $this->resolveClassName($node->class);
|
Chris@0
|
112 }
|
Chris@0
|
113 } elseif ($node instanceof Stmt\Catch_) {
|
Chris@0
|
114 foreach ($node->types as &$type) {
|
Chris@0
|
115 $type = $this->resolveClassName($type);
|
Chris@0
|
116 }
|
Chris@0
|
117 } elseif ($node instanceof Expr\FuncCall) {
|
Chris@0
|
118 if ($node->name instanceof Name) {
|
Chris@13
|
119 $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
|
Chris@0
|
120 }
|
Chris@0
|
121 } elseif ($node instanceof Expr\ConstFetch) {
|
Chris@13
|
122 $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
|
Chris@0
|
123 } elseif ($node instanceof Stmt\TraitUse) {
|
Chris@0
|
124 foreach ($node->traits as &$trait) {
|
Chris@0
|
125 $trait = $this->resolveClassName($trait);
|
Chris@0
|
126 }
|
Chris@0
|
127
|
Chris@0
|
128 foreach ($node->adaptations as $adaptation) {
|
Chris@0
|
129 if (null !== $adaptation->trait) {
|
Chris@0
|
130 $adaptation->trait = $this->resolveClassName($adaptation->trait);
|
Chris@0
|
131 }
|
Chris@0
|
132
|
Chris@0
|
133 if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
|
Chris@0
|
134 foreach ($adaptation->insteadof as &$insteadof) {
|
Chris@0
|
135 $insteadof = $this->resolveClassName($insteadof);
|
Chris@0
|
136 }
|
Chris@0
|
137 }
|
Chris@0
|
138 }
|
Chris@0
|
139 }
|
Chris@13
|
140
|
Chris@13
|
141 return null;
|
Chris@0
|
142 }
|
Chris@0
|
143
|
Chris@13
|
144 private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
|
Chris@0
|
145 // Add prefix for group uses
|
Chris@0
|
146 $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
|
Chris@0
|
147 // Type is determined either by individual element or whole use declaration
|
Chris@0
|
148 $type |= $use->type;
|
Chris@0
|
149
|
Chris@13
|
150 $this->nameContext->addAlias(
|
Chris@13
|
151 $name, (string) $use->getAlias(), $type, $use->getAttributes()
|
Chris@13
|
152 );
|
Chris@0
|
153 }
|
Chris@0
|
154
|
Chris@0
|
155 /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
|
Chris@0
|
156 private function resolveSignature($node) {
|
Chris@0
|
157 foreach ($node->params as $param) {
|
Chris@0
|
158 $param->type = $this->resolveType($param->type);
|
Chris@0
|
159 }
|
Chris@0
|
160 $node->returnType = $this->resolveType($node->returnType);
|
Chris@0
|
161 }
|
Chris@0
|
162
|
Chris@0
|
163 private function resolveType($node) {
|
Chris@0
|
164 if ($node instanceof Node\NullableType) {
|
Chris@0
|
165 $node->type = $this->resolveType($node->type);
|
Chris@0
|
166 return $node;
|
Chris@0
|
167 }
|
Chris@0
|
168 if ($node instanceof Name) {
|
Chris@0
|
169 return $this->resolveClassName($node);
|
Chris@0
|
170 }
|
Chris@0
|
171 return $node;
|
Chris@0
|
172 }
|
Chris@0
|
173
|
Chris@13
|
174 /**
|
Chris@13
|
175 * Resolve name, according to name resolver options.
|
Chris@13
|
176 *
|
Chris@13
|
177 * @param Name $name Function or constant name to resolve
|
Chris@13
|
178 * @param int $type One of Stmt\Use_::TYPE_*
|
Chris@13
|
179 *
|
Chris@13
|
180 * @return Name Resolved name, or original name with attribute
|
Chris@13
|
181 */
|
Chris@13
|
182 protected function resolveName(Name $name, int $type) : Name {
|
Chris@13
|
183 if (!$this->replaceNodes) {
|
Chris@13
|
184 $resolvedName = $this->nameContext->getResolvedName($name, $type);
|
Chris@13
|
185 if (null !== $resolvedName) {
|
Chris@13
|
186 $name->setAttribute('resolvedName', $resolvedName);
|
Chris@13
|
187 } else {
|
Chris@13
|
188 $name->setAttribute('namespacedName', FullyQualified::concat(
|
Chris@13
|
189 $this->nameContext->getNamespace(), $name, $name->getAttributes()));
|
Chris@13
|
190 }
|
Chris@13
|
191 return $name;
|
Chris@13
|
192 }
|
Chris@13
|
193
|
Chris@0
|
194 if ($this->preserveOriginalNames) {
|
Chris@0
|
195 // Save the original name
|
Chris@0
|
196 $originalName = $name;
|
Chris@0
|
197 $name = clone $originalName;
|
Chris@0
|
198 $name->setAttribute('originalName', $originalName);
|
Chris@0
|
199 }
|
Chris@0
|
200
|
Chris@13
|
201 $resolvedName = $this->nameContext->getResolvedName($name, $type);
|
Chris@13
|
202 if (null !== $resolvedName) {
|
Chris@13
|
203 return $resolvedName;
|
Chris@0
|
204 }
|
Chris@0
|
205
|
Chris@13
|
206 // unqualified names inside a namespace cannot be resolved at compile-time
|
Chris@13
|
207 // add the namespaced version of the name as an attribute
|
Chris@13
|
208 $name->setAttribute('namespacedName', FullyQualified::concat(
|
Chris@13
|
209 $this->nameContext->getNamespace(), $name, $name->getAttributes()));
|
Chris@13
|
210 return $name;
|
Chris@0
|
211 }
|
Chris@0
|
212
|
Chris@13
|
213 protected function resolveClassName(Name $name) {
|
Chris@13
|
214 return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
|
Chris@0
|
215 }
|
Chris@0
|
216
|
Chris@0
|
217 protected function addNamespacedName(Node $node) {
|
Chris@13
|
218 $node->namespacedName = Name::concat(
|
Chris@13
|
219 $this->nameContext->getNamespace(), (string) $node->name);
|
Chris@0
|
220 }
|
Chris@0
|
221 }
|