1
0
Fork 0

Split up PhpDocument::getDefinitionByNode()

pull/49/head
Felix Becker 2016-10-09 10:09:09 +02:00
parent 6be53ad658
commit 3a880934e5
2 changed files with 71 additions and 21 deletions

View File

@ -70,6 +70,13 @@ class PhpDocument
*/ */
private $definitions = []; private $definitions = [];
/**
* Map from fully qualified name (FQN) to array of nodes that reference the symbol
*
* @var Node[][]
*/
private $references;
/** /**
* @param string $uri The URI of the document * @param string $uri The URI of the document
* @param Project $project The Project this document belongs to (to register definitions etc) * @param Project $project The Project this document belongs to (to register definitions etc)
@ -284,23 +291,28 @@ class PhpDocument
} }
/** /**
* Returns the definition node for any node * Returns true if the given FQN is defined in this document
* The definition node MAY be in another document, check the ownerDocument attribute *
* @param string $fqn The fully qualified name of the symbol
* @return bool
*/
public function isDefined(string $fqn): bool
{
return isset($this->definitions[$fqn]);
}
/**
* Returns the FQN that is referenced by a node
* *
* @param Node $node * @param Node $node
* @return Node|null * @return string|null
*/ */
public function getDefinitionByNode(Node $node) public function getReferencedFqn(Node $node)
{ {
if ($node instanceof Node\Name) { if ($node instanceof Node\Name) {
$nameNode = $node; $nameNode = $node;
$node = $node->getAttribute('parentNode'); $node = $node->getAttribute('parentNode');
} }
// Variables always stay in the boundary of the file and need to be searched inside their function scope
// by traversing the AST
if ($node instanceof Node\Expr\Variable) {
return $this->getVariableDefinition($node);
}
if ( if (
($node instanceof Node\Stmt\ClassLike ($node instanceof Node\Stmt\ClassLike
@ -310,13 +322,15 @@ class PhpDocument
) { ) {
// For extends, implements and type hints use the name directly // For extends, implements and type hints use the name directly
$name = (string)$nameNode; $name = (string)$nameNode;
} else if ($node instanceof Node\Stmt\UseUse) { // Only the name node should be considered a reference, not the UseUse node itself
} else if ($node instanceof Node\Stmt\UseUse && isset($nameNode)) {
$name = (string)$node->name; $name = (string)$node->name;
$parent = $node->getAttribute('parentNode'); $parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Stmt\GroupUse) { if ($parent instanceof Node\Stmt\GroupUse) {
$name = $parent->prefix . '\\' . $name; $name = $parent->prefix . '\\' . $name;
} }
} else if ($node instanceof Node\Expr\New_) { // Only the name node should be considered a reference, not the New_ node itself
} else if ($node instanceof Node\Expr\New_ && isset($nameNode)) {
if (!($node->class instanceof Node\Name)) { if (!($node->class instanceof Node\Name)) {
// Cannot get definition of dynamic calls // Cannot get definition of dynamic calls
return null; return null;
@ -381,25 +395,50 @@ class PhpDocument
if (!isset($name)) { if (!isset($name)) {
return null; return null;
} }
// Search for the document where the class, interface, trait, function, method or property is defined // If the node is a function or constant, it could be namespaced, but PHP falls back to global
$document = $this->project->getDefinitionDocument($name); // The NameResolver therefor does not resolve these to namespaced names
if (!$document && $node instanceof Node\Expr\FuncCall) { // http://php.net/manual/en/language.namespaces.fallback.php
if ($node instanceof Node\Expr\FuncCall || $node instanceof Node\Expr\ConstFetch) {
// Find and try with namespace // Find and try with namespace
// Namespaces aren't added automatically by NameResolver because PHP falls back to global functions
$n = $node; $n = $node;
while (isset($n)) { while (isset($n)) {
$n = $n->getAttribute('parentNode'); $n = $n->getAttribute('parentNode');
if ($n instanceof Node\Stmt\Namespace_) { if ($n instanceof Node\Stmt\Namespace_) {
$name = (string)$n->name . '\\' . $name; $namespacedName = (string)$n->name . '\\' . $name;
$document = $this->project->getDefinitionDocument($name); // If the namespaced version is defined, return that
break; // Otherwise fall back to global
if ($this->project->isDefined($namespacedName)) {
return $namespacedName;
}
} }
} }
} }
return $name;
}
/**
* Returns the definition node for any node
* The definition node MAY be in another document, check the ownerDocument attribute
*
* @param Node $node
* @return Node|null
*/
public function getDefinitionByNode(Node $node)
{
// Variables always stay in the boundary of the file and need to be searched inside their function scope
// by traversing the AST
if ($node instanceof Node\Expr\Variable) {
return $this->getVariableDefinition($node);
}
$fqn = $this->getReferencedFqn($node);
if (!isset($fqn)) {
return null;
}
$document = $this->project->getDefinitionDocument($fqn);
if (!isset($document)) { if (!isset($document)) {
return null; return null;
} }
return $document->getDefinitionByFqn($name); return $document->getDefinitionByFqn($fqn);
} }
/** /**

View File

@ -15,7 +15,7 @@ class Project
* *
* @var PhpDocument[] * @var PhpDocument[]
*/ */
private $documents; private $documents = [];
/** /**
* An associative array [string => PhpDocument] * An associative array [string => PhpDocument]
@ -23,7 +23,7 @@ class Project
* *
* @var PhpDocument[] * @var PhpDocument[]
*/ */
private $definitions; private $definitions = [];
/** /**
* Instance of the PHP parser * Instance of the PHP parser
@ -84,6 +84,17 @@ class Project
return $this->definitions[$fqn] ?? null; return $this->definitions[$fqn] ?? null;
} }
/**
* Returns true if the given FQN is defined in the project
*
* @param string $fqn The fully qualified name of the symbol
* @return bool
*/
public function isDefined(string $fqn): bool
{
return isset($this->definitions[$fqn]);
}
/** /**
* Finds symbols in all documents, filtered by query parameter. * Finds symbols in all documents, filtered by query parameter.
* *