Move logic out of Definition class
parent
fb9efd4727
commit
13e42cc7d2
|
@ -45,127 +45,4 @@ class Definition
|
||||||
* @var \phpDocumentor\Type|null
|
* @var \phpDocumentor\Type|null
|
||||||
*/
|
*/
|
||||||
public $type;
|
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
|
class DefinitionResolver
|
||||||
{
|
{
|
||||||
private $project;
|
private $project;
|
||||||
|
private $typeResolver;
|
||||||
|
|
||||||
public function __construct(Project $project)
|
public function __construct(Project $project)
|
||||||
{
|
{
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
|
$this->typeResolver = new TypeResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,7 +37,7 @@ class DefinitionResolver
|
||||||
$def->symbolInformation = SymbolInformation::fromNode($defNode);
|
$def->symbolInformation = SymbolInformation::fromNode($defNode);
|
||||||
if ($defNode instanceof Node\Param) {
|
if ($defNode instanceof Node\Param) {
|
||||||
// Get parameter type
|
// Get parameter type
|
||||||
$def->type = Definition::getTypeFromNode($defNode);
|
$def->type = $this->getTypeFromNode($defNode);
|
||||||
} else {
|
} else {
|
||||||
// Resolve the type of the assignment/closure use node
|
// Resolve the type of the assignment/closure use node
|
||||||
$def->type = $this->resolveExpression($defNode);
|
$def->type = $this->resolveExpression($defNode);
|
||||||
|
@ -244,7 +246,7 @@ class DefinitionResolver
|
||||||
return $this->resolveExpression($defNode);
|
return $this->resolveExpression($defNode);
|
||||||
}
|
}
|
||||||
if ($defNode instanceof Node\Param) {
|
if ($defNode instanceof Node\Param) {
|
||||||
return Definition::getTypeFromNode($defNode);
|
return $this->getTypeFromNode($defNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($expr instanceof Node\Expr\FuncCall) {
|
if ($expr instanceof Node\Expr\FuncCall) {
|
||||||
|
@ -434,4 +436,123 @@ class DefinitionResolver
|
||||||
}
|
}
|
||||||
return new Types\Mixed;
|
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;
|
namespace LanguageServer\NodeVisitor;
|
||||||
|
|
||||||
use PhpParser\{NodeVisitorAbstract, Node};
|
use PhpParser\{NodeVisitorAbstract, Node};
|
||||||
use LanguageServer\Definition;
|
use LanguageServer\{Definition, DefinitionResolver};
|
||||||
use LanguageServer\Protocol\SymbolInformation;
|
use LanguageServer\Protocol\SymbolInformation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,9 +27,16 @@ class DefinitionCollector extends NodeVisitorAbstract
|
||||||
*/
|
*/
|
||||||
public $nodes = [];
|
public $nodes = [];
|
||||||
|
|
||||||
|
public $definitionResolver;
|
||||||
|
|
||||||
|
public function __construct(DefinitionResolver $definitionResolver)
|
||||||
|
{
|
||||||
|
$this->definitionResolver = $definitionResolver;
|
||||||
|
}
|
||||||
|
|
||||||
public function enterNode(Node $node)
|
public function enterNode(Node $node)
|
||||||
{
|
{
|
||||||
$fqn = Definition::getDefinedFqn($node);
|
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||||
// Only index definitions with an FQN (no variables)
|
// Only index definitions with an FQN (no variables)
|
||||||
if ($fqn === null) {
|
if ($fqn === null) {
|
||||||
return;
|
return;
|
||||||
|
@ -38,7 +45,7 @@ class DefinitionCollector extends NodeVisitorAbstract
|
||||||
$def = new Definition;
|
$def = new Definition;
|
||||||
$def->fqn = $fqn;
|
$def->fqn = $fqn;
|
||||||
$def->symbolInformation = SymbolInformation::fromNode($node, $fqn);
|
$def->symbolInformation = SymbolInformation::fromNode($node, $fqn);
|
||||||
$def->type = Definition::getTypeFromNode($node);
|
$def->type = $this->definitionResolver->getTypeFromNode($node);
|
||||||
$this->definitions[$fqn] = $def;
|
$this->definitions[$fqn] = $def;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,7 +186,7 @@ class PhpDocument
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
|
|
||||||
// Collect all definitions
|
// Collect all definitions
|
||||||
$definitionCollector = new DefinitionCollector;
|
$definitionCollector = new DefinitionCollector($this->definitionResolver);
|
||||||
$traverser->addVisitor($definitionCollector);
|
$traverser->addVisitor($definitionCollector);
|
||||||
|
|
||||||
// Collect all references
|
// Collect all references
|
||||||
|
@ -374,7 +374,7 @@ class PhpDocument
|
||||||
return $refCollector->nodes;
|
return $refCollector->nodes;
|
||||||
}
|
}
|
||||||
// Definition with a global FQN
|
// Definition with a global FQN
|
||||||
$fqn = Definition::getDefinedFqn($node);
|
$fqn = DefinitionResolver::getDefinedFqn($node);
|
||||||
if ($fqn === null) {
|
if ($fqn === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace LanguageServer\Tests\Server\TextDocument;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PhpParser\{NodeTraverser, Node};
|
use PhpParser\{NodeTraverser, Node};
|
||||||
use PhpParser\NodeVisitor\NameResolver;
|
use PhpParser\NodeVisitor\NameResolver;
|
||||||
use LanguageServer\{LanguageClient, Project, PhpDocument, Parser};
|
use LanguageServer\{LanguageClient, Project, PhpDocument, Parser, DefinitionResolver};
|
||||||
use LanguageServer\Protocol\ClientCapabilities;
|
use LanguageServer\Protocol\ClientCapabilities;
|
||||||
use LanguageServer\Tests\MockProtocolStream;
|
use LanguageServer\Tests\MockProtocolStream;
|
||||||
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
|
use LanguageServer\NodeVisitor\{ReferencesAdder, DefinitionCollector};
|
||||||
|
@ -24,7 +24,7 @@ class DefinitionCollectorTest extends TestCase
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
$traverser->addVisitor(new NameResolver);
|
$traverser->addVisitor(new NameResolver);
|
||||||
$traverser->addVisitor(new ReferencesAdder($document));
|
$traverser->addVisitor(new ReferencesAdder($document));
|
||||||
$definitionCollector = new DefinitionCollector;
|
$definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
|
||||||
$traverser->addVisitor($definitionCollector);
|
$traverser->addVisitor($definitionCollector);
|
||||||
$stmts = $parser->parse(file_get_contents($uri));
|
$stmts = $parser->parse(file_get_contents($uri));
|
||||||
$traverser->traverse($stmts);
|
$traverser->traverse($stmts);
|
||||||
|
@ -63,7 +63,7 @@ class DefinitionCollectorTest extends TestCase
|
||||||
$traverser = new NodeTraverser;
|
$traverser = new NodeTraverser;
|
||||||
$traverser->addVisitor(new NameResolver);
|
$traverser->addVisitor(new NameResolver);
|
||||||
$traverser->addVisitor(new ReferencesAdder($document));
|
$traverser->addVisitor(new ReferencesAdder($document));
|
||||||
$definitionCollector = new DefinitionCollector;
|
$definitionCollector = new DefinitionCollector(new DefinitionResolver($project));
|
||||||
$traverser->addVisitor($definitionCollector);
|
$traverser->addVisitor($definitionCollector);
|
||||||
$stmts = $parser->parse(file_get_contents($uri));
|
$stmts = $parser->parse(file_get_contents($uri));
|
||||||
$traverser->traverse($stmts);
|
$traverser->traverse($stmts);
|
||||||
|
|
Loading…
Reference in New Issue