Move logic out of Definition class
parent
fb9efd4727
commit
13e42cc7d2
|
@ -45,127 +45,4 @@ class Definition
|
|||
* @var \phpDocumentor\Type|null
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Returns the type a reference to this symbol will resolve to.
|
||||
* For properties and constants, this is the type of the property/constant.
|
||||
* For functions and methods, this is the return type.
|
||||
* For classes and interfaces, this is the class type (object).
|
||||
* Variables are not indexed for performance reasons.
|
||||
* Can also be a compound type.
|
||||
* If it is unknown, will be Types\Mixed.
|
||||
* Returns null if the node does not have a type.
|
||||
*
|
||||
* @param Node $node
|
||||
* @return \phpDocumentor\Type|null
|
||||
*/
|
||||
public static function getTypeFromNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Param) {
|
||||
// Parameters
|
||||
$docBlock = $node->getAttribute('docBlock');
|
||||
if ($docBlock !== null && count($paramTags = $docBlock->getTagsByName('param')) > 0) {
|
||||
// Use @param tag
|
||||
return $paramTags[0]->getType();
|
||||
}
|
||||
if ($node->type !== null) {
|
||||
// Use PHP7 return type hint
|
||||
if (is_string($node->type)) {
|
||||
// Resolve a string like "bool" to a type object
|
||||
$type = (new TypeResolver)->resolve($node->type);
|
||||
}
|
||||
$type = new Types\Object_(new Fqsen('\\' . (string)$node->type));
|
||||
if ($node->default !== null) {
|
||||
if (is_string($node->default)) {
|
||||
// Resolve a string like "bool" to a type object
|
||||
$defaultType = (new TypeResolver)->resolve($node->default);
|
||||
}
|
||||
$defaultType = new Types\Object_(new Fqsen('\\' . (string)$node->default));
|
||||
$type = new Types\Compound([$type, $defaultType]);
|
||||
}
|
||||
}
|
||||
// Unknown parameter type
|
||||
return new Types\Mixed;
|
||||
}
|
||||
if ($node instanceof Node\FunctionLike) {
|
||||
// Functions/methods
|
||||
$docBlock = $node->getAttribute('docBlock');
|
||||
if ($docBlock !== null && count($returnTags = $docBlock->getTagsByName('return')) > 0) {
|
||||
// Use @return tag
|
||||
return $returnTags[0]->getType();
|
||||
}
|
||||
if ($node->returnType !== null) {
|
||||
// Use PHP7 return type hint
|
||||
if (is_string($node->returnType)) {
|
||||
// Resolve a string like "bool" to a type object
|
||||
return (new TypeResolver)->resolve($node->returnType);
|
||||
}
|
||||
return new Types\Object_(new Fqsen('\\' . (string)$node->returnType));
|
||||
}
|
||||
// Unknown return type
|
||||
return new Types\Mixed;
|
||||
}
|
||||
if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) {
|
||||
// Property or constant
|
||||
$docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock');
|
||||
if ($docBlock !== null && count($varTags = $docBlock->getTagsByName('var')) > 0) {
|
||||
// Use @var tag
|
||||
return $varTags[0]->getType();
|
||||
}
|
||||
// TODO: read @property tags of class
|
||||
// TODO: Try to infer the type from default value / constant value
|
||||
// Unknown
|
||||
return new Types\Mixed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully qualified name (FQN) that is defined by a node
|
||||
* Returns null if the node does not declare any symbol that can be referenced by an FQN
|
||||
*
|
||||
* @param Node $node
|
||||
* @return string|null
|
||||
*/
|
||||
public static 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@ use function Sabre\Event\coroutine;
|
|||
class DefinitionResolver
|
||||
{
|
||||
private $project;
|
||||
private $typeResolver;
|
||||
|
||||
public function __construct(Project $project)
|
||||
{
|
||||
$this->project = $project;
|
||||
$this->typeResolver = new TypeResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,7 +37,7 @@ class DefinitionResolver
|
|||
$def->symbolInformation = SymbolInformation::fromNode($defNode);
|
||||
if ($defNode instanceof Node\Param) {
|
||||
// Get parameter type
|
||||
$def->type = Definition::getTypeFromNode($defNode);
|
||||
$def->type = $this->getTypeFromNode($defNode);
|
||||
} else {
|
||||
// Resolve the type of the assignment/closure use node
|
||||
$def->type = $this->resolveExpression($defNode);
|
||||
|
@ -244,7 +246,7 @@ class DefinitionResolver
|
|||
return $this->resolveExpression($defNode);
|
||||
}
|
||||
if ($defNode instanceof Node\Param) {
|
||||
return Definition::getTypeFromNode($defNode);
|
||||
return $this->getTypeFromNode($defNode);
|
||||
}
|
||||
}
|
||||
if ($expr instanceof Node\Expr\FuncCall) {
|
||||
|
@ -434,4 +436,123 @@ class DefinitionResolver
|
|||
}
|
||||
return new Types\Mixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type a reference to this symbol will resolve to.
|
||||
* For properties and constants, this is the type of the property/constant.
|
||||
* For functions and methods, this is the return type.
|
||||
* For classes and interfaces, this is the class type (object).
|
||||
* Variables are not indexed for performance reasons.
|
||||
* Can also be a compound type.
|
||||
* If it is unknown, will be Types\Mixed.
|
||||
* Returns null if the node does not have a type.
|
||||
*
|
||||
* @param Node $node
|
||||
* @return \phpDocumentor\Type|null
|
||||
*/
|
||||
public function getTypeFromNode(Node $node)
|
||||
{
|
||||
if ($node instanceof Node\Param) {
|
||||
// Parameters
|
||||
$docBlock = $node->getAttribute('docBlock');
|
||||
if ($docBlock !== null && count($paramTags = $docBlock->getTagsByName('param')) > 0) {
|
||||
// Use @param tag
|
||||
return $paramTags[0]->getType();
|
||||
}
|
||||
if ($node->type !== null) {
|
||||
// Use PHP7 return type hint
|
||||
if (is_string($node->type)) {
|
||||
// Resolve a string like "bool" to a type object
|
||||
$type = $this->typeResolver->resolve($node->type);
|
||||
}
|
||||
$type = new Types\Object_(new Fqsen('\\' . (string)$node->type));
|
||||
if ($node->default !== null) {
|
||||
$defaultType = $this->resolveExpression($node->default);
|
||||
$type = new Types\Compound([$type, $defaultType]);
|
||||
}
|
||||
}
|
||||
// Unknown parameter type
|
||||
return new Types\Mixed;
|
||||
}
|
||||
if ($node instanceof Node\FunctionLike) {
|
||||
// Functions/methods
|
||||
$docBlock = $node->getAttribute('docBlock');
|
||||
if ($docBlock !== null && count($returnTags = $docBlock->getTagsByName('return')) > 0) {
|
||||
// Use @return tag
|
||||
return $returnTags[0]->getType();
|
||||
}
|
||||
if ($node->returnType !== null) {
|
||||
// Use PHP7 return type hint
|
||||
if (is_string($node->returnType)) {
|
||||
// Resolve a string like "bool" to a type object
|
||||
return $this->typeResolver->resolve($node->returnType);
|
||||
}
|
||||
return new Types\Object_(new Fqsen('\\' . (string)$node->returnType));
|
||||
}
|
||||
// Unknown return type
|
||||
return new Types\Mixed;
|
||||
}
|
||||
if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) {
|
||||
// Property or constant
|
||||
$docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock');
|
||||
if ($docBlock !== null && count($varTags = $docBlock->getTagsByName('var')) > 0) {
|
||||
// Use @var tag
|
||||
return $varTags[0]->getType();
|
||||
}
|
||||
// TODO: read @property tags of class
|
||||
// TODO: Try to infer the type from default value / constant value
|
||||
// Unknown
|
||||
return new Types\Mixed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully qualified name (FQN) that is defined by a node
|
||||
* Returns null if the node does not declare any symbol that can be referenced by an FQN
|
||||
*
|
||||
* @param Node $node
|
||||
* @return string|null
|
||||
*/
|
||||
public static 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types = 1);
|
|||
namespace LanguageServer\NodeVisitor;
|
||||
|
||||
use PhpParser\{NodeVisitorAbstract, Node};
|
||||
use LanguageServer\Definition;
|
||||
use LanguageServer\{Definition, DefinitionResolver};
|
||||
use LanguageServer\Protocol\SymbolInformation;
|
||||
|
||||
/**
|
||||
|
@ -27,9 +27,16 @@ class DefinitionCollector extends NodeVisitorAbstract
|
|||
*/
|
||||
public $nodes = [];
|
||||
|
||||
public $definitionResolver;
|
||||
|
||||
public function __construct(DefinitionResolver $definitionResolver)
|
||||
{
|
||||
$this->definitionResolver = $definitionResolver;
|
||||
}
|
||||
|
||||
public function enterNode(Node $node)
|
||||
{
|
||||
$fqn = Definition::getDefinedFqn($node);
|
||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||
// Only index definitions with an FQN (no variables)
|
||||
if ($fqn === null) {
|
||||
return;
|
||||
|
@ -38,7 +45,7 @@ class DefinitionCollector extends NodeVisitorAbstract
|
|||
$def = new Definition;
|
||||
$def->fqn = $fqn;
|
||||
$def->symbolInformation = SymbolInformation::fromNode($node, $fqn);
|
||||
$def->type = Definition::getTypeFromNode($node);
|
||||
$def->type = $this->definitionResolver->getTypeFromNode($node);
|
||||
$this->definitions[$fqn] = $def;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ class PhpDocument
|
|||
$traverser = new NodeTraverser;
|
||||
|
||||
// Collect all definitions
|
||||
$definitionCollector = new DefinitionCollector;
|
||||
$definitionCollector = new DefinitionCollector($this->definitionResolver);
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
|
||||
// Collect all references
|
||||
|
@ -374,7 +374,7 @@ class PhpDocument
|
|||
return $refCollector->nodes;
|
||||
}
|
||||
// Definition with a global FQN
|
||||
$fqn = Definition::getDefinedFqn($node);
|
||||
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||
if ($fqn === null) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
|||
use PHPUnit\Framework\TestCase;
|
||||
use PhpParser\{NodeTraverser, Node};
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use LanguageServer\{LanguageClient, Project, PhpDocument, Parser};
|
||||
use LanguageServer\{LanguageClient, Project, PhpDocument, Parser, DefinitionResolver};
|
||||
use LanguageServer\Protocol\ClientCapabilities;
|
||||
use LanguageServer\Tests\MockProtocolStream;
|
||||
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
|
||||
|
@ -24,7 +24,7 @@ class DefinitionCollectorTest extends TestCase
|
|||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->addVisitor(new ReferencesAdder($document));
|
||||
$definitionCollector = new DefinitionCollector;
|
||||
$definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
$stmts = $parser->parse(file_get_contents($uri));
|
||||
$traverser->traverse($stmts);
|
||||
|
@ -63,7 +63,7 @@ class DefinitionCollectorTest extends TestCase
|
|||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new NameResolver);
|
||||
$traverser->addVisitor(new ReferencesAdder($document));
|
||||
$definitionCollector = new DefinitionCollector;
|
||||
$definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
|
||||
$traverser->addVisitor($definitionCollector);
|
||||
$stmts = $parser->parse(file_get_contents($uri));
|
||||
$traverser->traverse($stmts);
|
||||
|
|
Loading…
Reference in New Issue