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: if (null !== $node->type) {
122: $node->type = $this->resolveType($node->type);
123: }
124: $this->resolveAttrGroups($node);
125: } else if ($node instanceof Stmt\EnumCase) {
126: $this->resolveAttrGroups($node);
127: } elseif ($node instanceof Expr\StaticCall
128: || $node instanceof Expr\StaticPropertyFetch
129: || $node instanceof Expr\ClassConstFetch
130: || $node instanceof Expr\New_
131: || $node instanceof Expr\Instanceof_
132: ) {
133: if ($node->class instanceof Name) {
134: $node->class = $this->resolveClassName($node->class);
135: }
136: } elseif ($node instanceof Stmt\Catch_) {
137: foreach ($node->types as &$type) {
138: $type = $this->resolveClassName($type);
139: }
140: } elseif ($node instanceof Expr\FuncCall) {
141: if ($node->name instanceof Name) {
142: $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
143: }
144: } elseif ($node instanceof Expr\ConstFetch) {
145: $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
146: } elseif ($node instanceof Stmt\TraitUse) {
147: foreach ($node->traits as &$trait) {
148: $trait = $this->resolveClassName($trait);
149: }
150:
151: foreach ($node->adaptations as $adaptation) {
152: if (null !== $adaptation->trait) {
153: $adaptation->trait = $this->resolveClassName($adaptation->trait);
154: }
155:
156: if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
157: foreach ($adaptation->insteadof as &$insteadof) {
158: $insteadof = $this->resolveClassName($insteadof);
159: }
160: }
161: }
162: }
163:
164: return null;
165: }
166:
167: private function addAlias(Stmt\UseUse $use, int $type, ?Name $prefix = null) {
168: // Add prefix for group uses
169: $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
170: // Type is determined either by individual element or whole use declaration
171: $type |= $use->type;
172:
173: $this->nameContext->addAlias(
174: $name, (string) $use->getAlias(), $type, $use->getAttributes()
175: );
176: }
177:
178: /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
179: private function resolveSignature($node) {
180: foreach ($node->params as $param) {
181: $param->type = $this->resolveType($param->type);
182: $this->resolveAttrGroups($param);
183: }
184: $node->returnType = $this->resolveType($node->returnType);
185: }
186:
187: private function resolveType($node) {
188: if ($node instanceof Name) {
189: return $this->resolveClassName($node);
190: }
191: if ($node instanceof Node\NullableType) {
192: $node->type = $this->resolveType($node->type);
193: return $node;
194: }
195: if ($node instanceof Node\UnionType || $node instanceof Node\IntersectionType) {
196: foreach ($node->types as &$type) {
197: $type = $this->resolveType($type);
198: }
199: return $node;
200: }
201: return $node;
202: }
203:
204: /**
205: * Resolve name, according to name resolver options.
206: *
207: * @param Name $name Function or constant name to resolve
208: * @param int $type One of Stmt\Use_::TYPE_*
209: *
210: * @return Name Resolved name, or original name with attribute
211: */
212: protected function resolveName(Name $name, int $type) : Name {
213: if (!$this->replaceNodes) {
214: $resolvedName = $this->nameContext->getResolvedName($name, $type);
215: if (null !== $resolvedName) {
216: $name->setAttribute('resolvedName', $resolvedName);
217: } else {
218: $name->setAttribute('namespacedName', FullyQualified::concat(
219: $this->nameContext->getNamespace(), $name, $name->getAttributes()));
220: }
221: return $name;
222: }
223:
224: if ($this->preserveOriginalNames) {
225: // Save the original name
226: $originalName = $name;
227: $name = clone $originalName;
228: $name->setAttribute('originalName', $originalName);
229: }
230:
231: $resolvedName = $this->nameContext->getResolvedName($name, $type);
232: if (null !== $resolvedName) {
233: return $resolvedName;
234: }
235:
236: // unqualified names inside a namespace cannot be resolved at compile-time
237: // add the namespaced version of the name as an attribute
238: $name->setAttribute('namespacedName', FullyQualified::concat(
239: $this->nameContext->getNamespace(), $name, $name->getAttributes()));
240: return $name;
241: }
242:
243: protected function resolveClassName(Name $name) {
244: return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
245: }
246:
247: protected function addNamespacedName(Node $node) {
248: $node->namespacedName = Name::concat(
249: $this->nameContext->getNamespace(), (string) $node->name);
250: }
251:
252: protected function resolveAttrGroups(Node $node)
253: {
254: foreach ($node->attrGroups as $attrGroup) {
255: foreach ($attrGroup->attrs as $attr) {
256: $attr->name = $this->resolveClassName($attr->name);
257: }
258: }
259: }
260: }
261: