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