-<?php
+<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\Error;
use PhpParser\ErrorHandler;
+use PhpParser\NameContext;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
class NameResolver extends NodeVisitorAbstract
{
- /** @var null|Name Current namespace */
- protected $namespace;
-
- /** @var array Map of format [aliasType => [aliasName => originalName]] */
- protected $aliases;
-
- /** @var ErrorHandler Error handler */
- protected $errorHandler;
+ /** @var NameContext Naming context */
+ protected $nameContext;
/** @var bool Whether to preserve original names */
protected $preserveOriginalNames;
+ /** @var bool Whether to replace resolved nodes in place, or to add resolvedNode attributes */
+ protected $replaceNodes;
+
/**
* Constructs a name resolution visitor.
*
- * Options: If "preserveOriginalNames" is enabled, an "originalName" attribute will be added to
- * all name nodes that underwent resolution.
+ * Options:
+ * * preserveOriginalNames (default false): An "originalName" attribute will be added to
+ * all name nodes that underwent resolution.
+ * * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
+ * resolvedName attribute is added. (Names that cannot be statically resolved receive a
+ * namespacedName attribute, as usual.)
*
* @param ErrorHandler|null $errorHandler Error handler
* @param array $options Options
*/
public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
- $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
- $this->preserveOriginalNames = !empty($options['preserveOriginalNames']);
+ $this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
+ $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
+ $this->replaceNodes = $options['replaceNodes'] ?? true;
+ }
+
+ /**
+ * Get name resolution context.
+ *
+ * @return NameContext
+ */
+ public function getNameContext() : NameContext {
+ return $this->nameContext;
}
public function beforeTraverse(array $nodes) {
- $this->resetState();
+ $this->nameContext->startNamespace();
+ return null;
}
public function enterNode(Node $node) {
if ($node instanceof Stmt\Namespace_) {
- $this->resetState($node->name);
+ $this->nameContext->startNamespace($node->name);
} elseif ($node instanceof Stmt\Use_) {
foreach ($node->uses as $use) {
$this->addAlias($use, $node->type, null);
}
} elseif ($node instanceof Expr\FuncCall) {
if ($node->name instanceof Name) {
- $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION);
+ $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
}
} elseif ($node instanceof Expr\ConstFetch) {
- $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT);
+ $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
} elseif ($node instanceof Stmt\TraitUse) {
foreach ($node->traits as &$trait) {
$trait = $this->resolveClassName($trait);
}
}
}
- }
- protected function resetState(Name $namespace = null) {
- $this->namespace = $namespace;
- $this->aliases = array(
- Stmt\Use_::TYPE_NORMAL => array(),
- Stmt\Use_::TYPE_FUNCTION => array(),
- Stmt\Use_::TYPE_CONSTANT => array(),
- );
+ return null;
}
- protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
+ private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
// Add prefix for group uses
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration
$type |= $use->type;
- // Constant names are case sensitive, everything else case insensitive
- if ($type === Stmt\Use_::TYPE_CONSTANT) {
- $aliasName = $use->alias;
- } else {
- $aliasName = strtolower($use->alias);
- }
-
- if (isset($this->aliases[$type][$aliasName])) {
- $typeStringMap = array(
- Stmt\Use_::TYPE_NORMAL => '',
- Stmt\Use_::TYPE_FUNCTION => 'function ',
- Stmt\Use_::TYPE_CONSTANT => 'const ',
- );
-
- $this->errorHandler->handleError(new Error(
- sprintf(
- 'Cannot use %s%s as %s because the name is already in use',
- $typeStringMap[$type], $name, $use->alias
- ),
- $use->getAttributes()
- ));
- return;
- }
-
- $this->aliases[$type][$aliasName] = $name;
+ $this->nameContext->addAlias(
+ $name, (string) $use->getAlias(), $type, $use->getAttributes()
+ );
}
/** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
return $node;
}
- protected function resolveClassName(Name $name) {
- if ($this->preserveOriginalNames) {
- // Save the original name
- $originalName = $name;
- $name = clone $originalName;
- $name->setAttribute('originalName', $originalName);
- }
-
- // don't resolve special class names
- if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
- if (!$name->isUnqualified()) {
- $this->errorHandler->handleError(new Error(
- sprintf("'\\%s' is an invalid class name", $name->toString()),
- $name->getAttributes()
- ));
+ /**
+ * Resolve name, according to name resolver options.
+ *
+ * @param Name $name Function or constant name to resolve
+ * @param int $type One of Stmt\Use_::TYPE_*
+ *
+ * @return Name Resolved name, or original name with attribute
+ */
+ protected function resolveName(Name $name, int $type) : Name {
+ if (!$this->replaceNodes) {
+ $resolvedName = $this->nameContext->getResolvedName($name, $type);
+ if (null !== $resolvedName) {
+ $name->setAttribute('resolvedName', $resolvedName);
+ } else {
+ $name->setAttribute('namespacedName', FullyQualified::concat(
+ $this->nameContext->getNamespace(), $name, $name->getAttributes()));
}
return $name;
}
- // fully qualified names are already resolved
- if ($name->isFullyQualified()) {
- return $name;
- }
-
- $aliasName = strtolower($name->getFirst());
- if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
- // resolve aliases (for non-relative names)
- $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
- return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
- }
-
- // if no alias exists prepend current namespace
- return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
- }
-
- protected function resolveOtherName(Name $name, $type) {
if ($this->preserveOriginalNames) {
// Save the original name
$originalName = $name;
$name->setAttribute('originalName', $originalName);
}
- // fully qualified names are already resolved
- if ($name->isFullyQualified()) {
- return $name;
- }
-
- // resolve aliases for qualified names
- $aliasName = strtolower($name->getFirst());
- if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) {
- $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName];
- return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
+ $resolvedName = $this->nameContext->getResolvedName($name, $type);
+ if (null !== $resolvedName) {
+ return $resolvedName;
}
- if ($name->isUnqualified()) {
- if ($type === Stmt\Use_::TYPE_CONSTANT) {
- // constant aliases are case-sensitive, function aliases case-insensitive
- $aliasName = $name->getFirst();
- }
-
- if (isset($this->aliases[$type][$aliasName])) {
- // resolve unqualified aliases
- return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes());
- }
-
- if (null === $this->namespace) {
- // outside of a namespace unaliased unqualified is same as fully qualified
- return new FullyQualified($name, $name->getAttributes());
- }
-
- // unqualified names inside a namespace cannot be resolved at compile-time
- // add the namespaced version of the name as an attribute
- $name->setAttribute('namespacedName',
- FullyQualified::concat($this->namespace, $name, $name->getAttributes()));
- return $name;
- }
+ // unqualified names inside a namespace cannot be resolved at compile-time
+ // add the namespaced version of the name as an attribute
+ $name->setAttribute('namespacedName', FullyQualified::concat(
+ $this->nameContext->getNamespace(), $name, $name->getAttributes()));
+ return $name;
+ }
- // if no alias exists prepend current namespace
- return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
+ protected function resolveClassName(Name $name) {
+ return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
}
protected function addNamespacedName(Node $node) {
- $node->namespacedName = Name::concat($this->namespace, $node->name);
+ $node->namespacedName = Name::concat(
+ $this->nameContext->getNamespace(), (string) $node->name);
}
}