Refactor DefinitionResolver
Move logic to PhpDocument::getDefininedFqn() for reusability Fix DefinitionResolverTestpull/49/head
parent
7322a6c658
commit
7f95b76cf8
|
@ -13,14 +13,6 @@ class DefinitionCollector extends NodeVisitorAbstract
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Map from fully qualified name (FQN) to Node
|
* Map from fully qualified name (FQN) to Node
|
||||||
* Examples of fully qualified names:
|
|
||||||
* - testFunction()
|
|
||||||
* - TestNamespace\TestClass
|
|
||||||
* - TestNamespace\TestClass::TEST_CONSTANT
|
|
||||||
* - TestNamespace\TestClass::staticTestProperty
|
|
||||||
* - TestNamespace\TestClass::testProperty
|
|
||||||
* - TestNamespace\TestClass::staticTestMethod()
|
|
||||||
* - TestNamespace\TestClass::testMethod()
|
|
||||||
*
|
*
|
||||||
* @var Node[]
|
* @var Node[]
|
||||||
*/
|
*/
|
||||||
|
@ -28,45 +20,9 @@ class DefinitionCollector extends NodeVisitorAbstract
|
||||||
|
|
||||||
public function enterNode(Node $node)
|
public function enterNode(Node $node)
|
||||||
{
|
{
|
||||||
if ($node instanceof Node\Stmt\ClassLike && isset($node->name)) {
|
$fqn = $node->getAttribute('ownerDocument')->getDefinedFqn($node);
|
||||||
// Class, interface or trait declaration
|
if ($fqn !== null) {
|
||||||
$this->definitions[(string)$node->namespacedName] = $node;
|
$this->definitions[$fqn] = $node;
|
||||||
} else if ($node instanceof Node\Stmt\Function_) {
|
|
||||||
// Function: use functioName() as the name
|
|
||||||
$name = (string)$node->namespacedName . '()';
|
|
||||||
$this->definitions[$name] = $node;
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
$name = (string)$class->namespacedName . '::' . (string)$node->name . '()';
|
|
||||||
$this->definitions[$name] = $node;
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
$name = (string)$class->namespacedName . '::' . (string)$node->name;
|
|
||||||
$this->definitions[$name] = $node;
|
|
||||||
} else if ($node instanceof Node\Const_) {
|
|
||||||
$parent = $node->getAttribute('parentNode');
|
|
||||||
if ($parent instanceof Node\Stmt\Const_) {
|
|
||||||
// Basic constant: use CONSTANT_NAME as name
|
|
||||||
$name = (string)$node->namespacedName;
|
|
||||||
} else 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;
|
|
||||||
}
|
|
||||||
$name = (string)$class->namespacedName . '::' . $node->name;
|
|
||||||
}
|
|
||||||
$this->definitions[$name] = $node;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,14 +57,6 @@ class PhpDocument
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map from fully qualified name (FQN) to Node
|
* Map from fully qualified name (FQN) to Node
|
||||||
* Examples of fully qualified names:
|
|
||||||
* - testFunction()
|
|
||||||
* - TestNamespace\TestClass
|
|
||||||
* - TestNamespace\TestClass::TEST_CONSTANT
|
|
||||||
* - TestNamespace\TestClass::staticTestProperty
|
|
||||||
* - TestNamespace\TestClass::testProperty
|
|
||||||
* - TestNamespace\TestClass::staticTestMethod()
|
|
||||||
* - TestNamespace\TestClass::testMethod()
|
|
||||||
*
|
*
|
||||||
* @var Node[]
|
* @var Node[]
|
||||||
*/
|
*/
|
||||||
|
@ -299,6 +291,67 @@ class PhpDocument
|
||||||
return isset($this->definitions[$fqn]);
|
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)
|
||||||
|
{
|
||||||
|
if ($node instanceof Node\Name) {
|
||||||
|
$nameNode = $node;
|
||||||
|
$node = $node->getAttribute('parentNode');
|
||||||
|
}
|
||||||
|
// Only the class node should count as the definition, not the name node
|
||||||
|
// Anonymous classes don't count as a definition
|
||||||
|
if ($node instanceof Node\Stmt\ClassLike && !isset($nameNode) && 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
|
* Returns the FQN that is referenced by a node
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,18 +6,23 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PhpParser\{ParserFactory, NodeTraverser, Node};
|
use PhpParser\{ParserFactory, NodeTraverser, Node};
|
||||||
use PhpParser\NodeVisitor\NameResolver;
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
|
use LanguageServer\{LanguageClient, Project, PhpDocument};
|
||||||
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\NodeVisitors\{ReferencesAdder, DefinitionCollector};
|
use LanguageServer\NodeVisitors\{ReferencesAdder, DefinitionCollector};
|
||||||
|
|
||||||
class DefinitionCollectorTest extends TestCase
|
class DefinitionCollectorTest extends TestCase
|
||||||
{
|
{
|
||||||
public function test()
|
public function test()
|
||||||
{
|
{
|
||||||
|
$client = new LanguageClient(new MockProtocolStream());
|
||||||
|
$project = new Project($client);
|
||||||
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
|
$document = new PhpDocument('whatever', $project, $client, $parser);
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
$traverser->addVisitor(new NameResolver);
|
$traverser->addVisitor(new NameResolver);
|
||||||
$traverser->addVisitor(new ReferencesAdder);
|
$traverser->addVisitor(new ReferencesAdder($document));
|
||||||
$definitionCollector = new DefinitionCollector;
|
$definitionCollector = new DefinitionCollector;
|
||||||
$traverser->addVisitor($definitionCollector);
|
$traverser->addVisitor($definitionCollector);
|
||||||
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
|
||||||
$stmts = $parser->parse(file_get_contents(__DIR__ . '/../../fixtures/symbols.php'));
|
$stmts = $parser->parse(file_get_contents(__DIR__ . '/../../fixtures/symbols.php'));
|
||||||
$traverser->traverse($stmts);
|
$traverser->traverse($stmts);
|
||||||
$defs = $definitionCollector->definitions;
|
$defs = $definitionCollector->definitions;
|
||||||
|
|
Loading…
Reference in New Issue