1
0
Fork 0

Refactor FQN functions to own namespace (#100)

pull/101/head
Felix Becker 2016-10-20 01:00:20 +02:00 committed by GitHub
parent e993b9994a
commit 96694996f7
5 changed files with 257 additions and 241 deletions

View File

@ -37,7 +37,10 @@
"psr-4": {
"LanguageServer\\": "src/"
},
"files" : ["src/utils.php"]
"files" : [
"src/utils.php",
"src/Fqn.php"
]
},
"autoload-dev": {
"psr-4": {

245
src/Fqn.php Normal file
View File

@ -0,0 +1,245 @@
<?php
/**
* Contains pure functions for converting AST nodes from and to FQNs
*
* Examples of FQNs:
* - testFunction()
* - TestNamespace\TestClass
* - TestNamespace\TestClass::TEST_CONSTANT
* - TestNamespace\TestClass::staticTestProperty
* - TestNamespace\TestClass::testProperty
* - TestNamespace\TestClass::staticTestMethod()
* - TestNamespace\TestClass::testMethod()
*/
namespace LanguageServer\Fqn;
use PhpParser\Node;
/**
* Returns the FQN that is referenced by a node
*
* @param Node $node
* @return string|null
*/
function getReferencedFqn(Node $node)
{
$parent = $node->getAttribute('parentNode');
if (
$node instanceof Node\Name && (
$parent instanceof Node\Stmt\ClassLike
|| $parent instanceof Node\Param
|| $parent instanceof Node\Stmt\Function_
|| $parent instanceof Node\Expr\StaticCall
|| $parent instanceof Node\Expr\ClassConstFetch
|| $parent instanceof Node\Expr\StaticPropertyFetch
)
) {
// For extends, implements, type hints and classes of classes of static calls use the name directly
$name = (string)$node;
// Only the name node should be considered a reference, not the UseUse node itself
} else if ($parent instanceof Node\Stmt\UseUse) {
$name = (string)$parent->name;
$grandParent = $parent->getAttribute('parentNode');
if ($grandParent instanceof Node\Stmt\GroupUse) {
$name = $grandParent->prefix . '\\' . $name;
}
// Only the name node should be considered a reference, not the New_ node itself
} else if ($parent instanceof Node\Expr\New_) {
if (!($parent->class instanceof Node\Name)) {
// Cannot get definition of dynamic calls
return null;
}
$name = (string)$parent->class;
} else if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
if ($node->name instanceof Node\Expr || !($node->var instanceof Node\Expr\Variable)) {
// Cannot get definition of dynamic calls
return null;
}
// Need to resolve variable to a class
if ($node->var->name === 'this') {
// $this resolved to the class it is contained in
$n = $node;
while ($n = $n->getAttribute('parentNode')) {
if ($n instanceof Node\Stmt\Class_) {
if ($n->isAnonymous()) {
return null;
}
$name = (string)$n->namespacedName;
break;
}
}
if (!isset($name)) {
return null;
}
} else {
// Other variables resolve to their definition
$varDef = getVariableDefinition($node->var);
if (!isset($varDef)) {
return null;
}
if ($varDef instanceof Node\Param) {
if (!isset($varDef->type)) {
// Cannot resolve to class without a type hint
// TODO: parse docblock
return null;
}
$name = (string)$varDef->type;
} else if ($varDef instanceof Node\Expr\Assign) {
if ($varDef->expr instanceof Node\Expr\New_) {
if (!($varDef->expr->class instanceof Node\Name)) {
// Cannot get definition of dynamic calls
return null;
}
$name = (string)$varDef->expr->class;
} else {
return null;
}
} else {
return null;
}
}
$name .= '::' . (string)$node->name;
} else if ($parent instanceof Node\Expr\FuncCall) {
if ($parent->name instanceof Node\Expr) {
return null;
}
$name = (string)($node->getAttribute('namespacedName') ?? $parent->name);
} else if ($parent instanceof Node\Expr\ConstFetch) {
$name = (string)($node->getAttribute('namespacedName') ?? $parent->name);
} else if (
$node instanceof Node\Expr\ClassConstFetch
|| $node instanceof Node\Expr\StaticPropertyFetch
|| $node instanceof Node\Expr\StaticCall
) {
if ($node->class instanceof Node\Expr || $node->name instanceof Node\Expr) {
// Cannot get definition of dynamic names
return null;
}
$className = (string)$node->class;
if ($className === 'self' || $className === 'static' || $className === 'parent') {
// self and static are resolved to the containing class
$n = $node;
while ($n = $n->getAttribute('parentNode')) {
if ($n instanceof Node\Stmt\Class_) {
if ($n->isAnonymous()) {
return null;
}
if ($className === 'parent') {
// parent is resolved to the parent class
if (!isset($n->extends)) {
return null;
}
$className = (string)$n->extends;
} else {
$className = (string)$n->namespacedName;
}
break;
}
}
}
$name = (string)$className . '::' . $node->name;
} else {
return null;
}
if (
$node instanceof Node\Expr\MethodCall
|| $node instanceof Node\Expr\StaticCall
|| $parent instanceof Node\Expr\FuncCall
) {
$name .= '()';
}
if (!isset($name)) {
return null;
}
return $name;
}
/**
* Returns the assignment or parameter node where a variable was defined
*
* @param Node\Expr\Variable $n The variable access
* @return Node\Expr\Assign|Node\Param|Node\Expr\ClosureUse|null
*/
function getVariableDefinition(Node\Expr\Variable $var)
{
$n = $var;
// Traverse the AST up
do {
// If a function is met, check the parameters and use statements
if ($n instanceof Node\FunctionLike) {
foreach ($n->getParams() as $param) {
if ($param->name === $var->name) {
return $param;
}
}
// If it is a closure, also check use statements
if ($n instanceof Node\Expr\Closure) {
foreach ($n->uses as $use) {
if ($use->var === $var->name) {
return $use;
}
}
}
break;
}
// Check each previous sibling node for a variable assignment to that variable
while ($n->getAttribute('previousSibling') && $n = $n->getAttribute('previousSibling')) {
if ($n instanceof Node\Expr\Assign && $n->var instanceof Node\Expr\Variable && $n->var->name === $var->name) {
return $n;
}
}
} while (isset($n) && $n = $n->getAttribute('parentNode'));
// Return null if nothing was found
return null;
}
/**
* Returns the fully qualified name (FQN) that is defined by a node
*
* @param Node $node
* @return string|null
*/
function getDefinedFqn(Node $node)
{
// Anonymous classes don't count as a definition
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
// Class, interface or trait declaration
return (string)$node->namespacedName;
} else if ($node instanceof Node\Stmt\Function_) {
// Function: use functionName() as the name
return (string)$node->namespacedName . '()';
} else if ($node instanceof Node\Stmt\ClassMethod) {
// Class method: use ClassName::methodName() as name
$class = $node->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name . '()';
} else if ($node instanceof Node\Stmt\PropertyProperty) {
// Property: use ClassName::propertyName as name
$class = $node->getAttribute('parentNode')->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name;
} else if ($node instanceof Node\Const_) {
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Stmt\Const_) {
// Basic constant: use CONSTANT_NAME as name
return (string)$node->namespacedName;
}
if ($parent instanceof Node\Stmt\ClassConst) {
// Class constant: use ClassName::CONSTANT_NAME as name
$class = $parent->getAttribute('parentNode');
if (!isset($class->name) || $class->name instanceof Node\Expr) {
return null;
}
return (string)$class->namespacedName . '::' . $node->name;
}
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types = 1);
namespace LanguageServer\NodeVisitor;
use PhpParser\{NodeVisitorAbstract, Node};
use function LanguageServer\Fqn\getDefinedFqn;
/**
* Collects definitions of classes, interfaces, traits, methods, properties and constants
@ -20,7 +21,7 @@ class DefinitionCollector extends NodeVisitorAbstract
public function enterNode(Node $node)
{
$fqn = $node->getAttribute('ownerDocument')->getDefinedFqn($node);
$fqn = getDefinedFqn($node);
if ($fqn !== null) {
$this->definitions[$fqn] = $node;
}

View File

@ -3,6 +3,7 @@ declare(strict_types = 1);
namespace LanguageServer\NodeVisitor;
use function LanguageServer\Fqn\getReferencedFqn;
use PhpParser\{NodeVisitorAbstract, Node};
/**
@ -21,7 +22,7 @@ class ReferencesCollector extends NodeVisitorAbstract
public function enterNode(Node $node)
{
// Check if the node references any global symbol
$fqn = $node->getAttribute('ownerDocument')->getReferencedFqn($node);
$fqn = getReferencedFqn($node);
if ($fqn) {
$this->addReference($fqn, $node);
// Namespaced constant access and function calls also need to register a reference

View File

@ -16,6 +16,7 @@ use LanguageServer\NodeVisitor\{
use PhpParser\{Error, Node, NodeTraverser, Parser};
use PhpParser\NodeVisitor\NameResolver;
use phpDocumentor\Reflection\DocBlockFactory;
use function LanguageServer\Fqn\{getDefinedFqn, getVariableDefinition, getReferencedFqn};
class PhpDocument
{
@ -297,202 +298,6 @@ class PhpDocument
return isset($this->definitions[$fqn]);
}
/**
* Returns the fully qualified name (FQN) that is defined by a node
* Examples of FQNs:
* - testFunction()
* - TestNamespace\TestClass
* - TestNamespace\TestClass::TEST_CONSTANT
* - TestNamespace\TestClass::staticTestProperty
* - TestNamespace\TestClass::testProperty
* - TestNamespace\TestClass::staticTestMethod()
* - TestNamespace\TestClass::testMethod()
*
* @param Node $node
* @return string|null
*/
public function getDefinedFqn(Node $node)
{
// Anonymous classes don't count as a definition
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
// Class, interface or trait declaration
return (string)$node->namespacedName;
} else if ($node instanceof Node\Stmt\Function_) {
// Function: use functionName() as the name
return (string)$node->namespacedName . '()';
} else if ($node instanceof Node\Stmt\ClassMethod) {
// Class method: use ClassName::methodName() as name
$class = $node->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name . '()';
} else if ($node instanceof Node\Stmt\PropertyProperty) {
// Property: use ClassName::propertyName as name
$class = $node->getAttribute('parentNode')->getAttribute('parentNode');
if (!isset($class->name)) {
// Ignore anonymous classes
return null;
}
return (string)$class->namespacedName . '::' . (string)$node->name;
} else if ($node instanceof Node\Const_) {
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Stmt\Const_) {
// Basic constant: use CONSTANT_NAME as name
return (string)$node->namespacedName;
}
if ($parent instanceof Node\Stmt\ClassConst) {
// Class constant: use ClassName::CONSTANT_NAME as name
$class = $parent->getAttribute('parentNode');
if (!isset($class->name) || $class->name instanceof Node\Expr) {
return null;
}
return (string)$class->namespacedName . '::' . $node->name;
}
}
}
/**
* Returns the FQN that is referenced by a node
*
* @param Node $node
* @return string|null
*/
public function getReferencedFqn(Node $node)
{
$parent = $node->getAttribute('parentNode');
if (
$node instanceof Node\Name && (
$parent instanceof Node\Stmt\ClassLike
|| $parent instanceof Node\Param
|| $parent instanceof Node\Stmt\Function_
|| $parent instanceof Node\Expr\StaticCall
|| $parent instanceof Node\Expr\ClassConstFetch
|| $parent instanceof Node\Expr\StaticPropertyFetch
)
) {
// For extends, implements, type hints and classes of classes of static calls use the name directly
$name = (string)$node;
// Only the name node should be considered a reference, not the UseUse node itself
} else if ($parent instanceof Node\Stmt\UseUse) {
$name = (string)$parent->name;
$grandParent = $parent->getAttribute('parentNode');
if ($grandParent instanceof Node\Stmt\GroupUse) {
$name = $grandParent->prefix . '\\' . $name;
}
// Only the name node should be considered a reference, not the New_ node itself
} else if ($parent instanceof Node\Expr\New_) {
if (!($parent->class instanceof Node\Name)) {
// Cannot get definition of dynamic calls
return null;
}
$name = (string)$parent->class;
} else if ($node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\PropertyFetch) {
if ($node->name instanceof Node\Expr || !($node->var instanceof Node\Expr\Variable)) {
// Cannot get definition of dynamic calls
return null;
}
// Need to resolve variable to a class
if ($node->var->name === 'this') {
// $this resolved to the class it is contained in
$n = $node;
while ($n = $n->getAttribute('parentNode')) {
if ($n instanceof Node\Stmt\Class_) {
if ($n->isAnonymous()) {
return null;
}
$name = (string)$n->namespacedName;
break;
}
}
if (!isset($name)) {
return null;
}
} else {
// Other variables resolve to their definition
$varDef = $this->getVariableDefinition($node->var);
if (!isset($varDef)) {
return null;
}
if ($varDef instanceof Node\Param) {
if (!isset($varDef->type)) {
// Cannot resolve to class without a type hint
// TODO: parse docblock
return null;
}
$name = (string)$varDef->type;
} else if ($varDef instanceof Node\Expr\Assign) {
if ($varDef->expr instanceof Node\Expr\New_) {
if (!($varDef->expr->class instanceof Node\Name)) {
// Cannot get definition of dynamic calls
return null;
}
$name = (string)$varDef->expr->class;
} else {
return null;
}
} else {
return null;
}
}
$name .= '::' . (string)$node->name;
} else if ($parent instanceof Node\Expr\FuncCall) {
if ($parent->name instanceof Node\Expr) {
return null;
}
$name = (string)($node->getAttribute('namespacedName') ?? $parent->name);
} else if ($parent instanceof Node\Expr\ConstFetch) {
$name = (string)($node->getAttribute('namespacedName') ?? $parent->name);
} else if (
$node instanceof Node\Expr\ClassConstFetch
|| $node instanceof Node\Expr\StaticPropertyFetch
|| $node instanceof Node\Expr\StaticCall
) {
if ($node->class instanceof Node\Expr || $node->name instanceof Node\Expr) {
// Cannot get definition of dynamic names
return null;
}
$className = (string)$node->class;
if ($className === 'self' || $className === 'static' || $className === 'parent') {
// self and static are resolved to the containing class
$n = $node;
while ($n = $n->getAttribute('parentNode')) {
if ($n instanceof Node\Stmt\Class_) {
if ($n->isAnonymous()) {
return null;
}
if ($className === 'parent') {
// parent is resolved to the parent class
if (!isset($n->extends)) {
return null;
}
$className = (string)$n->extends;
} else {
$className = (string)$n->namespacedName;
}
break;
}
}
}
$name = (string)$className . '::' . $node->name;
} else {
return null;
}
if (
$node instanceof Node\Expr\MethodCall
|| $node instanceof Node\Expr\StaticCall
|| $parent instanceof Node\Expr\FuncCall
) {
$name .= '()';
}
if (!isset($name)) {
return null;
}
return $name;
}
/**
* Returns the definition node for any node
* The definition node MAY be in another document, check the ownerDocument attribute
@ -505,9 +310,9 @@ class PhpDocument
// 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);
return getVariableDefinition($node);
}
$fqn = $this->getReferencedFqn($node);
$fqn = getReferencedFqn($node);
if (!isset($fqn)) {
return null;
}
@ -558,7 +363,7 @@ class PhpDocument
return $refCollector->references;
}
// Definition with a global FQN
$fqn = $this->getDefinedFqn($node);
$fqn = getDefinedFqn($node);
if ($fqn === null) {
return [];
}
@ -574,43 +379,4 @@ class PhpDocument
}
return $nodes;
}
/**
* Returns the assignment or parameter node where a variable was defined
*
* @param Node\Expr\Variable $n The variable access
* @return Node\Expr\Assign|Node\Param|Node\Expr\ClosureUse|null
*/
public function getVariableDefinition(Node\Expr\Variable $var)
{
$n = $var;
// Traverse the AST up
do {
// If a function is met, check the parameters and use statements
if ($n instanceof Node\FunctionLike) {
foreach ($n->getParams() as $param) {
if ($param->name === $var->name) {
return $param;
}
}
// If it is a closure, also check use statements
if ($n instanceof Node\Expr\Closure) {
foreach ($n->uses as $use) {
if ($use->var === $var->name) {
return $use;
}
}
}
break;
}
// Check each previous sibling node for a variable assignment to that variable
while ($n->getAttribute('previousSibling') && $n = $n->getAttribute('previousSibling')) {
if ($n instanceof Node\Expr\Assign && $n->var instanceof Node\Expr\Variable && $n->var->name === $var->name) {
return $n;
}
}
} while (isset($n) && $n = $n->getAttribute('parentNode'));
// Return null if nothing was found
return null;
}
}