NameResolver.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <?php
  2. namespace PhpParser\NodeVisitor;
  3. use PhpParser\Error;
  4. use PhpParser\ErrorHandler;
  5. use PhpParser\Node;
  6. use PhpParser\Node\Expr;
  7. use PhpParser\Node\Name;
  8. use PhpParser\Node\Name\FullyQualified;
  9. use PhpParser\Node\Stmt;
  10. use PhpParser\NodeVisitorAbstract;
  11. class NameResolver extends NodeVisitorAbstract
  12. {
  13. /** @var null|Name Current namespace */
  14. protected $namespace;
  15. /** @var array Map of format [aliasType => [aliasName => originalName]] */
  16. protected $aliases;
  17. /** @var ErrorHandler Error handler */
  18. protected $errorHandler;
  19. /** @var bool Whether to preserve original names */
  20. protected $preserveOriginalNames;
  21. /**
  22. * Constructs a name resolution visitor.
  23. *
  24. * Options: If "preserveOriginalNames" is enabled, an "originalName" attribute will be added to
  25. * all name nodes that underwent resolution.
  26. *
  27. * @param ErrorHandler|null $errorHandler Error handler
  28. * @param array $options Options
  29. */
  30. public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
  31. $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
  32. $this->preserveOriginalNames = !empty($options['preserveOriginalNames']);
  33. }
  34. public function beforeTraverse(array $nodes) {
  35. $this->resetState();
  36. }
  37. public function enterNode(Node $node) {
  38. if ($node instanceof Stmt\Namespace_) {
  39. $this->resetState($node->name);
  40. } elseif ($node instanceof Stmt\Use_) {
  41. foreach ($node->uses as $use) {
  42. $this->addAlias($use, $node->type, null);
  43. }
  44. } elseif ($node instanceof Stmt\GroupUse) {
  45. foreach ($node->uses as $use) {
  46. $this->addAlias($use, $node->type, $node->prefix);
  47. }
  48. } elseif ($node instanceof Stmt\Class_) {
  49. if (null !== $node->extends) {
  50. $node->extends = $this->resolveClassName($node->extends);
  51. }
  52. foreach ($node->implements as &$interface) {
  53. $interface = $this->resolveClassName($interface);
  54. }
  55. if (null !== $node->name) {
  56. $this->addNamespacedName($node);
  57. }
  58. } elseif ($node instanceof Stmt\Interface_) {
  59. foreach ($node->extends as &$interface) {
  60. $interface = $this->resolveClassName($interface);
  61. }
  62. $this->addNamespacedName($node);
  63. } elseif ($node instanceof Stmt\Trait_) {
  64. $this->addNamespacedName($node);
  65. } elseif ($node instanceof Stmt\Function_) {
  66. $this->addNamespacedName($node);
  67. $this->resolveSignature($node);
  68. } elseif ($node instanceof Stmt\ClassMethod
  69. || $node instanceof Expr\Closure
  70. ) {
  71. $this->resolveSignature($node);
  72. } elseif ($node instanceof Stmt\Const_) {
  73. foreach ($node->consts as $const) {
  74. $this->addNamespacedName($const);
  75. }
  76. } elseif ($node instanceof Expr\StaticCall
  77. || $node instanceof Expr\StaticPropertyFetch
  78. || $node instanceof Expr\ClassConstFetch
  79. || $node instanceof Expr\New_
  80. || $node instanceof Expr\Instanceof_
  81. ) {
  82. if ($node->class instanceof Name) {
  83. $node->class = $this->resolveClassName($node->class);
  84. }
  85. } elseif ($node instanceof Stmt\Catch_) {
  86. foreach ($node->types as &$type) {
  87. $type = $this->resolveClassName($type);
  88. }
  89. } elseif ($node instanceof Expr\FuncCall) {
  90. if ($node->name instanceof Name) {
  91. $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION);
  92. }
  93. } elseif ($node instanceof Expr\ConstFetch) {
  94. $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT);
  95. } elseif ($node instanceof Stmt\TraitUse) {
  96. foreach ($node->traits as &$trait) {
  97. $trait = $this->resolveClassName($trait);
  98. }
  99. foreach ($node->adaptations as $adaptation) {
  100. if (null !== $adaptation->trait) {
  101. $adaptation->trait = $this->resolveClassName($adaptation->trait);
  102. }
  103. if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
  104. foreach ($adaptation->insteadof as &$insteadof) {
  105. $insteadof = $this->resolveClassName($insteadof);
  106. }
  107. }
  108. }
  109. }
  110. }
  111. protected function resetState(Name $namespace = null) {
  112. $this->namespace = $namespace;
  113. $this->aliases = array(
  114. Stmt\Use_::TYPE_NORMAL => array(),
  115. Stmt\Use_::TYPE_FUNCTION => array(),
  116. Stmt\Use_::TYPE_CONSTANT => array(),
  117. );
  118. }
  119. protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
  120. // Add prefix for group uses
  121. $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
  122. // Type is determined either by individual element or whole use declaration
  123. $type |= $use->type;
  124. // Constant names are case sensitive, everything else case insensitive
  125. if ($type === Stmt\Use_::TYPE_CONSTANT) {
  126. $aliasName = $use->alias;
  127. } else {
  128. $aliasName = strtolower($use->alias);
  129. }
  130. if (isset($this->aliases[$type][$aliasName])) {
  131. $typeStringMap = array(
  132. Stmt\Use_::TYPE_NORMAL => '',
  133. Stmt\Use_::TYPE_FUNCTION => 'function ',
  134. Stmt\Use_::TYPE_CONSTANT => 'const ',
  135. );
  136. $this->errorHandler->handleError(new Error(
  137. sprintf(
  138. 'Cannot use %s%s as %s because the name is already in use',
  139. $typeStringMap[$type], $name, $use->alias
  140. ),
  141. $use->getAttributes()
  142. ));
  143. return;
  144. }
  145. $this->aliases[$type][$aliasName] = $name;
  146. }
  147. /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
  148. private function resolveSignature($node) {
  149. foreach ($node->params as $param) {
  150. $param->type = $this->resolveType($param->type);
  151. }
  152. $node->returnType = $this->resolveType($node->returnType);
  153. }
  154. private function resolveType($node) {
  155. if ($node instanceof Node\NullableType) {
  156. $node->type = $this->resolveType($node->type);
  157. return $node;
  158. }
  159. if ($node instanceof Name) {
  160. return $this->resolveClassName($node);
  161. }
  162. return $node;
  163. }
  164. protected function resolveClassName(Name $name) {
  165. if ($this->preserveOriginalNames) {
  166. // Save the original name
  167. $originalName = $name;
  168. $name = clone $originalName;
  169. $name->setAttribute('originalName', $originalName);
  170. }
  171. // don't resolve special class names
  172. if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
  173. if (!$name->isUnqualified()) {
  174. $this->errorHandler->handleError(new Error(
  175. sprintf("'\\%s' is an invalid class name", $name->toString()),
  176. $name->getAttributes()
  177. ));
  178. }
  179. return $name;
  180. }
  181. // fully qualified names are already resolved
  182. if ($name->isFullyQualified()) {
  183. return $name;
  184. }
  185. $aliasName = strtolower($name->getFirst());
  186. if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
  187. // resolve aliases (for non-relative names)
  188. $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
  189. return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
  190. }
  191. // if no alias exists prepend current namespace
  192. return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
  193. }
  194. protected function resolveOtherName(Name $name, $type) {
  195. if ($this->preserveOriginalNames) {
  196. // Save the original name
  197. $originalName = $name;
  198. $name = clone $originalName;
  199. $name->setAttribute('originalName', $originalName);
  200. }
  201. // fully qualified names are already resolved
  202. if ($name->isFullyQualified()) {
  203. return $name;
  204. }
  205. // resolve aliases for qualified names
  206. $aliasName = strtolower($name->getFirst());
  207. if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
  208. $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
  209. return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
  210. }
  211. if ($name->isUnqualified()) {
  212. if ($type === Stmt\Use_::TYPE_CONSTANT) {
  213. // constant aliases are case-sensitive, function aliases case-insensitive
  214. $aliasName = $name->getFirst();
  215. }
  216. if (isset($this->aliases[$type][$aliasName])) {
  217. // resolve unqualified aliases
  218. return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
  219. }
  220. if (null === $this->namespace) {
  221. // outside of a namespace unaliased unqualified is same as fully qualified
  222. return new FullyQualified($name, $name->getAttributes());
  223. }
  224. // unqualified names inside a namespace cannot be resolved at compile-time
  225. // add the namespaced version of the name as an attribute
  226. $name->setAttribute('namespacedName',
  227. FullyQualified::concat($this->namespace, $name, $name->getAttributes()));
  228. return $name;
  229. }
  230. // if no alias exists prepend current namespace
  231. return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
  232. }
  233. protected function addNamespacedName(Node $node) {
  234. $node->namespacedName = Name::concat($this->namespace, $node->name);
  235. }
  236. }