Improvements
parent
279e2fb996
commit
f20a62f21c
|
@ -12,7 +12,14 @@ use function Sabre\Event\coroutine;
|
||||||
|
|
||||||
class DefinitionResolver
|
class DefinitionResolver
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var \LanguageServer\Project
|
||||||
|
*/
|
||||||
private $project;
|
private $project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \phpDocumentor\Reflection\TypeResolver
|
||||||
|
*/
|
||||||
private $typeResolver;
|
private $typeResolver;
|
||||||
|
|
||||||
public function __construct(Project $project)
|
public function __construct(Project $project)
|
||||||
|
@ -57,6 +64,9 @@ class DefinitionResolver
|
||||||
*/
|
*/
|
||||||
public function getDocumentationFromNode(Node $node)
|
public function getDocumentationFromNode(Node $node)
|
||||||
{
|
{
|
||||||
|
if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) {
|
||||||
|
$node = $node->getAttribute('parentNode');
|
||||||
|
}
|
||||||
if ($node instanceof Node\Param) {
|
if ($node instanceof Node\Param) {
|
||||||
$fn = $node->getAttribute('parentNode');
|
$fn = $node->getAttribute('parentNode');
|
||||||
$docBlock = $fn->getAttribute('docBlock');
|
$docBlock = $fn->getAttribute('docBlock');
|
||||||
|
@ -88,13 +98,16 @@ class DefinitionResolver
|
||||||
if ($node instanceof Node\Expr\Variable) {
|
if ($node instanceof Node\Expr\Variable) {
|
||||||
// Resolve the variable to a definition node (assignment, param or closure use)
|
// Resolve the variable to a definition node (assignment, param or closure use)
|
||||||
$defNode = self::resolveVariableToNode($node);
|
$defNode = self::resolveVariableToNode($node);
|
||||||
|
if ($defNode === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$def = new Definition;
|
$def = new Definition;
|
||||||
// Get symbol information from node (range, symbol kind)
|
// Get symbol information from node (range, symbol kind)
|
||||||
$def->symbolInformation = SymbolInformation::fromNode($defNode);
|
$def->symbolInformation = SymbolInformation::fromNode($defNode);
|
||||||
// Declaration line
|
// Declaration line
|
||||||
$def->declarationLine = $this->getDeclarationLineFromNode($node);
|
$def->declarationLine = $this->getDeclarationLineFromNode($defNode);
|
||||||
// Documentation
|
// Documentation
|
||||||
$def->documentation = $this->getDocumentationFromNode($node);
|
$def->documentation = $this->getDocumentationFromNode($defNode);
|
||||||
if ($defNode instanceof Node\Param) {
|
if ($defNode instanceof Node\Param) {
|
||||||
// Get parameter type
|
// Get parameter type
|
||||||
$def->type = $this->getTypeFromNode($defNode);
|
$def->type = $this->getTypeFromNode($defNode);
|
||||||
|
@ -110,19 +123,12 @@ class DefinitionResolver
|
||||||
if ($fqn === null) {
|
if ($fqn === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Return the Definition object from the project index
|
|
||||||
$def = $this->project->getDefinition($fqn);
|
|
||||||
if ($def === null) {
|
|
||||||
// If the node is a function or constant, it could be namespaced, but PHP falls back to global
|
// If the node is a function or constant, it could be namespaced, but PHP falls back to global
|
||||||
// http://php.net/manual/en/language.namespaces.fallback.php
|
// http://php.net/manual/en/language.namespaces.fallback.php
|
||||||
$parent = $node->getAttribute('parentNode');
|
$parent = $node->getAttribute('parentNode');
|
||||||
if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) {
|
$globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall;
|
||||||
$parts = explode('\\', $fqn);
|
// Return the Definition object from the project index
|
||||||
$fqn = end($parts);
|
return $this->project->getDefinition($fqn, $globalFallback);
|
||||||
$def = $this->project->getDefinition($fqn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $def;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -291,8 +297,8 @@ class DefinitionResolver
|
||||||
* Given an expression node, resolves that expression recursively to a type.
|
* Given an expression node, resolves that expression recursively to a type.
|
||||||
* If the type could not be resolved, returns Types\Mixed.
|
* If the type could not be resolved, returns Types\Mixed.
|
||||||
*
|
*
|
||||||
* @param Node\Expr $expr
|
* @param \PhpParser\Node\Expr $expr
|
||||||
* @return Type
|
* @return \phpDocumentor\Type
|
||||||
*/
|
*/
|
||||||
private function resolveExpression(Node\Expr $expr): Type
|
private function resolveExpression(Node\Expr $expr): Type
|
||||||
{
|
{
|
||||||
|
@ -316,7 +322,7 @@ class DefinitionResolver
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
||||||
$def = $this->project->getDefinition($fqn);
|
$def = $this->project->getDefinition($fqn, true);
|
||||||
if ($def !== null) {
|
if ($def !== null) {
|
||||||
return $def->type;
|
return $def->type;
|
||||||
}
|
}
|
||||||
|
@ -327,81 +333,62 @@ class DefinitionResolver
|
||||||
}
|
}
|
||||||
// Resolve constant
|
// Resolve constant
|
||||||
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
|
||||||
$def = $this->project->getDefinition($fqn);
|
$def = $this->project->getDefinition($fqn, true);
|
||||||
if ($def !== null) {
|
if ($def !== null) {
|
||||||
return $def->type;
|
return $def->type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($expr instanceof Node\Expr\MethodCall || $expr instanceof Node\Expr\PropertyFetch) {
|
||||||
|
if ($expr->name instanceof Node\Expr) {
|
||||||
|
return new Types\Mixed;
|
||||||
|
}
|
||||||
|
// Resolve object
|
||||||
|
$objType = $this->resolveExpression($expr->var);
|
||||||
|
if (!($objType instanceof Types\Compound)) {
|
||||||
|
$objType = new Types\Compound([$objType]);
|
||||||
|
}
|
||||||
|
for ($i = 0; $t = $objType->get($i); $i++) {
|
||||||
|
if ($t instanceof Types\This) {
|
||||||
|
$classFqn = self::getContainingClassFqn($expr);
|
||||||
|
if ($classFqn === null) {
|
||||||
|
return new Types\Mixed;
|
||||||
|
}
|
||||||
|
} else if (!($t instanceof Types\Object_) || $t->getFqsen() === null) {
|
||||||
|
return new Types\Mixed;
|
||||||
|
} else {
|
||||||
|
$classFqn = substr((string)$t->getFqsen(), 1);
|
||||||
|
}
|
||||||
|
$fqn = $classFqn . '::' . $expr->name;
|
||||||
if ($expr instanceof Node\Expr\MethodCall) {
|
if ($expr instanceof Node\Expr\MethodCall) {
|
||||||
// Resolve object
|
$fqn .= '()';
|
||||||
$objType = $this->resolveExpression($expr->var);
|
|
||||||
if (!($objType instanceof Types\Object_) || $objType->getFqsen() === null || $expr->name instanceof Node\Expr) {
|
|
||||||
// Need the class FQN of the object
|
|
||||||
return new Types\Mixed;
|
|
||||||
}
|
}
|
||||||
$fqn = (string)$objType->getFqsen() . '::' . $expr->name . '()';
|
|
||||||
$def = $this->project->getDefinition($fqn);
|
$def = $this->project->getDefinition($fqn);
|
||||||
if ($def !== null) {
|
if ($def !== null) {
|
||||||
return $def->type;
|
return $def->type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($expr instanceof Node\Expr\PropertyFetch) {
|
}
|
||||||
// Resolve object
|
if (
|
||||||
$objType = $this->resolveExpression($expr->var);
|
$expr instanceof Node\Expr\StaticCall
|
||||||
if (!($objType instanceof Types\Object_) || $objType->getFqsen() === null || $expr->name instanceof Node\Expr) {
|
|| $expr instanceof Node\Expr\StaticPropertyFetch
|
||||||
// Need the class FQN of the object
|
|| $expr instanceof Node\Expr\ClassConstFetch
|
||||||
|
) {
|
||||||
|
$classType = self::resolveClassNameToType($expr->class);
|
||||||
|
if (!($classType instanceof Types\Object_) || $classType->getFqsen() === null || $expr->name instanceof Node\Expr) {
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
$fqn = (string)$objType->getFqsen() . '::' . $expr->name;
|
$fqn = substr((string)$classType->getFqsen(), 1) . '::' . $expr->name;
|
||||||
$def = $this->project->getDefinition($fqn);
|
|
||||||
if ($def !== null) {
|
|
||||||
return $def->type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($expr instanceof Node\Expr\StaticCall) {
|
if ($expr instanceof Node\Expr\StaticCall) {
|
||||||
if ($expr->class instanceof Node\Expr || $expr->name instanceof Node\Expr) {
|
$fqn .= '()';
|
||||||
// Need the FQN
|
}
|
||||||
|
$def = $this->project->getDefinition($fqn);
|
||||||
|
if ($def === null) {
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
$fqn = (string)$expr->class . '::' . $expr->name . '()';
|
return $def->type;
|
||||||
}
|
|
||||||
if ($expr instanceof Node\Expr\StaticPropertyFetch || $expr instanceof Node\Expr\ClassConstFetch) {
|
|
||||||
if ($expr->class instanceof Node\Expr || $expr->name instanceof Node\Expr) {
|
|
||||||
// Need the FQN
|
|
||||||
return new Types\Mixed;
|
|
||||||
}
|
|
||||||
$fqn = (string)$expr->class . '::' . $expr->name;
|
|
||||||
}
|
}
|
||||||
if ($expr instanceof Node\Expr\New_) {
|
if ($expr instanceof Node\Expr\New_) {
|
||||||
if ($expr->class instanceof Node\Expr) {
|
return self::resolveClassNameToType($expr->class);
|
||||||
return new Types\Mixed;
|
|
||||||
}
|
|
||||||
if ($expr->class instanceof Node\Stmt\Class_) {
|
|
||||||
// Anonymous class
|
|
||||||
return new Types\Object_;
|
|
||||||
}
|
|
||||||
$class = (string)$expr->class;
|
|
||||||
if ($class === 'static') {
|
|
||||||
return new Types\Static_;
|
|
||||||
}
|
|
||||||
if ($class === 'self' || $class === 'parent') {
|
|
||||||
$classNode = getClosestNode($expr, Node\Stmt\Class_::class);
|
|
||||||
if ($class === 'parent') {
|
|
||||||
if ($classNode === null || $classNode->extends === null) {
|
|
||||||
return new Types\Object_;
|
|
||||||
}
|
|
||||||
// parent is resolved to the parent class
|
|
||||||
$classFqn = (string)$classNode->extends;
|
|
||||||
} else {
|
|
||||||
if ($classNode === null) {
|
|
||||||
return new Types\Self_;
|
|
||||||
}
|
|
||||||
// self is resolved to the containing class
|
|
||||||
$classFqn = (string)$classNode->namespacedName;
|
|
||||||
}
|
|
||||||
return new Types\Object_(new Fqsen('\\' . $classFqn));
|
|
||||||
}
|
|
||||||
return new Types\Object_(new Fqsen('\\' . $class));
|
|
||||||
}
|
}
|
||||||
if ($expr instanceof Node\Expr\Clone_ || $expr instanceof Node\Expr\Assign) {
|
if ($expr instanceof Node\Expr\Clone_ || $expr instanceof Node\Expr\Assign) {
|
||||||
return $this->resolveExpression($expr->expr);
|
return $this->resolveExpression($expr->expr);
|
||||||
|
@ -445,7 +432,7 @@ class DefinitionResolver
|
||||||
|| $expr instanceof Node\Expr\BinaryOp\NotEqual
|
|| $expr instanceof Node\Expr\BinaryOp\NotEqual
|
||||||
|| $expr instanceof Node\Expr\BinaryOp\NotIdentical
|
|| $expr instanceof Node\Expr\BinaryOp\NotIdentical
|
||||||
) {
|
) {
|
||||||
return new Types\Boolean_;
|
return new Types\Boolean;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
$expr instanceof Node\Expr\Concat
|
$expr instanceof Node\Expr\Concat
|
||||||
|
@ -509,9 +496,21 @@ class DefinitionResolver
|
||||||
}
|
}
|
||||||
$valueTypes = array_unique($keyTypes);
|
$valueTypes = array_unique($keyTypes);
|
||||||
$keyTypes = array_unique($keyTypes);
|
$keyTypes = array_unique($keyTypes);
|
||||||
$valueType = count($valueTypes) > 1 ? new Types\Compound($valueTypes) : $valueTypes[0];
|
if (empty($valueTypes)) {
|
||||||
$keyType = count($keyTypes) > 1 ? new Types\Compound($keyTypes) : $keyTypes[0];
|
$valueType = null;
|
||||||
return new Types\Array_($valueTypes, $keyTypes);
|
} else if (count($valueTypes) === 1) {
|
||||||
|
$valueType = $valueTypes[0];
|
||||||
|
} else {
|
||||||
|
$valueType = new Types\Compound($valueTypes);
|
||||||
|
}
|
||||||
|
if (empty($keyTypes)) {
|
||||||
|
$keyType = null;
|
||||||
|
} else if (count($keyTypes) === 1) {
|
||||||
|
$keyType = $keyTypes[0];
|
||||||
|
} else {
|
||||||
|
$keyType = new Types\Compound($keyTypes);
|
||||||
|
}
|
||||||
|
return new Types\Array_($valueType, $keyType);
|
||||||
}
|
}
|
||||||
if ($expr instanceof Node\Expr\ArrayDimFetch) {
|
if ($expr instanceof Node\Expr\ArrayDimFetch) {
|
||||||
$varType = $this->resolveExpression($expr->var);
|
$varType = $this->resolveExpression($expr->var);
|
||||||
|
@ -527,10 +526,51 @@ class DefinitionResolver
|
||||||
return new Types\Mixed;
|
return new Types\Mixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes any class name node (from a static method call, or new node) and returns a Type object
|
||||||
|
* Resolves keywords like self, static and parent
|
||||||
|
*
|
||||||
|
* @param Node $class
|
||||||
|
* @return Type
|
||||||
|
*/
|
||||||
|
private static function resolveClassNameToType(Node $class): Type
|
||||||
|
{
|
||||||
|
if ($class instanceof Node\Expr) {
|
||||||
|
return new Types\Mixed;
|
||||||
|
}
|
||||||
|
if ($class instanceof Node\Stmt\Class_) {
|
||||||
|
// Anonymous class
|
||||||
|
return new Types\Object_;
|
||||||
|
}
|
||||||
|
$className = (string)$class;
|
||||||
|
if ($className === 'static') {
|
||||||
|
return new Types\Static_;
|
||||||
|
}
|
||||||
|
if ($className === 'self' || $className === 'parent') {
|
||||||
|
$classNode = getClosestNode($class, Node\Stmt\Class_::class);
|
||||||
|
if ($className === 'parent') {
|
||||||
|
if ($classNode === null || $classNode->extends === null) {
|
||||||
|
return new Types\Object_;
|
||||||
|
}
|
||||||
|
// parent is resolved to the parent class
|
||||||
|
$classFqn = (string)$classNode->extends;
|
||||||
|
} else {
|
||||||
|
if ($classNode === null) {
|
||||||
|
return new Types\Self_;
|
||||||
|
}
|
||||||
|
// self is resolved to the containing class
|
||||||
|
$classFqn = (string)$classNode->namespacedName;
|
||||||
|
}
|
||||||
|
return new Types\Object_(new Fqsen('\\' . $classFqn));
|
||||||
|
}
|
||||||
|
return new Types\Object_(new Fqsen('\\' . $className));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type a reference to this symbol will resolve to.
|
* Returns the type a reference to this symbol will resolve to.
|
||||||
* For properties and constants, this is the type of the property/constant.
|
* For properties and constants, this is the type of the property/constant.
|
||||||
* For functions and methods, this is the return type.
|
* For functions and methods, this is the return type.
|
||||||
|
* For parameters, this is the type of the parameter.
|
||||||
* For classes and interfaces, this is the class type (object).
|
* For classes and interfaces, this is the class type (object).
|
||||||
* Variables are not indexed for performance reasons.
|
* Variables are not indexed for performance reasons.
|
||||||
* Can also be a compound type.
|
* Can also be a compound type.
|
||||||
|
@ -544,7 +584,7 @@ class DefinitionResolver
|
||||||
{
|
{
|
||||||
if ($node instanceof Node\Param) {
|
if ($node instanceof Node\Param) {
|
||||||
// Parameters
|
// Parameters
|
||||||
$docBlock = $node->getAttribute('docBlock');
|
$docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock');
|
||||||
if ($docBlock !== null && count($paramTags = $docBlock->getTagsByName('param')) > 0) {
|
if ($docBlock !== null && count($paramTags = $docBlock->getTagsByName('param')) > 0) {
|
||||||
// Use @param tag
|
// Use @param tag
|
||||||
return $paramTags[0]->getType();
|
return $paramTags[0]->getType();
|
||||||
|
|
|
@ -207,11 +207,19 @@ class Project
|
||||||
/**
|
/**
|
||||||
* Returns the Definition object by a specific FQN
|
* Returns the Definition object by a specific FQN
|
||||||
*
|
*
|
||||||
|
* @param string $fqn
|
||||||
|
* @param bool $globalFallback Whether to fallback to global if the namespaced FQN was not found
|
||||||
* @return Definition|null
|
* @return Definition|null
|
||||||
*/
|
*/
|
||||||
public function getDefinition(string $fqn)
|
public function getDefinition(string $fqn, $globalFallback = false)
|
||||||
{
|
{
|
||||||
return $this->definitions[$fqn] ?? null;
|
if (isset($this->definitions[$fqn])) {
|
||||||
|
return $this->definitions[$fqn];
|
||||||
|
} else if ($globalFallback) {
|
||||||
|
$parts = explode('\\', $fqn);
|
||||||
|
$fqn = end($parts);
|
||||||
|
return $this->getDefinition($fqn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue