1
0
Fork 0

Improvements

pull/155/head
Felix Becker 2016-11-18 03:24:30 +01:00
parent 279e2fb996
commit f20a62f21c
2 changed files with 135 additions and 87 deletions

View File

@ -12,7 +12,14 @@ use function Sabre\Event\coroutine;
class DefinitionResolver
{
/**
* @var \LanguageServer\Project
*/
private $project;
/**
* @var \phpDocumentor\Reflection\TypeResolver
*/
private $typeResolver;
public function __construct(Project $project)
@ -57,6 +64,9 @@ class DefinitionResolver
*/
public function getDocumentationFromNode(Node $node)
{
if ($node instanceof Node\Stmt\PropertyProperty || $node instanceof Node\Const_) {
$node = $node->getAttribute('parentNode');
}
if ($node instanceof Node\Param) {
$fn = $node->getAttribute('parentNode');
$docBlock = $fn->getAttribute('docBlock');
@ -88,13 +98,16 @@ class DefinitionResolver
if ($node instanceof Node\Expr\Variable) {
// Resolve the variable to a definition node (assignment, param or closure use)
$defNode = self::resolveVariableToNode($node);
if ($defNode === null) {
return null;
}
$def = new Definition;
// Get symbol information from node (range, symbol kind)
$def->symbolInformation = SymbolInformation::fromNode($defNode);
// Declaration line
$def->declarationLine = $this->getDeclarationLineFromNode($node);
$def->declarationLine = $this->getDeclarationLineFromNode($defNode);
// Documentation
$def->documentation = $this->getDocumentationFromNode($node);
$def->documentation = $this->getDocumentationFromNode($defNode);
if ($defNode instanceof Node\Param) {
// Get parameter type
$def->type = $this->getTypeFromNode($defNode);
@ -110,19 +123,12 @@ class DefinitionResolver
if ($fqn === 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
// http://php.net/manual/en/language.namespaces.fallback.php
$parent = $node->getAttribute('parentNode');
if ($parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall) {
$parts = explode('\\', $fqn);
$fqn = end($parts);
$def = $this->project->getDefinition($fqn);
}
}
return $def;
$globalFallback = $parent instanceof Node\Expr\ConstFetch || $parent instanceof Node\Expr\FuncCall;
// Return the Definition object from the project index
return $this->project->getDefinition($fqn, $globalFallback);
}
/**
@ -291,8 +297,8 @@ class DefinitionResolver
* Given an expression node, resolves that expression recursively to a type.
* If the type could not be resolved, returns Types\Mixed.
*
* @param Node\Expr $expr
* @return Type
* @param \PhpParser\Node\Expr $expr
* @return \phpDocumentor\Type
*/
private function resolveExpression(Node\Expr $expr): Type
{
@ -316,7 +322,7 @@ class DefinitionResolver
return new Types\Mixed;
}
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
$def = $this->project->getDefinition($fqn);
$def = $this->project->getDefinition($fqn, true);
if ($def !== null) {
return $def->type;
}
@ -327,81 +333,62 @@ class DefinitionResolver
}
// Resolve constant
$fqn = (string)($expr->getAttribute('namespacedName') ?? $expr->name);
$def = $this->project->getDefinition($fqn);
$def = $this->project->getDefinition($fqn, true);
if ($def !== null) {
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) {
// Resolve object
$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 .= '()';
}
$fqn = (string)$objType->getFqsen() . '::' . $expr->name . '()';
$def = $this->project->getDefinition($fqn);
if ($def !== null) {
return $def->type;
}
}
if ($expr instanceof Node\Expr\PropertyFetch) {
// Resolve object
$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
}
if (
$expr instanceof Node\Expr\StaticCall
|| $expr instanceof Node\Expr\StaticPropertyFetch
|| $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;
}
$fqn = (string)$objType->getFqsen() . '::' . $expr->name;
$def = $this->project->getDefinition($fqn);
if ($def !== null) {
return $def->type;
}
}
$fqn = substr((string)$classType->getFqsen(), 1) . '::' . $expr->name;
if ($expr instanceof Node\Expr\StaticCall) {
if ($expr->class instanceof Node\Expr || $expr->name instanceof Node\Expr) {
// Need the FQN
$fqn .= '()';
}
$def = $this->project->getDefinition($fqn);
if ($def === null) {
return new Types\Mixed;
}
$fqn = (string)$expr->class . '::' . $expr->name . '()';
}
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;
return $def->type;
}
if ($expr instanceof Node\Expr\New_) {
if ($expr->class instanceof Node\Expr) {
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));
return self::resolveClassNameToType($expr->class);
}
if ($expr instanceof Node\Expr\Clone_ || $expr instanceof Node\Expr\Assign) {
return $this->resolveExpression($expr->expr);
@ -445,7 +432,7 @@ class DefinitionResolver
|| $expr instanceof Node\Expr\BinaryOp\NotEqual
|| $expr instanceof Node\Expr\BinaryOp\NotIdentical
) {
return new Types\Boolean_;
return new Types\Boolean;
}
if (
$expr instanceof Node\Expr\Concat
@ -509,9 +496,21 @@ class DefinitionResolver
}
$valueTypes = array_unique($keyTypes);
$keyTypes = array_unique($keyTypes);
$valueType = count($valueTypes) > 1 ? new Types\Compound($valueTypes) : $valueTypes[0];
$keyType = count($keyTypes) > 1 ? new Types\Compound($keyTypes) : $keyTypes[0];
return new Types\Array_($valueTypes, $keyTypes);
if (empty($valueTypes)) {
$valueType = null;
} 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) {
$varType = $this->resolveExpression($expr->var);
@ -527,10 +526,51 @@ class DefinitionResolver
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.
* For properties and constants, this is the type of the property/constant.
* 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).
* Variables are not indexed for performance reasons.
* Can also be a compound type.
@ -544,7 +584,7 @@ class DefinitionResolver
{
if ($node instanceof Node\Param) {
// Parameters
$docBlock = $node->getAttribute('docBlock');
$docBlock = $node->getAttribute('parentNode')->getAttribute('docBlock');
if ($docBlock !== null && count($paramTags = $docBlock->getTagsByName('param')) > 0) {
// Use @param tag
return $paramTags[0]->getType();

View File

@ -207,11 +207,19 @@ class Project
/**
* 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
*/
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);
}
}
/**