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