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